Browse Source

Merge pull request #1768 from slime73/https

Add HTTPS Lua module.
Alex Szpakowski 3 years ago
parent
commit
81791a3217
31 changed files with 2264 additions and 4 deletions
  1. 1 1
      .gitignore
  2. 68 0
      CMakeLists.txt
  3. 110 3
      platform/xcode/liblove.xcodeproj/project.pbxproj
  4. 1 0
      src/common/config.h
  5. 26 0
      src/libraries/luahttps/Android.mk
  6. 1 0
      src/libraries/luahttps/java.txt
  7. 17 0
      src/libraries/luahttps/license.txt
  8. 212 0
      src/libraries/luahttps/src/android/AndroidClient.cpp
  9. 26 0
      src/libraries/luahttps/src/android/AndroidClient.h
  10. 171 0
      src/libraries/luahttps/src/android/java/org/love2d/luahttps/LuaHTTPS.java
  11. 16 0
      src/libraries/luahttps/src/apple/NSURLClient.h
  12. 88 0
      src/libraries/luahttps/src/apple/NSURLClient.mm
  13. 14 0
      src/libraries/luahttps/src/common/Connection.h
  14. 35 0
      src/libraries/luahttps/src/common/ConnectionClient.h
  15. 148 0
      src/libraries/luahttps/src/common/HTTPRequest.cpp
  16. 30 0
      src/libraries/luahttps/src/common/HTTPRequest.h
  17. 69 0
      src/libraries/luahttps/src/common/HTTPS.cpp
  18. 5 0
      src/libraries/luahttps/src/common/HTTPS.h
  19. 37 0
      src/libraries/luahttps/src/common/HTTPSClient.cpp
  20. 41 0
      src/libraries/luahttps/src/common/HTTPSClient.h
  21. 99 0
      src/libraries/luahttps/src/common/PlaintextConnection.cpp
  22. 19 0
      src/libraries/luahttps/src/common/PlaintextConnection.h
  23. 33 0
      src/libraries/luahttps/src/common/config.h
  24. 126 0
      src/libraries/luahttps/src/generic/CurlClient.cpp
  25. 34 0
      src/libraries/luahttps/src/generic/CurlClient.h
  26. 148 0
      src/libraries/luahttps/src/generic/OpenSSLConnection.cpp
  27. 60 0
      src/libraries/luahttps/src/generic/OpenSSLConnection.h
  28. 119 0
      src/libraries/luahttps/src/lua/main.cpp
  29. 466 0
      src/libraries/luahttps/src/windows/SChannelConnection.cpp
  30. 37 0
      src/libraries/luahttps/src/windows/SChannelConnection.h
  31. 7 0
      src/modules/love/love.cpp

+ 1 - 1
.gitignore

@@ -35,8 +35,8 @@ demos
 /src/.libs/
 *~
 Makefile*
-config*
 libtool
+/src/config.h
 /platform/unix/ar-lib
 /platform/unix/compile
 /platform/unix/config.guess

+ 68 - 0
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

+ 110 - 3
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 = "<group>"; };
 		FA7634491E28722A0066EF9E /* StreamBuffer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = StreamBuffer.h; sourceTree = "<group>"; };
 		FA7DA04C1C16874A0056B200 /* wrap_Math.lua */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = wrap_Math.lua; sourceTree = "<group>"; };
+		FA7E9206277E120900C24CB2 /* theora.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = theora.xcframework; path = ios/libraries/theora.xcframework; sourceTree = "<group>"; };
 		FA84DE5D2778D7DB002674C6 /* SpirvIntrinsics.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SpirvIntrinsics.h; sourceTree = "<group>"; };
 		FA84DE5E2778D7DC002674C6 /* glslang_c_interface.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = glslang_c_interface.h; sourceTree = "<group>"; };
 		FA84DE5F2778D7DC002674C6 /* glslang_c_shader_types.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = glslang_c_shader_types.h; sourceTree = "<group>"; };
 		FA84DE602778D7F3002674C6 /* SpirvIntrinsics.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = SpirvIntrinsics.cpp; sourceTree = "<group>"; };
-		FA7E9206277E120900C24CB2 /* theora.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = theora.xcframework; path = ios/libraries/theora.xcframework; sourceTree = "<group>"; };
 		FA84DE75277CB3D4002674C6 /* SDL2.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = SDL2.xcframework; path = ios/libraries/SDL2.xcframework; sourceTree = "<group>"; };
 		FA84DE79277D4C88002674C6 /* modplug.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = modplug.xcframework; path = ios/libraries/modplug.xcframework; sourceTree = "<group>"; };
 		FA84DE7B277E045E002674C6 /* ogg.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = ogg.xcframework; path = ios/libraries/ogg.xcframework; sourceTree = "<group>"; };
@@ -1907,6 +1927,20 @@
 		FA91DA8A1F377C3900C80E33 /* deprecation.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = deprecation.h; sourceTree = "<group>"; };
 		FA93C4501F315B960087CCD4 /* FormatHandler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FormatHandler.h; sourceTree = "<group>"; };
 		FA93C4511F315B960087CCD4 /* FormatHandler.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = FormatHandler.cpp; sourceTree = "<group>"; };
+		FA94725227A6EE1B00817677 /* main.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = main.cpp; sourceTree = "<group>"; };
+		FA94725427A6EE1B00817677 /* Connection.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Connection.h; sourceTree = "<group>"; };
+		FA94725527A6EE1B00817677 /* HTTPSClient.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = HTTPSClient.cpp; sourceTree = "<group>"; };
+		FA94725627A6EE1B00817677 /* HTTPSClient.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HTTPSClient.h; sourceTree = "<group>"; };
+		FA94725727A6EE1B00817677 /* PlaintextConnection.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PlaintextConnection.h; sourceTree = "<group>"; };
+		FA94725827A6EE1B00817677 /* config.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = config.h; sourceTree = "<group>"; };
+		FA94725A27A6EE1B00817677 /* HTTPS.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = HTTPS.cpp; sourceTree = "<group>"; };
+		FA94725B27A6EE1B00817677 /* HTTPRequest.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HTTPRequest.h; sourceTree = "<group>"; };
+		FA94725C27A6EE1B00817677 /* ConnectionClient.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ConnectionClient.h; sourceTree = "<group>"; };
+		FA94725D27A6EE1B00817677 /* PlaintextConnection.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = PlaintextConnection.cpp; sourceTree = "<group>"; };
+		FA94725E27A6EE1B00817677 /* HTTPS.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HTTPS.h; sourceTree = "<group>"; };
+		FA94725F27A6EE1B00817677 /* HTTPRequest.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = HTTPRequest.cpp; sourceTree = "<group>"; };
+		FA94729927A6F9AC00817677 /* NSURLClient.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = NSURLClient.mm; sourceTree = "<group>"; };
+		FA94729A27A6F9AC00817677 /* NSURLClient.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NSURLClient.h; sourceTree = "<group>"; };
 		FA9B4A0716E1578300074F42 /* SDL2.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SDL2.framework; path = macosx/Frameworks/SDL2.framework; sourceTree = "<group>"; };
 		FA9D53AA1F5307E900125C6B /* Deprecations.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = Deprecations.cpp; sourceTree = "<group>"; };
 		FA9D53AB1F5307E900125C6B /* Deprecations.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Deprecations.h; sourceTree = "<group>"; };
@@ -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 = "<group>";
 		};
+		FA94724527A6EE1B00817677 /* luahttps */ = {
+			isa = PBXGroup;
+			children = (
+				FA94724D27A6EE1B00817677 /* src */,
+			);
+			path = luahttps;
+			sourceTree = "<group>";
+		};
+		FA94724D27A6EE1B00817677 /* src */ = {
+			isa = PBXGroup;
+			children = (
+				FA94729827A6F9AC00817677 /* apple */,
+				FA94725327A6EE1B00817677 /* common */,
+				FA94725127A6EE1B00817677 /* lua */,
+			);
+			path = src;
+			sourceTree = "<group>";
+		};
+		FA94725127A6EE1B00817677 /* lua */ = {
+			isa = PBXGroup;
+			children = (
+				FA94725227A6EE1B00817677 /* main.cpp */,
+			);
+			path = lua;
+			sourceTree = "<group>";
+		};
+		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 = "<group>";
+		};
+		FA94729827A6F9AC00817677 /* apple */ = {
+			isa = PBXGroup;
+			children = (
+				FA94729A27A6F9AC00817677 /* NSURLClient.h */,
+				FA94729927A6F9AC00817677 /* NSURLClient.mm */,
+			);
+			path = apple;
+			sourceTree = "<group>";
+		};
 		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 */,

+ 1 - 0
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

+ 26 - 0
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)

+ 1 - 0
src/libraries/luahttps/java.txt

@@ -0,0 +1 @@
+src/android/java

+ 17 - 0
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.

+ 212 - 0
src/libraries/luahttps/src/android/AndroidClient.cpp

@@ -0,0 +1,212 @@
+#include "AndroidClient.h"
+
+#ifdef HTTPS_BACKEND_ANDROID
+
+#include <sstream>
+#include <type_traits>
+
+#include <dlfcn.h>
+
+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, "<init>", "()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

+ 26 - 0
src/libraries/luahttps/src/android/AndroidClient.h

@@ -0,0 +1,26 @@
+#pragma once
+
+#include "../common/config.h"
+
+#ifdef HTTPS_BACKEND_ANDROID
+
+#include <jni.h>
+
+#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

+ 171 - 0
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<String, String> headers;
+
+    public LuaHTTPS() {
+        headers = new HashMap<String, String>();
+        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<String> resultInterleaved = new ArrayList<String>();
+
+        for (Map.Entry<String, String> 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<String, String> 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<String, List<String>> 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;
+    }
+}

+ 16 - 0
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

+ 88 - 0
src/libraries/luahttps/src/apple/NSURLClient.mm

@@ -0,0 +1,88 @@
+#include "NSURLClient.h"
+
+#ifdef HTTPS_BACKEND_NSURL
+
+#import <Foundation/Foundation.h>
+
+#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

+ 14 - 0
src/libraries/luahttps/src/common/Connection.h

@@ -0,0 +1,14 @@
+#pragma once
+
+#include <cstdint>
+#include <string>
+
+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() {};
+};

+ 35 - 0
src/libraries/luahttps/src/common/ConnectionClient.h

@@ -0,0 +1,35 @@
+#pragma once
+
+#include "HTTPSClient.h"
+#include "HTTPRequest.h"
+#include "Connection.h"
+
+template<typename Connection>
+class ConnectionClient : public HTTPSClient
+{
+public:
+	virtual bool valid() const override;
+	virtual HTTPSClient::Reply request(const HTTPSClient::Request &req) override;
+
+private:
+	static Connection *factory();
+};
+
+template<typename Connection>
+bool ConnectionClient<Connection>::valid() const
+{
+	return Connection::valid();
+}
+
+template<typename Connection>
+Connection *ConnectionClient<Connection>::factory()
+{
+	return new Connection();
+}
+
+template<typename Connection>
+HTTPSClient::Reply ConnectionClient<Connection>::request(const HTTPSClient::Request &req)
+{
+	HTTPRequest request(factory);
+	return request.request(req);
+}

+ 148 - 0
src/libraries/luahttps/src/common/HTTPRequest.cpp

@@ -0,0 +1,148 @@
+#include <sstream>
+#include <string>
+#include <memory>
+#include <limits>
+#include <stdexcept>
+
+#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<Connection> 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<std::streamsize>::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<char>(response);
+		auto end = std::istreambuf_iterator<char>();
+		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;
+}

+ 30 - 0
src/libraries/luahttps/src/common/HTTPRequest.h

@@ -0,0 +1,30 @@
+#pragma once
+
+#include <functional>
+
+#include "HTTPSClient.h"
+#include "Connection.h"
+
+class HTTPRequest
+{
+public:
+	typedef std::function<Connection *()> 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);
+};

+ 69 - 0
src/libraries/luahttps/src/common/HTTPS.cpp

@@ -0,0 +1,69 @@
+#include "HTTPS.h"
+#include "config.h"
+#include "ConnectionClient.h"
+
+#include <stdexcept>
+
+#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<OpenSSLConnection> opensslclient;
+#endif
+#ifdef HTTPS_BACKEND_SCHANNEL
+	static ConnectionClient<SChannelConnection> 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");
+}

+ 5 - 0
src/libraries/luahttps/src/common/HTTPS.h

@@ -0,0 +1,5 @@
+#pragma once
+
+#include "HTTPSClient.h"
+
+HTTPSClient::Reply request(const HTTPSClient::Request &req);

+ 37 - 0
src/libraries/luahttps/src/common/HTTPSClient.cpp

@@ -0,0 +1,37 @@
+#include <algorithm>
+#include <cctype>
+
+#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)
+{
+}
+

+ 41 - 0
src/libraries/luahttps/src/common/HTTPSClient.h

@@ -0,0 +1,41 @@
+#pragma once
+
+#include <cstdint>
+#include <string>
+#include <map>
+
+class HTTPSClient
+{
+public:
+	struct ci_string_less
+	{
+		bool operator()(const std::string &lhs, const std::string &rhs) const;
+	};
+	using header_map = std::map<std::string, std::string, ci_string_less>;
+
+	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 ~HTTPSClient() {}
+	virtual bool valid() const = 0;
+	virtual Reply request(const Request &req) = 0;
+};

+ 99 - 0
src/libraries/luahttps/src/common/PlaintextConnection.cpp

@@ -0,0 +1,99 @@
+#include "config.h"
+#include <cstring>
+#ifndef HTTPS_USE_WINSOCK
+#	include <netdb.h>
+#	include <unistd.h>
+#	include <sys/types.h>
+#	include <sys/socket.h>
+#else
+#	include <winsock2.h>
+#	include <ws2tcpip.h>
+#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<size_t>(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<size_t>(written);
+}
+
+void PlaintextConnection::close()
+{
+	::close(fd);
+	fd = -1;
+}
+
+int PlaintextConnection::getFd() const
+{
+	return fd;
+}

+ 19 - 0
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;
+};

+ 33 - 0
src/libraries/luahttps/src/common/config.h

@@ -0,0 +1,33 @@
+#pragma once
+
+#if defined(HTTPS_HAVE_CONFIG_GENERATED_H)
+	#include "common/config-generated.h"
+#elif defined(WIN32) || defined(_WIN32)
+	#define HTTPS_BACKEND_SCHANNEL
+	#define HTTPS_USE_WINSOCK
+#elif defined(__ANDROID__)
+	#define HTTPS_BACKEND_ANDROID
+#elif defined(__APPLE__)
+	#define HTTPS_BACKEND_NSURL
+#elif defined(linux) || defined(__linux) || defined(__linux__)
+	#if defined __has_include
+		#if __has_include(<curl/curl.h>)
+			#define HTTPS_BACKEND_CURL
+		#endif
+		#if __has_include(<openssl/ssl.h>)
+			#define HTTPS_BACKEND_OPENSSL
+		#endif
+	#else
+		// Hope for the best...
+		#define HTTPS_BACKEND_CURL
+		#define HTTPS_BACKEND_OPENSSL
+	#endif
+#endif
+
+#if defined(WIN32) || defined(_WIN32) || defined(__CYGWIN__)
+	#define HTTPS_DLLEXPORT __declspec(dllexport)
+#elif defined(__GNUC__) || defined(__clang__)
+	#define HTTPS_DLLEXPORT __attribute__ ((visibility("default")))
+#else
+	#define HTTPS_DLLEXPORT
+#endif

+ 126 - 0
src/libraries/luahttps/src/generic/CurlClient.cpp

@@ -0,0 +1,126 @@
+#include "CurlClient.h"
+
+#ifdef HTTPS_BACKEND_CURL
+
+#include <dlfcn.h>
+#include <stdexcept>
+#include <sstream>
+#include <vector>
+
+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<std::string, std::string> &headers = *((std::map<std::string,std::string>*) 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<std::string> 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

+ 34 - 0
src/libraries/luahttps/src/generic/CurlClient.h

@@ -0,0 +1,34 @@
+#pragma once
+
+#include "../common/config.h"
+
+#ifdef HTTPS_BACKEND_CURL
+
+#include <curl/curl.h>
+
+#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

+ 148 - 0
src/libraries/luahttps/src/generic/OpenSSLConnection.cpp

@@ -0,0 +1,148 @@
+#include "OpenSSLConnection.h"
+
+#ifdef HTTPS_BACKEND_OPENSSL
+
+#include <dlfcn.h>
+
+// Not present in openssl 1.1 headers
+#define SSL_CTRL_OPTIONS 32
+
+template <class T>
+static inline bool loadSymbol(T &var, void *handle, const char *name)
+{
+	var = reinterpret_cast<T>(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

+ 60 - 0
src/libraries/luahttps/src/generic/OpenSSLConnection.h

@@ -0,0 +1,60 @@
+#pragma once
+
+#include "../common/config.h"
+
+#ifdef HTTPS_BACKEND_OPENSSL
+
+#include <openssl/ssl.h>
+
+#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

+ 119 - 0
src/libraries/luahttps/src/lua/main.cpp

@@ -0,0 +1,119 @@
+#include <lua.hpp>
+
+#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;
+}

+ 466 - 0
src/libraries/luahttps/src/windows/SChannelConnection.cpp

@@ -0,0 +1,466 @@
+#define SECURITY_WIN32
+#define NOMINMAX
+
+#include "SChannelConnection.h"
+
+#ifdef HTTPS_BACKEND_SCHANNEL
+
+#include <windows.h>
+#include <security.h>
+#include <schnlsp.h>
+#include <assert.h>
+#include <algorithm>
+#include <memory>
+#include <array>
+
+#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 <iostream>
+std::ostream &debug = std::cout;
+#else
+struct Debug
+{
+	template<typename T>
+	Debug &operator<<(const T&) { return *this; }
+} debug;
+#endif
+
+static void enqueue(std::vector<char> &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<char> &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<char> &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<CtxtHandle>& phContext, const std::string& szTargetName, ULONG fContextReq, std::vector<char>& inputBuffer, std::vector<char>& outputBuffer, ULONG *pfContextAttr)
+{
+	std::array<SecBuffer, 1> recvBuffers;
+	recvBuffers[0].BufferType = SECBUFFER_TOKEN;
+	recvBuffers[0].pvBuffer = outputBuffer.data();
+	recvBuffers[0].cbBuffer = outputBuffer.size();
+
+	std::array<SecBuffer, 2> 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<CtxtHandle>();
+		phNewContext = phContext.get();
+	}
+	else
+	{
+		phOldContext = phContext.get();
+	}
+
+	auto ret = InitializeSecurityContext(phCredential, phOldContext, const_cast<char*>(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<CtxtHandle> context;
+	std::vector<char> inputBuffer;
+	std::vector<char> 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<CtxtHandle*>(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<char*>(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<CtxtHandle*>(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<CtxtHandle*>(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

+ 37 - 0
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 <vector>
+
+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<char> encRecvBuffer;
+	std::vector<char> decRecvBuffer;
+
+	size_t decrypt(char *buffer, size_t size, bool recurse = true);
+	void destroyContext();
+};
+
+#endif // HTTPS_BACKEND_SCHANNEL

+ 7 - 0
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