diff --git a/CMakeLists.txt b/CMakeLists.txt index 23f23418f..3a6300757 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1489,6 +1489,73 @@ set(LOVE_SRC_3P_LUA53 add_library(love_3p_lua53 ${LOVE_SRC_3P_LUA53}) target_link_libraries(love_3p_lua53 ${LOVE_LUA_LIBRARY}) +# +# Lua HTTPS +# + +set(LOVE_SRC_3P_LUAHTTPS_ANDROID + src/libraries/luahttps/src/android/AndroidClient.cpp + src/libraries/luahttps/src/android/AndroidClient.h +) + +set(LOVE_SRC_3P_LUAHTTPS_APPLE + src/libraries/luahttps/src/apple/NSURLClient.mm + src/libraries/luahttps/src/apple/NSURLClient.h +) + +set(LOVE_SRC_3P_LUAHTTPS_COMMON + src/libraries/luahttps/src/common/config.h + src/libraries/luahttps/src/common/Connection.h + src/libraries/luahttps/src/common/ConnectionClient.h + src/libraries/luahttps/src/common/HTTPRequest.cpp + src/libraries/luahttps/src/common/HTTPRequest.h + src/libraries/luahttps/src/common/HTTPS.cpp + src/libraries/luahttps/src/common/HTTPS.h + src/libraries/luahttps/src/common/HTTPSClient.cpp + src/libraries/luahttps/src/common/HTTPSClient.h + src/libraries/luahttps/src/common/PlaintextConnection.cpp + src/libraries/luahttps/src/common/PlaintextConnection.h +) + +set(LOVE_SRC_3P_LUAHTTPS_GENERIC + src/libraries/luahttps/src/generic/CurlClient.cpp + src/libraries/luahttps/src/generic/CurlClient.h + src/libraries/luahttps/src/generic/OpenSSLConnection.cpp + src/libraries/luahttps/src/generic/OpenSSLConnection.h +) + +set(LOVE_SRC_3P_LUAHTTPS_LUA + src/libraries/luahttps/src/lua/main.cpp +) + +set(LOVE_SRC_3P_LUAHTTPS_WINDOWS + src/libraries/luahttps/src/windows/SChannelConnection.cpp + src/libraries/luahttps/src/windows/SChannelConnection.h +) + +# These are platform-dependent but have ifdef guards to make sure they only +# compile on supported platforms. +set(LOVE_SRC_3P_LUAHTTPS + ${LOVE_SRC_3P_LUAHTTPS_ANDROID} + ${LOVE_SRC_3P_LUAHTTPS_APPLE} + ${LOVE_SRC_3P_LUAHTTPS_COMMON} + ${LOVE_SRC_3P_LUAHTTPS_GENERIC} + ${LOVE_SRC_3P_LUAHTTPS_LUA} + ${LOVE_SRC_3P_LUAHTTPS_WINDOWS} +) + +set(LOVE_LINK_L3P_LUAHTTPS) +if(MSVC) + set(LOVE_LINK_L3P_LUAHTTPS + ${LOVE_LINK_L3P_LUASOCKET_LIBLUASOCKET} + ws2_32 + secur32 + ) +endif() + +add_library(love_3p_luahttps $${LOVE_SRC_3P_LUAHTTPS}) +target_link_libraries(love_3p_luahttps ${LOVE_LUA_LIBRARY} ${LOVE_LINK_L3P_LUAHTTPS}) + # # lz4 # @@ -1636,6 +1703,7 @@ set(LOVE_3P love_3p_lodepng love_3p_luasocket love_3p_lua53 + love_3p_luahttps love_3p_lz4 love_3p_noise1234 love_3p_physfs diff --git a/platform/xcode/liblove.xcodeproj/project.pbxproj b/platform/xcode/liblove.xcodeproj/project.pbxproj index 19b702fdd..bbafec76c 100644 --- a/platform/xcode/liblove.xcodeproj/project.pbxproj +++ b/platform/xcode/liblove.xcodeproj/project.pbxproj @@ -829,9 +829,9 @@ FA76344A1E28722A0066EF9E /* StreamBuffer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA7634481E28722A0066EF9E /* StreamBuffer.cpp */; }; FA76344B1E28722A0066EF9E /* StreamBuffer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA7634481E28722A0066EF9E /* StreamBuffer.cpp */; }; FA76344C1E28722A0066EF9E /* StreamBuffer.h in Headers */ = {isa = PBXBuildFile; fileRef = FA7634491E28722A0066EF9E /* StreamBuffer.h */; }; + FA7E9207277E120900C24CB2 /* theora.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = FA7E9206277E120900C24CB2 /* theora.xcframework */; }; FA84DE612778D7F3002674C6 /* SpirvIntrinsics.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA84DE602778D7F3002674C6 /* SpirvIntrinsics.cpp */; }; FA84DE622778D7F3002674C6 /* SpirvIntrinsics.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA84DE602778D7F3002674C6 /* SpirvIntrinsics.cpp */; }; - FA7E9207277E120900C24CB2 /* theora.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = FA7E9206277E120900C24CB2 /* theora.xcframework */; }; FA84DE76277CB3D5002674C6 /* SDL2.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = FA84DE75277CB3D4002674C6 /* SDL2.xcframework */; }; FA84DE7A277D4C88002674C6 /* modplug.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = FA84DE79277D4C88002674C6 /* modplug.xcframework */; }; FA84DE7C277E045E002674C6 /* ogg.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = FA84DE7B277E045E002674C6 /* ogg.xcframework */; }; @@ -844,6 +844,26 @@ FA91DA8D1F377C3900C80E33 /* deprecation.h in Headers */ = {isa = PBXBuildFile; fileRef = FA91DA8A1F377C3900C80E33 /* deprecation.h */; }; FA93C4531F315B960087CCD4 /* FormatHandler.h in Headers */ = {isa = PBXBuildFile; fileRef = FA93C4501F315B960087CCD4 /* FormatHandler.h */; }; FA93C4541F315B960087CCD4 /* FormatHandler.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA93C4511F315B960087CCD4 /* FormatHandler.cpp */; }; + FA94727827A6EE1B00817677 /* main.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA94725227A6EE1B00817677 /* main.cpp */; }; + FA94727927A6EE1B00817677 /* main.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA94725227A6EE1B00817677 /* main.cpp */; }; + FA94727A27A6EE1B00817677 /* Connection.h in Headers */ = {isa = PBXBuildFile; fileRef = FA94725427A6EE1B00817677 /* Connection.h */; }; + FA94727B27A6EE1B00817677 /* HTTPSClient.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA94725527A6EE1B00817677 /* HTTPSClient.cpp */; }; + FA94727C27A6EE1B00817677 /* HTTPSClient.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA94725527A6EE1B00817677 /* HTTPSClient.cpp */; }; + FA94727D27A6EE1B00817677 /* HTTPSClient.h in Headers */ = {isa = PBXBuildFile; fileRef = FA94725627A6EE1B00817677 /* HTTPSClient.h */; }; + FA94727E27A6EE1B00817677 /* PlaintextConnection.h in Headers */ = {isa = PBXBuildFile; fileRef = FA94725727A6EE1B00817677 /* PlaintextConnection.h */; }; + FA94727F27A6EE1B00817677 /* config.h in Headers */ = {isa = PBXBuildFile; fileRef = FA94725827A6EE1B00817677 /* config.h */; }; + FA94728127A6EE1B00817677 /* HTTPS.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA94725A27A6EE1B00817677 /* HTTPS.cpp */; }; + FA94728227A6EE1B00817677 /* HTTPS.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA94725A27A6EE1B00817677 /* HTTPS.cpp */; }; + FA94728327A6EE1B00817677 /* HTTPRequest.h in Headers */ = {isa = PBXBuildFile; fileRef = FA94725B27A6EE1B00817677 /* HTTPRequest.h */; }; + FA94728427A6EE1B00817677 /* ConnectionClient.h in Headers */ = {isa = PBXBuildFile; fileRef = FA94725C27A6EE1B00817677 /* ConnectionClient.h */; }; + FA94728527A6EE1B00817677 /* PlaintextConnection.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA94725D27A6EE1B00817677 /* PlaintextConnection.cpp */; }; + FA94728627A6EE1B00817677 /* PlaintextConnection.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA94725D27A6EE1B00817677 /* PlaintextConnection.cpp */; }; + FA94728727A6EE1B00817677 /* HTTPS.h in Headers */ = {isa = PBXBuildFile; fileRef = FA94725E27A6EE1B00817677 /* HTTPS.h */; }; + FA94728827A6EE1B00817677 /* HTTPRequest.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA94725F27A6EE1B00817677 /* HTTPRequest.cpp */; }; + FA94728927A6EE1B00817677 /* HTTPRequest.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA94725F27A6EE1B00817677 /* HTTPRequest.cpp */; }; + FA94729B27A6F9AD00817677 /* NSURLClient.mm in Sources */ = {isa = PBXBuildFile; fileRef = FA94729927A6F9AC00817677 /* NSURLClient.mm */; }; + FA94729C27A6F9AD00817677 /* NSURLClient.mm in Sources */ = {isa = PBXBuildFile; fileRef = FA94729927A6F9AC00817677 /* NSURLClient.mm */; }; + FA94729D27A6F9AD00817677 /* NSURLClient.h in Headers */ = {isa = PBXBuildFile; fileRef = FA94729A27A6F9AC00817677 /* NSURLClient.h */; }; FA9B4A0816E1578300074F42 /* SDL2.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FA9B4A0716E1578300074F42 /* SDL2.framework */; }; FA9D53AC1F5307E900125C6B /* Deprecations.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA9D53AA1F5307E900125C6B /* Deprecations.cpp */; }; FA9D53AD1F5307E900125C6B /* Deprecations.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA9D53AA1F5307E900125C6B /* Deprecations.cpp */; }; @@ -1892,11 +1912,11 @@ FA7634481E28722A0066EF9E /* StreamBuffer.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = StreamBuffer.cpp; sourceTree = ""; }; FA7634491E28722A0066EF9E /* StreamBuffer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = StreamBuffer.h; sourceTree = ""; }; FA7DA04C1C16874A0056B200 /* wrap_Math.lua */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = wrap_Math.lua; sourceTree = ""; }; + FA7E9206277E120900C24CB2 /* theora.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = theora.xcframework; path = ios/libraries/theora.xcframework; sourceTree = ""; }; FA84DE5D2778D7DB002674C6 /* SpirvIntrinsics.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SpirvIntrinsics.h; sourceTree = ""; }; FA84DE5E2778D7DC002674C6 /* glslang_c_interface.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = glslang_c_interface.h; sourceTree = ""; }; FA84DE5F2778D7DC002674C6 /* glslang_c_shader_types.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = glslang_c_shader_types.h; sourceTree = ""; }; FA84DE602778D7F3002674C6 /* SpirvIntrinsics.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = SpirvIntrinsics.cpp; sourceTree = ""; }; - FA7E9206277E120900C24CB2 /* theora.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = theora.xcframework; path = ios/libraries/theora.xcframework; sourceTree = ""; }; FA84DE75277CB3D4002674C6 /* SDL2.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = SDL2.xcframework; path = ios/libraries/SDL2.xcframework; sourceTree = ""; }; FA84DE79277D4C88002674C6 /* modplug.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = modplug.xcframework; path = ios/libraries/modplug.xcframework; sourceTree = ""; }; FA84DE7B277E045E002674C6 /* ogg.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = ogg.xcframework; path = ios/libraries/ogg.xcframework; sourceTree = ""; }; @@ -1907,6 +1927,20 @@ FA91DA8A1F377C3900C80E33 /* deprecation.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = deprecation.h; sourceTree = ""; }; FA93C4501F315B960087CCD4 /* FormatHandler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FormatHandler.h; sourceTree = ""; }; FA93C4511F315B960087CCD4 /* FormatHandler.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = FormatHandler.cpp; sourceTree = ""; }; + FA94725227A6EE1B00817677 /* main.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = main.cpp; sourceTree = ""; }; + FA94725427A6EE1B00817677 /* Connection.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Connection.h; sourceTree = ""; }; + FA94725527A6EE1B00817677 /* HTTPSClient.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = HTTPSClient.cpp; sourceTree = ""; }; + FA94725627A6EE1B00817677 /* HTTPSClient.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HTTPSClient.h; sourceTree = ""; }; + FA94725727A6EE1B00817677 /* PlaintextConnection.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PlaintextConnection.h; sourceTree = ""; }; + FA94725827A6EE1B00817677 /* config.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = config.h; sourceTree = ""; }; + FA94725A27A6EE1B00817677 /* HTTPS.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = HTTPS.cpp; sourceTree = ""; }; + FA94725B27A6EE1B00817677 /* HTTPRequest.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HTTPRequest.h; sourceTree = ""; }; + FA94725C27A6EE1B00817677 /* ConnectionClient.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ConnectionClient.h; sourceTree = ""; }; + FA94725D27A6EE1B00817677 /* PlaintextConnection.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = PlaintextConnection.cpp; sourceTree = ""; }; + FA94725E27A6EE1B00817677 /* HTTPS.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HTTPS.h; sourceTree = ""; }; + FA94725F27A6EE1B00817677 /* HTTPRequest.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = HTTPRequest.cpp; sourceTree = ""; }; + FA94729927A6F9AC00817677 /* NSURLClient.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = NSURLClient.mm; sourceTree = ""; }; + FA94729A27A6F9AC00817677 /* NSURLClient.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NSURLClient.h; sourceTree = ""; }; FA9B4A0716E1578300074F42 /* SDL2.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SDL2.framework; path = macosx/Frameworks/SDL2.framework; sourceTree = ""; }; FA9D53AA1F5307E900125C6B /* Deprecations.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = Deprecations.cpp; sourceTree = ""; }; FA9D53AB1F5307E900125C6B /* Deprecations.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Deprecations.h; sourceTree = ""; }; @@ -2368,6 +2402,7 @@ FA0B79D31A958EA3000E1D17 /* glad */, FAF13FBF1E20934C00F898D2 /* glslang */, FA0B79D81A958EA3000E1D17 /* lodepng */, + FA94724527A6EE1B00817677 /* luahttps */, FAAA3FD21F64B3AD00F89E99 /* lua53 */, FA0B79DB1A958EA3000E1D17 /* luasocket */, FAB17BE31ABFAA9000F9BA27 /* lz4 */, @@ -3469,6 +3504,59 @@ name = ios; sourceTree = ""; }; + FA94724527A6EE1B00817677 /* luahttps */ = { + isa = PBXGroup; + children = ( + FA94724D27A6EE1B00817677 /* src */, + ); + path = luahttps; + sourceTree = ""; + }; + FA94724D27A6EE1B00817677 /* src */ = { + isa = PBXGroup; + children = ( + FA94729827A6F9AC00817677 /* apple */, + FA94725327A6EE1B00817677 /* common */, + FA94725127A6EE1B00817677 /* lua */, + ); + path = src; + sourceTree = ""; + }; + FA94725127A6EE1B00817677 /* lua */ = { + isa = PBXGroup; + children = ( + FA94725227A6EE1B00817677 /* main.cpp */, + ); + path = lua; + sourceTree = ""; + }; + FA94725327A6EE1B00817677 /* common */ = { + isa = PBXGroup; + children = ( + FA94725827A6EE1B00817677 /* config.h */, + FA94725427A6EE1B00817677 /* Connection.h */, + FA94725C27A6EE1B00817677 /* ConnectionClient.h */, + FA94725F27A6EE1B00817677 /* HTTPRequest.cpp */, + FA94725B27A6EE1B00817677 /* HTTPRequest.h */, + FA94725A27A6EE1B00817677 /* HTTPS.cpp */, + FA94725E27A6EE1B00817677 /* HTTPS.h */, + FA94725527A6EE1B00817677 /* HTTPSClient.cpp */, + FA94725627A6EE1B00817677 /* HTTPSClient.h */, + FA94725D27A6EE1B00817677 /* PlaintextConnection.cpp */, + FA94725727A6EE1B00817677 /* PlaintextConnection.h */, + ); + path = common; + sourceTree = ""; + }; + FA94729827A6F9AC00817677 /* apple */ = { + isa = PBXGroup; + children = ( + FA94729A27A6F9AC00817677 /* NSURLClient.h */, + FA94729927A6F9AC00817677 /* NSURLClient.mm */, + ); + path = apple; + sourceTree = ""; + }; FAAA3FD21F64B3AD00F89E99 /* lua53 */ = { isa = PBXGroup; children = ( @@ -3861,7 +3949,6 @@ FAF6C9C623C2DE2900D7B5BC /* InReadableOrder.cpp */, FAF6C9D523C2DE2900D7B5BC /* Logger.cpp */, FAF6C9D723C2DE2900D7B5BC /* Logger.h */, - FAEC22942534EE6700EBD925 /* NonSemanticDebugPrintf.h */, FAF6C9C923C2DE2900D7B5BC /* spirv.hpp */, FAF6C9CA23C2DE2900D7B5BC /* SpvBuilder.cpp */, FAF6C9C223C2DE2900D7B5BC /* SpvBuilder.h */, @@ -3907,6 +3994,7 @@ 217DFBEA1D9F6D490055D849 /* io.h in Headers */, FA0B79221A958E3B000E1D17 /* delay.h in Headers */, FA0B79481A958E3B000E1D17 /* Vector.h in Headers */, + FA94728427A6EE1B00817677 /* ConnectionClient.h in Headers */, FA0B7CCF1A95902C000E1D17 /* Audio.h in Headers */, FA0B7EC01A95902C000E1D17 /* Thread.h in Headers */, FABDA9E12552448300B5C523 /* b2_wheel_joint.h in Headers */, @@ -3971,6 +4059,7 @@ FA0B7AC31A958EA3000E1D17 /* list.h in Headers */, FA0B7B2D1A958EA3000E1D17 /* core.h in Headers */, FA1E88841DF363DB00E808AA /* Filter.h in Headers */, + FA94727D27A6EE1B00817677 /* HTTPSClient.h in Headers */, 217DFC061D9F6D490055D849 /* tp.lua.h in Headers */, FABDA9E02552448300B5C523 /* b2_polygon_shape.h in Headers */, FAB17BF71ABFC4B100F9BA27 /* lz4hc.h in Headers */, @@ -4103,7 +4192,9 @@ 217DFBFD1D9F6D490055D849 /* smtp.lua.h in Headers */, FABDA9772552448200B5C523 /* b2_shape.h in Headers */, FA27B39F1B498151008A9DCE /* Video.h in Headers */, + FA94727A27A6EE1B00817677 /* Connection.h in Headers */, FA18CF3223DCF67900263725 /* spirv_cfg.hpp in Headers */, + FA94727F27A6EE1B00817677 /* config.h in Headers */, 217DFC041D9F6D490055D849 /* timeout.h in Headers */, FA0B7E7A1A95902C000E1D17 /* wrap_WheelJoint.h in Headers */, FA0B7DD81A95902C000E1D17 /* MathModule.h in Headers */, @@ -4165,6 +4256,7 @@ FA0B7EAB1A95902C000E1D17 /* wrap_Sound.h in Headers */, FA0B7B2C1A958EA3000E1D17 /* checked.h in Headers */, FABDA9A62552448300B5C523 /* b2_polygon_contact.h in Headers */, + FA94728727A6EE1B00817677 /* HTTPS.h in Headers */, FA0B7D2A1A95902C000E1D17 /* wrap_GlyphData.h in Headers */, FACA02F11F5E396B0084B28F /* DataModule.h in Headers */, FABDA9C82552448300B5C523 /* b2_body.h in Headers */, @@ -4176,6 +4268,7 @@ FAF1407F1E20934C00F898D2 /* localintermediate.h in Headers */, FAF1406B1E20934C00F898D2 /* glslang_tab.cpp.h in Headers */, 217DFBF71D9F6D490055D849 /* options.h in Headers */, + FA94728327A6EE1B00817677 /* HTTPRequest.h in Headers */, FAF6C9ED23C2DE2900D7B5BC /* spvIR.h in Headers */, FA0B7E3B1A95902C000E1D17 /* World.h in Headers */, FA0B7DC31A95902C000E1D17 /* wrap_Joystick.h in Headers */, @@ -4186,6 +4279,7 @@ FA0B7E651A95902C000E1D17 /* wrap_PolygonShape.h in Headers */, FA18CEE323DBC6E000263725 /* Metal.h in Headers */, FABDA9952552448300B5C523 /* b2_edge_polygon_contact.h in Headers */, + FA94729D27A6F9AD00817677 /* NSURLClient.h in Headers */, FA0B7AC91A958EA3000E1D17 /* win32.h in Headers */, FABDA9B92552448300B5C523 /* b2_common.h in Headers */, FA0B7DFC1A95902C000E1D17 /* Body.h in Headers */, @@ -4315,6 +4409,7 @@ FA0B7E411A95902C000E1D17 /* wrap_ChainShape.h in Headers */, FA18CF1A23DCF67900263725 /* barrier.hpp in Headers */, FA0B7E171A95902C000E1D17 /* Joint.h in Headers */, + FA94727E27A6EE1B00817677 /* PlaintextConnection.h in Headers */, FA0B793F1A958E3B000E1D17 /* types.h in Headers */, FABDA9BD2552448300B5C523 /* b2_stack_allocator.h in Headers */, FABDA9CF2552448300B5C523 /* b2_mouse_joint.h in Headers */, @@ -4513,6 +4608,7 @@ FA18CF2323DCF67900263725 /* spirv_cfg.cpp in Sources */, FAE64A962071365100BC7981 /* physfs_platform_windows.c in Sources */, FA4B66CA1ABBCF1900558F15 /* Timer.cpp in Sources */, + FA94727927A6EE1B00817677 /* main.cpp in Sources */, FA0B7E921A95902C000E1D17 /* ModPlugDecoder.cpp in Sources */, FA0B7E521A95902C000E1D17 /* wrap_FrictionJoint.cpp in Sources */, FADF54351E3DAE6E00012CC0 /* wrap_SpriteBatch.cpp in Sources */, @@ -4584,6 +4680,7 @@ FA0B7E011A95902C000E1D17 /* CircleShape.cpp in Sources */, FAE64A8C2071363100BC7981 /* physfs_byteorder.c in Sources */, FA0B7E461A95902C000E1D17 /* wrap_Contact.cpp in Sources */, + FA94727C27A6EE1B00817677 /* HTTPSClient.cpp in Sources */, FA0B7D161A95902C000E1D17 /* Font.cpp in Sources */, FA0B7EB61A95902C000E1D17 /* wrap_System.cpp in Sources */, FA0B7DAC1A95902C000E1D17 /* STBHandler.cpp in Sources */, @@ -4642,6 +4739,7 @@ FA0B7DA01A95902C000E1D17 /* KTXHandler.cpp in Sources */, FA0B7CEC1A95902C000E1D17 /* Event.cpp in Sources */, FA27B3AB1B498151008A9DCE /* VideoStream.cpp in Sources */, + FA94728227A6EE1B00817677 /* HTTPS.cpp in Sources */, FA0B7E581A95902C000E1D17 /* wrap_Joint.cpp in Sources */, FAECA1B51F31648A0095D008 /* FormatHandler.cpp in Sources */, FA4F2C0A1DE936E600CA37D7 /* mime.c in Sources */, @@ -4650,6 +4748,8 @@ FA4F2C101DE936FE00CA37D7 /* udp.c in Sources */, FAF6C9E023C2DE2900D7B5BC /* SpvTools.cpp in Sources */, FAE64A8F2071364200BC7981 /* physfs_platform_unix.c in Sources */, + FA94729C27A6F9AD00817677 /* NSURLClient.mm in Sources */, + FA94728927A6EE1B00817677 /* HTTPRequest.cpp in Sources */, FA18CEC623D3AE6800263725 /* wrap_Buffer.cpp in Sources */, FA0B7E2B1A95902C000E1D17 /* RevoluteJoint.cpp in Sources */, FA0B7B291A958EA3000E1D17 /* simplexnoise1234.cpp in Sources */, @@ -4715,6 +4815,7 @@ FA29C0061E12355B00268CD8 /* StreamBuffer.cpp in Sources */, FABDA9B62552448300B5C523 /* b2_fixture.cpp in Sources */, FAE64A882071363100BC7981 /* physfs_archiver_unpacked.c in Sources */, + FA94728627A6EE1B00817677 /* PlaintextConnection.cpp in Sources */, FA18CEDF23DBC6E000263725 /* Shader.mm in Sources */, FA0B7CF51A95902C000E1D17 /* File.cpp in Sources */, FA0B7E341A95902C000E1D17 /* WeldJoint.cpp in Sources */, @@ -4927,6 +5028,7 @@ FAF1406E1E20934C00F898D2 /* Initialize.cpp in Sources */, FAF6C9DF23C2DE2900D7B5BC /* SpvTools.cpp in Sources */, FA0B7EB81A95902C000E1D17 /* Channel.cpp in Sources */, + FA94727827A6EE1B00817677 /* main.cpp in Sources */, 217DFC091D9F6D490055D849 /* unix.c in Sources */, FACA02EE1F5E396B0084B28F /* Compressor.cpp in Sources */, FAF140801E20934C00F898D2 /* parseConst.cpp in Sources */, @@ -4998,6 +5100,7 @@ FA0B7D821A95902C000E1D17 /* CompressedImageData.cpp in Sources */, FA18CF2823DCF67900263725 /* spirv_msl.cpp in Sources */, FAF1409D1E20934C00F898D2 /* reflection.cpp in Sources */, + FA94727B27A6EE1B00817677 /* HTTPSClient.cpp in Sources */, FAAA3FDB1F64B3AD00F89E99 /* lutf8lib.c in Sources */, FA18CEDE23DBC6E000263725 /* Shader.mm in Sources */, FAF1408A1E20934C00F898D2 /* Pp.cpp in Sources */, @@ -5056,6 +5159,7 @@ FA41A3C81C0A1F950084430C /* ASTCHandler.cpp in Sources */, FABDA9DA2552448300B5C523 /* b2_math.cpp in Sources */, FA0B7E781A95902C000E1D17 /* wrap_WheelJoint.cpp in Sources */, + FA94728127A6EE1B00817677 /* HTTPS.cpp in Sources */, FA0B7DDC1A95902C000E1D17 /* wrap_BezierCurve.cpp in Sources */, FA0B7D061A95902C000E1D17 /* wrap_File.cpp in Sources */, FAC7CD871FE35E95006A60C7 /* physfs_archiver_vdf.c in Sources */, @@ -5064,6 +5168,8 @@ FADF54201E3DA52C00012CC0 /* wrap_ParticleSystem.cpp in Sources */, FA0B7D9F1A95902C000E1D17 /* KTXHandler.cpp in Sources */, FA1E88831DF363DB00E808AA /* Filter.cpp in Sources */, + FA94729B27A6F9AD00817677 /* NSURLClient.mm in Sources */, + FA94728827A6EE1B00817677 /* HTTPRequest.cpp in Sources */, FA0B7CEB1A95902C000E1D17 /* Event.cpp in Sources */, FA1557C31CE90BD200AFF582 /* EXRHandler.cpp in Sources */, FA27B3AA1B498151008A9DCE /* VideoStream.cpp in Sources */, @@ -5129,6 +5235,7 @@ FA18CF3F23DCF67900263725 /* spirv_parser.cpp in Sources */, 217DFBF21D9F6D490055D849 /* mime.c in Sources */, 217DFBDF1D9F6D490055D849 /* except.c in Sources */, + FA94728527A6EE1B00817677 /* PlaintextConnection.cpp in Sources */, FA28EBD51E352DB5003446F4 /* FenceSync.cpp in Sources */, FA0B7E1B1A95902C000E1D17 /* MouseJoint.cpp in Sources */, FA0B7CF41A95902C000E1D17 /* File.cpp in Sources */, diff --git a/src/common/config.h b/src/common/config.h index 0b4c137b1..34aa3f2ed 100644 --- a/src/common/config.h +++ b/src/common/config.h @@ -164,6 +164,7 @@ # define LOVE_ENABLE_ENET # define LOVE_ENABLE_LUASOCKET # define LOVE_ENABLE_LUA53 +# define LOVE_ENABLE_LUAHTTPS #endif // Check we have a sane configuration diff --git a/src/libraries/luahttps/Android.mk b/src/libraries/luahttps/Android.mk new file mode 100644 index 000000000..0421533c5 --- /dev/null +++ b/src/libraries/luahttps/Android.mk @@ -0,0 +1,26 @@ +LOCAL_PATH := $(call my-dir) +include $(CLEAR_VARS) + +LOCAL_MODULE := https +LOCAL_MODULE_FILENAME := https + +LOCAL_CFLAGS := -DNOMINMAX +LOCAL_CPPFLAGS := -std=c++11 + +LOCAL_ARM_NEON := true + +LOCAL_C_INCLUDES := \ + ${LOCAL_PATH}/src \ + ${LOCAL_PATH}/src/android + +LOCAL_SRC_FILES := \ + src/lua/main.cpp \ + src/common/HTTPS.cpp \ + src/common/HTTPRequest.cpp \ + src/common/HTTPSClient.cpp \ + src/common/PlaintextConnection.cpp \ + src/android/AndroidClient.cpp + +LOCAL_SHARED_LIBRARIES := liblove + +include $(BUILD_SHARED_LIBRARY) diff --git a/src/libraries/luahttps/java.txt b/src/libraries/luahttps/java.txt new file mode 100644 index 000000000..c42bd619c --- /dev/null +++ b/src/libraries/luahttps/java.txt @@ -0,0 +1 @@ +src/android/java \ No newline at end of file diff --git a/src/libraries/luahttps/license.txt b/src/libraries/luahttps/license.txt new file mode 100644 index 000000000..8054877df --- /dev/null +++ b/src/libraries/luahttps/license.txt @@ -0,0 +1,17 @@ +Copyright (c) 2019-2022 LOVE Development Team + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. diff --git a/src/libraries/luahttps/src/android/AndroidClient.cpp b/src/libraries/luahttps/src/android/AndroidClient.cpp new file mode 100644 index 000000000..2b5b4d7d6 --- /dev/null +++ b/src/libraries/luahttps/src/android/AndroidClient.cpp @@ -0,0 +1,212 @@ +#include "AndroidClient.h" + +#ifdef HTTPS_BACKEND_ANDROID + +#include +#include + +#include + +static std::string replace(const std::string &str, const std::string &from, const std::string &to) +{ + std::stringstream ss; + size_t oldpos = 0; + + while (true) + { + size_t pos = str.find(from, oldpos); + + if (pos == std::string::npos) + { + ss << str.substr(oldpos); + break; + } + + ss << str.substr(oldpos, pos - oldpos) << to; + oldpos = pos + from.length(); + } + + return ss.str(); +} + +static jstring newStringUTF(JNIEnv *env, const std::string &str) +{ + // We want std::string that contains null byte, hence length of 1. + static std::string null("", 1); + + std::string newStr = replace(str, null, "\xC0\x80"); + jstring jstr = env->NewStringUTF(newStr.c_str()); + return jstr; +} + +static std::string getStringUTF(JNIEnv *env, jstring str) +{ + // We want std::string that contains null byte, hence length of 1. + static std::string null("", 1); + + const char *c = env->GetStringUTFChars(str, nullptr); + std::string result = replace(c, "\xC0\x80", null); + + env->ReleaseStringUTFChars(str, c); + return result; +} + +AndroidClient::AndroidClient() +: HTTPSClient() +, SDL_AndroidGetJNIEnv(nullptr) +{ + // Look for SDL_AndroidGetJNIEnv + SDL_AndroidGetJNIEnv = (decltype(SDL_AndroidGetJNIEnv)) dlsym(RTLD_DEFAULT, "SDL_AndroidGetJNIEnv"); + // Look for SDL_AndroidGetActivity + SDL_AndroidGetActivity = (decltype(SDL_AndroidGetActivity)) dlsym(RTLD_DEFAULT, "SDL_AndroidGetActivity"); +} + +bool AndroidClient::valid() const +{ + if (SDL_AndroidGetJNIEnv && SDL_AndroidGetActivity) + { + JNIEnv *env = SDL_AndroidGetJNIEnv(); + + if (env) + { + jclass httpsClass = getHTTPSClass(); + if (env->ExceptionCheck()) + { + env->ExceptionClear(); + return false; + } + + env->DeleteLocalRef(httpsClass); + return true; + } + } + + return false; +} + +HTTPSClient::Reply AndroidClient::request(const HTTPSClient::Request &req) +{ + JNIEnv *env = SDL_AndroidGetJNIEnv(); + jclass httpsClass = getHTTPSClass(); + + if (httpsClass == nullptr) + { + env->ExceptionClear(); + throw std::runtime_error("Could not find class 'org.love2d.luahttps.LuaHTTPS'"); + } + + jmethodID constructor = env->GetMethodID(httpsClass, "", "()V"); + jmethodID setURL = env->GetMethodID(httpsClass, "setUrl", "(Ljava/lang/String;)V"); + jmethodID request = env->GetMethodID(httpsClass, "request", "()Z"); + jmethodID getInterleavedHeaders = env->GetMethodID(httpsClass, "getInterleavedHeaders", "()[Ljava/lang/String;"); + jmethodID getResponse = env->GetMethodID(httpsClass, "getResponse", "()[B"); + jmethodID getResponseCode = env->GetMethodID(httpsClass, "getResponseCode", "()I"); + + jobject httpsObject = env->NewObject(httpsClass, constructor); + + // Set URL + jstring url = env->NewStringUTF(req.url.c_str()); + env->CallVoidMethod(httpsObject, setURL, url); + env->DeleteLocalRef(url); + + // Set post data + if (req.method == Request::POST) + { + jmethodID setPostData = env->GetMethodID(httpsClass, "setPostData", "([B)V"); + jbyteArray byteArray = env->NewByteArray((jsize) req.postdata.length()); + jbyte *byteArrayData = env->GetByteArrayElements(byteArray, nullptr); + + memcpy(byteArrayData, req.postdata.data(), req.postdata.length()); + env->ReleaseByteArrayElements(byteArray, byteArrayData, 0); + + env->CallVoidMethod(httpsObject, setPostData, byteArray); + env->DeleteLocalRef(byteArray); + } + + // Set headers + if (!req.headers.empty()) + { + jmethodID addHeader = env->GetMethodID(httpsClass, "addHeader", "(Ljava/lang/String;Ljava/lang/String;)V"); + + for (auto &header : req.headers) + { + jstring headerKey = newStringUTF(env, header.first); + jstring headerValue = newStringUTF(env, header.second); + + env->CallVoidMethod(httpsObject, addHeader, headerKey, headerValue); + env->DeleteLocalRef(headerKey); + env->DeleteLocalRef(headerValue); + } + } + + // Do request + HTTPSClient::Reply response; + jboolean status = env->CallBooleanMethod(httpsObject, request); + + // Get response + response.responseCode = env->CallIntMethod(httpsObject, getResponseCode); + + if (status) + { + // Get headers + jobjectArray interleavedHeaders = (jobjectArray) env->CallObjectMethod(httpsObject, getInterleavedHeaders); + int len = env->GetArrayLength(interleavedHeaders); + + for (int i = 0; i < len; i += 2) + { + jstring key = (jstring) env->GetObjectArrayElement(interleavedHeaders, i); + jstring value = (jstring) env->GetObjectArrayElement(interleavedHeaders, i + 1); + + response.headers[getStringUTF(env, key)] = getStringUTF(env, value); + + env->DeleteLocalRef(key); + env->DeleteLocalRef(value); + } + + env->DeleteLocalRef(interleavedHeaders); + + // Get response data + jbyteArray responseData = (jbyteArray) env->CallObjectMethod(httpsObject, getResponse); + + if (responseData) + { + int len = env->GetArrayLength(responseData); + jbyte *responseByte = env->GetByteArrayElements(responseData, nullptr); + + response.body = std::string((char *) responseByte, len); + + env->DeleteLocalRef(responseData); + } + } + + return response; +} + +jclass AndroidClient::getHTTPSClass() const +{ + JNIEnv *env = SDL_AndroidGetJNIEnv(); + + jclass classLoaderClass = env->FindClass("java/lang/ClassLoader"); + jmethodID loadClass = env->GetMethodID(classLoaderClass, "loadClass", "(Ljava/lang/String;)Ljava/lang/Class;"); + + jobject activity = SDL_AndroidGetActivity(); + + if (activity == nullptr) + return nullptr; + + jclass gameActivity = env->GetObjectClass(activity); + jmethodID getLoader = env->GetMethodID(gameActivity, "getClassLoader", "()Ljava/lang/ClassLoader;"); + jobject classLoader = env->CallObjectMethod(activity, getLoader); + + jstring httpsClassName = env->NewStringUTF("org.love2d.luahttps.LuaHTTPS"); + jclass httpsClass = (jclass) env->CallObjectMethod(classLoader, loadClass, httpsClassName); + + env->DeleteLocalRef(gameActivity); + env->DeleteLocalRef(httpsClassName); + env->DeleteLocalRef(activity); + env->DeleteLocalRef(classLoaderClass); + + return httpsClass; +} + +#endif // HTTPS_BACKEND_ANDROID diff --git a/src/libraries/luahttps/src/android/AndroidClient.h b/src/libraries/luahttps/src/android/AndroidClient.h new file mode 100644 index 000000000..59ddd06d7 --- /dev/null +++ b/src/libraries/luahttps/src/android/AndroidClient.h @@ -0,0 +1,26 @@ +#pragma once + +#include "../common/config.h" + +#ifdef HTTPS_BACKEND_ANDROID + +#include + +#include "../common/HTTPSClient.h" + +class AndroidClient: public HTTPSClient +{ +public: + AndroidClient(); + + bool valid() const override; + HTTPSClient::Reply request(const HTTPSClient::Request &req) override; + +private: + JNIEnv *(*SDL_AndroidGetJNIEnv)(); + jobject (*SDL_AndroidGetActivity)(); + + jclass getHTTPSClass() const; +}; + +#endif diff --git a/src/libraries/luahttps/src/android/java/org/love2d/luahttps/LuaHTTPS.java b/src/libraries/luahttps/src/android/java/org/love2d/luahttps/LuaHTTPS.java new file mode 100644 index 000000000..0c9982580 --- /dev/null +++ b/src/libraries/luahttps/src/android/java/org/love2d/luahttps/LuaHTTPS.java @@ -0,0 +1,171 @@ +package org.love2d.luahttps; + +import android.text.TextUtils; +import android.util.Log; + +import androidx.annotation.Keep; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.HttpURLConnection; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +@Keep +class LuaHTTPS { + static private String TAG = "LuaHTTPS"; + + private String urlString; + private byte[] postData; + private byte[] response; + private int responseCode; + private HashMap headers; + + public LuaHTTPS() { + headers = new HashMap(); + reset(); + } + + public void reset() { + urlString = null; + postData = null; + response = null; + responseCode = 0; + headers.clear(); + } + + @Keep + public void setUrl(String url) { + urlString = url; + } + + @Keep + public void setPostData(byte[] postData) { + this.postData = postData; + } + + @Keep + public void addHeader(String key, String value) { + headers.put(key, value); + } + + @Keep + public String[] getInterleavedHeaders() { + ArrayList resultInterleaved = new ArrayList(); + + for (Map.Entry header: headers.entrySet()) { + String key = header.getKey(); + String value = header.getValue(); + + if (key != null && value != null) { + resultInterleaved.add(key); + resultInterleaved.add(value); + } + } + + String[] result = new String[resultInterleaved.size()]; + resultInterleaved.toArray(result); + return result; + } + + @Keep + public int getResponseCode() { + return responseCode; + } + + @Keep + public byte[] getResponse() { + return response; + } + + @Keep + public boolean request() { + if (urlString == null) { + return false; + } + + URL url; + try { + url = new URL(urlString); + + if (!url.getProtocol().equals("http") && !url.getProtocol().equals("https")) { + return false; + } + } catch (MalformedURLException e) { + Log.e(TAG, "Error", e); + return false; + } + + HttpURLConnection connection; + try { + connection = (HttpURLConnection) url.openConnection(); + } catch (IOException e) { + Log.e(TAG, "Error", e); + return false; + } + + // Set header + for (Map.Entry headerData: headers.entrySet()) { + connection.setRequestProperty(headerData.getKey(), headerData.getValue()); + } + + // Set post data + if (postData != null) { + connection.setDoOutput(true); + connection.setChunkedStreamingMode(0); + + try { + OutputStream out = connection.getOutputStream(); + out.write(postData); + } catch (Exception e) { + Log.e(TAG, "Error", e); + connection.disconnect(); + return false; + } + } + + // Request + try { + InputStream in; + + // Set response code + responseCode = connection.getResponseCode(); + if (responseCode >= 400) { + in = connection.getErrorStream(); + } else { + in = connection.getInputStream(); + } + + // Read response + int readed; + byte[] temp = new byte[4096]; + ByteArrayOutputStream response = new ByteArrayOutputStream(); + + while ((readed = in.read(temp)) != -1) { + response.write(temp, 0, readed); + } + + this.response = response.toByteArray(); + response.close(); + + // Read headers + headers.clear(); + for (Map.Entry> header: connection.getHeaderFields().entrySet()) { + headers.put(header.getKey(), TextUtils.join(", ", header.getValue())); + } + } catch (Exception e) { + Log.e(TAG, "Error", e); + connection.disconnect(); + return false; + } + + connection.disconnect(); + return true; + } +} diff --git a/src/libraries/luahttps/src/apple/NSURLClient.h b/src/libraries/luahttps/src/apple/NSURLClient.h new file mode 100644 index 000000000..b98dab6cf --- /dev/null +++ b/src/libraries/luahttps/src/apple/NSURLClient.h @@ -0,0 +1,16 @@ +#pragma once + +#include "../common/config.h" + +#ifdef HTTPS_BACKEND_NSURL + +#include "../common/HTTPSClient.h" + +class NSURLClient : public HTTPSClient +{ +public: + virtual bool valid() const override; + virtual HTTPSClient::Reply request(const HTTPSClient::Request &req) override; +}; + +#endif // HTTPS_BACKEND_NSURL diff --git a/src/libraries/luahttps/src/apple/NSURLClient.mm b/src/libraries/luahttps/src/apple/NSURLClient.mm new file mode 100644 index 000000000..cc1e3a4ea --- /dev/null +++ b/src/libraries/luahttps/src/apple/NSURLClient.mm @@ -0,0 +1,88 @@ +#include "NSURLClient.h" + +#ifdef HTTPS_BACKEND_NSURL + +#import + +#if ! __has_feature(objc_arc) +#error "ARC is off" +#endif + +bool NSURLClient::valid() const +{ + return true; +} + +static std::string toCppString(NSData *data) +{ + return std::string((const char*) data.bytes, (size_t) data.length); +} + +static std::string toCppString(NSString *str) +{ + return std::string([str UTF8String]); +} + +HTTPSClient::Reply NSURLClient::request(const HTTPSClient::Request &req) +{ @autoreleasepool { + NSURL *url = [NSURL URLWithString:@(req.url.c_str())]; + NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url]; + + NSData *bodydata = nil; + switch(req.method) + { + case Request::GET: + [request setHTTPMethod:@"GET"]; + break; + case Request::POST: + bodydata = [NSData dataWithBytesNoCopy:(void*) req.postdata.data() length:req.postdata.size() freeWhenDone:NO]; + [request setHTTPMethod:@"POST"]; + [request setHTTPBody:bodydata]; + break; + } + + for (auto &header : req.headers) + [request setValue:@(header.second.c_str()) forHTTPHeaderField:@(header.first.c_str())]; + + __block NSHTTPURLResponse *response = nil; + __block NSError *error = nil; + __block NSData *body = nil; + + dispatch_semaphore_t sem = dispatch_semaphore_create(0); + + NSURLSessionTask *task = [[NSURLSession sharedSession] dataTaskWithRequest:request + completionHandler:^(NSData *data, NSURLResponse *resp, NSError *err) { + body = data; + response = (NSHTTPURLResponse *)resp; + error = err; + dispatch_semaphore_signal(sem); + }]; + + [task resume]; + + dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER); + + HTTPSClient::Reply reply; + reply.responseCode = 400; + + if (body) + { + reply.body = toCppString(body); + } + + if (response) + { + reply.responseCode = [response statusCode]; + + NSDictionary *headers = [response allHeaderFields]; + for (NSString *key in headers) + { + NSString *value = headers[key]; + reply.headers[toCppString(key)] = toCppString(value); + } + } + + return reply; +}} + +#endif // HTTPS_BACKEND_NSURL diff --git a/src/libraries/luahttps/src/common/Connection.h b/src/libraries/luahttps/src/common/Connection.h new file mode 100644 index 000000000..815c265ed --- /dev/null +++ b/src/libraries/luahttps/src/common/Connection.h @@ -0,0 +1,14 @@ +#pragma once + +#include +#include + +class Connection +{ +public: + virtual bool connect(const std::string &hostname, uint16_t port) = 0; + virtual size_t read(char *buffer, size_t size) = 0; + virtual size_t write(const char *buffer, size_t size) = 0; + virtual void close() = 0; + virtual ~Connection() {}; +}; diff --git a/src/libraries/luahttps/src/common/ConnectionClient.h b/src/libraries/luahttps/src/common/ConnectionClient.h new file mode 100644 index 000000000..8ddbf671f --- /dev/null +++ b/src/libraries/luahttps/src/common/ConnectionClient.h @@ -0,0 +1,35 @@ +#pragma once + +#include "HTTPSClient.h" +#include "HTTPRequest.h" +#include "Connection.h" + +template +class ConnectionClient : public HTTPSClient +{ +public: + virtual bool valid() const override; + virtual HTTPSClient::Reply request(const HTTPSClient::Request &req) override; + +private: + static Connection *factory(); +}; + +template +bool ConnectionClient::valid() const +{ + return Connection::valid(); +} + +template +Connection *ConnectionClient::factory() +{ + return new Connection(); +} + +template +HTTPSClient::Reply ConnectionClient::request(const HTTPSClient::Request &req) +{ + HTTPRequest request(factory); + return request.request(req); +} diff --git a/src/libraries/luahttps/src/common/HTTPRequest.cpp b/src/libraries/luahttps/src/common/HTTPRequest.cpp new file mode 100644 index 000000000..bdb03c161 --- /dev/null +++ b/src/libraries/luahttps/src/common/HTTPRequest.cpp @@ -0,0 +1,148 @@ +#include +#include +#include +#include +#include + +#include "HTTPRequest.h" +#include "PlaintextConnection.h" + +HTTPRequest::HTTPRequest(ConnectionFactory factory) + : factory(factory) +{ +} + +HTTPSClient::Reply HTTPRequest::request(const HTTPSClient::Request &req) +{ + HTTPSClient::Reply reply; + reply.responseCode = 0; + + auto info = parseUrl(req.url); + if (!info.valid) + return reply; + + std::unique_ptr conn; + if (info.schema == "http") + conn.reset(new PlaintextConnection()); + else if (info.schema == "https") + conn.reset(factory()); + else + throw std::runtime_error("Unknown url schema"); + + if (!conn->connect(info.hostname, info.port)) + return reply; + + // Build the request + { + std::stringstream request; + request << (req.method == HTTPSClient::Request::GET ? "GET " : "POST ") << info.query << " HTTP/1.1\r\n"; + + for (auto &header : req.headers) + request << header.first << ": " << header.second << "\r\n"; + + request << "Connection: Close\r\n"; + + request << "Host: " << info.hostname << "\r\n"; + + if (req.method == HTTPSClient::Request::POST && req.headers.count("Content-Type") == 0) + request << "Content-Type: application/x-www-form-urlencoded\r\n"; + + if (req.method == HTTPSClient::Request::POST) + request << "Content-Length: " << req.postdata.size() << "\r\n"; + + request << "\r\n"; + + if (req.method == HTTPSClient::Request::POST) + request << req.postdata; + + // Send it + std::string requestData = request.str(); + conn->write(requestData.c_str(), requestData.size()); + } + + // Now receive the reply + std::stringstream response; + { + char buffer[8192]; + + while (true) + { + size_t read = conn->read(buffer, sizeof(buffer)); + response.write(buffer, read); + if (read == 0) + break; + } + + conn->close(); + } + + reply.responseCode = 500; + // And parse it + { + std::string protocol; + response >> protocol; + if (protocol != "HTTP/1.1") + return reply; + + response >> reply.responseCode; + response.ignore(std::numeric_limits::max(), '\n'); + + for (std::string line; getline(response, line, '\n') && line != "\r"; ) + { + auto sep = line.find(':'); + reply.headers[line.substr(0, sep)] = line.substr(sep+1, line.size()-sep-1); + } + + auto begin = std::istreambuf_iterator(response); + auto end = std::istreambuf_iterator(); + reply.body = std::string(begin, end); + } + + return reply; +} + +HTTPRequest::DissectedURL HTTPRequest::parseUrl(const std::string &url) +{ + DissectedURL dis; + dis.valid = false; + + // Schema + auto schemaStart = 0; + auto schemaEnd = url.find("://"); + dis.schema = url.substr(schemaStart, schemaEnd-schemaStart); + + // Auth+Hostname+Port + auto connStart = schemaEnd+3; + auto connEnd = url.find('/', connStart); + if (connEnd == std::string::npos) + connEnd = url.size(); + + // TODO: Auth + if (url.find("@", connStart, connEnd-connStart) != std::string::npos) + return dis; + + // Port + auto portStart = url.find(':', connStart); + auto portEnd = connEnd; + if (portStart == std::string::npos || portStart > portEnd) + { + dis.port = dis.schema == "http" ? 80 : 443; + portStart = portEnd; + } + else + dis.port = std::stoi(url.substr(portStart+1, portEnd-portStart-1)); + + // Hostname + auto hostnameStart = connStart; + auto hostnameEnd = portStart; + dis.hostname = url.substr(hostnameStart, hostnameEnd-hostnameStart); + + // And the query + dis.query = url.substr(connEnd); + if (dis.query.size() == 0) + dis.query = "/"; + + dis.valid = true; + + return dis; +} diff --git a/src/libraries/luahttps/src/common/HTTPRequest.h b/src/libraries/luahttps/src/common/HTTPRequest.h new file mode 100644 index 000000000..14722d654 --- /dev/null +++ b/src/libraries/luahttps/src/common/HTTPRequest.h @@ -0,0 +1,30 @@ +#pragma once + +#include + +#include "HTTPSClient.h" +#include "Connection.h" + +class HTTPRequest +{ +public: + typedef std::function ConnectionFactory; + HTTPRequest(ConnectionFactory factory); + + HTTPSClient::Reply request(const HTTPSClient::Request &req); + +private: + ConnectionFactory factory; + + struct DissectedURL + { + bool valid; + std::string schema; + std::string hostname; + uint16_t port; + std::string query; + // TODO: Auth? + }; + + DissectedURL parseUrl(const std::string &url); +}; diff --git a/src/libraries/luahttps/src/common/HTTPS.cpp b/src/libraries/luahttps/src/common/HTTPS.cpp new file mode 100644 index 000000000..d4ca45452 --- /dev/null +++ b/src/libraries/luahttps/src/common/HTTPS.cpp @@ -0,0 +1,69 @@ +#include "HTTPS.h" +#include "config.h" +#include "ConnectionClient.h" + +#include + +#ifdef HTTPS_BACKEND_CURL +# include "../generic/CurlClient.h" +#endif +#ifdef HTTPS_BACKEND_OPENSSL +# include "../generic/OpenSSLConnection.h" +#endif +#ifdef HTTPS_BACKEND_SCHANNEL +# include "../windows/SChannelConnection.h" +#endif +#ifdef HTTPS_BACKEND_NSURL +# include "../apple/NSURLClient.h" +#endif +#ifdef HTTPS_BACKEND_ANDROID +# include "../android/AndroidClient.h" +#endif + +#ifdef HTTPS_BACKEND_CURL + static CurlClient curlclient; +#endif +#ifdef HTTPS_BACKEND_OPENSSL + static ConnectionClient opensslclient; +#endif +#ifdef HTTPS_BACKEND_SCHANNEL + static ConnectionClient schannelclient; +#endif +#ifdef HTTPS_BACKEND_NSURL + static NSURLClient nsurlclient; +#endif +#ifdef HTTPS_BACKEND_ANDROID + static AndroidClient androidclient; +#endif + +static HTTPSClient *clients[] = { +#ifdef HTTPS_BACKEND_CURL + &curlclient, +#endif +#ifdef HTTPS_BACKEND_OPENSSL + &opensslclient, +#endif +#ifdef HTTPS_BACKEND_SCHANNEL + &schannelclient, +#endif +#ifdef HTTPS_BACKEND_NSURL + &nsurlclient, +#endif +#ifdef HTTPS_BACKEND_ANDROID + &androidclient, +#endif + nullptr, +}; + +HTTPSClient::Reply request(const HTTPSClient::Request &req) +{ + for (size_t i = 0; clients[i]; ++i) + { + HTTPSClient &client = *clients[i]; + + if (client.valid()) + return client.request(req); + } + + throw std::runtime_error("No applicable HTTPS implementation found"); +} diff --git a/src/libraries/luahttps/src/common/HTTPS.h b/src/libraries/luahttps/src/common/HTTPS.h new file mode 100644 index 000000000..65f72e39b --- /dev/null +++ b/src/libraries/luahttps/src/common/HTTPS.h @@ -0,0 +1,5 @@ +#pragma once + +#include "HTTPSClient.h" + +HTTPSClient::Reply request(const HTTPSClient::Request &req); diff --git a/src/libraries/luahttps/src/common/HTTPSClient.cpp b/src/libraries/luahttps/src/common/HTTPSClient.cpp new file mode 100644 index 000000000..09f2204d8 --- /dev/null +++ b/src/libraries/luahttps/src/common/HTTPSClient.cpp @@ -0,0 +1,37 @@ +#include +#include + +#include "HTTPSClient.h" + +// This may not be the order you expect, as shorter strings always compare less, +// but it's sufficient for our map +bool HTTPSClient::ci_string_less::operator()(const std::string &lhs, const std::string &rhs) const +{ + const size_t lhs_size = lhs.size(); + const size_t rhs_size = rhs.size(); + const size_t steps = std::min(lhs_size, rhs_size); + + if (lhs_size < rhs_size) + return true; + else if (lhs_size > rhs_size) + return false; + + for (size_t i = 0; i < steps; ++i) + { + char l = std::tolower(lhs[i]); + char r = std::tolower(rhs[i]); + if (l < r) + return true; + else if (l > r) + return false; + } + + return false; +} + +HTTPSClient::Request::Request(const std::string &url) + : url(url) + , method(GET) +{ +} + diff --git a/src/libraries/luahttps/src/common/HTTPSClient.h b/src/libraries/luahttps/src/common/HTTPSClient.h new file mode 100644 index 000000000..c90ccfae4 --- /dev/null +++ b/src/libraries/luahttps/src/common/HTTPSClient.h @@ -0,0 +1,40 @@ +#pragma once + +#include +#include +#include + +class HTTPSClient +{ +public: + struct ci_string_less + { + bool operator()(const std::string &lhs, const std::string &rhs) const; + }; + using header_map = std::map; + + struct Request + { + Request(const std::string &url); + + header_map headers; + std::string url; + std::string postdata; + + enum Method + { + GET, + POST, + } method; + }; + + struct Reply + { + header_map headers; + std::string body; + int responseCode; + }; + + virtual bool valid() const = 0; + virtual Reply request(const Request &req) = 0; +}; diff --git a/src/libraries/luahttps/src/common/PlaintextConnection.cpp b/src/libraries/luahttps/src/common/PlaintextConnection.cpp new file mode 100644 index 000000000..95bcd37dc --- /dev/null +++ b/src/libraries/luahttps/src/common/PlaintextConnection.cpp @@ -0,0 +1,99 @@ +#include "config.h" +#include +#ifndef HTTPS_USE_WINSOCK +# include +# include +# include +# include +#else +# include +# include +#endif // HTTPS_USE_WINSOCK + +#include "PlaintextConnection.h" + +#ifdef HTTPS_USE_WINSOCK + static void close(int fd) + { + closesocket(fd); + } +#endif // HTTPS_USE_WINSOCK + +PlaintextConnection::PlaintextConnection() + : fd(-1) +{ +#ifdef HTTPS_USE_WINSOCK + static bool wsaInit = false; + if (!wsaInit) + { + WSADATA data; + WSAStartup(MAKEWORD(2, 2), &data); + } +#endif // HTTPS_USE_WINSOCK +} + +PlaintextConnection::~PlaintextConnection() +{ + if (fd != -1) + ::close(fd); +} + +bool PlaintextConnection::connect(const std::string &hostname, uint16_t port) +{ + addrinfo hints; + std::memset(&hints, 0, sizeof(hints)); + hints.ai_flags = hints.ai_protocol = 0; + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + + addrinfo *addrs = nullptr; + std::string portString = std::to_string(port); + getaddrinfo(hostname.c_str(), portString.c_str(), &hints, &addrs); + + // Try all addresses returned + bool connected = false; + for (addrinfo *addr = addrs; !connected && addr; addr = addr->ai_next) + { + fd = socket(addr->ai_family, SOCK_STREAM, 0); + connected = ::connect(fd, addr->ai_addr, addr->ai_addrlen) == 0; + if (!connected) + ::close(fd); + } + + freeaddrinfo(addrs); + + if (!connected) + { + fd = -1; + return false; + } + + return true; +} + +size_t PlaintextConnection::read(char *buffer, size_t size) +{ + auto read = ::recv(fd, buffer, size, 0); + if (read < 0) + read = 0; + return static_cast(read); +} + +size_t PlaintextConnection::write(const char *buffer, size_t size) +{ + auto written = ::send(fd, buffer, size, 0); + if (written < 0) + written = 0; + return static_cast(written); +} + +void PlaintextConnection::close() +{ + ::close(fd); + fd = -1; +} + +int PlaintextConnection::getFd() const +{ + return fd; +} diff --git a/src/libraries/luahttps/src/common/PlaintextConnection.h b/src/libraries/luahttps/src/common/PlaintextConnection.h new file mode 100644 index 000000000..a7c05c1f2 --- /dev/null +++ b/src/libraries/luahttps/src/common/PlaintextConnection.h @@ -0,0 +1,19 @@ +#pragma once + +#include "Connection.h" + +class PlaintextConnection : public Connection +{ +public: + PlaintextConnection(); + virtual bool connect(const std::string &hostname, uint16_t port); + virtual size_t read(char *buffer, size_t size); + virtual size_t write(const char *buffer, size_t size); + virtual void close(); + virtual ~PlaintextConnection(); + + int getFd() const; + +private: + int fd; +}; diff --git a/src/libraries/luahttps/src/generic/CurlClient.cpp b/src/libraries/luahttps/src/generic/CurlClient.cpp new file mode 100644 index 000000000..31c62c2d3 --- /dev/null +++ b/src/libraries/luahttps/src/generic/CurlClient.cpp @@ -0,0 +1,126 @@ +#include "CurlClient.h" + +#ifdef HTTPS_BACKEND_CURL + +#include +#include +#include +#include + +CurlClient::Curl::Curl() +{ + void *handle = dlopen("libcurl.so", RTLD_LAZY); + if (!handle) + { + loaded = false; + return; + } + + void (*global_init)() = (void(*)()) dlsym(handle, "curl_global_init"); + easy_init = (CURL*(*)()) dlsym(handle, "curl_easy_init"); + easy_cleanup = (void(*)(CURL*)) dlsym(handle, "curl_easy_cleanup"); + easy_setopt = (CURLcode(*)(CURL*,CURLoption,...)) dlsym(handle, "curl_easy_setopt"); + easy_perform = (CURLcode(*)(CURL*)) dlsym(handle, "curl_easy_perform"); + easy_getinfo = (CURLcode(*)(CURL*,CURLINFO,...)) dlsym(handle, "curl_easy_getinfo"); + slist_append = (curl_slist*(*)(curl_slist*,const char*)) dlsym(handle, "curl_slist_append"); + slist_free_all = (void(*)(curl_slist*)) dlsym(handle, "curl_slist_free_all"); + + loaded = (global_init && easy_init && easy_cleanup && easy_setopt && easy_perform && easy_getinfo && slist_append && slist_free_all); + + if (!loaded) + return; + + global_init(); +} + +static size_t stringstreamWriter(char *ptr, size_t size, size_t nmemb, void *userdata) +{ + std::stringstream *ss = (std::stringstream*) userdata; + size_t count = size*nmemb; + ss->write(ptr, count); + return count; +} + +static size_t headerWriter(char *ptr, size_t size, size_t nmemb, void *userdata) +{ + std::map &headers = *((std::map*) userdata); + size_t count = size*nmemb; + std::string line(ptr, count); + size_t split = line.find(':'); + size_t newline = line.find('\r'); + if (newline == std::string::npos) + newline = line.size(); + + if (split != std::string::npos) + headers[line.substr(0, split)] = line.substr(split+1, newline-split-1); + return count; +} + +bool CurlClient::valid() const +{ + return curl.loaded; +} + +HTTPSClient::Reply CurlClient::request(const HTTPSClient::Request &req) +{ + Reply reply; + reply.responseCode = 400; + + CURL *handle = curl.easy_init(); + if (!handle) + throw std::runtime_error("Could not create curl request"); + + curl.easy_setopt(handle, CURLOPT_URL, req.url.c_str()); + curl.easy_setopt(handle, CURLOPT_FOLLOWLOCATION, 1L); + + if (req.method == Request::POST) + { + curl.easy_setopt(handle, CURLOPT_POST, 1L); + curl.easy_setopt(handle, CURLOPT_POSTFIELDS, req.postdata.c_str()); + curl.easy_setopt(handle, CURLOPT_POSTFIELDSIZE, req.postdata.size()); + } + + // Curl doesn't copy memory, keep the strings around + std::vector lines; + for (auto &header : req.headers) + { + std::stringstream line; + line << header.first << ": " << header.second; + lines.push_back(line.str()); + } + + curl_slist *sendHeaders = nullptr; + for (auto &line : lines) + sendHeaders = curl.slist_append(sendHeaders, line.c_str()); + + if (sendHeaders) + curl.easy_setopt(handle, CURLOPT_HTTPHEADER, sendHeaders); + + std::stringstream body; + + curl.easy_setopt(handle, CURLOPT_WRITEFUNCTION, stringstreamWriter); + curl.easy_setopt(handle, CURLOPT_WRITEDATA, &body); + + curl.easy_setopt(handle, CURLOPT_HEADERFUNCTION, headerWriter); + curl.easy_setopt(handle, CURLOPT_HEADERDATA, &reply.headers); + + curl.easy_perform(handle); + + if (sendHeaders) + curl.slist_free_all(sendHeaders); + + { + long responseCode; + curl.easy_getinfo(handle, CURLINFO_RESPONSE_CODE, &responseCode); + reply.responseCode = (int) responseCode; + } + + reply.body = body.str(); + + curl.easy_cleanup(handle); + return reply; +} + +CurlClient::Curl CurlClient::curl; + +#endif // HTTPS_BACKEND_CURL diff --git a/src/libraries/luahttps/src/generic/CurlClient.h b/src/libraries/luahttps/src/generic/CurlClient.h new file mode 100644 index 000000000..9274fdfc5 --- /dev/null +++ b/src/libraries/luahttps/src/generic/CurlClient.h @@ -0,0 +1,34 @@ +#pragma once + +#include "../common/config.h" + +#ifdef HTTPS_BACKEND_CURL + +#include + +#include "../common/HTTPSClient.h" + +class CurlClient : public HTTPSClient +{ +public: + virtual bool valid() const override; + virtual HTTPSClient::Reply request(const HTTPSClient::Request &req) override; + +private: + static struct Curl + { + Curl(); + bool loaded; + + CURL *(*easy_init)(); + void (*easy_cleanup)(CURL *handle); + CURLcode (*easy_setopt)(CURL *handle, CURLoption option, ...); + CURLcode (*easy_perform)(CURL *easy_handle); + CURLcode (*easy_getinfo)(CURL *curl, CURLINFO info, ...); + + curl_slist *(*slist_append)(curl_slist *list, const char *string); + void (*slist_free_all)(curl_slist *list); + } curl; +}; + +#endif // HTTPS_BACKEND_CURL diff --git a/src/libraries/luahttps/src/generic/OpenSSLConnection.cpp b/src/libraries/luahttps/src/generic/OpenSSLConnection.cpp new file mode 100644 index 000000000..e1f2dd7fa --- /dev/null +++ b/src/libraries/luahttps/src/generic/OpenSSLConnection.cpp @@ -0,0 +1,148 @@ +#include "OpenSSLConnection.h" + +#ifdef HTTPS_BACKEND_OPENSSL + +#include + +// Not present in openssl 1.1 headers +#define SSL_CTRL_OPTIONS 32 + +template +static inline bool loadSymbol(T &var, void *handle, const char *name) +{ + var = reinterpret_cast(dlsym(handle, name)); + return var != nullptr; +} + +OpenSSLConnection::SSLFuncs::SSLFuncs() +{ + valid = false; + + // Try OpenSSL 1.1 + void *sslhandle = dlopen("libssl.so.1.1", RTLD_LAZY); + void *cryptohandle = dlopen("libcrypto.so.1.1", RTLD_LAZY); + // Try OpenSSL 1.0 + if (!sslhandle || !cryptohandle) + { + sslhandle = dlopen("libssl.so.1.0.0", RTLD_LAZY); + cryptohandle = dlopen("libcrypto.so.1.0.0", RTLD_LAZY); + } + // Try OpenSSL without version + if (!sslhandle || !cryptohandle) + { + sslhandle = dlopen("libssl.so", RTLD_LAZY); + cryptohandle = dlopen("libcrypto.so", RTLD_LAZY); + } + // Give up + if (!sslhandle || !cryptohandle) + return; + + valid = true; + valid = valid && (loadSymbol(library_init, sslhandle, "SSL_library_init") || + loadSymbol(init_ssl, sslhandle, "OPENSSL_init_ssl")); + + valid = valid && loadSymbol(CTX_new, sslhandle, "SSL_CTX_new"); + valid = valid && loadSymbol(CTX_ctrl, sslhandle, "SSL_CTX_ctrl"); + valid = valid && loadSymbol(CTX_set_verify, sslhandle, "SSL_CTX_set_verify"); + valid = valid && loadSymbol(CTX_set_default_verify_paths, sslhandle, "SSL_CTX_set_default_verify_paths"); + valid = valid && loadSymbol(CTX_free, sslhandle, "SSL_CTX_free"); + + valid = valid && loadSymbol(SSL_new, sslhandle, "SSL_new"); + valid = valid && loadSymbol(SSL_free, sslhandle, "SSL_free"); + valid = valid && loadSymbol(set_fd, sslhandle, "SSL_set_fd"); + valid = valid && loadSymbol(connect, sslhandle, "SSL_connect"); + valid = valid && loadSymbol(read, sslhandle, "SSL_read"); + valid = valid && loadSymbol(write, sslhandle, "SSL_write"); + valid = valid && loadSymbol(shutdown, sslhandle, "SSL_shutdown"); + valid = valid && loadSymbol(get_verify_result, sslhandle, "SSL_get_verify_result"); + valid = valid && loadSymbol(get_peer_certificate, sslhandle, "SSL_get_peer_certificate"); + + valid = valid && (loadSymbol(SSLv23_method, sslhandle, "SSLv23_method") || + loadSymbol(SSLv23_method, sslhandle, "TLS_method")); + + valid = valid && loadSymbol(check_host, cryptohandle, "X509_check_host"); + + if (library_init) + library_init(); + else if(init_ssl) + init_ssl(0, nullptr); + // else not valid +} + +bool OpenSSLConnection::valid() +{ + return ssl.valid; +} + +OpenSSLConnection::OpenSSLConnection() + : conn(nullptr) +{ + context = ssl.CTX_new(ssl.SSLv23_method()); + if (!context) + return; + + ssl.CTX_ctrl(context, SSL_CTRL_OPTIONS, SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3, nullptr); + ssl.CTX_set_verify(context, SSL_VERIFY_PEER, nullptr); + ssl.CTX_set_default_verify_paths(context); +} + +OpenSSLConnection::~OpenSSLConnection() +{ + if (conn) + ssl.SSL_free(conn); + + if (context) + ssl.CTX_free(context); +} + +bool OpenSSLConnection::connect(const std::string &hostname, uint16_t port) +{ + if (!context) + return false; + + if (!socket.connect(hostname, port)) + return false; + + conn = ssl.SSL_new(context); + if (!conn) + { + socket.close(); + return false; + } + + ssl.set_fd(conn, socket.getFd()); + if (ssl.connect(conn) != 1 || ssl.get_verify_result(conn) != X509_V_OK) + { + socket.close(); + return false; + } + + X509 *cert = ssl.get_peer_certificate(conn); + if (ssl.check_host(cert, hostname.c_str(), hostname.size(), 0, nullptr) != 1) + { + close(); + return false; + } + + return true; +} + +size_t OpenSSLConnection::read(char *buffer, size_t size) +{ + return ssl.read(conn, buffer, (int) size); +} + +size_t OpenSSLConnection::write(const char *buffer, size_t size) +{ + return ssl.write(conn, buffer, (int) size); +} + +void OpenSSLConnection::close() +{ + ssl.shutdown(conn); + socket.close(); +} + +OpenSSLConnection::SSLFuncs OpenSSLConnection::ssl; + +#endif // HTTPS_BACKEND_OPENSSL diff --git a/src/libraries/luahttps/src/generic/OpenSSLConnection.h b/src/libraries/luahttps/src/generic/OpenSSLConnection.h new file mode 100644 index 000000000..5be17be06 --- /dev/null +++ b/src/libraries/luahttps/src/generic/OpenSSLConnection.h @@ -0,0 +1,60 @@ +#pragma once + +#include "../common/config.h" + +#ifdef HTTPS_BACKEND_OPENSSL + +#include + +#include "../common/Connection.h" +#include "../common/PlaintextConnection.h" + +class OpenSSLConnection : public Connection +{ +public: + OpenSSLConnection(); + virtual bool connect(const std::string &hostname, uint16_t port) override; + virtual size_t read(char *buffer, size_t size) override; + virtual size_t write(const char *buffer, size_t size) override; + virtual void close() override; + virtual ~OpenSSLConnection(); + + static bool valid(); + +private: + PlaintextConnection socket; + SSL_CTX *context; + SSL *conn; + + struct SSLFuncs + { + SSLFuncs(); + bool valid; + + int (*library_init)(); + int (*init_ssl)(uint64_t opts, const void *settings); + + SSL_CTX *(*CTX_new)(const SSL_METHOD *method); + long (*CTX_ctrl)(SSL_CTX *ctx, int cmd, long larg, void *parg); + void (*CTX_set_verify)(SSL_CTX *ctx, int mode, void *verify_callback); + int (*CTX_set_default_verify_paths)(SSL_CTX *ctx); + void (*CTX_free)(SSL_CTX *ctx); + + SSL *(*SSL_new)(SSL_CTX *ctx); + void (*SSL_free)(SSL *ctx); + int (*set_fd)(SSL *ssl, int fd); + int (*connect)(SSL *ssl); + int (*read)(SSL *ssl, void *buf, int num); + int (*write)(SSL *ssl, const void *buf, int num); + int (*shutdown)(SSL *ssl); + long (*get_verify_result)(const SSL *ssl); + X509 *(*get_peer_certificate)(const SSL *ssl); + + const SSL_METHOD *(*SSLv23_method)(); + + int (*check_host)(X509 *cert, const char *name, size_t namelen, unsigned int flags, char **peername); + }; + static SSLFuncs ssl; +}; + +#endif // HTTPS_BACKEND_OPENSSL diff --git a/src/libraries/luahttps/src/lua/main.cpp b/src/libraries/luahttps/src/lua/main.cpp new file mode 100644 index 000000000..9ebff25d5 --- /dev/null +++ b/src/libraries/luahttps/src/lua/main.cpp @@ -0,0 +1,119 @@ +#include + +#include "../common/HTTPS.h" +#include "../common/config.h" + +static std::string w_checkstring(lua_State *L, int idx) +{ + size_t len; + const char *str = luaL_checklstring(L, idx, &len); + return std::string(str, len); +} + +static void w_pushstring(lua_State *L, const std::string &str) +{ + lua_pushlstring(L, str.data(), str.size()); +} + +static void w_readheaders(lua_State *L, int idx, HTTPSClient::header_map &headers) +{ + if (idx < 0) + idx += lua_gettop(L) + 1; + + lua_pushnil(L); + while (lua_next(L, idx)) + { + auto header = w_checkstring(L, -2); + headers[header] = w_checkstring(L, -1); + lua_pop(L, 1); + } + lua_pop(L, 1); +} + +static HTTPSClient::Request::Method w_optmethod(lua_State *L, int idx, HTTPSClient::Request::Method defaultMethod) +{ + if (lua_isnoneornil(L, idx)) + return defaultMethod; + + auto str = w_checkstring(L, idx); + if (str == "get") + return HTTPSClient::Request::GET; + else if (str == "post") + return HTTPSClient::Request::POST; + else + luaL_argerror(L, idx, "expected one of \"get\" or \"set\""); + + return defaultMethod; +} + +static int w_request(lua_State *L) +{ + auto url = w_checkstring(L, 1); + HTTPSClient::Request req(url); + + bool advanced = false; + + if (lua_istable(L, 2)) + { + advanced = true; + + HTTPSClient::Request::Method defaultMethod = HTTPSClient::Request::GET; + + lua_getfield(L, 2, "data"); + if (!lua_isnoneornil(L, -1)) + { + req.postdata = w_checkstring(L, -1); + defaultMethod = HTTPSClient::Request::POST; + } + lua_pop(L, 1); + + lua_getfield(L, 2, "method"); + req.method = w_optmethod(L, -1, defaultMethod); + lua_pop(L, 1); + + lua_getfield(L, 2, "headers"); + if (!lua_isnoneornil(L, -1)) + w_readheaders(L, -1, req.headers); + lua_pop(L, 1); + } + + HTTPSClient::Reply reply; + + try + { + reply = request(req); + } + catch (const std::exception& e) + { + std::string errorMessage = e.what(); + lua_pushnil(L); + lua_pushstring(L, errorMessage.c_str()); + return 2; + } + + lua_pushinteger(L, reply.responseCode); + w_pushstring(L, reply.body); + + if (advanced) + { + lua_newtable(L); + for (const auto &header : reply.headers) + { + w_pushstring(L, header.first); + w_pushstring(L, header.second); + lua_settable(L, -3); + } + } + + return advanced ? 3 : 2; +} + +extern "C" int HTTPS_DLLEXPORT luaopen_https(lua_State *L) +{ + lua_newtable(L); + + lua_pushcfunction(L, w_request); + lua_setfield(L, -2, "request"); + + return 1; +} diff --git a/src/libraries/luahttps/src/windows/SChannelConnection.cpp b/src/libraries/luahttps/src/windows/SChannelConnection.cpp new file mode 100644 index 000000000..36e289a18 --- /dev/null +++ b/src/libraries/luahttps/src/windows/SChannelConnection.cpp @@ -0,0 +1,466 @@ +#define SECURITY_WIN32 +#define NOMINMAX + +#include "SChannelConnection.h" + +#ifdef HTTPS_BACKEND_SCHANNEL + +#include +#include +#include +#include +#include +#include +#include + +#ifndef SCH_USE_STRONG_CRYPTO +# define SCH_USE_STRONG_CRYPTO 0x00400000 +#endif +#ifndef SP_PROT_TLS1_1_CLIENT +# define SP_PROT_TLS1_1_CLIENT 0x00000200 +#endif +#ifndef SP_PROT_TLS1_2_CLIENT +# define SP_PROT_TLS1_2_CLIENT 0x00000800 +#endif + +#ifdef DEBUG_SCHANNEL +#include +std::ostream &debug = std::cout; +#else +struct Debug +{ + template + Debug &operator<<(const T&) { return *this; } +} debug; +#endif + +static void enqueue(std::vector &buffer, char *data, size_t size) +{ + size_t oldSize = buffer.size(); + buffer.resize(oldSize + size); + memcpy(&buffer[oldSize], data, size); +} + +static void enqueue_prepend(std::vector &buffer, char *data, size_t size) +{ + size_t oldSize = buffer.size(); + buffer.resize(oldSize + size); + memmove(&buffer[size], &buffer[0], oldSize); + memcpy(&buffer[0], data, size); +} + +static size_t dequeue(std::vector &buffer, char *data, size_t size) +{ + size = std::min(size, buffer.size()); + size_t remaining = buffer.size() - size; + + memcpy(data, &buffer[0], size); + memmove(&buffer[0], &buffer[size], remaining); + buffer.resize(remaining); + + return size; +} + +SChannelConnection::SChannelConnection() + : context(nullptr) +{ +} + +SChannelConnection::~SChannelConnection() +{ + destroyContext(); +} + +SECURITY_STATUS InitializeSecurityContext(CredHandle *phCredential, std::unique_ptr& phContext, const std::string& szTargetName, ULONG fContextReq, std::vector& inputBuffer, std::vector& outputBuffer, ULONG *pfContextAttr) +{ + std::array recvBuffers; + recvBuffers[0].BufferType = SECBUFFER_TOKEN; + recvBuffers[0].pvBuffer = outputBuffer.data(); + recvBuffers[0].cbBuffer = outputBuffer.size(); + + std::array sendBuffers; + sendBuffers[0].BufferType = SECBUFFER_TOKEN; + sendBuffers[0].pvBuffer = inputBuffer.data(); + sendBuffers[0].cbBuffer = inputBuffer.size(); + sendBuffers[1].BufferType = SECBUFFER_EMPTY; + sendBuffers[1].pvBuffer = nullptr; + sendBuffers[1].cbBuffer = 0; + + SecBufferDesc recvBufferDesc, sendBufferDesc; + recvBufferDesc.ulVersion = sendBufferDesc.ulVersion = SECBUFFER_VERSION; + recvBufferDesc.pBuffers = &recvBuffers[0]; + recvBufferDesc.cBuffers = recvBuffers.size(); + + if (!inputBuffer.empty()) + { + sendBufferDesc.pBuffers = &sendBuffers[0]; + sendBufferDesc.cBuffers = sendBuffers.size(); + } + else + { + sendBufferDesc.pBuffers = nullptr; + sendBufferDesc.cBuffers = 0; + } + + CtxtHandle* phOldContext = nullptr; + CtxtHandle* phNewContext = nullptr; + if (!phContext) + { + phContext = std::make_unique(); + phNewContext = phContext.get(); + } + else + { + phOldContext = phContext.get(); + } + + auto ret = InitializeSecurityContext(phCredential, phOldContext, const_cast(szTargetName.c_str()), fContextReq, 0, 0, &sendBufferDesc, 0, phNewContext, &recvBufferDesc, pfContextAttr, nullptr); + + outputBuffer.resize(recvBuffers[0].cbBuffer); + + // Clear the input buffer, so the reader can append + // If we have unprocessed data, leave it in the buffer + size_t unprocessed = 0; + if (sendBuffers[1].BufferType == SECBUFFER_EXTRA) + unprocessed = sendBuffers[1].cbBuffer; + + if (unprocessed > 0) + memmove(inputBuffer.data(), inputBuffer.data() + inputBuffer.size() - unprocessed, unprocessed); + + inputBuffer.resize(unprocessed); + + return ret; +} + +bool SChannelConnection::connect(const std::string &hostname, uint16_t port) +{ + debug << "Trying to connect to " << hostname << ":" << port << "\n"; + if (!socket.connect(hostname, port)) + return false; + debug << "Connected\n"; + + SCHANNEL_CRED cred; + memset(&cred, 0, sizeof(cred)); + + cred.dwVersion = SCHANNEL_CRED_VERSION; + cred.grbitEnabledProtocols = SP_PROT_TLS1_CLIENT | SP_PROT_TLS1_1_CLIENT | SP_PROT_TLS1_2_CLIENT; + cred.dwFlags = SCH_CRED_AUTO_CRED_VALIDATION | SCH_CRED_NO_DEFAULT_CREDS | SCH_USE_STRONG_CRYPTO | SCH_CRED_REVOCATION_CHECK_CHAIN; + + CredHandle credHandle; + if (AcquireCredentialsHandle(nullptr, (char*) UNISP_NAME, SECPKG_CRED_OUTBOUND, nullptr, &cred, nullptr, nullptr, &credHandle, nullptr) != SEC_E_OK) + { + debug << "Failed to acquire handle\n"; + socket.close(); + return false; + } + debug << "Acquired handle\n"; + + + static constexpr size_t bufferSize = 8192; + bool done = false, success = false, contextCreated = false; + + ULONG contextAttr; + std::unique_ptr context; + std::vector inputBuffer; + std::vector outputBuffer; + + do + { + outputBuffer.resize(bufferSize); + + bool recvData = false; + bool sendData = false; + auto ret = InitializeSecurityContext(&credHandle, context, hostname, ISC_REQ_STREAM, inputBuffer, outputBuffer, &contextAttr); + switch (ret) + { + /*case SEC_I_COMPLETE_NEEDED: + case SEC_I_COMPLETE_AND_CONTINUE: + if (CompleteAuthToken(context.get(), &outputBuffer) != SEC_E_OK) + done = true; + else if (ret == SEC_I_COMPLETE_NEEDED) + success = done = true; + break;*/ + case SEC_I_CONTINUE_NEEDED: + debug << "Initialize: continue needed\n"; + recvData = true; + sendData = true; + break; + case SEC_E_INCOMPLETE_CREDENTIALS: + debug << "Initialize failed: incomplete credentials\n"; + done = true; + break; + case SEC_E_INCOMPLETE_MESSAGE: + debug << "Initialize: incomplete message\n"; + recvData = true; + break; + case SEC_E_OK: + debug << "Initialize succeeded\n"; + success = done = true; + sendData = true; + break; + default: + debug << "Initialize done: " << outputBuffer.size() << " bytes of output and unknown status " << ret << "\n"; + done = true; + success = false; + break; + } + + if (!done) + contextCreated = true; + + if (sendData && !outputBuffer.empty()) + { + socket.write(outputBuffer.data(), outputBuffer.size()); + debug << "Sent " << outputBuffer.size() << " bytes of data\n"; + } + + if (recvData) + { + size_t unprocessed = inputBuffer.size(); + inputBuffer.resize(unprocessed + bufferSize); + size_t actual = socket.read(inputBuffer.data() + unprocessed, bufferSize); + inputBuffer.resize(actual + unprocessed); + + debug << "Received " << actual << " bytes of data\n"; + if (unprocessed > 0) + debug << " had " << unprocessed << " bytes of remaining, unprocessed data\n"; + + if (actual + unprocessed == 0) + { + debug << "No data to submit, break\n"; + break; + } + } + } while (!done); + + debug << "Done!\n"; + + if (success) + { + SecPkgContext_Flags resultFlags; + QueryContextAttributes(context.get(), SECPKG_ATTR_FLAGS, &resultFlags); + if (resultFlags.Flags & ISC_REQ_CONFIDENTIALITY == 0) + { + debug << "Resulting context is not encrypted, marking as failed\n"; + success = false; + } + if (resultFlags.Flags & ISC_REQ_INTEGRITY == 0) + { + debug << "Resulting context is not signed, marking as failed\n"; + success = false; + } + } + + if (success) + this->context = context.release(); + else if (contextCreated) + DeleteSecurityContext(context.get()); + + return success; +} + +size_t SChannelConnection::read(char *buffer, size_t size) +{ + if (decRecvBuffer.size() > 0) + { + size = dequeue(decRecvBuffer, buffer, size); + debug << "Read " << size << " bytes of previously decoded data\n"; + return size; + } + else if (encRecvBuffer.size() > 0) + { + size = dequeue(encRecvBuffer, buffer, size); + debug << "Read " << size << " bytes of extra data\n"; + } + else + { + size = socket.read(buffer, size); + debug << "Received " << size << " bytes of data\n"; + } + + return decrypt(buffer, size); +} + +size_t SChannelConnection::decrypt(char *buffer, size_t size, bool recurse) +{ + if (size == 0) + return 0; + + SecBuffer secBuffers[4]; + secBuffers[0].cbBuffer = size; + secBuffers[0].BufferType = SECBUFFER_DATA; + secBuffers[0].pvBuffer = buffer; + + for (size_t i = 1; i < 4; ++i) + { + secBuffers[i].BufferType = SECBUFFER_EMPTY; + secBuffers[i].pvBuffer = nullptr; + secBuffers[i].cbBuffer = 0; + } + + SecBufferDesc secBufferDesc; + secBufferDesc.ulVersion = SECBUFFER_VERSION; + secBufferDesc.cBuffers = 4; + secBufferDesc.pBuffers = &secBuffers[0]; + + auto ret = DecryptMessage(static_cast(context), &secBufferDesc, 0, nullptr); // FIXME + debug << "DecryptMessage returns: " << ret << "\n"; + switch (ret) + { + case SEC_E_OK: + { + void *actualDataStart = buffer; + for (size_t i = 0; i < 4; ++i) + { + auto &buffer = secBuffers[i]; + if (buffer.BufferType == SECBUFFER_DATA) + { + actualDataStart = buffer.pvBuffer; + size = buffer.cbBuffer; + } + else if (buffer.BufferType == SECBUFFER_EXTRA) + { + debug << "\tExtra data in buffer " << i << " (" << buffer.cbBuffer << " bytes)\n"; + enqueue(encRecvBuffer, static_cast(buffer.pvBuffer), buffer.cbBuffer); + } + else if (buffer.BufferType != SECBUFFER_EMPTY) + debug << "\tBuffer of type " << buffer.BufferType << "\n"; + } + + if (actualDataStart) + memmove(buffer, actualDataStart, size); + + break; + } + case SEC_E_INCOMPLETE_MESSAGE: + { + // Move all our current data to encRecvBuffer + enqueue(encRecvBuffer, buffer, size); + + // Now try to read some more data from the socket + size_t bufferSize = encRecvBuffer.size() + 8192; + char *recvBuffer = new char[bufferSize]; + size_t recvd = socket.read(recvBuffer+encRecvBuffer.size(), 8192); + debug << recvd << " bytes of extra data read from socket\n"; + + if (recvd == 0 && !recurse) + { + debug << "Recursion prevented, bailing\n"; + return 0; + } + + // Fill our buffer with the queued data and the newly received data + size_t totalSize = encRecvBuffer.size() + recvd; + dequeue(encRecvBuffer, recvBuffer, encRecvBuffer.size()); + debug << "Trying to decrypt with " << totalSize << " bytes of data\n"; + + // Now try to decrypt that + size_t decrypted = decrypt(recvBuffer, totalSize, false); + debug << "\tObtained " << decrypted << " bytes of decrypted data\n"; + + // Copy the first size bytes to the output buffer + size = std::min(size, decrypted); + memcpy(buffer, recvBuffer, size); + + // And write the remainder to our queued decrypted data... + // Note: we prepend, since our recursive call may already have written + // something and we can be sure decrypt wasn't called if the buffer was + // non-empty in read + enqueue_prepend(decRecvBuffer, recvBuffer+size, decrypted-size); + debug << "\tStoring " << decrypted-size << " bytes of extra decrypted data\n"; + return size; + } + // TODO: More? + default: + size = 0; + break; + } + + debug << "\tDecrypted " << size << " bytes of data\n"; + + return size; +} + +size_t SChannelConnection::write(const char *buffer, size_t size) +{ + static constexpr size_t bufferSize = 8192; + assert(size <= bufferSize); + + SecPkgContext_StreamSizes Sizes; + QueryContextAttributes( + static_cast(context), + SECPKG_ATTR_STREAM_SIZES, + &Sizes); + debug << "stream sizes:\n\theader: " << Sizes.cbHeader << "\n\tfooter: " << Sizes.cbTrailer << "\n"; + + char *sendBuffer = new char[bufferSize + Sizes.cbHeader + Sizes.cbTrailer]; + memcpy(sendBuffer+Sizes.cbHeader, buffer, size); + + SecBuffer secBuffers[4]; + secBuffers[0].cbBuffer = Sizes.cbHeader; + secBuffers[0].BufferType = SECBUFFER_STREAM_HEADER; + secBuffers[0].pvBuffer = sendBuffer; + + secBuffers[1].cbBuffer = size; + secBuffers[1].BufferType = SECBUFFER_DATA; + secBuffers[1].pvBuffer = sendBuffer+Sizes.cbHeader; + + secBuffers[2].cbBuffer = Sizes.cbTrailer; + secBuffers[2].pvBuffer = sendBuffer+Sizes.cbHeader+size; + secBuffers[2].BufferType = SECBUFFER_STREAM_TRAILER; + + secBuffers[3].cbBuffer = 0; + secBuffers[3].BufferType = SECBUFFER_EMPTY; + secBuffers[3].pvBuffer = nullptr; + + SecBufferDesc secBufferDesc; + secBufferDesc.ulVersion = SECBUFFER_VERSION; + secBufferDesc.cBuffers = 4; + secBufferDesc.pBuffers = secBuffers; + + auto ret = EncryptMessage(static_cast(context), 0, &secBufferDesc, 0); // FIXME + debug << "Send:\n\tHeader size: " << secBuffers[0].cbBuffer << "\n\t\ttype: " << secBuffers[0].BufferType << "\n\tData size: " << secBuffers[1].cbBuffer << "\n\t\ttype: " << secBuffers[1].BufferType << "\n\tFooter size: " << secBuffers[2].cbBuffer << "\n\t\ttype: " << secBuffers[2].BufferType << "\n"; + + size_t sendSize = 0; + for (size_t i = 0; i < 4; ++i) + if (secBuffers[i].cbBuffer != bufferSize) + sendSize += secBuffers[i].cbBuffer; + + debug << "\tReal length? " << sendSize << "\n"; + switch (ret) + { + case SEC_E_OK: + socket.write(sendBuffer, sendSize); + break; + // TODO: More? + default: + size = 0; + break; + } + + delete[] sendBuffer; + return size; +} + +void SChannelConnection::destroyContext() +{ + if (context) + { + DeleteSecurityContext(context); + delete context; + context = nullptr; + } +} + +void SChannelConnection::close() +{ + destroyContext(); + socket.close(); +} + +bool SChannelConnection::valid() +{ + return true; +} + +#endif // HTTPS_BACKEND_SCHANNEL diff --git a/src/libraries/luahttps/src/windows/SChannelConnection.h b/src/libraries/luahttps/src/windows/SChannelConnection.h new file mode 100644 index 000000000..460b7d061 --- /dev/null +++ b/src/libraries/luahttps/src/windows/SChannelConnection.h @@ -0,0 +1,37 @@ +#pragma once + +#include "../common/config.h" + +#ifdef HTTPS_BACKEND_SCHANNEL + +#include "../common/Connection.h" +#include "../common/PlaintextConnection.h" + +#include + +struct _SecHandle; +using CtxtHandle = _SecHandle; + +class SChannelConnection : public Connection +{ +public: + SChannelConnection(); + virtual bool connect(const std::string &hostname, uint16_t port) override; + virtual size_t read(char *buffer, size_t size) override; + virtual size_t write(const char *buffer, size_t size) override; + virtual void close() override; + virtual ~SChannelConnection(); + + static bool valid(); + +private: + PlaintextConnection socket; + CtxtHandle *context; + std::vector encRecvBuffer; + std::vector decRecvBuffer; + + size_t decrypt(char *buffer, size_t size, bool recurse = true); + void destroyContext(); +}; + +#endif // HTTPS_BACKEND_SCHANNEL diff --git a/src/modules/love/love.cpp b/src/modules/love/love.cpp index feed8ca75..6df24c6d6 100644 --- a/src/modules/love/love.cpp +++ b/src/modules/love/love.cpp @@ -173,6 +173,10 @@ extern "C" extern int luaopen_love_arg(lua_State*); extern int luaopen_love_callbacks(lua_State*); extern int luaopen_love_boot(lua_State*); + +#ifdef LOVE_ENABLE_LUAHTTPS + extern int luaopen_https(lua_State*); +#endif } static const luaL_Reg modules[] = { @@ -656,6 +660,9 @@ int luaopen_love(lua_State *L) #ifdef LOVE_ENABLE_LUA53 love::luax_preload(L, luaopen_luautf8, "utf8"); #endif +#ifdef LOVE_ENABLE_LUAHTTPS + love::luax_preload(L, luaopen_https, "https"); +#endif #ifdef LOVE_ENABLE_WINDOW // In some environments, LuaJIT is limited to 2GB and LuaJIT sometimes panic when it