Skip to content

Commit

Permalink
Merging latest changes from CocoaHTTPServer project.
Browse files Browse the repository at this point in the history
  • Loading branch information
robbiehanson committed Mar 29, 2012
1 parent d982c56 commit 47f87cc
Show file tree
Hide file tree
Showing 2 changed files with 213 additions and 6 deletions.
4 changes: 4 additions & 0 deletions Xcode/WebServerIPhone/Vendor/CocoaHTTPServer/HTTPServer.m
Expand Up @@ -108,6 +108,10 @@ - (void)dealloc
dispatch_release(connectionQueue);

[asyncSocket setDelegate:nil delegateQueue:NULL];




}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
Expand Down
215 changes: 209 additions & 6 deletions Xcode/WebServerIPhone/Vendor/CocoaHTTPServer/WebSocket.m
Expand Up @@ -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)

Expand All @@ -37,6 +64,12 @@ - (void)sendResponseHeaders;
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

@implementation WebSocket
{
BOOL isRFC6455;
BOOL nextFrameMasked;
NSUInteger nextOpCode;
NSData *maskingKey;
}

+ (BOOL)isWebSocketRequest:(HTTPMessage *)request
{
Expand Down Expand Up @@ -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
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
Expand Down Expand Up @@ -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];
}
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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];


Expand Down Expand Up @@ -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:)])
Expand All @@ -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

Expand Down Expand Up @@ -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();
Expand All @@ -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
Expand Down

0 comments on commit 47f87cc

Please sign in to comment.