Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

hs.http must follow redirects #1889 #3279

Merged
merged 1 commit into from
Nov 2, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 47 additions & 0 deletions Hammerspoon Tests/HShttp.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
//
// HShttp.m
// Hammerspoon
//
// Created by Alex Chen on 08/21/2022.

#import "HSTestCase.h"

@interface HThttp : HSTestCase

@end

@implementation HThttp

- (void)setUp {
[super setUpWithRequire:@"test_http"];
// Put setup code here. This method is called before the invocation of each test method in the class.
}

- (void)tearDown {
// Put teardown code here. This method is called after the invocation of each test method in the class.
[super tearDown];
}

// Http tests

- (void)testHttpDoAsyncRequestWithCachePolicyParam {
RUN_TWO_PART_LUA_TEST_WITH_TIMEOUT(5)
}

- (void)testHttpDoAsyncRequestWithRedirectParamButNoCachePolicyParam {
RUN_LUA_TEST()
}

- (void)testHttpDoAsyncRequestWithNoEnableRedirectParam {
RUN_TWO_PART_LUA_TEST_WITH_TIMEOUT(5)
}

- (void)testHttpDoAsyncRequestWithRedirection {
RUN_TWO_PART_LUA_TEST_WITH_TIMEOUT(5)
}

- (void)testHttpDoAsyncRequestWithoutRedirection {
RUN_TWO_PART_LUA_TEST_WITH_TIMEOUT(5)
}

@end
10 changes: 10 additions & 0 deletions Hammerspoon.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -554,6 +554,9 @@
949CDC001990759B00906CCE /* ConsoleWindow.xib in Resources */ = {isa = PBXBuildFile; fileRef = 949CDBFF1990759B00906CCE /* ConsoleWindow.xib */; };
94A1E5481993AC5C003AEB26 /* MJFileUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = 94A1E5471993AC5C003AEB26 /* MJFileUtils.m */; };
B8524F32201A896000844F3D /* libhid.m in Sources */ = {isa = PBXBuildFile; fileRef = B8524F25201A875600844F3D /* libhid.m */; };
CE139CB028B24B6500743C90 /* HShttp.m in Sources */ = {isa = PBXBuildFile; fileRef = CE139CAF28B24B6500743C90 /* HShttp.m */; };
CE139CB128B24DAC00743C90 /* http.lua in Resources */ = {isa = PBXBuildFile; fileRef = 4F4CB4421B73AFD2000EA9B6 /* http.lua */; };
CE139CB328B2539500743C90 /* test_http.lua in Resources */ = {isa = PBXBuildFile; fileRef = CE139CB228B2539500743C90 /* test_http.lua */; };
D02F95291A00221C00E28BB2 /* HSrequire_all.m in Sources */ = {isa = PBXBuildFile; fileRef = D02F95281A00221C00E28BB2 /* HSrequire_all.m */; };
D077B5BF1A001B5300369B30 /* variables.m in Sources */ = {isa = PBXBuildFile; fileRef = D077B5BE1A001B5300369B30 /* variables.m */; };
D61F4A341DCFA6DB00AEE223 /* libsharing.m in Sources */ = {isa = PBXBuildFile; fileRef = D61F4A311DCFA6C600AEE223 /* libsharing.m */; };
Expand Down Expand Up @@ -2005,6 +2008,8 @@
B8524F24201A875600844F3D /* hid.lua */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = hid.lua; path = extensions/hid/hid.lua; sourceTree = "<group>"; };
B8524F25201A875600844F3D /* libhid.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = libhid.m; path = extensions/hid/libhid.m; sourceTree = "<group>"; };
B8524F31201A88CD00844F3D /* libhid.dylib */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.dylib"; includeInIndex = 0; path = libhid.dylib; sourceTree = BUILT_PRODUCTS_DIR; };
CE139CAF28B24B6500743C90 /* HShttp.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = HShttp.m; sourceTree = "<group>"; };
CE139CB228B2539500743C90 /* test_http.lua */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = test_http.lua; path = extensions/http/test_http.lua; sourceTree = "<group>"; };
D02F95241A00221C00E28BB2 /* Hammerspoon Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "Hammerspoon Tests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; };
D02F95271A00221C00E28BB2 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
D02F95281A00221C00E28BB2 /* HSrequire_all.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = HSrequire_all.m; sourceTree = "<group>"; };
Expand Down Expand Up @@ -3150,6 +3155,7 @@
4F4CB4411B73AFC1000EA9B6 /* http */ = {
isa = PBXGroup;
children = (
CE139CB228B2539500743C90 /* test_http.lua */,
4F4CB4421B73AFD2000EA9B6 /* http.lua */,
4F4CB4431B73AFD2000EA9B6 /* libhttp.m */,
);
Expand Down Expand Up @@ -4199,6 +4205,7 @@
4FDD8E7B1C85A05700085D7A /* lsunit.lua */,
D02F95261A00221C00E28BB2 /* Supporting Files */,
4FDD8E7D1C85A06800085D7A /* testinit.lua */,
CE139CAF28B24B6500743C90 /* HShttp.m */,
);
path = "Hammerspoon Tests";
sourceTree = "<group>";
Expand Down Expand Up @@ -6976,6 +6983,7 @@
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
CE139CB128B24DAC00743C90 /* http.lua in Resources */,
4F7BB9D426447C390077A926 /* test_hotkey.lua in Resources */,
2273893624860C0E003EBC68 /* test_websocket.lua in Resources */,
4FCBAFD923B1152F007BA1D0 /* test_math.lua in Resources */,
Expand All @@ -6998,6 +7006,7 @@
4FDD8E7C1C85A05700085D7A /* lsunit.lua in Resources */,
4FDD8E7E1C85A06800085D7A /* testinit.lua in Resources */,
4FDD8E8B1C8C8BD200085D7A /* test_coresetup.lua in Resources */,
CE139CB328B2539500743C90 /* test_http.lua in Resources */,
4FEE39DB1F0A69D600935F90 /* test_crash.lua in Resources */,
4CB349F31C7F74EE006F0DE0 /* test_applescript.lua in Resources */,
4FB74B3C2049AB8B00B08851 /* test_task.lua in Resources */,
Expand Down Expand Up @@ -7739,6 +7748,7 @@
files = (
4FFF9FB525D89B3B00D2997C /* HSmouse.m in Sources */,
4FDD8E8F1C8DED1C00085D7A /* HSappfinder.m in Sources */,
CE139CB028B24B6500743C90 /* HShttp.m in Sources */,
6AE181321D39C8F10097211C /* HSnoises.m in Sources */,
4FCC52CE1E1527EF007F93D0 /* HSbrightness.m in Sources */,
22A6626225FC087000AA329E /* HSserial.m in Sources */,
Expand Down
43 changes: 41 additions & 2 deletions extensions/http/libhttp.m
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ static id responseBodyToId(NSHTTPURLResponse *httpResponse, NSData *bodyData) {
// Definition of the collection delegate to receive callbacks from NSUrlConnection
@interface connectionDelegate : NSObject<NSURLConnectionDelegate>
@property int fn;
@property bool enableRedirect;
@property(nonatomic, retain) NSMutableData* receivedData;
@property(nonatomic, retain) NSHTTPURLResponse* httpResponse;
@property(nonatomic, retain) NSURLConnection* connection;
Expand Down Expand Up @@ -96,6 +97,37 @@ - (void)connection:(NSURLConnection * __unused)connection didFailWithError:(NSEr
_lua_stackguard_exit(skin.L);
}

- (NSURLRequest *)connection:(NSURLConnection *)connection
willSendRequest:(NSURLRequest *)request
redirectResponse:(NSURLResponse *)response {

if (self.fn == LUA_NOREF) {
return nil;
}

if ([response isKindOfClass:[NSHTTPURLResponse class]] && self.enableRedirect == false) {
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse*)response;

LuaSkin *skin = [LuaSkin sharedWithState:NULL];
lua_State *L = skin.L;
_lua_stackguard_entry(L);

[skin pushLuaRef:refTable ref:self.fn];
lua_pushinteger(L, (int)httpResponse.statusCode);
[skin pushNSObject:responseBodyToId(self.httpResponse, self.receivedData)];
[skin pushNSObject:httpResponse.allHeaderFields];
[skin protectedCallAndError:@"hs.http connectionDelefate:didFinishLoading during redirection" nargs:3 nresults:0];

remove_delegate(L, self);
_lua_stackguard_exit(L);

[connection cancel];
return nil;
}

return request;
}

@end

// If the user specified a request body, get it from stack,
Expand Down Expand Up @@ -166,7 +198,7 @@ static void extractHeadersFromStack(lua_State* L, int index, NSMutableURLRequest
}
}

/// hs.http.doAsyncRequest(url, method, data, headers, callback, [cachePolicy])
/// hs.http.doAsyncRequest(url, method, data, headers, callback, [cachePolicy|enableRedirect])
/// Function
/// Creates an HTTP request and executes it asynchronously
///
Expand All @@ -180,20 +212,25 @@ static void extractHeadersFromStack(lua_State* L, int index, NSMutableURLRequest
/// * body - A string containing the body of the response
/// * headers - A table containing the HTTP headers of the response
/// * cachePolicy - An optional string containing the cache policy ("protocolCachePolicy", "ignoreLocalCache", "ignoreLocalAndRemoteCache", "returnCacheOrLoad", "returnCacheDontLoad" or "reloadRevalidatingCache"). Defaults to `protocolCachePolicy`.
/// * enableRedirect - An optional boolean to indicate whether to redirect the http request. Defaults to true.
///
/// Returns:
/// * None
///
/// Notes:
/// * If authentication is required in order to download the request, the required credentials must be specified as part of the URL (e.g. "http://user:[email protected]/"). If authentication fails, or credentials are missing, the connection will attempt to continue without credentials.
/// * If the Content-Type response header begins `text/` then the response body return value is a UTF8 string. Any other content type passes the response body, unaltered, as a stream of bytes.
/// * If enableRedirect is set to true, response body will be empty string. Http body will be dropped even though response has the body. This seems the limitation of 'connection:willSendRequest:redirectResponse' method.
static int http_doAsyncRequest(lua_State* L){
LuaSkin *skin = [LuaSkin sharedWithState:L];
[skin checkArgs:LS_TSTRING, LS_TSTRING, LS_TSTRING|LS_TNIL, LS_TTABLE|LS_TNIL, LS_TFUNCTION, LS_TSTRING | LS_TOPTIONAL, LS_TBREAK];
[skin checkArgs:LS_TSTRING, LS_TSTRING, LS_TSTRING|LS_TNIL, LS_TTABLE|LS_TNIL, LS_TFUNCTION, LS_TSTRING | LS_TBOOLEAN | LS_TOPTIONAL, LS_TBREAK];

NSString* cachePolicy = nil;
bool enableRedirect = true;
if (lua_type(L, 6) == LUA_TSTRING) {
cachePolicy = [skin toNSObjectAtIndex:6];
} else if (lua_type(L, 6) == LUA_TBOOLEAN) {
enableRedirect = lua_toboolean(L, 6);
}

NSMutableURLRequest* request = getRequestFromStack(L, cachePolicy);
Expand All @@ -204,6 +241,8 @@ static int http_doAsyncRequest(lua_State* L){
lua_pushvalue(L, 5);

connectionDelegate* delegate = [[connectionDelegate alloc] init];
delegate.enableRedirect = enableRedirect;

delegate.receivedData = [[NSMutableData alloc] init];
delegate.fn = [skin luaRef:refTable];

Expand Down
126 changes: 126 additions & 0 deletions extensions/http/test_http.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
-- hs.http = require("hs.http")
-- hs = require("hs")

_G["respCode"] = 0
_G["respBody"] = ""
_G["respHeaders"] = {}

_G["callback"] = function(code, body, headers)
_G["respCode"] = code
_G["respBody"] = body
_G["respHeaders"] = headers
end

function testHttpDoAsyncRequestWithCachePolicyParamValues()
if (type(_G["respCode"]) == "number" and type(_G["respBody"]) == "string" and type(_G["respHeaders"]) == "table") then
-- check return code
assertIsEqual(200, _G["respCode"])
assertGreaterThan(0, string.len(_G["respBody"]))
return success()
else
return "Waiting for success..."
end
end

-- check request should be redirected if [enableRedirect|cachePolicy] param is given as cachePolicy
-- check point: response code == 200
function testHttpDoAsyncRequestWithCachePolicyParam()
_G["respCode"] = 0
_G["respBody"] = ""
_G["respHeaders"] = {}
hs.http.doAsyncRequest(
'http://google.com',
'GET',
nil,
{ ['accept-language'] = 'en', ['user-agent'] = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.88 Safari/537.36', Accept = '*/*' },
_G["callback"],
'protocolCachePolicy'
)

return success()
end

function testHttpDoAsyncRequestWithoutEnableRedirectAndCachePolicyParamValues()
if (type(_G["respCode"]) == "number" and type(_G["respBody"]) == "string" and type(_G["respHeaders"]) == "table") then
-- check return code
assertIsEqual(200, _G["respCode"])
assertGreaterThan(0, string.len(_G["respBody"]))
return success()
else
return "Waiting for success..."
end
end

-- check request should be redirected if [enableRedirect|cachePolicy] param is not given.
-- check point: response code == 200
function testHttpDoAsyncRequestWithoutEnableRedirectAndCachePolicyParam()
_G["respCode"] = 0
_G["respBody"] = ""
_G["respHeaders"] = {}
hs.http.doAsyncRequest(
'http://google.com',
'GET',
nil,
{ ['accept-language'] = 'en', ['user-agent'] = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.88 Safari/537.36', Accept = '*/*' },
_G["callback"]
)

return success()
end

function testHttpDoAsyncRequestWithRedirectionValues()
if (type(_G["respCode"]) == "number" and type(_G["respBody"]) == "string" and type(_G["respHeaders"]) == "table") then
-- check return code
assertIsEqual(200, _G["respCode"])
assertGreaterThan(0, string.len(_G["respBody"]))
return success()
else
return "Waiting for success..."
end
end

-- check request should be redirected if [enableRedirect|cachePolicy] param is set to true as enableRedirect
-- check point: response code == 200
function testHttpDoAsyncRequestWithRedirection()
_G["respCode"] = 0
_G["respBody"] = ""
_G["respHeaders"] = {}
hs.http.doAsyncRequest(
'http://google.com',
'GET',
nil,
{ ['accept-language'] = 'en', ['user-agent'] = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.88 Safari/537.36', Accept = '*/*' },
_G["callback"],
true
)

return success()
end

function testHttpDoAsyncRequestWithoutRedirectionValues()
if (type(_G["respCode"]) == "number" and type(_G["respBody"]) == "string" and type(_G["respHeaders"]) == "table") then
-- check return code
assertIsEqual(301, _G["respCode"])
return success()
else
return "Waiting for success..."
end
end

-- check request should not be redirected if [enableRedirect|cachePolicy] param is set to false as enableRedirect
-- check point: response code == 301
function testHttpDoAsyncRequestWithoutRedirection()
_G["respCode"] = 0
_G["respBody"] = ""
_G["respHeaders"] = {}
hs.http.doAsyncRequest(
'http://google.com',
'GET',
nil,
{ ['accept-language'] = 'en', ['user-agent'] = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.88 Safari/537.36', Accept = '*/*' },
_G["callback"],
false
)

return success()
end