diff --git a/Xcode/WebServerIPhone/Vendor/CocoaHTTPServer/HTTPServer.m b/Xcode/WebServerIPhone/Vendor/CocoaHTTPServer/HTTPServer.m index 12b9f71ee..4ff1f166e 100644 --- a/Xcode/WebServerIPhone/Vendor/CocoaHTTPServer/HTTPServer.m +++ b/Xcode/WebServerIPhone/Vendor/CocoaHTTPServer/HTTPServer.m @@ -108,6 +108,10 @@ - (void)dealloc dispatch_release(connectionQueue); [asyncSocket setDelegate:nil delegateQueue:NULL]; + + + + } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/Xcode/WebServerIPhone/Vendor/CocoaHTTPServer/WebSocket.m b/Xcode/WebServerIPhone/Vendor/CocoaHTTPServer/WebSocket.m index 781567da9..e4b622938 100644 --- a/Xcode/WebServerIPhone/Vendor/CocoaHTTPServer/WebSocket.m +++ b/Xcode/WebServerIPhone/Vendor/CocoaHTTPServer/WebSocket.m @@ -22,7 +22,34 @@ #define TAG_PREFIX 300 #define TAG_MSG_PLUS_SUFFIX 301 +#define TAG_MSG_WITH_LENGTH 302 +#define TAG_MSG_MASKING_KEY 303 +#define TAG_PAYLOAD_PREFIX 304 +#define TAG_PAYLOAD_LENGTH 305 +#define TAG_PAYLOAD_LENGTH16 306 +#define TAG_PAYLOAD_LENGTH64 307 + +#define WS_OP_CONTINUATION_FRAME 0 +#define WS_OP_TEXT_FRAME 1 +#define WS_OP_BINARY_FRAME 2 +#define WS_OP_CONNECTION_CLOSE 8 +#define WS_OP_PING 9 +#define WS_OP_PONG 10 + +static inline BOOL WS_OP_IS_FINAL_FRAGMENT(UInt8 frame) +{ + return (frame & 0x80) ? YES : NO; +} + +static inline BOOL WS_PAYLOAD_IS_MASKED(UInt8 frame) +{ + return (frame & 0x80) ? YES : NO; +} +static inline NSUInteger WS_PAYLOAD_LENGTH(UInt8 frame) +{ + return frame & 0x7F; +} @interface WebSocket (PrivateAPI) @@ -37,6 +64,12 @@ - (void)sendResponseHeaders; //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @implementation WebSocket +{ + BOOL isRFC6455; + BOOL nextFrameMasked; + NSUInteger nextOpCode; + NSData *maskingKey; +} + (BOOL)isWebSocketRequest:(HTTPMessage *)request { @@ -106,6 +139,16 @@ + (BOOL)isVersion76Request:(HTTPMessage *)request return isVersion76; } ++ (BOOL)isRFC6455Request:(HTTPMessage *)request +{ + NSString *key = [request headerField:@"Sec-WebSocket-Key"]; + BOOL isRFC6455 = (key != nil); + + HTTPLogTrace2(@"%@: %@ - %@", THIS_FILE, THIS_METHOD, (isRFC6455 ? @"YES" : @"NO")); + + return isRFC6455; +} + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// #pragma mark Setup and Teardown //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -139,6 +182,7 @@ - (id)initWithRequest:(HTTPMessage *)aRequest socket:(GCDAsyncSocket *)socket isOpen = NO; isVersion76 = [[self class] isVersion76Request:request]; + isRFC6455 = [[self class] isRFC6455Request:request]; term = [[NSData alloc] initWithBytes:"\xFF" length:1]; } @@ -276,6 +320,12 @@ - (NSString *)locationResponseHeaderValue return location; } +- (NSString *)secWebSocketKeyResponseHeaderValue { + NSString *key = [request headerField: @"Sec-WebSocket-Key"]; + NSString *guid = @"258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; + return [[key stringByAppendingString: guid] dataUsingEncoding: NSUTF8StringEncoding].sha1Digest.base64Encoded; +} + - (void)sendResponseHeaders { HTTPLogTrace(); @@ -351,6 +401,11 @@ - (void)sendResponseHeaders [wsResponse setHeaderField:originField value:originValue]; [wsResponse setHeaderField:locationField value:locationValue]; + NSString *acceptValue = [self secWebSocketKeyResponseHeaderValue]; + if (acceptValue) { + [wsResponse setHeaderField: @"Sec-WebSocket-Accept" value: acceptValue]; + } + NSData *responseHeaders = [wsResponse messageData]; @@ -469,7 +524,7 @@ - (void)didOpen // Don't forget to invoke [super didOpen] in your method. // Start reading for messages - [asyncSocket readDataToLength:1 withTimeout:TIMEOUT_NONE tag:TAG_PREFIX]; + [asyncSocket readDataToLength:1 withTimeout:TIMEOUT_NONE tag:(isRFC6455 ? TAG_PAYLOAD_PREFIX : TAG_PREFIX)]; // Notify delegate if ([delegate respondsToSelector:@selector(webSocketDidOpen:)]) @@ -483,12 +538,43 @@ - (void)sendMessage:(NSString *)msg HTTPLogTrace(); NSData *msgData = [msg dataUsingEncoding:NSUTF8StringEncoding]; + NSMutableData *data = nil; - NSMutableData *data = [NSMutableData dataWithCapacity:([msgData length] + 2)]; - - [data appendBytes:"\x00" length:1]; - [data appendData:msgData]; - [data appendBytes:"\xFF" length:1]; + if (isRFC6455) + { + NSUInteger length = msgData.length; + if (length <= 125) + { + data = [NSMutableData dataWithCapacity:(length + 2)]; + [data appendBytes: "\x81" length:1]; + UInt8 len = (UInt8)length; + [data appendBytes: &len length:1]; + [data appendData:msgData]; + } + else if (length <= 0xFFFF) + { + data = [NSMutableData dataWithCapacity:(length + 4)]; + [data appendBytes: "\x81\x7E" length:2]; + UInt16 len = (UInt16)length; + [data appendBytes: (UInt8[]){len >> 8, len & 0xFF} length:2]; + [data appendData:msgData]; + } + else + { + data = [NSMutableData dataWithCapacity:(length + 10)]; + [data appendBytes: "\x81\x7F" length:2]; + [data appendBytes: (UInt8[]){0, 0, 0, 0, (UInt8)(length >> 24), (UInt8)(length >> 16), (UInt8)(length >> 8), length & 0xFF} length:8]; + [data appendData:msgData]; + } + } + else + { + data = [NSMutableData dataWithCapacity:([msgData length] + 2)]; + + [data appendBytes:"\x00" length:1]; + [data appendData:msgData]; + [data appendBytes:"\xFF" length:1]; + } // Remember: GCDAsyncSocket is thread-safe @@ -530,10 +616,42 @@ - (void)didClose [[NSNotificationCenter defaultCenter] postNotificationName:WebSocketDidDieNotification object:self]; } +#pragma mark WebSocket Frame + +- (BOOL)isValidWebSocketFrame:(UInt8)frame +{ + NSUInteger rsv = frame & 0x70; + NSUInteger opcode = frame & 0x0F; + if (rsv || (3 <= opcode && opcode <= 7) || (0xB <= opcode && opcode <= 0xF)) + { + return NO; + } + return YES; +} + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// #pragma mark AsyncSocket Delegate //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// 0 1 2 3 +// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +// +-+-+-+-+-------+-+-------------+-------------------------------+ +// |F|R|R|R| opcode|M| Payload len | Extended payload length | +// |I|S|S|S| (4) |A| (7) | (16/64) | +// |N|V|V|V| |S| | (if payload len==126/127) | +// | |1|2|3| |K| | | +// +-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - + +// | Extended payload length continued, if payload len == 127 | +// + - - - - - - - - - - - - - - - +-------------------------------+ +// | |Masking-key, if MASK set to 1 | +// +-------------------------------+-------------------------------+ +// | Masking-key (continued) | Payload Data | +// +-------------------------------- - - - - - - - - - - - - - - - + +// : Payload Data continued ... : +// + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + +// | Payload Data continued ... | +// +---------------------------------------------------------------+ + - (void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag { HTTPLogTrace(); @@ -559,6 +677,91 @@ - (void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)t [self didClose]; } } + else if (tag == TAG_PAYLOAD_PREFIX) + { + UInt8 *pFrame = (UInt8 *)[data bytes]; + UInt8 frame = *pFrame; + + if ([self isValidWebSocketFrame: frame]) + { + nextOpCode = (frame & 0x0F); + [asyncSocket readDataToLength:1 withTimeout:TIMEOUT_NONE tag:TAG_PAYLOAD_LENGTH]; + } + else + { + // Unsupported frame type + [self didClose]; + } + } + else if (tag == TAG_PAYLOAD_LENGTH) + { + UInt8 frame = *(UInt8 *)[data bytes]; + BOOL masked = WS_PAYLOAD_IS_MASKED(frame); + NSUInteger length = WS_PAYLOAD_LENGTH(frame); + nextFrameMasked = masked; + maskingKey = nil; + if (length <= 125) + { + if (nextFrameMasked) + { + [asyncSocket readDataToLength:4 withTimeout:TIMEOUT_NONE tag:TAG_MSG_MASKING_KEY]; + } + [asyncSocket readDataToLength:length withTimeout:TIMEOUT_NONE tag:TAG_MSG_WITH_LENGTH]; + } + else if (length == 126) + { + [asyncSocket readDataToLength:2 withTimeout:TIMEOUT_NONE tag:TAG_PAYLOAD_LENGTH16]; + } + else + { + [asyncSocket readDataToLength:8 withTimeout:TIMEOUT_NONE tag:TAG_PAYLOAD_LENGTH64]; + } + } + else if (tag == TAG_PAYLOAD_LENGTH16) + { + UInt8 *pFrame = (UInt8 *)[data bytes]; + NSUInteger length = ((NSUInteger)pFrame[0] << 8) | (NSUInteger)pFrame[1]; + if (nextFrameMasked) { + [asyncSocket readDataToLength:4 withTimeout:TIMEOUT_NONE tag:TAG_MSG_MASKING_KEY]; + } + [asyncSocket readDataToLength:length withTimeout:TIMEOUT_NONE tag:TAG_MSG_WITH_LENGTH]; + } + else if (tag == TAG_PAYLOAD_LENGTH64) + { + // FIXME: 64bit data size in memory? + [self didClose]; + } + else if (tag == TAG_MSG_WITH_LENGTH) + { + NSUInteger msgLength = [data length]; + if (nextFrameMasked && maskingKey) { + NSMutableData *masked = data.mutableCopy; + UInt8 *pData = (UInt8 *)masked.mutableBytes; + UInt8 *pMask = (UInt8 *)maskingKey.bytes; + for (NSUInteger i = 0; i < msgLength; i++) + { + pData[i] = pData[i] ^ pMask[i % 4]; + } + data = masked; + } + if (nextOpCode == WS_OP_TEXT_FRAME) + { + NSString *msg = [[NSString alloc] initWithBytes:[data bytes] length:msgLength encoding:NSUTF8StringEncoding]; + [self didReceiveMessage:msg]; + } + else + { + [self didClose]; + return; + } + + // Read next frame + [asyncSocket readDataToLength:1 withTimeout:TIMEOUT_NONE tag:TAG_PAYLOAD_PREFIX]; + } + else if (tag == TAG_MSG_MASKING_KEY) + { + maskingKey = data.copy; + } else { NSUInteger msgLength = [data length] - 1; // Excluding ending 0xFF frame