From 26bb54472f8e94c820b6282301cd231ea5e38e61 Mon Sep 17 00:00:00 2001 From: Malte Tancred Date: Wed, 12 Aug 2015 17:05:56 +0200 Subject: [PATCH 1/4] Fixed: preserve type of boolean values when parsing CPPredicate format When parsing a predicate format that contains a constant boolean expression (e.g., 'key = YES') the value of the returned constant value expression has the wrong type. Instead of using native types as values, the parser uses [CPNumber +numberWithBool:]. This commit changes the format parser (CPPredicateScanner) to use native booleans for constant value expression. There's a test case that checks the parse result and an additional test that ensures boolean expressions evaluate as expected with different kinds of objects. The latter test passed before the fix but was added to ensure compliance with Cocoa. --- Foundation/CPPredicate/_CPPredicate.j | 4 +-- Tests/Foundation/CPPredicateTest.j | 51 +++++++++++++++++++++++++++ 2 files changed, 53 insertions(+), 2 deletions(-) diff --git a/Foundation/CPPredicate/_CPPredicate.j b/Foundation/CPPredicate/_CPPredicate.j index 201bcccd0..56bfcd643 100644 --- a/Foundation/CPPredicate/_CPPredicate.j +++ b/Foundation/CPPredicate/_CPPredicate.j @@ -605,11 +605,11 @@ } if ([self scanPredicateKeyword:@"TRUE"] || [self scanPredicateKeyword:@"YES"]) { - return [CPExpression expressionForConstantValue:[CPNumber numberWithBool:YES]]; + return [CPExpression expressionForConstantValue:YES]; } if ([self scanPredicateKeyword:@"FALSE"] || [self scanPredicateKeyword:@"NO"]) { - return [CPExpression expressionForConstantValue:[CPNumber numberWithBool:NO]]; + return [CPExpression expressionForConstantValue:NO]; } if ([self scanPredicateKeyword:@"SELF"]) { diff --git a/Tests/Foundation/CPPredicateTest.j b/Tests/Foundation/CPPredicateTest.j index 114f440e9..670a13b5b 100644 --- a/Tests/Foundation/CPPredicateTest.j +++ b/Tests/Foundation/CPPredicateTest.j @@ -526,6 +526,57 @@ [self assertTrue:[result isEqual:data] message:"[1, 2, 3] filtered with '" + [ctpred description] + "' should return [1, 2, 3]"]; } +- (void)testConstantValueExpressionWithBooleanValueIsBoolean +{ + var right = [[CPPredicate predicateWithFormat:@"k = YES"] rightExpression]; + + [self assert:CPConstantValueExpressionType equals:[right expressionType]]; + [self assert:YES equals:[right constantValue]]; + [self assert:'boolean' equals:typeof [right constantValue]]; + + right = [[CPPredicate predicateWithFormat:@"k = NO"] rightExpression]; + + [self assert:CPConstantValueExpressionType equals:[right expressionType]]; + [self assert:NO equals:[right constantValue]]; + [self assert:'boolean' equals:typeof [right constantValue]]; +} + +- (void)testEvaluateBooleanConstantValueExpressionWithDifferentObjects +{ + var yesPredicate = [CPPredicate predicateWithFormat:@"self = YES"], + noPredicate = [CPPredicate predicateWithFormat:@"self = NO"]; + + var evalYesPredicate = function(object) { return [yesPredicate evaluateWithObject:object]; }, + evalNoPredicate = function(object) { return [noPredicate evaluateWithObject:object]; }; + + var fixtures = [ + { + message:@"booleans", + values: [YES, NO], + expected_yes: [YES, NO], + expected_no: [ NO, YES] + }, + { + message:@"numbers", + values: [ 0, 1, 2], + expected_yes: [ NO, YES, NO], + expected_no: [YES, NO, NO] + }, + { + message:@"strings", + values: [@"1", @"2", @"true", @"false", @"", @"YES", @"NO"], + expected_yes: [ NO , NO , NO , NO , NO, NO , NO ], + expected_no: [ NO , NO , NO , NO , NO, NO , NO ] + }]; + + for (var i = 0; i < [fixtures count]; i++) + { + var fixture = fixtures[i]; + [self assert:fixture.expected_yes equals:fixture.values.map(evalYesPredicate) message:fixture.message + " with YES-predicate"]; + [self assert:fixture.expected_no equals:fixture.values.map(evalNoPredicate) message:fixture.message + " with NO-predicate"]; + } +} + @end @implementation CPObject (PredicateTesting) From bd0bd2e087d7063487ec85b9042701d795af591e Mon Sep 17 00:00:00 2001 From: Alexandre Wilhelm Date: Thu, 13 Aug 2015 17:08:59 -0700 Subject: [PATCH 2/4] New: added timeout and cache feature in CPURLRequest This PR adds the possibility to set the timeout and the cache policy of a CPURLRequest. For that, there are new things in the framework : - CFHTTPRequest has now the functions setTimeout(), getTimeout() and isTimeoutRequest - CPURLRequest has now the methods +requestWithURL:cachePolicy:timeoutInterval: and -initWithURL:cachePolicy:timeoutInterval: - CPURLConnection does not call the delegate didReceiveData: when a request has timed out. I will call the delegate didFailWithError: - Cache is now possible for a CPURLRequest, only CPURLRequestReturnCacheDataElseLoad, CPURLRequestReturnCacheDataDontLoad and CPURLRequestReloadIgnoringLocalCacheData are supported. By default CPURLRequestReloadIgnoringLocalCacheData. - Default timeout has been set to 60sec as in cocoa. - A request is considered as timeout when there isn't any response (text/xml/type) and when the status of the response is 0. --- Foundation/CPURLConnection.j | 30 ++++++++++++-- Foundation/CPURLRequest.j | 78 +++++++++++++++++++++++++++++++++--- Objective-J/CFHTTPRequest.js | 38 ++++++++++++++++-- 3 files changed, 132 insertions(+), 14 deletions(-) diff --git a/Foundation/CPURLConnection.j b/Foundation/CPURLConnection.j index b962d107f..911ac5a91 100644 --- a/Foundation/CPURLConnection.j +++ b/Foundation/CPURLConnection.j @@ -112,6 +112,7 @@ var CPURLConnectionDelegate = nil; try { var aCFHTTPRequest = new CFHTTPRequest(); + aCFHTTPRequest.setTimeout([aRequest timeoutInterval] * 1000); aCFHTTPRequest.setWithCredentials([aRequest withCredentials]); aCFHTTPRequest.open([aRequest HTTPMethod], [[aRequest URL] absoluteString], NO); @@ -125,7 +126,7 @@ var CPURLConnectionDelegate = nil; aCFHTTPRequest.send([aRequest HTTPBody]); - if (!aCFHTTPRequest.success()) + if (!aCFHTTPRequest.success() || aCFHTTPRequest.isTimeoutRequest()) return nil; return [CPData dataWithRawString:aCFHTTPRequest.responseText()]; @@ -176,6 +177,7 @@ var CPURLConnectionDelegate = nil; (window.location.protocol === "file:" || window.location.protocol === "app:")); _HTTPRequest = new CFHTTPRequest(); + _HTTPRequest.setTimeout([aRequest timeoutInterval] * 1000); _HTTPRequest.setWithCredentials([aRequest withCredentials]); if (shouldStartImmediately) @@ -210,6 +212,7 @@ var CPURLConnectionDelegate = nil; _HTTPRequest.open([_request HTTPMethod], [[_request URL] absoluteString], YES); _HTTPRequest.onreadystatechange = function() { [self _readyStateDidChange]; }; + _HTTPRequest.ontimeout = function() { [self _timeout]; }; var fields = [_request allHTTPHeaderFields], key = nil, @@ -222,11 +225,16 @@ var CPURLConnectionDelegate = nil; } catch (anException) { - if ([_delegate respondsToSelector:@selector(connection:didFailWithError:)]) - [_delegate connection:self didFailWithError:anException]; + [self _sendDelegateDidFailWithError:anException]; } } +- (void)_sendDelegateDidFailWithError:(CPException)anException +{ + if ([_delegate respondsToSelector:@selector(connection:didFailWithError:)]) + [_delegate connection:self didFailWithError:anException]; +} + /* Cancels the current request. */ @@ -249,10 +257,24 @@ var CPURLConnectionDelegate = nil; return _isLocalFileConnection; } +/*! + @ignore +*/ +- (void)_timeout +{ + var exception = [CPException exceptionWithName:@"Timeout exception" + reason:"The request timed out." + userInfo:@{}]; + + [self _sendDelegateDidFailWithError:exception]; +} + /* @ignore */ - (void)_readyStateDidChange { - if (_HTTPRequest.readyState() === CFHTTPRequest.CompleteState) + var isTimeoutRequest = !_HTTPRequest.status() && !_HTTPRequest.responseText(); + + if (_HTTPRequest.readyState() === CFHTTPRequest.CompleteState && !_HTTPRequest.isTimeoutRequest()) { var statusCode = _HTTPRequest.status(), URL = [_request URL]; diff --git a/Foundation/CPURLRequest.j b/Foundation/CPURLRequest.j index 8d6a39e3b..e34061c90 100644 --- a/Foundation/CPURLRequest.j +++ b/Foundation/CPURLRequest.j @@ -25,6 +25,12 @@ @import "CPString.j" @import "CPURL.j" +@typedef CPURLRequestCachePolicy +CPURLRequestUseProtocolCachePolicy = 0; +CPURLRequestReloadIgnoringLocalCacheData = 1; +CPURLRequestReturnCacheDataElseLoad = 2; +CPURLRequestReturnCacheDataDontLoad = 3; + /*! @class CPURLRequest @ingroup foundation @@ -35,14 +41,16 @@ */ @implementation CPURLRequest : CPObject { - CPURL _URL @accessors(property=URL); + CPURL _URL @accessors(property=URL); // FIXME: this should be CPData - CPString _HTTPBody @accessors(property=HTTPBody); - CPString _HTTPMethod @accessors(property=HTTPMethod); - BOOL _withCredentials @accessors(property=withCredentials); + CPString _HTTPBody @accessors(property=HTTPBody); + CPString _HTTPMethod @accessors(property=HTTPMethod); + BOOL _withCredentials @accessors(property=withCredentials); - CPDictionary _HTTPHeaderFields @accessors(readonly, getter=allHTTPHeaderFields); + CPDictionary _HTTPHeaderFields @accessors(readonly, getter=allHTTPHeaderFields); + CPTimeInterval _timeoutInterval @accessors(readonly, getter=timeoutInterval); + CPURLRequestCachePolicy _cachePolicy @accessors(readonly, getter=cachePolicy); } /*! @@ -55,6 +63,18 @@ return [[CPURLRequest alloc] initWithURL:aURL]; } +/*! + Creates a request with a specified URL, cachePolicy and timeoutInterval + @param aURL the URL of the request + @param aCachePolicy the cache policy of the request + @param aTimeoutInterval the timeoutInterval of the request + @return a CPURLRequest +*/ ++ (id)requestWithURL:(CPURL)anURL cachePolicy:(CPURLRequestCachePolicy)aCachePolicy timeoutInterval:(CPTimeInterval)aTimeoutInterval +{ + return [[CPURLRequest alloc] initWithURL:anURL cachePolicy:aCachePolicy timeoutInterval:aTimeoutInterval]; +} + /*! Equal to `[receiver initWithURL:nil]`. */ @@ -63,6 +83,27 @@ return [self initWithURL:nil]; } +/*! + Initializes the request with a URL. This is the designated initializer. + + @param aURL the url to set + @param aCachePolicy the cache policy of the request + @param aTimeoutInterval the timeoutInterval of the request + @return the initialized CPURLRequest +*/ +- (id)initWithURL:(CPURL)anURL cachePolicy:(CPURLRequestCachePolicy)aCachePolicy timeoutInterval:(CPTimeInterval)aTimeoutInterval +{ + self = [self initWithURL:anURL]; + + if (self) + { + _cachePolicy = aCachePolicy; + _timeoutInterval = aTimeoutInterval; + } + + return self; +} + /*! Initializes the request with a URL. This is the designated initializer. @@ -81,9 +122,34 @@ _HTTPMethod = @"GET"; _HTTPHeaderFields = @{}; _withCredentials = NO; + _timeoutInterval = 60.0; + _cachePolicy = CPURLRequestUseProtocolCachePolicy; [self setValue:"Thu, 01 Jan 1970 00:00:00 GMT" forHTTPHeaderField:"If-Modified-Since"]; - [self setValue:"no-cache" forHTTPHeaderField:"Cache-Control"]; + + switch (_cachePolicy) + { + case CPURLRequestUseProtocolCachePolicy: + // TODO: implement everything about cache... + [self setValue:"no-cache" forHTTPHeaderField:"Cache-Control"]; + break; + + case CPURLRequestReturnCacheDataElseLoad: + [self setValue:"max-stale=31536000" forHTTPHeaderField:"Cache-Control"]; + break; + + case CPURLRequestReturnCacheDataDontLoad: + [self setValue:"only-if-cached" forHTTPHeaderField:"Cache-Control"]; + break; + + case CPURLRequestReloadIgnoringLocalCacheData: + [self setValue:"no-cache" forHTTPHeaderField:"Cache-Control"]; + break; + + default: + [self setValue:"no-cache" forHTTPHeaderField:"Cache-Control"]; + } + [self setValue:"XMLHttpRequest" forHTTPHeaderField:"X-Requested-With"]; } diff --git a/Objective-J/CFHTTPRequest.js b/Objective-J/CFHTTPRequest.js index 0d34667f2..7d0a93d91 100644 --- a/Objective-J/CFHTTPRequest.js +++ b/Objective-J/CFHTTPRequest.js @@ -105,6 +105,7 @@ GLOBAL(CFHTTPRequest) = function() // by default, all requests will assume that credentials should not be sent. this._nativeRequest.withCredentials = false; + this._nativeRequest.timeout = 60000; var self = this; this._stateChangeHandler = function() @@ -112,7 +113,13 @@ GLOBAL(CFHTTPRequest) = function() determineAndDispatchHTTPRequestEvents(self); }; + this._timeoutHandler = function() + { + dispatchTimeoutHTTPRequestEvents(self); + }; + this._nativeRequest.onreadystatechange = this._stateChangeHandler; + this._nativeRequest.ontimeout = this._timeoutHandler; if (CFHTTPRequest.AuthenticationDelegate !== nil) this._eventDispatcher.addEventListener("HTTP403", function() @@ -209,6 +216,16 @@ CFHTTPRequest.prototype.getResponseHeader = function(/*String*/ aHeader) return this._nativeRequest.getResponseHeader(aHeader); }; +CFHTTPRequest.prototype.setTimeout = function(/*int*/ aTimeout) +{ + this._nativeRequest.timeout = aTimeout; +}; + +CFHTTPRequest.prototype.getTimeout = function(/*int*/ aTimeout) +{ + return this._nativeRequest.timeout +}; + CFHTTPRequest.prototype.getAllResponseHeaders = function() { return this._nativeRequest.getAllResponseHeaders(); @@ -235,7 +252,10 @@ CFHTTPRequest.prototype.send = function(/*Object*/ aBody) if (!this._isOpen) { delete this._nativeRequest.onreadystatechange; + delete this._nativeRequest.ontimeout; + this._nativeRequest.open(this._method, this._URL, this._async, this._user, this._password); + this._nativeRequest.ontimeout = this._timeoutHandler; this._nativeRequest.onreadystatechange = this._stateChangeHandler; } @@ -287,15 +307,25 @@ CFHTTPRequest.prototype.withCredentials = function() return this._nativeRequest.withCredentials; }; +CFHTTPRequest.prototype.isTimeoutRequest = function() +{ + // Can we consider that as a timeout ? + return !this.success() && !this._nativeRequest.response && !this._nativeRequest.responseText && !this._nativeRequest.responseType && !this._nativeRequest.responseURL && !this._nativeRequest.responseXML; +}; + +function dispatchTimeoutHTTPRequestEvents(/*CFHTTPRequest*/ aRequest) +{ + aRequest._eventDispatcher.dispatchEvent({ type:"timeout", request:aRequest}); +} + function determineAndDispatchHTTPRequestEvents(/*CFHTTPRequest*/ aRequest) { - var eventDispatcher = aRequest._eventDispatcher; + var eventDispatcher = aRequest._eventDispatcher, + nativeRequest = aRequest._nativeRequest, + readyStates = ["uninitialized", "loading", "loaded", "interactive", "complete"]; eventDispatcher.dispatchEvent({ type:"readystatechange", request:aRequest}); - var nativeRequest = aRequest._nativeRequest, - readyStates = ["uninitialized", "loading", "loaded", "interactive", "complete"]; - if (readyStates[aRequest.readyState()] === "complete") { var status = "HTTP" + aRequest.status(); From d4b035ac1ac28321d5f6d91df39ac3b6d60a3456 Mon Sep 17 00:00:00 2001 From: Alexandre Wilhelm Date: Fri, 14 Aug 2015 12:40:22 -0700 Subject: [PATCH 3/4] Fixed: style in CPURLConnection.j CPURLRequest.j CFHTTPRequest.js --- Foundation/CPURLConnection.j | 10 ++++------ Foundation/CPURLRequest.j | 8 ++------ Objective-J/CFHTTPRequest.js | 2 +- 3 files changed, 7 insertions(+), 13 deletions(-) diff --git a/Foundation/CPURLConnection.j b/Foundation/CPURLConnection.j index 911ac5a91..9107f7890 100644 --- a/Foundation/CPURLConnection.j +++ b/Foundation/CPURLConnection.j @@ -212,7 +212,7 @@ var CPURLConnectionDelegate = nil; _HTTPRequest.open([_request HTTPMethod], [[_request URL] absoluteString], YES); _HTTPRequest.onreadystatechange = function() { [self _readyStateDidChange]; }; - _HTTPRequest.ontimeout = function() { [self _timeout]; }; + _HTTPRequest.ontimeout = function() { [self _didTimeout]; }; var fields = [_request allHTTPHeaderFields], key = nil, @@ -260,11 +260,11 @@ var CPURLConnectionDelegate = nil; /*! @ignore */ -- (void)_timeout +- (void)_didTimeout { var exception = [CPException exceptionWithName:@"Timeout exception" - reason:"The request timed out." - userInfo:@{}]; + reason:"The request timed out." + userInfo:@{}]; [self _sendDelegateDidFailWithError:exception]; } @@ -272,8 +272,6 @@ var CPURLConnectionDelegate = nil; /* @ignore */ - (void)_readyStateDidChange { - var isTimeoutRequest = !_HTTPRequest.status() && !_HTTPRequest.responseText(); - if (_HTTPRequest.readyState() === CFHTTPRequest.CompleteState && !_HTTPRequest.isTimeoutRequest()) { var statusCode = _HTTPRequest.status(), diff --git a/Foundation/CPURLRequest.j b/Foundation/CPURLRequest.j index e34061c90..9c0ad1b46 100644 --- a/Foundation/CPURLRequest.j +++ b/Foundation/CPURLRequest.j @@ -93,9 +93,7 @@ CPURLRequestReturnCacheDataDontLoad = 3; */ - (id)initWithURL:(CPURL)anURL cachePolicy:(CPURLRequestCachePolicy)aCachePolicy timeoutInterval:(CPTimeInterval)aTimeoutInterval { - self = [self initWithURL:anURL]; - - if (self) + if (self = [self initWithURL:anURL]) { _cachePolicy = aCachePolicy; _timeoutInterval = aTimeoutInterval; @@ -112,9 +110,7 @@ CPURLRequestReturnCacheDataDontLoad = 3; */ - (id)initWithURL:(CPURL)aURL { - self = [super init]; - - if (self) + if (self = [super init]) { [self setURL:aURL]; diff --git a/Objective-J/CFHTTPRequest.js b/Objective-J/CFHTTPRequest.js index 7d0a93d91..2f4a06f8c 100644 --- a/Objective-J/CFHTTPRequest.js +++ b/Objective-J/CFHTTPRequest.js @@ -223,7 +223,7 @@ CFHTTPRequest.prototype.setTimeout = function(/*int*/ aTimeout) CFHTTPRequest.prototype.getTimeout = function(/*int*/ aTimeout) { - return this._nativeRequest.timeout + return this._nativeRequest.timeout; }; CFHTTPRequest.prototype.getAllResponseHeaders = function() From acb9931fe67c840bed9ec9125de9aabace9791d1 Mon Sep 17 00:00:00 2001 From: Alexandre Wilhelm Date: Fri, 14 Aug 2015 13:28:27 -0700 Subject: [PATCH 4/4] Fixed: not possible to to synchrone request and set a timeout --- Foundation/CPURLConnection.j | 3 +-- Objective-J/CFHTTPRequest.js | 1 - 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/Foundation/CPURLConnection.j b/Foundation/CPURLConnection.j index 9107f7890..cd195b04a 100644 --- a/Foundation/CPURLConnection.j +++ b/Foundation/CPURLConnection.j @@ -112,7 +112,6 @@ var CPURLConnectionDelegate = nil; try { var aCFHTTPRequest = new CFHTTPRequest(); - aCFHTTPRequest.setTimeout([aRequest timeoutInterval] * 1000); aCFHTTPRequest.setWithCredentials([aRequest withCredentials]); aCFHTTPRequest.open([aRequest HTTPMethod], [[aRequest URL] absoluteString], NO); @@ -126,7 +125,7 @@ var CPURLConnectionDelegate = nil; aCFHTTPRequest.send([aRequest HTTPBody]); - if (!aCFHTTPRequest.success() || aCFHTTPRequest.isTimeoutRequest()) + if (!aCFHTTPRequest.success()) return nil; return [CPData dataWithRawString:aCFHTTPRequest.responseText()]; diff --git a/Objective-J/CFHTTPRequest.js b/Objective-J/CFHTTPRequest.js index 2f4a06f8c..eff9a49df 100644 --- a/Objective-J/CFHTTPRequest.js +++ b/Objective-J/CFHTTPRequest.js @@ -105,7 +105,6 @@ GLOBAL(CFHTTPRequest) = function() // by default, all requests will assume that credentials should not be sent. this._nativeRequest.withCredentials = false; - this._nativeRequest.timeout = 60000; var self = this; this._stateChangeHandler = function()