From 9cd57b731ab1c666d4a1cb725538fdc137763d12 Mon Sep 17 00:00:00 2001 From: Starla Insigna Date: Sat, 30 Jul 2011 11:19:14 -0400 Subject: Initial commit (version 0.2.1) --- libs/cocoslive/CLScoreServerPost.m | 335 +++++++++++++++++++++++++++++++++++++ 1 file changed, 335 insertions(+) create mode 100755 libs/cocoslive/CLScoreServerPost.m (limited to 'libs/cocoslive/CLScoreServerPost.m') diff --git a/libs/cocoslive/CLScoreServerPost.m b/libs/cocoslive/CLScoreServerPost.m new file mode 100755 index 0000000..43ee7b2 --- /dev/null +++ b/libs/cocoslive/CLScoreServerPost.m @@ -0,0 +1,335 @@ +/* + * cocos2d for iPhone: http://www.cocos2d-iphone.org + * + * Copyright (c) 2008-2010 Ricardo Quesada + * Copyright (c) 2011 Zynga Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + */ + + +#import "CLScoreServerPost.h" +#import "ccMacros.h" + +// free function used to sort +NSInteger alphabeticSort(id string1, id string2, void *reverse); + +NSInteger alphabeticSort(id string1, id string2, void *reverse) +{ + if ((NSInteger *)reverse == NO) + return [string2 localizedCaseInsensitiveCompare:string1]; + return [string1 localizedCaseInsensitiveCompare:string2]; +} + + +@interface CLScoreServerPost (Private) +-(void) addValue:(NSString*)value key:(NSString*)key; +-(void) calculateHashAndAddValue:(id)value key:(NSString*)key; +-(NSString*) getHashForData; +-(NSData*) getBodyValues; +-(NSString*) encodeData:(NSString*)data; +-(NSMutableURLRequest *) scoreServerRequestWithURLString:(NSString *)url; +-(BOOL) submitScore:(NSDictionary*)dict forUpdate:(BOOL)isUpdate; +@end + + +@implementation CLScoreServerPost + +@synthesize postStatus = postStatus_; +@synthesize ranking = ranking_; +@synthesize scoreDidUpdate = scoreDidUpdate_; +@synthesize connection = connection_; + ++(id) serverWithGameName:(NSString*) name gameKey:(NSString*) key delegate:(id) delegate +{ + return [[[self alloc] initWithGameName:name gameKey:key delegate:delegate] autorelease]; +} + +-(id) initWithGameName:(NSString*) name gameKey:(NSString*) key delegate:(id)aDelegate +{ + self = [super init]; + if( self ) { + gameKey = [key retain]; + gameName = [name retain]; + bodyValues = [[NSMutableArray arrayWithCapacity:5] retain]; + delegate = [aDelegate retain]; + receivedData = [[NSMutableData data] retain]; + + ranking_ = kServerPostInvalidRanking; + } + + return self; +} + +-(void) dealloc +{ + CCLOGINFO(@"deallocing %@", self); + [delegate release]; + [gameKey release]; + [gameName release]; + [bodyValues release]; + [receivedData release]; + [connection_ release]; + [super dealloc]; +} + + +#pragma mark ScoreServer send scores +-(BOOL) sendScore: (NSDictionary*) dict +{ + return [self submitScore:dict forUpdate:NO]; +} + +-(BOOL) updateScore: (NSDictionary*) dict +{ + if (![dict objectForKey:@"cc_playername"]) { + // fail. cc_playername + cc_device_id are needed to update an score + [NSException raise:@"cocosLive:updateScore" format:@"cc_playername not found"]; + } + return [self submitScore:dict forUpdate:YES]; +} + +-(BOOL) submitScore: (NSDictionary*)dict forUpdate:(BOOL)isUpdate +{ + [receivedData setLength:0]; + [bodyValues removeAllObjects]; + + // reset status + postStatus_ = kPostStatusOK; + + // create the request + NSMutableURLRequest *post = [self scoreServerRequestWithURLString:(isUpdate ? SCORE_SERVER_UPDATE_URL : SCORE_SERVER_SEND_URL)]; + + CC_MD5_Init( &md5Ctx); + + // hash SHALL be calculated in certain order + NSArray *keys = [dict allKeys]; + int reverseSort = NO; + NSArray *sortedKeys = [keys sortedArrayUsingFunction:alphabeticSort context:&reverseSort]; + for( id key in sortedKeys ) + [self calculateHashAndAddValue:[dict objectForKey:key] key:key]; + + // device id is hashed to prevent spoofing this same score from different devices + // one way to prevent a replay attack is to send cc_id & cc_time and use it as primary keys + + [self addValue:[[UIDevice currentDevice] uniqueIdentifier] key:@"cc_device_id"]; + [self addValue:gameName key:@"cc_gamename"]; + [self addValue:[self getHashForData] key:@"cc_hash"]; + [self addValue:SCORE_SERVER_PROTOCOL_VERSION key:@"cc_prot_ver"]; + + [post setHTTPBody: [self getBodyValues] ]; + + // create the connection with the request + // and start loading the data + self.connection=[NSURLConnection connectionWithRequest:post delegate:self]; + + if ( ! connection_) + return NO; + + return YES; +} + +-(NSMutableURLRequest *) scoreServerRequestWithURLString:(NSString *)url { + NSMutableURLRequest *request=[NSMutableURLRequest requestWithURL:[NSURL URLWithString: url] + cachePolicy:NSURLRequestUseProtocolCachePolicy + timeoutInterval:10.0]; + + [request setHTTPMethod: @"POST"]; + [request setValue:@"application/x-www-form-urlencoded" forHTTPHeaderField:@"Content-Type"]; + return request; +} + +-(void) calculateHashAndAddValue:(id) value key:(NSString*) key +{ + NSString *val; + // value shall be a string or nsnumber + if( [value respondsToSelector:@selector(stringValue)] ) + val = [value stringValue]; + else if( [value isKindOfClass:[NSString class]] ) + val = value; + else + [NSException raise:@"Invalid format for value" format:@"Invalid format for value. addValue"]; + + [self addValue:val key:key]; + + const char * data = [val UTF8String]; + CC_MD5_Update( &md5Ctx, data, strlen(data) ); +} + +-(void) addValue:(NSString*)value key:(NSString*) key +{ + + NSString *encodedValue = [self encodeData:value]; + NSString *encodedKey = [self encodeData:key]; + + [bodyValues addObject: [NSString stringWithFormat:@"%@=%@", encodedKey, encodedValue] ]; +} + +-(NSData*) getBodyValues { + NSMutableData *data = [[NSMutableData alloc] init]; + + BOOL first=YES; + for( NSString *s in bodyValues ) { + if( !first) + [data appendBytes:"&" length:1]; + + [data appendBytes:[s UTF8String] length:[s length]]; + first = NO; + } + + return [data autorelease]; +} + +-(NSString*) getHashForData +{ + NSString *ret; + unsigned char pTempKey[16]; + + // update the hash with the secret key + const char *data = [gameKey UTF8String]; + CC_MD5_Update(&md5Ctx, data, strlen(data)); + + // then get the hash + CC_MD5_Final( pTempKey, &md5Ctx); + +// NSData *nsdata = [NSData dataWithBytes:pTempKey length:16]; + ret = [NSString stringWithString:@""]; + for( int i=0;i<16;i++) { + ret = [NSString stringWithFormat:@"%@%02x", ret, pTempKey[i] ]; + } + + return ret; +} + +-(NSString*) encodeData:(NSString*) data +{ + NSString *newData; + + newData = [data stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]; + + // '&' and '=' should be encoded manually + newData = [newData stringByReplacingOccurrencesOfString:@"&" withString:@"%26"]; + newData = [newData stringByReplacingOccurrencesOfString:@"=" withString:@"%3D"]; + + return newData; +} + +#pragma mark NSURLConnection Delegate + +- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response +{ + // this method is called when the server has determined that it + // has enough information to create the NSURLResponse + + // it can be called multiple times, for example in the case of a + // redirect, so each time we reset the data. + // receivedData is declared as a method instance elsewhere + [receivedData setLength:0]; +} + +- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data +{ + // append the new data to the receivedData + // receivedData is declared as a method instance elsewhere + [receivedData appendData:data]; + +// NSString *dataString = [NSString stringWithCString:[data bytes] length: [data length]]; +// CCLOG( @"data: %@", dataString); +} + +- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error +{ + CCLOG(@"Connection failed"); + + // wifi problems ? + postStatus_ = kPostStatusConnectionFailed; + + // release the connection + self.connection = nil; + + if( [delegate respondsToSelector:@selector(scorePostFail:) ] ) + [delegate scorePostFail:self]; +} + +- (void)connectionDidFinishLoading:(NSURLConnection *)connection +{ + // release the connection + self.connection = nil; + +// NSString *dataString = [NSString stringWithCString:[receivedData bytes] length: [receivedData length]]; + NSString *dataString = [NSString stringWithCString:[receivedData bytes] encoding: NSASCIIStringEncoding]; + + if( [dataString hasPrefix:@"OK:"] ) { + // parse ranking and other possible answers + NSArray *values = [dataString componentsSeparatedByString:@":"]; + NSArray *answer = [ [values objectAtIndex:1] componentsSeparatedByString:@","]; + NSEnumerator *nse = [answer objectEnumerator]; + + // Create a holder for each line we are going to work with + NSString *line; + + // Loop through all the lines in the lines array processing each one + while( (line = [nse nextObject]) ) { + NSArray *keyvalue = [line componentsSeparatedByString:@"="]; +// NSLog(@"%@",keyvalue); + if( [[keyvalue objectAtIndex:0] isEqual:@"ranking"] ) { + ranking_ = [[keyvalue objectAtIndex:1] intValue]; + } else if( [[keyvalue objectAtIndex:0] isEqual:@"score_updated"] ) { + scoreDidUpdate_ = [[keyvalue objectAtIndex:1] boolValue]; + } + + } + if( [delegate respondsToSelector:@selector(scorePostOk:) ] ) + [delegate scorePostOk:self]; + + } else if( [dataString hasPrefix: @"OK"] ) { + + // Ok + postStatus_ = kPostStatusOK; + + if( [delegate respondsToSelector:@selector(scorePostOk:) ] ) + [delegate scorePostOk:self]; + + + } else { + + NSLog(@"cocoslive: Post Score failed. Reason: %@", dataString); + + // Error parsing answer + postStatus_ = kPostStatusPostFailed; + + if( [delegate respondsToSelector:@selector(scorePostFail:) ] ) + [delegate scorePostFail:self]; + } +} + +-(NSURLRequest *)connection:(NSURLConnection *)connection + willSendRequest:(NSURLRequest *)request + redirectResponse:(NSURLResponse *)redirectResponse +{ + NSURLRequest *newRequest=request; + if (redirectResponse) { + newRequest=nil; + } + + return newRequest; +} + +@end -- cgit 1.4.1