Skip to content

Commit

Permalink
hs.http must follow redirects #1889
Browse files Browse the repository at this point in the history
1. new parameter "enableRedirect" for hs.http.doAsyncRequest
2. add 5 tests to test this change

pr-feature
  • Loading branch information
pdckxd committed Aug 22, 2022
1 parent 1bd0c18 commit 0e7a1e5
Show file tree
Hide file tree
Showing 4 changed files with 249 additions and 2 deletions.
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
45 changes: 43 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,22 +212,29 @@ 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 given, cachePolicy parameter can't be omitted or set to nil. If enableRedirect is set to true, response body will be empty string. 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_TOPTIONAL, LS_TBOOLEAN | LS_TOPTIONAL, LS_TBREAK];

NSString* cachePolicy = nil;
if (lua_type(L, 6) == LUA_TSTRING) {
cachePolicy = [skin toNSObjectAtIndex:6];
}

bool enableRedirect = true;
if (lua_type(L, 7) == LUA_TBOOLEAN) {
enableRedirect = lua_toboolean(L, 7);
}

NSMutableURLRequest* request = getRequestFromStack(L, cachePolicy);
getBodyFromStack(L, 3, request);
extractHeadersFromStack(L, 4, request);
Expand All @@ -204,6 +243,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
149 changes: 149 additions & 0 deletions extensions/http/test_http.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
hs.http = require("hs.http")

respCode = 0
respBody = ""
respHeaders = {}

callback = function(code, body, headers)
respCode = code
respBody = body
respHeaders = headers
end

-- check error should happen if cachePolicy is set to nil and enableRedirect is given
-- check point: pcall returns false, and error message contains "incorrect type 'nil' for argument 6 (expected string)"
function testHttpDoAsyncRequestWithRedirectParamButNoCachePolicyParam()
local ok, errMsg = pcall(function()
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 = '*/*' },
callback,
nil, -- this will trigger exception
false
)
end)
assertIsEqual(false, ok)
print(hs.inspect(errMsg))
local startIdx, _ = string.find(errMsg, "incorrect type 'nil' for argument 6 %(expected string%)")
assertGreaterThanOrEqualTo(0, startIdx)

return success()
end

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

-- check request should be redirected if enableRedirect is not given and cachePolicy param is given
-- check point: response code == 200
function testHttpDoAsyncRequestWithCachePolicyParam()
respCode = 0
respBody = ""
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 = '*/*' },
callback,
'protocolCachePolicy'
)

return success()
end

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

-- check request should be redirected if enableRedirect param is not given.
-- check point: response code == 200
function testHttpDoAsyncRequestWithNoEnableRedirectParam()
respCode = 0
respBody = ""
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 = '*/*' },
callback
)

return success()
end

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

-- check request should be redirected if enableRedirect is set to true
-- check point: response code == 200
function testHttpDoAsyncRequestWithRedirection()
respCode = 0
respBody = ""
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 = '*/*' },
callback,
'protocolCachePolicy',
true
)

return success()
end

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

-- check request should not be redirected if enableRedirect is set to false
-- check point: response code == 301
function testHttpDoAsyncRequestWithoutRedirection()
respCode = 0
respBody = ""
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 = '*/*' },
callback,
'protocolCachePolicy',
false
)

return success()
end

0 comments on commit 0e7a1e5

Please sign in to comment.