Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WebSocket should support data frame whose length is larger than 64k #164

Closed
wants to merge 17 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions CocoaHTTPServer.podspec.json → CocoaHTTPServer2.podspec.json
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
{
"name": "CocoaHTTPServer",
"version": "2.3",
"name": "CocoaHTTPServer2",
"version": "2.4.1",
"license": "BSD",
"summary": "A small, lightweight, embeddable HTTP server for Mac OS X or iOS applications.",
"homepage": "https://github.com/robbiehanson/CocoaHTTPServer",
"homepage": "https://github.com/huangjimmy/CocoaHTTPServer",
"authors": {
"Robbie Hanson": "[email protected]"
},
"source": {
"git": "https://github.com/robbiehanson/CocoaHTTPServer.git",
"tag": "2.3"
"git": "https://github.com/huangjimmy/CocoaHTTPServer.git",
"tag": "2.4.1"
},
"source_files": "{Core,Extensions}/**/*.{h,m}",
"requires_arc": true,
Expand Down
12 changes: 6 additions & 6 deletions Core/Mime/MultipartFormDataParser.m
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ + (NSData*) decodedDataFromData:(NSData*) data encoding:(int) encoding;
- (int) findHeaderEnd:(NSData*) workingData fromOffset:(int) offset;
- (int) findContentEnd:(NSData*) data fromOffset:(int) offset;

- (int) numberOfBytesToLeavePendingWithData:(NSData*) data length:(NSUInteger) length encoding:(int) encoding;
- (NSInteger) numberOfBytesToLeavePendingWithData:(NSData*) data length:(NSInteger) length encoding:(int) encoding;
- (int) offsetTillNewlineSinceOffset:(int) offset inData:(NSData*) data;

- (int) processPreamble:(NSData*) workingData;
Expand Down Expand Up @@ -244,10 +244,10 @@ - (BOOL) appendData:(NSData *)data {
// this case, we didn't find the boundary, so the data is related to the current part.
// we leave the sizeToLeavePending amount of bytes to make sure we don't include
// boundary part in processed data.
NSUInteger sizeToPass = workingData.length - offset - sizeToLeavePending;
NSInteger sizeToPass = workingData.length - offset - sizeToLeavePending;

// if we parse BASE64 encoded data, or Quoted-Printable data, we will make sure we don't break the format
int leaveTrailing = [self numberOfBytesToLeavePendingWithData:data length:sizeToPass encoding:currentEncoding];
NSInteger leaveTrailing = [self numberOfBytesToLeavePendingWithData:data length:sizeToPass encoding:currentEncoding];
sizeToPass -= leaveTrailing;

if( sizeToPass <= 0 ) {
Expand Down Expand Up @@ -417,14 +417,14 @@ - (int) findContentEnd:(NSData*) data fromOffset:(int) offset {
}


- (int) numberOfBytesToLeavePendingWithData:(NSData*) data length:(int) length encoding:(int) encoding {
- (NSInteger) numberOfBytesToLeavePendingWithData:(NSData*) data length:(NSInteger) length encoding:(int) encoding {
// If we have BASE64 or Quoted-Printable encoded data, we have to be sure
// we don't break the format.
int sizeToLeavePending = 0;
NSInteger sizeToLeavePending = 0;

if( encoding == contentTransferEncoding_base64 ) {
char* bytes = (char*) data.bytes;
int i;
NSInteger i;
for( i = length - 1; i > 0; i++ ) {
if( * (uint16_t*) (bytes + i) == 0x0A0D ) {
break;
Expand Down
9 changes: 7 additions & 2 deletions Core/WebSocket.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@

+ (BOOL)isWebSocketRequest:(HTTPMessage *)request;

- (id)initWithRequest:(HTTPMessage *)request socket:(GCDAsyncSocket *)socket;
- (id)initWithRequest:(HTTPMessage *)request socket:(GCDAsyncSocket *)socket NS_DESIGNATED_INITIALIZER;

/**
* Delegate option.
Expand Down Expand Up @@ -64,7 +64,7 @@
* Sends a message over the WebSocket.
* This method is thread-safe.
**/
- (void)sendData:(NSData *)msg;
- (void)sendData:(NSData *)msg isBinary:(BOOL)binary;

/**
* Subclass API
Expand All @@ -73,6 +73,7 @@
**/
- (void)didOpen;
- (void)didReceiveMessage:(NSString *)msg;
- (void)didReceiveData:(NSData *)data;
- (void)didClose;

@end
Expand Down Expand Up @@ -100,6 +101,10 @@

- (void)webSocket:(WebSocket *)ws didReceiveMessage:(NSString *)msg;

- (void)webSocket:(WebSocket *)ws didReceiveData:(NSData *)data;

- (void)webSocketDidClose:(WebSocket *)ws;

- (void)webSocket:(WebSocket *)ws didReceiveData:(NSData *)data;

@end
153 changes: 133 additions & 20 deletions Core/WebSocket.m
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,6 @@
#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;
Expand All @@ -67,7 +62,10 @@ @implementation WebSocket
{
BOOL isRFC6455;
BOOL nextFrameMasked;
NSUInteger firstFrameOpCode;
NSUInteger nextOpCode;
NSMutableData *accumuData;
BOOL finFlag;
NSData *maskingKey;
}

Expand Down Expand Up @@ -155,6 +153,12 @@ + (BOOL)isRFC6455Request:(HTTPMessage *)request

@synthesize websocketQueue;

- (instancetype)init
{
[NSException raise:NSInternalInconsistencyException format:@"Initializer disallowed: %s", __PRETTY_FUNCTION__];
return [self initWithRequest:nil socket:nil];
}

- (id)initWithRequest:(HTTPMessage *)aRequest socket:(GCDAsyncSocket *)socket
{
HTTPLogTrace();
Expand Down Expand Up @@ -536,10 +540,10 @@ - (void)didOpen
- (void)sendMessage:(NSString *)msg
{
NSData *msgData = [msg dataUsingEncoding:NSUTF8StringEncoding];
[self sendData:msgData];
[self sendData:msgData isBinary:NO];
}

- (void)sendData:(NSData *)msgData
- (void)sendData:(NSData *)msgData isBinary:(BOOL)binary
{
HTTPLogTrace();

Expand All @@ -548,26 +552,33 @@ - (void)sendData:(NSData *)msgData
if (isRFC6455)
{
NSUInteger length = msgData.length;
UInt8 firstByte = 0x80;
if(binary)
firstByte |= WS_OP_BINARY_FRAME;
else
firstByte |= WS_OP_TEXT_FRAME;

if (length <= 125)
{
data = [NSMutableData dataWithCapacity:(length + 2)];
[data appendBytes: "\x81" length:1];

[data appendBytes: &firstByte 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];
[data appendBytes: (UInt8[]){firstByte, 0x7e} 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[]){firstByte, 0x7f} 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];
}
Expand Down Expand Up @@ -602,6 +613,22 @@ - (void)didReceiveMessage:(NSString *)msg
}
}

- (void)didReceiveData:(NSData *)data
{
HTTPLogTrace();

// Override me to process incoming data.
// This method is invoked on the websocketQueue.
//
// For completeness, you should invoke [super didReceiveData:data] in your method.

// Notify delegate
if ([delegate respondsToSelector:@selector(webSocket:didReceiveData:)])
{
[delegate webSocket:self didReceiveData:data];
}
}

- (void)didClose
{
HTTPLogTrace();
Expand Down Expand Up @@ -656,7 +683,14 @@ - (BOOL)isValidWebSocketFrame:(UInt8)frame
// + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
// | Payload Data continued ... |
// +---------------------------------------------------------------+

//
// An unfragmented message consists of a single frame with the FIN bit set (Section 5.2) and an opcode other than 0.
//
// A fragmented message consists of a single frame with the FIN bit
// clear and an opcode other than 0, followed by zero or more frames
// with the FIN bit clear and the opcode set to 0, and terminated by
// a single frame with the FIN bit set and an opcode of 0.
//
- (void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag
{
HTTPLogTrace();
Expand Down Expand Up @@ -690,6 +724,18 @@ - (void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)t
if ([self isValidWebSocketFrame: frame])
{
nextOpCode = (frame & 0x0F);
finFlag = ((frame & 0xF0) != 0);
if(finFlag){

}
else if(nextOpCode != 0){
// A fragmented message consists of a single frame with the FIN bit
// clear and an opcode other than 0, followed by zero or more frames
// with the FIN bit clear and the opcode set to 0, and terminated by
// a single frame with the FIN bit set and an opcode of 0.
accumuData = [[NSMutableData alloc] init];
firstFrameOpCode = nextOpCode;//this is the first frame of fragmented frames
}
[asyncSocket readDataToLength:1 withTimeout:TIMEOUT_NONE tag:TAG_PAYLOAD_LENGTH];
}
else
Expand Down Expand Up @@ -733,8 +779,25 @@ - (void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)t
}
else if (tag == TAG_PAYLOAD_LENGTH64)
{
// FIXME: 64bit data size in memory?
[self didClose];

UInt8 *pFrame = (UInt8 *)[data bytes];
NSUInteger length =
((NSUInteger)pFrame[7]) |
((NSUInteger)pFrame[6] << 8) |
((NSUInteger)pFrame[5] << 16) |
((NSUInteger)pFrame[4] << 24);

#ifdef __arm64__
length |= ((NSUInteger)pFrame[3] << 32) |
((NSUInteger)pFrame[2] << 40) |
((NSUInteger)pFrame[1] << 48) |
((NSUInteger)pFrame[0] << 56);
#endif

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_MSG_WITH_LENGTH)
{
Expand All @@ -751,9 +814,51 @@ - (void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)t
}
if (nextOpCode == WS_OP_TEXT_FRAME)
{
NSString *msg = [[NSString alloc] initWithBytes:[data bytes] length:msgLength encoding:NSUTF8StringEncoding];
[self didReceiveMessage:msg];
if(accumuData){
[accumuData appendData:data];
}
else{
NSString *msg = [[NSString alloc] initWithBytes:[data bytes] length:msgLength encoding:NSUTF8StringEncoding];
[self didReceiveMessage:msg];
}

}
else if(nextOpCode == WS_OP_PING || nextOpCode == WS_OP_PONG){
//FIX ME: respond to PING and PONG
//But at least, we should not close connection for ping and pong
}
else if (nextOpCode == WS_OP_BINARY_FRAME)
{

if(accumuData){
[accumuData appendData:data];
}
else{
[self didReceiveData:data];
}
}
else if(nextOpCode == WS_OP_CONTINUATION_FRAME){
// A fragmented message consists of a single frame with the FIN bit
// clear and an opcode other than 0, followed by zero or more frames
// with the FIN bit clear and the opcode set to 0, and terminated by
// a single frame with the FIN bit set and an opcode of 0.
[accumuData appendData:data];
if(firstFrameOpCode == WS_OP_TEXT_FRAME){
if(finFlag){
//last frame of fragmented frames
NSString *msg = [[NSString alloc] initWithBytes:[accumuData bytes] length:msgLength encoding:NSUTF8StringEncoding];
[self didReceiveMessage:msg];
accumuData = nil;
}
}
else if(firstFrameOpCode == WS_OP_BINARY_FRAME){
if(finFlag){
//last frame of fragmented frames
[self didReceiveData:accumuData];
accumuData = nil;
}
}
}
else
{
[self didClose];
Expand All @@ -770,11 +875,19 @@ - (void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)t
else
{
NSUInteger msgLength = [data length] - 1; // Excluding ending 0xFF frame

NSString *msg = [[NSString alloc] initWithBytes:[data bytes] length:msgLength encoding:NSUTF8StringEncoding];

[self didReceiveMessage:msg];


if (nextOpCode == WS_OP_TEXT_FRAME)
{
NSString *msg = [[NSString alloc] initWithBytes:[data bytes] length:msgLength encoding:NSUTF8StringEncoding];

[self didReceiveMessage:msg];
}
else if(nextOpCode == WS_OP_BINARY_FRAME)
{
NSData *msg = [data subdataWithRange:NSMakeRange(0, msgLength)];

[self didReceiveData:msg];
}

// Read next message
[asyncSocket readDataToLength:1 withTimeout:TIMEOUT_NONE tag:TAG_PREFIX];
Expand Down
4 changes: 2 additions & 2 deletions Vendor/CocoaAsyncSocket/GCDAsyncSocket.h
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,8 @@
// Compiling for Mac OS X

#define IS_SECURE_TRANSPORT_AVAILABLE YES
#define SECURE_TRANSPORT_MAYBE_AVAILABLE 1
#define SECURE_TRANSPORT_MAYBE_UNAVAILABLE 0
#define SECURE_TRANSPORT_MAYBE_AVAILABLE 0
#define SECURE_TRANSPORT_MAYBE_UNAVAILABLE 1

#endif

Expand Down
5 changes: 2 additions & 3 deletions Vendor/CocoaAsyncSocket/GCDAsyncSocket.m
Original file line number Diff line number Diff line change
Expand Up @@ -4263,10 +4263,9 @@ - (void)doReadData
}
else
{
#if SECURE_TRANSPORT_MAYBE_AVAILABLE

estimatedBytesAvailable = socketFDBytesAvailable;

#if SECURE_TRANSPORT_MAYBE_AVAILABLE
if (flags & kSocketSecure)
{
// There are 2 buffers to be aware of here.
Expand Down Expand Up @@ -4303,9 +4302,9 @@ - (void)doReadData
estimatedBytesAvailable += sslInternalBufSize;
}

#endif
hasBytesAvailable = (estimatedBytesAvailable > 0);

#endif
}

if ((hasBytesAvailable == NO) && ([preBuffer availableBytes] == 0))
Expand Down
Loading