diff --git a/.DS_Store b/.DS_Store index 9cdb1ee..24e0309 100644 Binary files a/.DS_Store and b/.DS_Store differ diff --git a/Example/.DS_Store b/Example/.DS_Store index d45085c..1c2a373 100644 Binary files a/Example/.DS_Store and b/Example/.DS_Store differ diff --git a/Example/MultipartFormDataParser.h b/Example/MultipartFormDataParser.h new file mode 100644 index 0000000..ac0e8ab --- /dev/null +++ b/Example/MultipartFormDataParser.h @@ -0,0 +1,65 @@ + +#import "MultipartMessageHeader.h" + +/* +Part one: http://tools.ietf.org/html/rfc2045 (Format of Internet Message Bodies) +Part two: http://tools.ietf.org/html/rfc2046 (Media Types) +Part three: http://tools.ietf.org/html/rfc2047 (Message Header Extensions for Non-ASCII Text) +Part four: http://tools.ietf.org/html/rfc4289 (Registration Procedures) +Part five: http://tools.ietf.org/html/rfc2049 (Conformance Criteria and Examples) + +Internet message format: http://tools.ietf.org/html/rfc2822 + +Multipart/form-data http://tools.ietf.org/html/rfc2388 +*/ + +@class MultipartFormDataParser; + +//----------------------------------------------------------------- +// protocol MultipartFormDataParser +//----------------------------------------------------------------- + +@protocol MultipartFormDataParserDelegate +@optional +- (void) processContent:(NSData*) data WithHeader:(MultipartMessageHeader*) header; +- (void) processEndOfPartWithHeader:(MultipartMessageHeader*) header; +- (void) processPreambleData:(NSData*) data; +- (void) processEpilogueData:(NSData*) data; +- (void) processStartOfPartWithHeader:(MultipartMessageHeader*) header; +@end + +//----------------------------------------------------------------- +// interface MultipartFormDataParser +//----------------------------------------------------------------- + +@interface MultipartFormDataParser : NSObject { +NSMutableData* pendingData; + NSData* boundaryData; + MultipartMessageHeader* currentHeader; + + BOOL waitingForCRLF; + BOOL reachedEpilogue; + BOOL processedPreamble; + BOOL checkForContentEnd; + +#if __has_feature(objc_arc_weak) + __weak id delegate; +#else + __unsafe_unretained id delegate; +#endif + int currentEncoding; + NSStringEncoding formEncoding; +} + +- (BOOL) appendData:(NSData*) data; + +- (id) initWithBoundary:(NSString*) boundary formEncoding:(NSStringEncoding) formEncoding; + +#if __has_feature(objc_arc_weak) + @property(weak, readwrite) id delegate; +#else + @property(unsafe_unretained, readwrite) id delegate; +#endif +@property(readwrite) NSStringEncoding formEncoding; + +@end diff --git a/Example/MultipartFormDataParser.m b/Example/MultipartFormDataParser.m new file mode 100644 index 0000000..87eb428 --- /dev/null +++ b/Example/MultipartFormDataParser.m @@ -0,0 +1,523 @@ + +#import "MultipartFormDataParser.h" +#import "DDData.h" +#import "HTTPLogging.h" + +//----------------------------------------------------------------- +#pragma mark log level + +#ifdef DEBUG +static const int httpLogLevel = HTTP_LOG_LEVEL_WARN; +#else +static const int httpLogLevel = HTTP_LOG_LEVEL_WARN; +#endif + + +//----------------------------------------------------------------- +// interface MultipartFormDataParser (private) +//----------------------------------------------------------------- + + +@interface MultipartFormDataParser (private) ++ (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:(int) length encoding:(int) encoding; +- (int) offsetTillNewlineSinceOffset:(int) offset inData:(NSData*) data; + +- (int) processPreamble:(NSData*) workingData; + +@end + + +//----------------------------------------------------------------- +// implementation MultipartFormDataParser +//----------------------------------------------------------------- + + +@implementation MultipartFormDataParser +@synthesize delegate,formEncoding; + +- (id) initWithBoundary:(NSString*) boundary formEncoding:(NSStringEncoding) _formEncoding { + if( nil == (self = [super init]) ){ + return self; + } + if( nil == boundary ) { + HTTPLogWarn(@"MultipartFormDataParser: init with zero boundary"); + return nil; + } + boundaryData = [[@"\r\n--" stringByAppendingString:boundary] dataUsingEncoding:NSASCIIStringEncoding]; + + pendingData = [[NSMutableData alloc] init]; + currentEncoding = contentTransferEncoding_binary; + currentHeader = nil; + + formEncoding = _formEncoding; + reachedEpilogue = NO; + processedPreamble = NO; + + return self; +} + + +- (BOOL) appendData:(NSData *)data { + // Can't parse without boundary; + if( nil == boundaryData ) { + HTTPLogError(@"MultipartFormDataParser: Trying to parse multipart without specifying a valid boundary"); + assert(false); + return NO; + } + NSData* workingData = data; + + if( pendingData.length ) { + [pendingData appendData:data]; + workingData = pendingData; + } + + // the parser saves parse stat in the offset variable, which indicates offset of unhandled part in + // currently received chunk. Before returning, we always drop all data up to offset, leaving + // only unhandled for the next call + + int offset = 0; + + // don't parse data unless its size is greater then boundary length, so we couldn't + // misfind the boundary, if it got split into different data chunks + int sizeToLeavePending = boundaryData.length; + + if( !reachedEpilogue && workingData.length <= sizeToLeavePending ) { + // not enough data even to start parsing. + // save to pending data. + if( !pendingData.length ) { + [pendingData appendData:data]; + } + if( checkForContentEnd ) { + if( pendingData.length >= 2 ) { + if( *(uint16_t*)(pendingData.bytes + offset) == 0x2D2D ) { + // we found the multipart end. all coming next is an epilogue. + HTTPLogVerbose(@"MultipartFormDataParser: End of multipart message"); + waitingForCRLF = YES; + reachedEpilogue = YES; + offset+= 2; + } + else { + checkForContentEnd = NO; + waitingForCRLF = YES; + return YES; + } + } else { + return YES; + } + + } + else { + return YES; + } + } + while( true ) { + if( checkForContentEnd ) { + // the flag will be raised to check if the last part was the last one. + if( offset < workingData.length -1 ) { + char* bytes = (char*) workingData.bytes; + if( *(uint16_t*)(bytes + offset) == 0x2D2D ) { + // we found the multipart end. all coming next is an epilogue. + HTTPLogVerbose(@"MultipartFormDataParser: End of multipart message"); + checkForContentEnd = NO; + reachedEpilogue = YES; + // still wait for CRLF, that comes after boundary, but before epilogue. + waitingForCRLF = YES; + offset += 2; + } + else { + // it's not content end, we have to wait till separator line end before next part comes + waitingForCRLF = YES; + checkForContentEnd = NO; + } + } + else { + // we haven't got enough data to check for content end. + // save current unhandled data (it may be 1 byte) to pending and recheck on next chunk received + if( offset < workingData.length ) { + [pendingData setData:[NSData dataWithBytes:workingData.bytes + workingData.length-1 length:1]]; + } + else { + // there is no unhandled data now, wait for more chunks + [pendingData setData:[NSData data]]; + } + return YES; + } + } + if( waitingForCRLF ) { + + // the flag will be raised in the code below, meaning, we've read the boundary, but + // didnt find the end of boundary line yet. + + offset = [self offsetTillNewlineSinceOffset:offset inData:workingData]; + if( -1 == offset ) { + // didnt find the endl again. + if( offset ) { + // we still have to save the unhandled data (maybe it's 1 byte CR) + if( *((char*)workingData.bytes + workingData.length -1) == '\r' ) { + [pendingData setData:[NSData dataWithBytes:workingData.bytes + workingData.length-1 length:1]]; + } + else { + // or save nothing if it wasnt + [pendingData setData:[NSData data]]; + } + } + return YES; + } + waitingForCRLF = NO; + } + if( !processedPreamble ) { + // got to find the first boundary before the actual content begins. + offset = [self processPreamble:workingData]; + // wait for more data for preamble + if( -1 == offset ) + return YES; + // invoke continue to skip newline after boundary. + continue; + } + + if( reachedEpilogue ) { + // parse all epilogue data to delegate. + if( [delegate respondsToSelector:@selector(processEpilogueData:)] ) { + NSData* epilogueData = [NSData dataWithBytesNoCopy: (char*) workingData.bytes + offset length: workingData.length - offset freeWhenDone:NO]; + [delegate processEpilogueData: epilogueData]; + } + return YES; + } + + if( nil == currentHeader ) { + // nil == currentHeader is a state flag, indicating we are waiting for header now. + // whenever part is over, currentHeader is set to nil. + + // try to find CRLFCRLF bytes in the data, which indicates header end. + // we won't parse header parts, as they won't be too large. + int headerEnd = [self findHeaderEnd:workingData fromOffset:offset]; + if( -1 == headerEnd ) { + // didn't recieve the full header yet. + if( !pendingData.length) { + // store the unprocessed data till next chunks come + [pendingData appendBytes:data.bytes + offset length:data.length - offset]; + } + else { + if( offset ) { + // save the current parse state; drop all handled data and save unhandled only. + pendingData = [[NSMutableData alloc] initWithBytes: (char*) workingData.bytes + offset length:workingData.length - offset]; + } + } + return YES; + } + else { + + // let the header parser do it's job from now on. + NSData * headerData = [NSData dataWithBytesNoCopy: (char*) workingData.bytes + offset length:headerEnd + 2 - offset freeWhenDone:NO]; + currentHeader = [[MultipartMessageHeader alloc] initWithData:headerData formEncoding:formEncoding]; + + if( nil == currentHeader ) { + // we've found the data is in wrong format. + HTTPLogError(@"MultipartFormDataParser: MultipartFormDataParser: wrong input format, coulnd't get a valid header"); + return NO; + } + if( [delegate respondsToSelector:@selector(processStartOfPartWithHeader:)] ) { + [delegate processStartOfPartWithHeader:currentHeader]; + } + + HTTPLogVerbose(@"MultipartFormDataParser: MultipartFormDataParser: Retrieved part header."); + } + // skip the two trailing \r\n, in addition to the whole header. + offset = headerEnd + 4; + } + // after we've got the header, we try to + // find the boundary in the data. + int contentEnd = [self findContentEnd:workingData fromOffset:offset]; + + if( contentEnd == -1 ) { + + // 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. + int 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]; + sizeToPass -= leaveTrailing; + + if( sizeToPass <= 0 ) { + // wait for more data! + if( offset ) { + [pendingData setData:[NSData dataWithBytes:(char*) workingData.bytes + offset length:workingData.length - offset]]; + } + return YES; + } + // decode the chunk and let the delegate use it (store in a file, for example) + NSData* decodedData = [MultipartFormDataParser decodedDataFromData:[NSData dataWithBytesNoCopy:(char*)workingData.bytes + offset length:workingData.length - offset - sizeToLeavePending freeWhenDone:NO] encoding:currentEncoding]; + + if( [delegate respondsToSelector:@selector(processContent:WithHeader:)] ) { + HTTPLogVerbose(@"MultipartFormDataParser: Processed %d bytes of body",sizeToPass); + + [delegate processContent: decodedData WithHeader:currentHeader]; + } + + // store the unprocessed data till the next chunks come. + [pendingData setData:[NSData dataWithBytes:(char*)workingData.bytes + workingData.length - sizeToLeavePending length:sizeToLeavePending]]; + return YES; + } + else { + + // Here we found the boundary. + // let the delegate process it, and continue going to the next parts. + if( [delegate respondsToSelector:@selector(processContent:WithHeader:)] ) { + [delegate processContent:[NSData dataWithBytesNoCopy:(char*) workingData.bytes + offset length:contentEnd - offset freeWhenDone:NO] WithHeader:currentHeader]; + } + + if( [delegate respondsToSelector:@selector(processEndOfPartWithHeader:)] ){ + [delegate processEndOfPartWithHeader:currentHeader]; + HTTPLogVerbose(@"MultipartFormDataParser: End of body part"); + } + currentHeader = nil; + + // set up offset to continue with the remaining data (if any) + offset = contentEnd + boundaryData.length; + checkForContentEnd = YES; + // setting the flag tells the parser to skip all the data till CRLF + } + } + return YES; +} + + +//----------------------------------------------------------------- +#pragma mark private methods + +- (int) offsetTillNewlineSinceOffset:(int) offset inData:(NSData*) data { + char* bytes = (char*) data.bytes; + int length = data.length; + if( offset >= length - 1 ) + return -1; + + while ( *(uint16_t*)(bytes + offset) != 0x0A0D ) { + // find the trailing \r\n after the boundary. The boundary line might have any number of whitespaces before CRLF, according to rfc2046 + + // in debug, we might also want to know, if the file is somehow misformatted. +#ifdef DEBUG + if( !isspace(*(bytes+offset)) ) { + HTTPLogWarn(@"MultipartFormDataParser: Warning, non-whitespace character '%c' between boundary bytes and CRLF in boundary line",*(bytes+offset) ); + } + if( !isspace(*(bytes+offset+1)) ) { + HTTPLogWarn(@"MultipartFormDataParser: Warning, non-whitespace character '%c' between boundary bytes and CRLF in boundary line",*(bytes+offset+1) ); + } +#endif + offset++; + if( offset >= length ) { + // no endl found within current data + return -1; + } + } + + offset += 2; + return offset; +} + + +- (int) processPreamble:(NSData*) data { + int offset = 0; + + char* boundaryBytes = (char*) boundaryData.bytes + 2; // the first boundary won't have CRLF preceding. + char* dataBytes = (char*) data.bytes; + int boundaryLength = boundaryData.length - 2; + int dataLength = data.length; + + // find the boundary without leading CRLF. + while( offset < dataLength - boundaryLength +1 ) { + int i; + for( i = 0;i < boundaryLength; i++ ) { + if( boundaryBytes[i] != dataBytes[offset + i] ) + break; + } + if( i == boundaryLength ) { + break; + } + offset++; + } + + if( offset == dataLength ) { + // the end of preamble wasn't found in this chunk + int sizeToProcess = dataLength - boundaryLength; + if( sizeToProcess > 0) { + if( [delegate respondsToSelector:@selector(processPreambleData:)] ) { + NSData* preambleData = [NSData dataWithBytesNoCopy: (char*) data.bytes length: data.length - offset - boundaryLength freeWhenDone:NO]; + [delegate processPreambleData:preambleData]; + HTTPLogVerbose(@"MultipartFormDataParser: processed preamble"); + } + pendingData = [NSMutableData dataWithBytes: data.bytes + data.length - boundaryLength length:boundaryLength]; + } + return -1; + } + else { + if ( offset && [delegate respondsToSelector:@selector(processPreambleData:)] ) { + NSData* preambleData = [NSData dataWithBytesNoCopy: (char*) data.bytes length: offset freeWhenDone:NO]; + [delegate processPreambleData:preambleData]; + } + offset +=boundaryLength; + // tells to skip CRLF after the boundary. + processedPreamble = YES; + waitingForCRLF = YES; + } + return offset; +} + + + +- (int) findHeaderEnd:(NSData*) workingData fromOffset:(int)offset { + char* bytes = (char*) workingData.bytes; + int inputLength = workingData.length; + uint16_t separatorBytes = 0x0A0D; + + while( true ) { + if(inputLength < offset + 3 ) { + // wait for more data + return -1; + } + if( (*((uint16_t*) (bytes+offset)) == separatorBytes) && (*((uint16_t*) (bytes+offset)+1) == separatorBytes) ) { + return offset; + } + offset++; + } + return -1; +} + + +- (int) findContentEnd:(NSData*) data fromOffset:(int) offset { + char* boundaryBytes = (char*) boundaryData.bytes; + char* dataBytes = (char*) data.bytes; + int boundaryLength = boundaryData.length; + int dataLength = data.length; + + while( offset < dataLength - boundaryLength +1 ) { + int i; + for( i = 0;i < boundaryLength; i++ ) { + if( boundaryBytes[i] != dataBytes[offset + i] ) + break; + } + if( i == boundaryLength ) { + return offset; + } + offset++; + } + return -1; +} + + +- (int) numberOfBytesToLeavePendingWithData:(NSData*) data length:(int) 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; + + if( encoding == contentTransferEncoding_base64 ) { + char* bytes = (char*) data.bytes; + int i; + for( i = length - 1; i > 0; i++ ) { + if( * (uint16_t*) (bytes + i) == 0x0A0D ) { + break; + } + } + // now we've got to be sure that the length of passed data since last line + // is multiplier of 4. + sizeToLeavePending = (length - i) & ~0x11; // size to leave pending = length-i - (length-i) %4; + return sizeToLeavePending; + } + + if( encoding == contentTransferEncoding_quotedPrintable ) { + // we don't pass more less then 3 bytes anyway. + if( length <= 2 ) + return length; + // check the last bytes to be start of encoded symbol. + const char* bytes = data.bytes + length - 2; + if( bytes[0] == '=' ) + return 2; + if( bytes[1] == '=' ) + return 1; + return 0; + } + return 0; +} + + +//----------------------------------------------------------------- +#pragma mark decoding + + ++ (NSData*) decodedDataFromData:(NSData*) data encoding:(int) encoding { + switch (encoding) { + case contentTransferEncoding_base64: { + return [data base64Decoded]; + } break; + + case contentTransferEncoding_quotedPrintable: { + return [self decodedDataFromQuotedPrintableData:data]; + } break; + + default: { + return data; + } break; + } +} + + ++ (NSData*) decodedDataFromQuotedPrintableData:(NSData *)data { +// http://tools.ietf.org/html/rfc2045#section-6.7 + + const char hex [] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', }; + + NSMutableData* result = [[NSMutableData alloc] initWithLength:data.length]; + const char* bytes = (const char*) data.bytes; + int count = 0; + int length = data.length; + while( count < length ) { + if( bytes[count] == '=' ) { + [result appendBytes:bytes length:count]; + bytes = bytes + count + 1; + length -= count + 1; + count = 0; + + if( length < 3 ) { + HTTPLogWarn(@"MultipartFormDataParser: warning, trailing '=' in quoted printable data"); + } + // soft newline + if( bytes[0] == '\r' ) { + bytes += 1; + if(bytes[1] == '\n' ) { + bytes += 2; + } + continue; + } + char encodedByte = 0; + + for( int i = 0; i < sizeof(hex); i++ ) { + if( hex[i] == bytes[0] ) { + encodedByte += i << 4; + } + if( hex[i] == bytes[1] ) { + encodedByte += i; + } + } + [result appendBytes:&encodedByte length:1]; + bytes += 2; + } + +#ifdef DEBUG + if( (unsigned char) bytes[count] > 126 ) { + HTTPLogWarn(@"MultipartFormDataParser: Warning, character with code above 126 appears in quoted printable encoded data"); + } +#endif + + count++; + } + return result; +} + + +@end diff --git a/Example/MultipartMessageHeader.h b/Example/MultipartMessageHeader.h new file mode 100644 index 0000000..4d10f96 --- /dev/null +++ b/Example/MultipartMessageHeader.h @@ -0,0 +1,33 @@ +// +// MultipartMessagePart.h +// HttpServer +// +// Created by Валерий Гаврилов on 29.03.12. +// Copyright (c) 2012 LLC "Online Publishing Partners" (onlinepp.ru). All rights reserved. +// + +#import + + +//----------------------------------------------------------------- +// interface MultipartMessageHeader +//----------------------------------------------------------------- +enum { + contentTransferEncoding_unknown, + contentTransferEncoding_7bit, + contentTransferEncoding_8bit, + contentTransferEncoding_binary, + contentTransferEncoding_base64, + contentTransferEncoding_quotedPrintable, +}; + +@interface MultipartMessageHeader : NSObject { + NSMutableDictionary* fields; + int encoding; + NSString* contentDispositionName; +} +@property (strong,readonly) NSDictionary* fields; +@property (readonly) int encoding; + +- (id) initWithData:(NSData*) data formEncoding:(NSStringEncoding) encoding; +@end diff --git a/Example/MultipartMessageHeader.m b/Example/MultipartMessageHeader.m new file mode 100644 index 0000000..a9c930f --- /dev/null +++ b/Example/MultipartMessageHeader.m @@ -0,0 +1,86 @@ +// +// MultipartMessagePart.m +// HttpServer +// +// Created by Валерий Гаврилов on 29.03.12. +// Copyright (c) 2012 LLC "Online Publishing Partners" (onlinepp.ru). All rights reserved. + +#import "MultipartMessageHeader.h" +#import "MultipartMessageHeaderField.h" + +#import "HTTPLogging.h" + +//----------------------------------------------------------------- +#pragma mark log level + +#ifdef DEBUG +static const int httpLogLevel = HTTP_LOG_LEVEL_WARN; +#else +static const int httpLogLevel = HTTP_LOG_LEVEL_WARN; +#endif + +//----------------------------------------------------------------- +// implementation MultipartMessageHeader +//----------------------------------------------------------------- + + +@implementation MultipartMessageHeader +@synthesize fields,encoding; + + +- (id) initWithData:(NSData *)data formEncoding:(NSStringEncoding) formEncoding { + if( nil == (self = [super init]) ) { + return self; + } + + fields = [[NSMutableDictionary alloc] initWithCapacity:1]; + + // In case encoding is not mentioned, + encoding = contentTransferEncoding_unknown; + + char* bytes = (char*)data.bytes; + int length = data.length; + int offset = 0; + + // split header into header fields, separated by \r\n + uint16_t fields_separator = 0x0A0D; // \r\n + while( offset < length - 2 ) { + + // the !isspace condition is to support header unfolding + if( (*(uint16_t*) (bytes+offset) == fields_separator) && ((offset == length - 2) || !(isspace(bytes[offset+2])) )) { + NSData* fieldData = [NSData dataWithBytesNoCopy:bytes length:offset freeWhenDone:NO]; + MultipartMessageHeaderField* field = [[MultipartMessageHeaderField alloc] initWithData: fieldData contentEncoding:formEncoding]; + if( field ) { + [fields setObject:field forKey:field.name]; + HTTPLogVerbose(@"MultipartFormDataParser: Processed Header field '%@'",field.name); + } + else { + NSString* fieldStr = [[NSString alloc] initWithData:fieldData encoding:NSASCIIStringEncoding]; + HTTPLogWarn(@"MultipartFormDataParser: Failed to parse MIME header field. Input ASCII string:%@",fieldStr); + } + + // move to the next header field + bytes += offset + 2; + length -= offset + 2; + offset = 0; + continue; + } + ++ offset; + } + + if( !fields.count ) { + // it was an empty header. + // we have to set default values. + // default header. + [fields setObject:@"text/plain" forKey:@"Content-Type"]; + } + + return self; +} + +- (NSString *)description { + return [NSString stringWithFormat:@"%@",fields]; +} + + +@end diff --git a/Example/MultipartMessageHeaderField.h b/Example/MultipartMessageHeaderField.h new file mode 100644 index 0000000..69a4a07 --- /dev/null +++ b/Example/MultipartMessageHeaderField.h @@ -0,0 +1,23 @@ + +#import + +//----------------------------------------------------------------- +// interface MultipartMessageHeaderField +//----------------------------------------------------------------- + +@interface MultipartMessageHeaderField : NSObject { + NSString* name; + NSString* value; + NSMutableDictionary* params; +} + +@property (strong, readonly) NSString* value; +@property (strong, readonly) NSDictionary* params; +@property (strong, readonly) NSString* name; + +//- (id) initWithLine:(NSString*) line; +//- (id) initWithName:(NSString*) paramName value:(NSString*) paramValue; + +- (id) initWithData:(NSData*) data contentEncoding:(NSStringEncoding) encoding; + +@end diff --git a/Example/MultipartMessageHeaderField.m b/Example/MultipartMessageHeaderField.m new file mode 100644 index 0000000..5dea457 --- /dev/null +++ b/Example/MultipartMessageHeaderField.m @@ -0,0 +1,217 @@ + +#import "MultipartMessageHeaderField.h" +#import "HTTPLogging.h" + +//----------------------------------------------------------------- +#pragma mark log level + +#ifdef DEBUG +static const int httpLogLevel = HTTP_LOG_LEVEL_WARN; +#else +static const int httpLogLevel = HTTP_LOG_LEVEL_WARN; +#endif + + +// helpers +int findChar(const char* str,int length, char c); +NSString* extractParamValue(const char* bytes, int length, NSStringEncoding encoding); + +//----------------------------------------------------------------- +// interface MultipartMessageHeaderField (private) +//----------------------------------------------------------------- + + +@interface MultipartMessageHeaderField (private) +-(BOOL) parseHeaderValueBytes:(char*) bytes length:(int) length encoding:(NSStringEncoding) encoding; +@end + + +//----------------------------------------------------------------- +// implementation MultipartMessageHeaderField +//----------------------------------------------------------------- + +@implementation MultipartMessageHeaderField +@synthesize name,value,params; + +- (id) initWithData:(NSData *)data contentEncoding:(NSStringEncoding)encoding { + + params = [[NSMutableDictionary alloc] initWithCapacity:1]; + + char* bytes = (char*)data.bytes; + int length = data.length; + + int separatorOffset = findChar(bytes, length, ':'); + if( (-1 == separatorOffset) || (separatorOffset >= length-2) ) { + HTTPLogError(@"MultipartFormDataParser: Bad format.No colon in field header."); + // tear down + return nil; + } + + // header name is always ascii encoded; + name = [[NSString alloc] initWithBytes: bytes length: separatorOffset encoding: NSASCIIStringEncoding]; + if( nil == name ) { + HTTPLogError(@"MultipartFormDataParser: Bad MIME header name."); + // tear down + return nil; + } + + // skip the separator and the next ' ' symbol + bytes += separatorOffset + 2; + length -= separatorOffset + 2; + + separatorOffset = findChar(bytes, length, ';'); + if( separatorOffset == -1 ) { + // couldn't find ';', means we don't have extra params here. + value = [[NSString alloc] initWithBytes:bytes length: length encoding:encoding]; + + if( nil == value ) { + HTTPLogError(@"MultipartFormDataParser: Bad MIME header value for header name: '%@'",name); + // tear down + return nil; + } + return self; + } + + value = [[NSString alloc] initWithBytes:bytes length: separatorOffset encoding:encoding]; + HTTPLogVerbose(@"MultipartFormDataParser: Processing header field '%@' : '%@'",name,value); + // skipe the separator and the next ' ' symbol + bytes += separatorOffset + 2; + length -= separatorOffset + 2; + + // parse the "params" part of the header + if( ![self parseHeaderValueBytes:bytes length:length encoding:encoding] ) { + NSString* paramsStr = [[NSString alloc] initWithBytes:bytes length:length encoding:NSASCIIStringEncoding]; + HTTPLogError(@"MultipartFormDataParser: Bad params for header with name '%@' and value '%@'",name,value); + HTTPLogError(@"MultipartFormDataParser: Params str: %@",paramsStr); + + return nil; + } + return self; +} + + +-(BOOL) parseHeaderValueBytes:(char*) bytes length:(int) length encoding:(NSStringEncoding) encoding { + int offset = 0; + NSString* currentParam = nil; + BOOL insideQuote = NO; + while( offset < length ) { + if( bytes[offset] == '\"' ) { + if( !offset || bytes[offset-1] != '\\' ) { + insideQuote = !insideQuote; + } + } + + // skip quoted symbols + if( insideQuote ) { + ++ offset; + continue; + } + if( bytes[offset] == '=' ) { + if( currentParam ) { + // found '=' before terminating previous param. + return NO; + } + currentParam = [[NSString alloc] initWithBytes:bytes length:offset encoding:NSASCIIStringEncoding]; + + bytes+=offset + 1; + length -= offset + 1; + offset = 0; + continue; + } + if( bytes[offset] == ';' ) { + if( !currentParam ) { + // found ; before stating '='. + HTTPLogError(@"MultipartFormDataParser: Unexpected ';' when parsing header"); + return NO; + } + NSString* paramValue = extractParamValue(bytes, offset,encoding); + if( nil == paramValue ) { + HTTPLogWarn(@"MultipartFormDataParser: Failed to exctract paramValue for key %@ in header %@",currentParam,name); + } + else { +#ifdef DEBUG + if( [params objectForKey:currentParam] ) { + HTTPLogWarn(@"MultipartFormDataParser: param %@ mentioned more then once in header %@",currentParam,name); + } +#endif + [params setObject:paramValue forKey:currentParam]; + HTTPLogVerbose(@"MultipartFormDataParser: header param: %@ = %@",currentParam,paramValue); + } + + currentParam = nil; + + // ';' separator has ' ' following, skip them. + bytes+=offset + 2; + length -= offset + 2; + offset = 0; + } + ++ offset; + } + + // add last param + if( insideQuote ) { + HTTPLogWarn(@"MultipartFormDataParser: unterminated quote in header %@",name); +// return YES; + } + if( currentParam ) { + NSString* paramValue = extractParamValue(bytes, length,encoding); + + if( nil == paramValue ) { + HTTPLogError(@"MultipartFormDataParser: Failed to exctract paramValue for key %@ in header %@",currentParam,name); + } + +#ifdef DEBUG + if( [params objectForKey:currentParam] ) { + HTTPLogWarn(@"MultipartFormDataParser: param %@ mentioned more then once in one header",currentParam); + } +#endif + [params setObject:paramValue forKey:currentParam]; + HTTPLogVerbose(@"MultipartFormDataParser: header param: %@ = %@",currentParam,paramValue); + currentParam = nil; + } + + return YES; +} + + +- (NSString *)description { + return [NSString stringWithFormat:@"%@:%@\n params: %@",name,value,params]; +} + + +@end + + +int findChar(const char* str,int length, char c) { + int offset = 0; + while( offset < length ) { + if( str[offset] == c ) + return offset; + ++ offset; + } + return -1; +} + + +NSString* extractParamValue(const char* bytes, int length, NSStringEncoding encoding) { + if( !length ) + return nil; + NSMutableString* value = nil; + + if( bytes[0] == '"' ) { + // values may be quoted. Strip the quotes to get what we need. + value = [[NSMutableString alloc] initWithBytes:bytes + 1 length: length - 2 encoding:encoding]; + } + else { + value = [[NSMutableString alloc] initWithBytes:bytes length: length encoding:encoding]; + } + // restore escaped symbols + NSRange range= [value rangeOfString:@"\\"]; + while ( range.length ) { + [value deleteCharactersInRange:range]; + range.location ++; + range = [value rangeOfString:@"\\" options:NSLiteralSearch range: range]; + } + return value; +} + diff --git a/Example/RoutingHTTPServer.xcodeproj/project.pbxproj b/Example/RoutingHTTPServer.xcodeproj/project.pbxproj index 7d3adab..403d34b 100644 --- a/Example/RoutingHTTPServer.xcodeproj/project.pbxproj +++ b/Example/RoutingHTTPServer.xcodeproj/project.pbxproj @@ -7,8 +7,19 @@ objects = { /* Begin PBXBuildFile section */ - 7B8A347416E6ED070088D209 /* AssetCollection.m in Sources */ = {isa = PBXBuildFile; fileRef = 7B8A347316E6ED070088D209 /* AssetCollection.m */; }; + 7B4B9CE816EA60B7002CCC63 /* MultipartFormDataParser.m in Sources */ = {isa = PBXBuildFile; fileRef = 7B4B9CE316EA60B5002CCC63 /* MultipartFormDataParser.m */; }; + 7B4B9CE916EA60B7002CCC63 /* MultipartMessageHeader.m in Sources */ = {isa = PBXBuildFile; fileRef = 7B4B9CE516EA60B6002CCC63 /* MultipartMessageHeader.m */; }; + 7B4B9CEA16EA60B7002CCC63 /* MultipartMessageHeaderField.m in Sources */ = {isa = PBXBuildFile; fileRef = 7B4B9CE716EA60B7002CCC63 /* MultipartMessageHeaderField.m */; }; + 7B4B9CEE16EA642A002CCC63 /* indexUp.html in Resources */ = {isa = PBXBuildFile; fileRef = 7B4B9CEC16EA642A002CCC63 /* indexUp.html */; }; + 7B4B9CEF16EA642A002CCC63 /* upload.html in Resources */ = {isa = PBXBuildFile; fileRef = 7B4B9CED16EA642A002CCC63 /* upload.html */; }; + 7B4B9D1F16EA6673002CCC63 /* FlashWavRecorder in Resources */ = {isa = PBXBuildFile; fileRef = 7B4B9D1E16EA6673002CCC63 /* FlashWavRecorder */; }; + 7BA4102F16E7FC5D0009FC4E /* AtoZ.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7BA4102E16E7FC5D0009FC4E /* AtoZ.framework */; }; + 7BA4107216E82EA40009FC4E /* README.md in Resources */ = {isa = PBXBuildFile; fileRef = 7BA4107116E82EA40009FC4E /* README.md */; }; 7BB3EF041674F81100BABF35 /* twitter_bootstrap_admin in Resources */ = {isa = PBXBuildFile; fileRef = 7BB3EF031674F81100BABF35 /* twitter_bootstrap_admin */; }; + 7BDAB22D16E9596C00589480 /* KSHTMLWriter.framework in Resources */ = {isa = PBXBuildFile; fileRef = 7BDAB22916E9595100589480 /* KSHTMLWriter.framework */; }; + 7BDAB22E16E9597700589480 /* KSHTMLWriter.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7BDAB22916E9595100589480 /* KSHTMLWriter.framework */; }; + 7BDAB24B16E9727600589480 /* KSHTMLWriter.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = 7BDAB22916E9595100589480 /* KSHTMLWriter.framework */; }; + 7BDAB26216E9A0F400589480 /* record.js.js in Sources */ = {isa = PBXBuildFile; fileRef = 7BDAB26016E99A4800589480 /* record.js.js */; }; C99CEC8A147959C200F648A5 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C99CEC89147959C200F648A5 /* Cocoa.framework */; }; C99CEC94147959C200F648A5 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = C99CEC92147959C200F648A5 /* InfoPlist.strings */; }; C99CEC96147959C200F648A5 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = C99CEC95147959C200F648A5 /* main.m */; }; @@ -43,6 +54,34 @@ /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ + 7BDAB22416E9595100589480 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 7BDAB21E16E9595000589480 /* KSHTMLWriter.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 22352B9014C6FCB00031F5DD; + remoteInfo = KSHTMLWriterLib; + }; + 7BDAB22616E9595100589480 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 7BDAB21E16E9595000589480 /* KSHTMLWriter.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 22352BA414C6FCB00031F5DD; + remoteInfo = KSHTMLWriterTests; + }; + 7BDAB22816E9595100589480 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 7BDAB21E16E9595000589480 /* KSHTMLWriter.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 7B05D75F155658190035A999; + remoteInfo = KSHTMLWriter; + }; + 7BDAB22A16E9595900589480 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 7BDAB21E16E9595000589480 /* KSHTMLWriter.xcodeproj */; + proxyType = 1; + remoteGlobalIDString = 7B05D75E155658190035A999; + remoteInfo = KSHTMLWriter; + }; C99CECAA147959C300F648A5 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = C99CEC7C147959C200F648A5 /* Project object */; @@ -52,10 +91,53 @@ }; /* End PBXContainerItemProxy section */ +/* Begin PBXCopyFilesBuildPhase section */ + 7BDAB22C16E9596200589480 /* CopyFiles */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + 7BDAB24B16E9727600589480 /* KSHTMLWriter.framework in CopyFiles */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + /* Begin PBXFileReference section */ - 7B8A347216E6ED070088D209 /* AssetCollection.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AssetCollection.h; sourceTree = ""; }; - 7B8A347316E6ED070088D209 /* AssetCollection.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AssetCollection.m; sourceTree = ""; }; + 7B4B9CE216EA60B5002CCC63 /* MultipartFormDataParser.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MultipartFormDataParser.h; sourceTree = ""; }; + 7B4B9CE316EA60B5002CCC63 /* MultipartFormDataParser.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MultipartFormDataParser.m; sourceTree = ""; }; + 7B4B9CE416EA60B5002CCC63 /* MultipartMessageHeader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MultipartMessageHeader.h; sourceTree = ""; }; + 7B4B9CE516EA60B6002CCC63 /* MultipartMessageHeader.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MultipartMessageHeader.m; sourceTree = ""; }; + 7B4B9CE616EA60B6002CCC63 /* MultipartMessageHeaderField.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MultipartMessageHeaderField.h; sourceTree = ""; }; + 7B4B9CE716EA60B7002CCC63 /* MultipartMessageHeaderField.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MultipartMessageHeaderField.m; sourceTree = ""; }; + 7B4B9CEC16EA642A002CCC63 /* indexUp.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = indexUp.html; sourceTree = ""; }; + 7B4B9CED16EA642A002CCC63 /* upload.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = upload.html; sourceTree = ""; }; + 7B4B9D1E16EA6673002CCC63 /* FlashWavRecorder */ = {isa = PBXFileReference; lastKnownFileType = folder; path = FlashWavRecorder; sourceTree = ""; }; + 7BA4102E16E7FC5D0009FC4E /* AtoZ.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AtoZ.framework; path = ../../../../../../Library/Frameworks/AtoZ.framework; sourceTree = ""; }; + 7BA4107116E82EA40009FC4E /* README.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = README.md; path = ../README.md; sourceTree = ""; }; 7BB3EF031674F81100BABF35 /* twitter_bootstrap_admin */ = {isa = PBXFileReference; lastKnownFileType = folder; name = twitter_bootstrap_admin; path = ../twitter_bootstrap_admin; sourceTree = ""; }; + 7BC77C0216E7EBDC00A7CFC2 /* About.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = About.txt; sourceTree = ""; }; + 7BC77C0316E7EBDC00A7CFC2 /* GCDAsyncSocket.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GCDAsyncSocket.h; sourceTree = ""; }; + 7BC77C0416E7EBDC00A7CFC2 /* GCDAsyncSocket.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = GCDAsyncSocket.m; sourceTree = ""; }; + 7BC77C0616E7EBDC00A7CFC2 /* About.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = About.txt; sourceTree = ""; }; + 7BC77C0716E7EBDC00A7CFC2 /* DDAbstractDatabaseLogger.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DDAbstractDatabaseLogger.h; sourceTree = ""; }; + 7BC77C0816E7EBDC00A7CFC2 /* DDAbstractDatabaseLogger.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DDAbstractDatabaseLogger.m; sourceTree = ""; }; + 7BC77C0916E7EBDC00A7CFC2 /* DDASLLogger.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DDASLLogger.h; sourceTree = ""; }; + 7BC77C0A16E7EBDC00A7CFC2 /* DDASLLogger.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DDASLLogger.m; sourceTree = ""; }; + 7BC77C0B16E7EBDC00A7CFC2 /* DDFileLogger.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DDFileLogger.h; sourceTree = ""; }; + 7BC77C0C16E7EBDC00A7CFC2 /* DDFileLogger.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DDFileLogger.m; sourceTree = ""; }; + 7BC77C0D16E7EBDC00A7CFC2 /* DDLog.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DDLog.h; sourceTree = ""; }; + 7BC77C0E16E7EBDC00A7CFC2 /* DDLog.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DDLog.m; sourceTree = ""; }; + 7BC77C0F16E7EBDC00A7CFC2 /* DDTTYLogger.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DDTTYLogger.h; sourceTree = ""; }; + 7BC77C1016E7EBDC00A7CFC2 /* DDTTYLogger.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DDTTYLogger.m; sourceTree = ""; }; + 7BC77C1216E7EBDC00A7CFC2 /* ContextFilterLogFormatter.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ContextFilterLogFormatter.h; sourceTree = ""; }; + 7BC77C1316E7EBDC00A7CFC2 /* ContextFilterLogFormatter.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ContextFilterLogFormatter.m; sourceTree = ""; }; + 7BC77C1416E7EBDC00A7CFC2 /* DispatchQueueLogFormatter.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DispatchQueueLogFormatter.h; sourceTree = ""; }; + 7BC77C1516E7EBDC00A7CFC2 /* DispatchQueueLogFormatter.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DispatchQueueLogFormatter.m; sourceTree = ""; }; + 7BC77C1616E7EBDC00A7CFC2 /* README.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = README.txt; sourceTree = ""; }; + 7BDAB21E16E9595000589480 /* KSHTMLWriter.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = KSHTMLWriter.xcodeproj; path = ../../KSHTMLWriter/KSHTMLWriter.xcodeproj; sourceTree = ""; }; + 7BDAB26016E99A4800589480 /* record.js.js */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.javascript; path = record.js.js; sourceTree = ""; }; C99CEC85147959C200F648A5 /* RoutingHTTPServer.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = RoutingHTTPServer.app; sourceTree = BUILT_PRODUCTS_DIR; }; C99CEC89147959C200F648A5 /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = System/Library/Frameworks/Cocoa.framework; sourceTree = SDKROOT; }; C99CEC8C147959C200F648A5 /* AppKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AppKit.framework; path = System/Library/Frameworks/AppKit.framework; sourceTree = SDKROOT; }; @@ -118,7 +200,7 @@ C99CECFF14795B7800F648A5 /* GCDAsyncSocket.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = GCDAsyncSocket.h; path = ../External/CocoaHTTPServer/Vendor/CocoaAsyncSocket/GCDAsyncSocket.h; sourceTree = ""; }; C99CED0014795B7800F648A5 /* GCDAsyncSocket.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = GCDAsyncSocket.m; path = ../External/CocoaHTTPServer/Vendor/CocoaAsyncSocket/GCDAsyncSocket.m; sourceTree = ""; }; C99CED0214795B8000F648A5 /* DDLog.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = DDLog.h; path = ../External/CocoaHTTPServer/Vendor/CocoaLumberjack/DDLog.h; sourceTree = ""; }; - C99CED0314795B8000F648A5 /* DDLog.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = DDLog.m; path = ../External/CocoaHTTPServer/Vendor/CocoaLumberjack/DDLog.m; sourceTree = ""; }; + C99CED0314795B8000F648A5 /* DDLog.m */ = {isa = PBXFileReference; explicitFileType = sourcecode.c.objc; fileEncoding = 4; name = DDLog.m; path = ../External/CocoaHTTPServer/Vendor/CocoaLumberjack/DDLog.m; sourceTree = ""; }; C99CED0814795C2700F648A5 /* Security.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Security.framework; path = System/Library/Frameworks/Security.framework; sourceTree = SDKROOT; }; /* End PBXFileReference section */ @@ -127,6 +209,8 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 7BA4102F16E7FC5D0009FC4E /* AtoZ.framework in Frameworks */, + 7BDAB22E16E9597700589480 /* KSHTMLWriter.framework in Frameworks */, C99CED0914795C2700F648A5 /* Security.framework in Frameworks */, C99CEC8A147959C200F648A5 /* Cocoa.framework in Frameworks */, ); @@ -144,9 +228,81 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 7B4B9CEB16EA642A002CCC63 /* multipart */ = { + isa = PBXGroup; + children = ( + 7B4B9CEC16EA642A002CCC63 /* indexUp.html */, + 7B4B9CED16EA642A002CCC63 /* upload.html */, + ); + path = multipart; + sourceTree = ""; + }; + 7BC77C0016E7EBDC00A7CFC2 /* Vendor */ = { + isa = PBXGroup; + children = ( + 7BC77C0116E7EBDC00A7CFC2 /* CocoaAsyncSocket */, + 7BC77C0516E7EBDC00A7CFC2 /* CocoaLumberjack */, + ); + name = Vendor; + path = ../External/CocoaHTTPServer/Vendor; + sourceTree = ""; + }; + 7BC77C0116E7EBDC00A7CFC2 /* CocoaAsyncSocket */ = { + isa = PBXGroup; + children = ( + 7BC77C0216E7EBDC00A7CFC2 /* About.txt */, + 7BC77C0316E7EBDC00A7CFC2 /* GCDAsyncSocket.h */, + 7BC77C0416E7EBDC00A7CFC2 /* GCDAsyncSocket.m */, + ); + path = CocoaAsyncSocket; + sourceTree = ""; + }; + 7BC77C0516E7EBDC00A7CFC2 /* CocoaLumberjack */ = { + isa = PBXGroup; + children = ( + 7BC77C0616E7EBDC00A7CFC2 /* About.txt */, + 7BC77C0716E7EBDC00A7CFC2 /* DDAbstractDatabaseLogger.h */, + 7BC77C0816E7EBDC00A7CFC2 /* DDAbstractDatabaseLogger.m */, + 7BC77C0916E7EBDC00A7CFC2 /* DDASLLogger.h */, + 7BC77C0A16E7EBDC00A7CFC2 /* DDASLLogger.m */, + 7BC77C0B16E7EBDC00A7CFC2 /* DDFileLogger.h */, + 7BC77C0C16E7EBDC00A7CFC2 /* DDFileLogger.m */, + 7BC77C0D16E7EBDC00A7CFC2 /* DDLog.h */, + 7BC77C0E16E7EBDC00A7CFC2 /* DDLog.m */, + 7BC77C0F16E7EBDC00A7CFC2 /* DDTTYLogger.h */, + 7BC77C1016E7EBDC00A7CFC2 /* DDTTYLogger.m */, + 7BC77C1116E7EBDC00A7CFC2 /* Extensions */, + ); + path = CocoaLumberjack; + sourceTree = ""; + }; + 7BC77C1116E7EBDC00A7CFC2 /* Extensions */ = { + isa = PBXGroup; + children = ( + 7BC77C1216E7EBDC00A7CFC2 /* ContextFilterLogFormatter.h */, + 7BC77C1316E7EBDC00A7CFC2 /* ContextFilterLogFormatter.m */, + 7BC77C1416E7EBDC00A7CFC2 /* DispatchQueueLogFormatter.h */, + 7BC77C1516E7EBDC00A7CFC2 /* DispatchQueueLogFormatter.m */, + 7BC77C1616E7EBDC00A7CFC2 /* README.txt */, + ); + path = Extensions; + sourceTree = ""; + }; + 7BDAB21F16E9595000589480 /* Products */ = { + isa = PBXGroup; + children = ( + 7BDAB22516E9595100589480 /* libKSHTMLWriterLib.a */, + 7BDAB22716E9595100589480 /* KSHTMLWriterTests.octest */, + 7BDAB22916E9595100589480 /* KSHTMLWriter.framework */, + ); + name = Products; + sourceTree = ""; + }; C99CEC7A147959C200F648A5 = { isa = PBXGroup; children = ( + 7BA4107116E82EA40009FC4E /* README.md */, + 7BA4102E16E7FC5D0009FC4E /* AtoZ.framework */, 7BB3EF031674F81100BABF35 /* twitter_bootstrap_admin */, C99CEC8F147959C200F648A5 /* RoutingHTTPServer */, C99CECAC147959C300F648A5 /* Tests */, @@ -169,9 +325,7 @@ C99CEC88147959C200F648A5 /* Frameworks */ = { isa = PBXGroup; children = ( - C99CEC89147959C200F648A5 /* Cocoa.framework */, - C99CED0814795C2700F648A5 /* Security.framework */, - C99CECA7147959C300F648A5 /* SenTestingKit.framework */, + 7BDAB21E16E9595000589480 /* KSHTMLWriter.xcodeproj */, C99CEC8B147959C200F648A5 /* Other Frameworks */, ); name = Frameworks; @@ -180,6 +334,9 @@ C99CEC8B147959C200F648A5 /* Other Frameworks */ = { isa = PBXGroup; children = ( + C99CEC89147959C200F648A5 /* Cocoa.framework */, + C99CED0814795C2700F648A5 /* Security.framework */, + C99CECA7147959C300F648A5 /* SenTestingKit.framework */, C99CEC8C147959C200F648A5 /* AppKit.framework */, C99CEC8D147959C200F648A5 /* CoreData.framework */, C99CEC8E147959C200F648A5 /* Foundation.framework */, @@ -190,12 +347,13 @@ C99CEC8F147959C200F648A5 /* RoutingHTTPServer */ = { isa = PBXGroup; children = ( + 7B4B9D1E16EA6673002CCC63 /* FlashWavRecorder */, + 7B4B9CEB16EA642A002CCC63 /* multipart */, C99CEC9B147959C200F648A5 /* AppDelegate.h */, C99CEC9C147959C200F648A5 /* AppDelegate.m */, C99CEC9E147959C300F648A5 /* MainMenu.xib */, - 7B8A347216E6ED070088D209 /* AssetCollection.h */, - 7B8A347316E6ED070088D209 /* AssetCollection.m */, C99CEC90147959C200F648A5 /* Supporting Files */, + 7BDAB26016E99A4800589480 /* record.js.js */, ); path = RoutingHTTPServer; sourceTree = ""; @@ -235,6 +393,12 @@ C99CECC214795B0C00F648A5 /* Routing HTTP */ = { isa = PBXGroup; children = ( + 7B4B9CE216EA60B5002CCC63 /* MultipartFormDataParser.h */, + 7B4B9CE316EA60B5002CCC63 /* MultipartFormDataParser.m */, + 7B4B9CE416EA60B5002CCC63 /* MultipartMessageHeader.h */, + 7B4B9CE516EA60B6002CCC63 /* MultipartMessageHeader.m */, + 7B4B9CE616EA60B6002CCC63 /* MultipartMessageHeaderField.h */, + 7B4B9CE716EA60B7002CCC63 /* MultipartMessageHeaderField.m */, C99CECC314795B3200F648A5 /* HTTPResponseProxy.h */, C99CECC414795B3200F648A5 /* HTTPResponseProxy.m */, C99CECC514795B3200F648A5 /* Route.h */, @@ -254,6 +418,7 @@ C99CECD514795B3C00F648A5 /* HTTP */ = { isa = PBXGroup; children = ( + 7BC77C0016E7EBDC00A7CFC2 /* Vendor */, C99CECE714795B6400F648A5 /* DDData.h */, C99CECE814795B6400F648A5 /* DDData.m */, C99CED0214795B8000F648A5 /* DDLog.h */, @@ -300,10 +465,12 @@ C99CEC81147959C200F648A5 /* Sources */, C99CEC82147959C200F648A5 /* Frameworks */, C99CEC83147959C200F648A5 /* Resources */, + 7BDAB22C16E9596200589480 /* CopyFiles */, ); buildRules = ( ); dependencies = ( + 7BDAB22B16E9595900589480 /* PBXTargetDependency */, ); name = RoutingHTTPServer; productName = RoutingHTTPServer; @@ -347,6 +514,12 @@ mainGroup = C99CEC7A147959C200F648A5; productRefGroup = C99CEC86147959C200F648A5 /* Products */; projectDirPath = ""; + projectReferences = ( + { + ProductGroup = 7BDAB21F16E9595000589480 /* Products */; + ProjectRef = 7BDAB21E16E9595000589480 /* KSHTMLWriter.xcodeproj */; + }, + ); projectRoot = ""; targets = ( C99CEC84147959C200F648A5 /* RoutingHTTPServer */, @@ -355,15 +528,44 @@ }; /* End PBXProject section */ +/* Begin PBXReferenceProxy section */ + 7BDAB22516E9595100589480 /* libKSHTMLWriterLib.a */ = { + isa = PBXReferenceProxy; + fileType = archive.ar; + path = libKSHTMLWriterLib.a; + remoteRef = 7BDAB22416E9595100589480 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 7BDAB22716E9595100589480 /* KSHTMLWriterTests.octest */ = { + isa = PBXReferenceProxy; + fileType = wrapper.cfbundle; + path = KSHTMLWriterTests.octest; + remoteRef = 7BDAB22616E9595100589480 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 7BDAB22916E9595100589480 /* KSHTMLWriter.framework */ = { + isa = PBXReferenceProxy; + fileType = wrapper.framework; + path = KSHTMLWriter.framework; + remoteRef = 7BDAB22816E9595100589480 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; +/* End PBXReferenceProxy section */ + /* Begin PBXResourcesBuildPhase section */ C99CEC83147959C200F648A5 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + 7BDAB22D16E9596C00589480 /* KSHTMLWriter.framework in Resources */, C99CEC94147959C200F648A5 /* InfoPlist.strings in Resources */, C99CEC9A147959C200F648A5 /* Credits.rtf in Resources */, C99CECA0147959C300F648A5 /* MainMenu.xib in Resources */, 7BB3EF041674F81100BABF35 /* twitter_bootstrap_admin in Resources */, + 7BA4107216E82EA40009FC4E /* README.md in Resources */, + 7B4B9CEE16EA642A002CCC63 /* indexUp.html in Resources */, + 7B4B9CEF16EA642A002CCC63 /* upload.html in Resources */, + 7B4B9D1F16EA6673002CCC63 /* FlashWavRecorder in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -420,7 +622,10 @@ C99CECFD14795B6C00F648A5 /* HTTPFileResponse.m in Sources */, C99CECFE14795B6C00F648A5 /* HTTPRedirectResponse.m in Sources */, C99CED0114795B7800F648A5 /* GCDAsyncSocket.m in Sources */, - 7B8A347416E6ED070088D209 /* AssetCollection.m in Sources */, + 7BDAB26216E9A0F400589480 /* record.js.js in Sources */, + 7B4B9CE816EA60B7002CCC63 /* MultipartFormDataParser.m in Sources */, + 7B4B9CE916EA60B7002CCC63 /* MultipartMessageHeader.m in Sources */, + 7B4B9CEA16EA60B7002CCC63 /* MultipartMessageHeaderField.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -435,6 +640,11 @@ /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ + 7BDAB22B16E9595900589480 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = KSHTMLWriter; + targetProxy = 7BDAB22A16E9595900589480 /* PBXContainerItemProxy */; + }; C99CECAB147959C300F648A5 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = C99CEC84147959C200F648A5 /* RoutingHTTPServer */; @@ -505,7 +715,7 @@ GCC_WARN_UNUSED_VARIABLE = YES; MACOSX_DEPLOYMENT_TARGET = 10.7; ONLY_ACTIVE_ARCH = YES; - SDKROOT = macosx; + SDKROOT = ""; }; name = Debug; }; @@ -529,19 +739,24 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES; GCC_WARN_UNUSED_VARIABLE = YES; MACOSX_DEPLOYMENT_TARGET = 10.7; - SDKROOT = macosx; + SDKROOT = ""; }; name = Release; }; C99CECB8147959C300F648A5 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { + ARCHS = "$(NATIVE_ARCH_ACTUAL)"; CLANG_ENABLE_OBJC_ARC = YES; COMBINE_HIDPI_IMAGES = YES; + GCC_C_LANGUAGE_STANDARD = "compiler-default"; GCC_INCREASE_PRECOMPILED_HEADER_SHARING = YES; GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = "RoutingHTTPServer/RoutingHTTPServer-Prefix.pch"; + GCC_VERSION = ""; INFOPLIST_FILE = "RoutingHTTPServer/RoutingHTTPServer-Info.plist"; + LD_RUNPATH_SEARCH_PATHS = "@loader_path/Frameworks/ /Library/Frameworks/"; + ONLY_ACTIVE_ARCH = YES; OTHER_LDFLAGS = ( "-framework", AtoZ, @@ -555,12 +770,17 @@ C99CECB9147959C300F648A5 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { + ARCHS = "$(NATIVE_ARCH_ACTUAL)"; CLANG_ENABLE_OBJC_ARC = YES; COMBINE_HIDPI_IMAGES = YES; + GCC_C_LANGUAGE_STANDARD = "compiler-default"; GCC_INCREASE_PRECOMPILED_HEADER_SHARING = YES; GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = "RoutingHTTPServer/RoutingHTTPServer-Prefix.pch"; + GCC_VERSION = ""; INFOPLIST_FILE = "RoutingHTTPServer/RoutingHTTPServer-Info.plist"; + LD_RUNPATH_SEARCH_PATHS = "@loader_path/Frameworks/ /Library/Frameworks/"; + ONLY_ACTIVE_ARCH = YES; OTHER_LDFLAGS = ( "-framework", AtoZ, @@ -582,6 +802,7 @@ GCC_PREFIX_HEADER = "RoutingHTTPServer/RoutingHTTPServer-Prefix.pch"; INFOPLIST_FILE = "RoutingHTTPServerTests/RoutingHTTPServerTests-Info.plist"; PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = ""; TEST_HOST = "$(BUNDLE_LOADER)"; WRAPPER_EXTENSION = octest; }; @@ -598,6 +819,7 @@ GCC_PREFIX_HEADER = "RoutingHTTPServer/RoutingHTTPServer-Prefix.pch"; INFOPLIST_FILE = "RoutingHTTPServerTests/RoutingHTTPServerTests-Info.plist"; PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = ""; TEST_HOST = "$(BUNDLE_LOADER)"; WRAPPER_EXTENSION = octest; }; diff --git a/Example/RoutingHTTPServer/AppDelegate.h b/Example/RoutingHTTPServer/AppDelegate.h index 679f1c0..88b6e9d 100644 --- a/Example/RoutingHTTPServer/AppDelegate.h +++ b/Example/RoutingHTTPServer/AppDelegate.h @@ -1,35 +1,37 @@ -#import #import -#import "AssetCollection.h" - +#import "HTTPConnection.h" #define REQ RouteRequest #define RES RouteResponse -@class RoutingHTTPServer; +#define UPLOAD_FILE_PROGRESS @"uploadfileprogress" +//#define HTTPLogVerbose(arg,...) NSLog(arg,...) +@class RoutingHTTPServer; @interface AppDelegate : NSObject -@property (STRNG) RoutingHTTPServer *http; +@property (NATOM, STRNG) RoutingHTTPServer *http; @property (WK) IBOutlet WebView *webView; @property (WK) IBOutlet NSW *window; @property (WK) IBOutlet NSTXTF *urlField; +@property (ASS) IBOutlet NSTextView *stdOutView; @property (WK) IBOutlet NSTV *shortcuts; @property (WK) IBOutlet NSAC *queriesController; -@property (STRNG) NSMA *queries; -@property (STRNG) NSS *baseURL; +@property (NATOM, STRNG) NSMA *queries; +@property (NATOM, STRNG) NSS *baseURL; @property (WK) IBOutlet NSArrayController *assetController; @property (WK) IBOutlet NSTableView *assetTable; -@property (NATOM) AssetCollection *assets; +@property (NATOM, STRNG) AssetCollection *assets; @property (WK) IBOutlet NSPathControl *jsPathBar; @property (WK) IBOutlet NSPathControl *cssPathBar; @property (WK) IBOutlet NSPathControl *htmlPathBar; - (void)setupRoutes; +- (IBAction) selectAssets: (id) sender; @end @@ -38,6 +40,6 @@ @end @interface Shortcut : NSObject -@property (STRNG, nonatomic) NSS* uri, *syntax; +@property (STRNG, NATOM) NSS* uri, *syntax; - (id) initWithURI:(NSS*)uri syntax:(NSS*)syntax; @end diff --git a/Example/RoutingHTTPServer/AppDelegate.m b/Example/RoutingHTTPServer/AppDelegate.m index b9abd44..2dde410 100644 --- a/Example/RoutingHTTPServer/AppDelegate.m +++ b/Example/RoutingHTTPServer/AppDelegate.m @@ -1,8 +1,17 @@ #import "AppDelegate.h" +#import +#import "RoutingHTTPServer.h" +#import "HTTPConnection.h" +#import "HTTPAsyncFileResponse.h" +#import "HTTPFileResponse.h" +#import "HTTPConnection.h" +#import "HTTPDataResponse.h" #define $SHORT(A,B) [Shortcut.alloc initWithURI:A syntax:B] - +#define vLOG(A) [((AppDelegate*)[NSApp sharedApplication].delegate).textOutField appendToStdOutView:A] // $(@"%s: %@", __PRETTY_FUNCTION__, [NSString stringWithFormat: args])] +//#define NSLog(...) [((AppDelegate*)[NSApp sharedApplication].delegate).textOutField appendToStdOutView:A] // $(@"%s: %@", __PRETTY_FUNCTION__, [NSString stringWithFormat: args])] +//#define NSLog(args...) _AZSimpleLog(__FILE__,__LINE__,__PRETTY_FUNCTION__,args); @implementation AppDelegate // essential for list view to work. @@ -10,59 +19,215 @@ - (AssetCollection*) assets { return _assets = AssetCollection.sharedInstance; - (void)setupRoutes { - [@[ @[ @"/hello", @"/hello" ], - @[ @"/hello/:name", @"/hello/somename"], - @[ @"{^/page/(\\d+)}", @"/page/9999"], - @[ @"{^/ugh.png", @"/ugh.png"], - @[ @"/colorlist", @"/colorlist" ], - @[ @"/selector", @"/selector" ], - @[ @"/widgets", @"/widgets" ]] each:^(id obj) { - - [_queriesController addObject:$SHORT(obj[1], obj[0])]; + // [_http get:@"*" withBlock:^(REQ *req, RES *res) { NSLog(@"Req:%@... Params: %@", req, req.params); NSLog(@"Res:%@... Params: %@", res, res.headers); }]; +// NSMutableString *xml = [NSMutableString string]; +// KSXMLWriter *writer = [[KSXMLWriter alloc] initWithOutputWriter:xml]; +// +// [writer startElement:@"foo" attributes:nil]; +// [writer writeCharacters:@"bar"]; +// [writer endElement]; +// +// KSHTMLWriter *_writer = [[KSHTMLWriteralloc initWithOutputWriter:output encoding:NSUTF8StringEncoding]; +// [_writer writeString:@"\n"]; +// +// [_writer pushAttribute:@"xmlns" value:@"http://www.sitemaps.org/schemas/sitemap/0.9"]; +// [_writer startElement:@"sitemapindex"]; + + // NSUI lastItemInMatrix = [_queriesController.arrangedObjects indexOfObjectPassingTest:^BOOL(Shortcut *shortcut, NSUI idx, BOOL *stop) { return [shortcut.uri isEqualToString:@"/custom"]; }]; + + [@[ @[ @"/hello", @"/hello" ], @[ @"/hello/:name", @"/hello/somename"], + @[ @"{^/page/(\\d+)$}", @"/page/9999"], @[ @"/info", @"/info" ], + @[ @"/customHTML", @"/custom"]] + + eachWithIndex:^(id obj, NSI idx) { [_queriesController insertObject:$SHORT(obj[0], obj[1]) atArrangedObjectIndex:idx]; + + [_http get:obj[0] withBlock:^(REQ *req, RES *res) { [res respondWithString + + : idx == 1 ? @"This text is showing because the URL ends with '/hello'" + : idx == 2 ? $( @"Hello %@!", req.params[@"name"]) + : idx == 3 ? $( @"/page/%@", req.params[@"captures"][0]) + : idx == 4 ? $( @"This could be written as ' %@ ' \n\n Which would output req: %@ \n and response: %@.", @"[_http get:@\"/info\" withBlock:^(REQ *req, RES *res) { AZLOG(req); AZLOG(res); }]; \n [res respondWithString:theResponse]; }]; \n}];'", req, res) + : idx == 5 ? ^{ return @"custom placeholder"; }() : @"" + + ]; }]; }]; + + [@[ @[ @"/bootstrap", @"/bootstrap"], @[ @"{^/ugh.png", @"/ugh.png"], + @[ @"/colorlist", @"/colorlist" ], @[ @"/selector", @"/selector"], + @[ @"/widgets", @"/widgets"], @[ @"/xml", @"/xml"], + @[ @"/recognize", @"/recognize"], + @[ @"/wav:", @"/wav"], @[ @"http://mrgray.com/sip/sipml5/", @"/sip"], + @[ @"{^/vageen.png", @"/vageen.png"]] + each:^(id obj) { [_queriesController addObject: $SHORT(obj[1], obj[0])]; }]; + + + +// [[_queriesController.arrangedObjects subarrayToIndex:lastItemInMatrix + 1] eachWithIndex:^(Shortcut *obj, NSInteger idx) { + + + [_http get:@"/sip" withBlock:^(RouteRequest *request, RouteResponse *response) { + [response setStatusCode:302]; // or 301 + [response setHeader:@"Location" value:@"http://mrgray.com/sip/sipml5/"];//[self.baseURL stringByAppendingString:@"/new"]]; }]; - [[_queriesController.arrangedObjects subarrayFromIndex:0 toIndex:2] eachWithIndex:^(Shortcut *obj, NSInteger idx) { - [_http get:obj.uri withBlock:^(REQ *req, RES *res) { +// [_http get:@"/info" withBlock:^(REQ *req, RES *res) { AZLOG(req); AZLOG(res); }]; - NSS *theResponse = idx == 0 ? @"Hello!" - : idx == 1 ? $(@"Hello %@!", req.params[@"name"]) - : $(@"You requested page %@", req.params[@"captures"][0]); - [res respondWithString:theResponse]; + [_http get:@"/bootstrap" withBlock:^(REQ *req, RES *res) { + [Bootstrap initWithUserStyle:nil script:nil andInnerHTML:nil calling:^(id sender) { +// initWithUserStyles:@"" script:@"" andInnerHTML:@"

HELLO

" calling:^(id sender) { + [res respondWithString:[(NSS*)sender copy]]; + }]; + }]; + + [_http get:@"/indexUP.html" withBlock:^(REQ *req, RES *res) { + NSLog(@"%@", req.params); + [res respondWithFile:$(@"%@%@",[NSBundle.mainBundle resourcePath],@"/indexUP.html")]; + }]; + + [_http get:@"/recognize" withBlock:^(REQ *req, RES *res) { + GoogleTTS *u = GoogleTTS.instance; + [u getText:NSS.dicksonBible withCompletion:^(NSString *text, NSString *wavpath) { + [res respondWithFile:wavpath]; }]; }]; + // SELECTORS AS STRINGS + [@[ @[ @"/colorlist", @"colorlist:withResponse:"], + @[ @"/selector", @"handleSelectorRequest:withResponse:"]] each:^(id obj) { + [_http handleMethod:@"GET" withPath:obj[0] target:self selector:$SEL(obj[1])]; + }]; + [_http get:@"{^/ugh.png" withBlock:^(RouteRequest *req, RouteResponse *res) { + NSIMG* rando = [NSIMG.systemImages.randomElement scaledToMax:AZMinDim(_webView.bounds.size)]; + [res respondWithData: PNGRepresentation(rando)]; + //[rando.bitmap representationUsingType:NSJPEGFileType properties:nil]]; + }]; + + [_http get:@"{^/vageen.png" withBlock:^(REQ *req, RES *res) { +// [self contactSheetWith: [NSIMG.frameworkImages withMaxItems:10] rect:AZScreenFrame() cols:3 +// callback:^(NSIMG *i) { + [res respondWithData:[[NSImage contactSheetWith:[NSIMG.frameworkImages withMaxRandomItems:10] + inFrame:_webView.bounds columns:4] + .bitmap representationUsingType: NSJPEGFileType properties:nil]]; +// NSData *result = [i.bitmap representationUsingType:NSJPEGFileType properties:nil]; + // NSData *d = [rando.representations[0] bitmapRepresentation];// bitmapRepresentation;//][0] representationUsingType:NSPNGFileType properties:nil];// TIFFRepresentation]; +// [res respondWithData:result]; }]; + }]; + [_http get:@"/record/*.*" withBlock:^(RouteRequest *req, RouteResponse *res) { + NSLog(@"req params: %@", req.params); +// [res setStatusCode:302]; // or 301 +// [res setHeader:@"Location" value:[self.baseURL stringByAppendingString:@"/record/"]]; + [res respondWithFile:[[[NSBundle.mainBundle resourcePath] withPath:@"FlashWavRecorder"]withPath:req.params[@"wildcards"][0]]]; + }]; + [_http post:@"/uploadwav" withBlock:^(REQ *req, RES *res) { // Create a new widget, [request body] contains the POST body data. For this example we're just going to echo it back. + NSLog(@"Post to /uploadwav %@", req.params); + }]; +} + // ADB target:self selector:@selector()]; + + // NSData *d = [rando.representations[0] bitmapRepresentation];// bitmapRepresentation;//][0] representationUsingType:NSPNGFileType properties:nil];// TIFFRepresentation]; + + +// NSS *thePath = req.params[@"filepath"] ?: @"/Users/localadmin/Desktop/blanche.withspeech.flac"; +// GoogleTTS *u = [GoogleTTS instanceWithWordsToSpeak:NSS.dicksonisms.randomElement]; +// [NSThread performBlockInBackground:^{ +// u.words = NSS.dicksonisms.randomElement; +// [res respondWithFile:u.nonFlacFile]; +// +// [[NSThread mainThread] performBlock:^{ +// [res respondWithFile:wavpath async:YES]; +// }]; +// }]; +// }]; + + //[GoogleTTS instanceWithWordsToSpeak:NSS.dicksonisms.randomElement completion:^(NSString *t, NSS*file) { +// NSLog(@"wavpath:%@.... u/nonflac: %@",wavpath, u.nonFlacFile); +// NSData *bytes = [NSData dataWithContentsOfURL:$URL(u.nonFlacFile)];//wav g.nonFlacFile]]; +// NSLog(@"sending:'%ld' bytes", [bytes length]); +// [res respondWithFile:u.nonFlacFile]; + +/** + [_http post:@"/xml" withBlock:^(RouteRequest *request, RouteResponse *response) { + NSData *bodyData = [request body]; + NSString *xml = [[NSString alloc] initWithBytes:[bodyData bytes] length:[bodyData length] encoding:NSUTF8StringEncoding]; + + // Green? + NSRange tagRange = [xml rangeOfString:@""]; + if (tagRange.location != NSNotFound) { + NSUInteger start = tagRange.location + tagRange.length; + NSUInteger end = [xml rangeOfString:@"<" options:0 range:NSMakeRange(start, [xml length] - start)].location; + if (end != NSNotFound) { + NSString *greenLevel = [xml substringWithRange:NSMakeRange(start, end - start)]; + [response respondWithString:greenLevel]; + } + } + }]; [_http post:@"/widgets" withBlock:^(REQ *req, RES *res) { // Create a new widget, [request body] contains the POST body data. For this example we're just going to echo it back. NSLog(@"POST: %@", req.body); [res respondWithData:req.body]; }]; - +*/ // Routes can also be handled through selectors - [@[ @[@"/colorlist", @"colorlist:withResponse:"], - @[ @"/selector", @"handleSelectorRequest:withResponse:"]] each:^(id obj) { - [_http handleMethod:@"GET" withPath:obj[0] target:self selector:$SEL(obj[1])]; - }]; -// ADB target:self selector:@selector()]; - [_http get:@"{^/ugh.png" withBlock:^(RouteRequest *req, RouteResponse *res) { - NSImage *image = [[[NSImage systemImages]randomElement] scaleToFillSize:NSMakeSize(344,344)]; - NSData *d = [image TIFFRepresentation]; +/** + [_http get:@"/wav" withBlock:^(RouteRequest *req, RouteResponse *res) { + + GoogleTTS *g = [GoogleTTS instanceWithWordsToSpeak:NSS.dicksonisms.randomElement completion:^(NSString *s) { - [res respondWithData:d]; //[self PNGRepresentationOfImage:image]]; + NSURL *urlPath = [NSURL fileURLWithPath:g.nonFlacFile]; +// NSString *wavbundlepath = [urlPath absoluteString]; +// NSLog(@"wavbundlepath: %@",wavbundlepath); + NSLog(@"Text from google: %s.... playing WAV."); + NSData *bytes=[NSData dataWithContentsOfURL:[NSURL fileURLWithPath:g.nonFlacFile]]; + [res respondWithData:bytes]; + }]; +// NSLog(@"bytes: %@",bytes); }]; - [_http get:@"{^/vageen.png" withBlock:^(REQ *req, RES *res) { - [self contactSheetWith: [NSIMG.frameworkImages withMaxItems:10] rect:AZScreenFrame() cols:3 callback:^(NSIMG *i) { -// [response respondWithFile: (NSS *)path async:(BOOL)async; + NSString *recordPostLength = [NSString stringWithFormat:@"%d", [bytes length]]; + +// NSMutableString *urlstr = [NSMutableString stringWithFormat:@"%@", @"http://www.myserver.com/api/UploadFile?Name="]; +// [urlstr appendString:@"Temp"]; +// [urlstr appendFormat:@"&MemberID=%d", 0]; +// [urlstr appendFormat:@"&Type=%@",@"Recording"]; +// [urlstr appendFormat:@"&client=%@",@"ios"]; +// NSLog(@"urlstr.......%@",urlstr); + + NSMutableURLRequest *recordRequest = [[NSMutableURLRequest alloc] init] ; + [recordRequest setURL:[NSURL URLWithString:urlstr]]; + + NSInputStream *dataStream = [NSInputStream inputStreamWithData:bytes]; + [recordRequest setHTTPBodyStream:dataStream]; - NSS* path = @"/tmp/atoztempfile.CE4DED94-E457-4A0A-B214-B1866616DDBA.png";// [i asTempFile]; - NSLog(@"image: %@ path: %@", i, path); + [recordRequest setHTTPMethod:@"POST"]; -// [i openInPreview]; - [res respondWithFile:path];// (NSS *)path async:(BOOL)async; + NSURLResponse *recordResponse; + NSError *recordError; + NSData *recordResponseData = [NSURLConnection sendSynchronousRequest:recordRequest returningResponse:&recordResponse error:&recordError]; -// [i lockFocus]; + NSString *recordResp = [[NSString alloc]initWithData:recordResponseData encoding:NSUTF8StringEncoding]; + NSLog(@"recordResp:%@", recordResp); + recordResponceJson = [recordResp JSONValue]; + NSLog(@"recordResponceJson = %@",recordResponceJson); + recId = [recordResponceJson valueForKey:@"ID"]; + NSLog(@"recId....%@", recId); +*/ +// [res respondWithFile:@"/Users/localadmin/Desktop/2206 167.jpg"]; // OK + +// NSS * tmp = [[NSTemporaryDirectory() withPath:NSS.UUIDString] withExt:@"png"]; // OK +// [NSIMG.frameworkImages.randomElement saveAs:tmp]; +// [res respondWithFile:tmp async:YES]; + + +//// [response respondWithFile: (NSS *)path async:(BOOL)async; +// +// NSS* path = @"/tmp/atoztempfile.CE4DED94-E457-4A0A-B214-B1866616DDBA.png";// [i asTempFile]; +// NSLog(@"image: %@ path: %@", i, path); +// +//// [i openInPreview]; +// [res respondWithFile:path];// (NSS *)path async:(BOOL)async; +// +//// [i lockFocus]; // NSBIR *bitmapRep = [NSBIR.alloc initWithFocusedViewRect:AZRectFromSize(i.size)]; // [i unlockFocus]; // @@ -73,12 +238,12 @@ - (void)setupRoutes // NSData *rep = [bitmapRep representationUsingType:NSPNGFileType properties:Nil]; // NSLog(@"idata: %@", rep); // [res respondWithData:rep];//PNGRepresentation(i)];//rep]; //[self PNGRepresentationOfImage:image]]; - }]; +// }]; - }]; - NSLog(@"Queries: %@.. Arranged: %@", _queries, _queriesController.arrangedObjects); +// }]; +// NSLog(@"Queries: %@.. Arranged: %@", _queries, _queriesController.arrangedObjects); // [_shortcuts reloadData]; -} + - (void)tableViewSelectionDidChange:(NSNotification *)notification; { @@ -138,25 +303,23 @@ -(RoutingHTTPServer*) http NSS *appVersion = bundleInfo[@"CFBundleShortVersionString"] ?: bundleInfo[@"CFBundleVersion"]; _http.defaultHeaders = @{ @"Server": $(@"%@/%@", bundleInfo[@"CFBundleName"],appVersion) }; _http.type = @"_http._tcp."; - [self setupRoutes]; _http.port = 8080; _baseURL = $(@"http://localhost:%i", _http.port); + [self setupRoutes]; NSError *error; if (![_http start:&error]) NSLog(@"Error starting HTTP server: %@", error) else [self loadURL:nil]; return _http; } + - (void)awakeFromNib { + + _queries = NSMA.new; + _urlField.delegate = self; + self.http.documentRoot = LogAndReturn([NSB bundleForClass:KSHTMLWriter.class].resourcePath);//@"/";//[[Bootstrap class]reso];// withPath:@"twitter_bootstrap_admin"]; +// _http.connectionClass = [WTZHTTPConnection class]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleUploadProgressNotification:) name:UPLOAD_FILE_PROGRESS object:nil]; + [_assetTable registerForDraggedTypes:@[AssetDataType]]; - _queries = NSMA.new; - _urlField.delegate = self; - self.http.documentRoot = [NSB.mainBundle.resourcePath withPath:@"twitter_bootstrap_admin"]; // [@"~/Sites" stringByExpandingTildeInPath]]; -// _assets = [AssetCollection sharedInstance]; - [@[ @[_cssPathBar, @"style"], @[_htmlPathBar, @"div"], @[_jsPathBar, @"javascript"]] each:^(id obj) { - NSS* path = [[obj[0] URL] path] ; - AssetType type = [(NSString*)obj[1] assetFromString]; - NSLog(@"adding type: %@ from path: %@", assetStringValue[type], path); - [self.assets addFolder:path matchingType:type]; - }]; } @@ -216,6 +379,217 @@ - (BOOL)tableView:(NSTableView *)aTableView acceptDrop:(id )info } +- (IBAction) selectAssets: (id) sender +{ + + NSLog(@"opening"); + NSOpenPanel* openPanel = NSOpenPanel.openPanel; + [openPanel setCanChooseFiles:YES]; + [openPanel setCanChooseDirectories:YES]; + [openPanel setAllowsMultipleSelection: YES]; + NSA *tarr = @[@"js", @"css", @"txt", @"html", @"php", @"shtml"]; + [openPanel setAllowedFileTypes:tarr]; + [openPanel beginSheetModalForWindow: _window completionHandler: ^(NSI result) { + if (result == NSFileHandlingPanelOKButton) { +// NSURL *url = [[panel URLs] objectAtIndex: 0]; +// url = [openPanel URL]; +// [ @[ @[_cssPathBar, @"style"], @[_htmlPathBar, @"div"], @[_jsPathBar, @"javascript"]] each:^(NSA *obj) { + [openPanel.URLs each:^(NSURL* obj) { + NSS* path = obj.path; + AssetType type = [path.pathExtension assetFromString]; + NSLog(@"adding type: %@ from path: %@", assetStringValue[type], path); + [self.assets insertObject:[Asset instanceOfType:type withPath:path orContents:nil isInline:NO] inAssetsAtIndex:_assets.countOfAssets]; +// [self.assets addFolder:path matchingType:type]; + }]; + } + }]; +} // openEarthinizerDoc + + +// +//- (void) handlePosts +//{ +// [_http post:@"/xml" withBlock:^(RouteRequest *request, RouteResponse *response) { +// NSData *bodyData = [request body]; +// NSString *xml = [[NSString alloc] initWithBytes:[bodyData bytes] length:[bodyData length] encoding:NSUTF8StringEncoding]; +// +// // Green? +// NSRange tagRange = [xml rangeOfString:@""]; +// if (tagRange.location != NSNotFound) { +// NSUInteger start = tagRange.location + tagRange.length; +// NSUInteger end = [xml rangeOfString:@"<" options:0 range:NSMakeRange(start, [xml length] - start)].location; +// if (end != NSNotFound) { +// NSString *greenLevel = [xml substringWithRange:NSMakeRange(start, end - start)]; +// [response respondWithString:greenLevel]; +// } +// } +// }]; +//// [_http post:@"/widgets" withBlock:^(REQ *req, RES *res) { // Create a new widget, [request body] contains the POST body data. For this example we're just going to echo it back. +// NSLog(@"POST: %@", req.body); +// [res respondWithData:req.body]; }]; + +// NSLog(@"POST: %@", req.body); +// [res respondWithData:req.body]; }]; +// HTTPConnection* *con = [res connection]; +// [con respondWithFile:tmp async:YES]; +// NSLog(@"%@, %@, %@", req.body, req.headers, req);//req.connection ); +// [res.connection processBodyDa] +// NSData *d = [req body]; + +// [d writeToFile:tmp atomically:YES]; + +// [res.connection BodyData:req.body]; + + + // OK +//newu //] wit atomically:] + +// if([method isEqualToString:@"POST"] && [path isEqualToString:@"/upload.html"]) { + // here we need to make sure, boundary is set in header +// NSString* contentType = req.headers[@"Content-Type"]; +// int paramsSeparator = [contentType rangeOfString:@";"].location; +// if( NSNotFound == paramsSeparator ) { +//// return NO; +// } +// if( paramsSeparator >= contentType.length - 1 ) { +//// return NO; +// } +// NSString* type = [contentType substringToIndex:paramsSeparator]; +// if( ![type isEqualToString:@"application/json"] ) { //![type isEqualToString:@"multipart/form-data"] ) { +// // we expect multipart/form-data content type +//// return NO; +// } +// return YES; +// }]; +//} +// +/** // enumerate all params in content-type, and find boundary there + NSArray* params = [[contentType substringFromIndex:paramsSeparator + 1] componentsSeparatedByString:@";"]; + for( NSString* param in params ) { + paramsSeparator = [param rangeOfString:@"="].location; + if( (NSNotFound == paramsSeparator) || paramsSeparator >= param.length - 1 ) { + continue; + } + NSString* paramName = [param substringWithRange:NSMakeRange(1, paramsSeparator-1)]; + NSString* paramValue = [param substringFromIndex:paramsSeparator+1]; + + if( [paramName isEqualToString: @"boundary"] ) { + // let's separate the boundary from content-type, to make it more handy to handle + [request setHeaderField:@"boundary" value:paramValue]; + } + } + // check if boundary specified + if( nil == [request headerField:@"boundary"] ) { + return NO; + } + return YES; + } + return [super expectsRequestBodyFromMethod:method atPath:path]; +} +*/ +//- (NSObject *)httpResponseForMethod:(NSString *)method URI:(NSString *)path +//{ +// HTTPLogTrace(); +// +// if ([method isEqualToString:@"POST"] && [path isEqualToString:@"/upload.html"]) +// { + +// // this method will generate response with links to uploaded file +// NSMutableString* filesStr = [[NSMutableString alloc] init]; +// +// for( NSString* filePath in uploadedFiles ) { +// //generate links +// [filesStr appendFormat:@" %@
",filePath, [filePath lastPathComponent]]; +// } +// NSString* templatePath = [[config documentRoot] stringByAppendingPathComponent:@"upload.html"]; +// NSDictionary* replacementDict = [NSDictionary dictionaryWithObject:filesStr forKey:@"MyFiles"]; + // use dynamic file response to apply our links to response template +// return [[HTTPDynamicFileResponse alloc] initWithFilePath:templatePath forConnection:self separator:@"%" replacementDictionary:replacementDict]; +// } +// if( [method isEqualToString:@"GET"] && [path hasPrefix:@"/upload/"] ) { +// // let download the uploaded files +// return [[HTTPFileResponse alloc] initWithFilePath: [[config documentRoot] stringByAppendingString:path] forConnection:self]; +// } +// +// return [super httpResponseForMethod:method URI:path]; +//} + +//- (void)prepareForBodyWithSize:(UInt64)contentLength +//{ +// HTTPLogTrace(); +// +// // set up mime parser +// NSString* boundary = [request headerField:@"boundary"]; +// parser = [[MultipartFormDataParser alloc] initWithBoundary:boundary formEncoding:NSUTF8StringEncoding]; +// parser.delegate = self; +// +// uploadedFiles = [[NSMutableArray alloc] init]; +//} +// +//- (void)processBodyData:(NSData *)postDataChunk +//{ +// HTTPLogTrace(); +// // append data to the parser. It will invoke callbacks to let us handle +// // parsed data. +// [parser appendData:postDataChunk]; +//} +#pragma mark multipart form data parser delegate +//- (void) processStartOfPartWithHeader:(MultipartMessageHeader*) header { +// // in this sample, we are not interested in parts, other then file parts. +// // check content disposition to find out filename +// +// MultipartMessageHeaderField* disposition = [header.fields objectForKey:@"Content-Disposition"]; +// NSString* filename = [[disposition.params objectForKey:@"filename"] lastPathComponent]; +// +// if ( (nil == filename) || [filename isEqualToString: @""] ) { +// // it's either not a file part, or +// // an empty form sent. we won't handle it. +// return; +// } +// NSString* uploadDirPath = [[config documentRoot] stringByAppendingPathComponent:@"upload"]; +// +// BOOL isDir = YES; +// if (![[NSFileManager defaultManager]fileExistsAtPath:uploadDirPath isDirectory:&isDir ]) { +// [[NSFileManager defaultManager]createDirectoryAtPath:uploadDirPath withIntermediateDirectories:YES attributes:nil error:nil]; +// } +// +// NSString* filePath = [uploadDirPath stringByAppendingPathComponent: filename]; +// if( [[NSFileManager defaultManager] fileExistsAtPath:filePath] ) { +// storeFile = nil; +// } +// else { +// NSLog(@"Saving file to %@", filePath); +// +// HTTPLogVerbose(@"Saving file to %@", filePath); +// [[NSFileManager defaultManager] createFileAtPath:filePath contents:nil attributes:nil]; +// storeFile = [NSFileHandle fileHandleForWritingAtPath:filePath]; +// [uploadedFiles addObject: [NSString stringWithFormat:@"/upload/%@", filename]]; +// } +//} + +//- (void) processContent:(NSData*) data WithHeader:(MultipartMessageHeader*) header +//{ +// // here we just write the output from parser to the file. +// if( storeFile ) { +// [storeFile writeData:data]; +// } +//} +// +//- (void) processEndOfPartWithHeader:(MultipartMessageHeader*) header +//{ +// // as the file part is over, we close the file. +// [storeFile closeFile]; +// storeFile = nil; +//} +// +// +// //注意这里并不能直接改变progressView.progress的值 因为NSNotification也是运行在非主线程中的! +//- (void)handleUploadProgressNotification:(NSNotification *) notification +//{ +// NSNumber *uploadProgress = (NSNumber *)[notification object]; +//// [self performSelectorOnMainThread:@selector(changeProgressViewValue:) withObject:uploadProgress waitUntilDone:NO]; +//} + @end diff --git a/Example/RoutingHTTPServer/AssetCollection.h b/Example/RoutingHTTPServer/AssetCollection.h deleted file mode 100644 index e305f3c..0000000 --- a/Example/RoutingHTTPServer/AssetCollection.h +++ /dev/null @@ -1,40 +0,0 @@ - -typedef NS_ENUM(NSUI, AssetType){ JS, CSS, HTML, PHP, BASH, ObjC, TXT, UNKNOWN = 99 }; -extern NSString * const assetStringValue[]; -extern NSString * const assetTagName[]; - -@interface NSString (AssetType) -- (NSS*) wrapInHTML; -- (AssetType)assetFromString; -@end -#define AssetDataType @"AssetDataTypeFprTableViewDrag" - - -@interface Asset : BaseModel - -@property (RONLY) NSN *priority; -@property (NATOM, STRNG) NSS *path, - *contents; -@property (NATOM, ASS) BOOL isInline, - isActive; -@property (NATOM, ASS) AssetType assetType; -@property (NATOM, STRNG) NSS *markup; - - -+ (instancetype) instanceOfType:(AssetType)type withPath:(NSS*)path orContents:(NSS*)contents isInline:(BOOL)isit; -@end - -@interface AssetCollection : BaseModel -@property (NATOM, STRNG) NSMutableArray *folders, *assets; - -// Subclass specific KVO Compliant "items" accessors to trigger NSArrayController updates on inserts / removals. -- (id) objectInAssetsAtIndex: (NSUI)idx; -- (void) removeObjectFromAssetsAtIndex: (NSUI)idx; -- (void) insertObject: (Asset*)a inAssetsAtIndex:(NSUI)idx; -- (NSUI) countOfAssets; - -- (void) addFolder: (NSS*)path matchingType:(AssetType)fileType; -@end - -@interface AssetTypeTransformer: NSValueTransformer -@end diff --git a/Example/RoutingHTTPServer/AssetCollection.m b/Example/RoutingHTTPServer/AssetCollection.m deleted file mode 100644 index 0db8c18..0000000 --- a/Example/RoutingHTTPServer/AssetCollection.m +++ /dev/null @@ -1,117 +0,0 @@ -// -// AssetCollection.m -// RoutingHTTPServer -// -// Created by Alex Gray on 05/03/2013. -// -// - -#import "AssetCollection.h" -#import - -//NSString* assetType(AssetType enumVal) -//{ -// static NSArray * assetTypes = nil; -// return assetTypes ?: [NSArray.alloc initWithObjects:AssetTypeArray][enumVal]; -//} - - - -// To convert enum to string: NSString *str = FormatType_toString[theEnumValue]; -NSString * const assetStringValue[] = { @"js",@"css",@"html",@"php",@"sh",@"m",@"txt",@"n/a" }; -NSString * const assetTagName[] = { @"script",@"style",@"div",@"php",@"sh",@"m",@"txt",@"n/a" }; - - -@implementation NSString (AssetType) -- (AssetType)assetFromString -{ - static NSD *types = nil; if (!types) types = - @{ @"js" : @(JS), @"html" : @(HTML), @"css" : @(CSS), @"php" : @(PHP), @"sh" : @(BASH), @"m" : @(ObjC), @"txt" : @(TXT), @"n/a" :@(UNKNOWN) }; - return (AssetType)[types[self] intValue]; -} -- (NSS*)wrapInHTML { return $(@"%@", self); } - -// static NSD *types = nil; if (!types) types = -// @{ @"script" : @(JS), @"div" : @(HTML), @"style" : @(CSS), @"php" : @(PHP), @"sh" : @(BASH), @"m" : @(ObjC), @"txt" : @(TXT), @"n/a" :@(UNKNOWN) }; -// return (AssetType)[types[self] intValue]; -//} -@end - - -@implementation Asset - -+ (instancetype) instanceOfType:(AssetType)type withPath:(NSS*)path orContents:(NSS*)contents isInline:(BOOL)isit; -{ - Asset *n = Asset.instance; - n.assetType = type != NSNotFound ? type : UNKNOWN ; - n.path = path; - n.isInline = path == nil || isit ?: NO; - n.contents = contents; - return n; -} -- (NSN*) priority { return @([AssetCollection.sharedInstance.assets indexOfObject:self]); } - -- (NSS*) markup -{ - return $(@"<%@ src='%@'>", assetStringValue[self.assetType], self.path, assetStringValue[self.assetType]); -} - -- (NSS*) cssInline { return $(@"\n", self.contents) } -- (NSS*) cssTag { return $(@"\n", self.path) } -- (NSS*) jsInline { return $(@"\n", self.path) } - - -@end - -@implementation AssetCollection -- (void) setUp { _folders = NSMA.new; _assets = NSMA.new; } - - -- (void) addFolder: (NSS*)path matchingType:(AssetType)fileType -{ - [_folders addObject:path]; - NSS * ext = assetStringValue[fileType]; - NSError *error; - NSA* filed = [AZFILEMANAGER contentsOfDirectoryAtPath:path error:&error]; - if (!error) { - filed = [filed filter:^BOOL(id object) { - return [[object pathExtension] isEqualToString:ext]; - }]; - AZLOG(filed); - [[filed map:^id(id obj) { return [Asset instanceOfType:fileType withPath:obj orContents:nil isInline:NO]; }] each:^(id obj) { -// AZLOG([obj propertiesPlease]); - [self insertObject:obj inAssetsAtIndex:self.assets.count]; - }]; - } - NSLog(@"folders: %@ assets:%@", _folders, _assets); - -} - -- (NSUI)countOfAssets { return self.assets.count; } - -- (id)objectInAssetsAtIndex:(NSUI)index { return self.assets[index]; } - -- (void)removeObjectFromAssetsAtIndex:(NSUI)index { [self.assets removeObjectAtIndex:index]; } - -- (void)insertObject:(Asset*)todo inAssetsAtIndex:(NSUI)index { [self.assets insertObject:todo atIndex:index]; } - -@end - -@implementation AssetTypeTransformer - -+(Class)transformedValueClass { - return NSString.class; -} - --(id)transformedValue:(id)value { -// AssetType t = [value intValue]; - return assetStringValue[[value intValue]]; -// if (quality == kQualityBest) -// return @"Best"; -// else if (quality == kQualityWorst) -// return @"Worst"; -// - return nil; -} -@end \ No newline at end of file diff --git a/Example/RoutingHTTPServer/FlashWavRecorder b/Example/RoutingHTTPServer/FlashWavRecorder new file mode 160000 index 0000000..51410f2 --- /dev/null +++ b/Example/RoutingHTTPServer/FlashWavRecorder @@ -0,0 +1 @@ +Subproject commit 51410f2ff83fd09aaf8254034ba54f85a7d1a5f7 diff --git a/Example/RoutingHTTPServer/en.lproj/MainMenu.xib b/Example/RoutingHTTPServer/en.lproj/MainMenu.xib index 740bb04..a4a4ace 100644 --- a/Example/RoutingHTTPServer/en.lproj/MainMenu.xib +++ b/Example/RoutingHTTPServer/en.lproj/MainMenu.xib @@ -30,6 +30,7 @@ NSTableView NSTextField NSTextFieldCell + NSTextView NSUserDefaultsController NSView NSWindowTemplate @@ -1311,7 +1312,7 @@ 15 2 - {{335, 390}, {598, 273}} + {{335, 390}, {536, 289}} 1954021376 RoutingHTTPServer NSWindow @@ -1329,83 +1330,283 @@ 256 - + - 270 - {{0, 244}, {143, 29}} - - - _NS:9 - YES - - -1808793535 - 1088 - - - LucidaGrande - 14 - 16 - - _NS:9 - - YES - - 6 - System - textColor - - 3 - MAA - + 274 + + + + 256 + + + + 266 + {{0, 228}, {203, 29}} + + + + _NS:9 + YES + + -1808793535 + 1088 + + + LucidaGrande + 14 + 16 + + _NS:9 + + YES + + 6 + System + textColor + + 3 + MAA + + + + 3 + MQA + + + NO + + + + 274 + + Apple HTML pasteboard type + Apple PDF pasteboard type + Apple PICT pasteboard type + Apple URL pasteboard type + Apple Web Archive pasteboard type + NSColor pasteboard type + NSFilenamesPboardType + NSStringPboardType + NeXT RTFD pasteboard type + NeXT Rich Text Format v1.0 pasteboard type + NeXT TIFF v4.0 pasteboard type + WebURLsWithTitlesPboardType + public.png + public.url + public.url-name + + {203, 227} + + + + _NS:9 + + + + + + + + + + + YES + YES + + + {203, 257} + + + + _NS:9 + NSView - - 3 - MQA + + + 256 + + + + 2304 + + + + 2322 + + Apple HTML pasteboard type + Apple PDF pasteboard type + Apple PICT pasteboard type + Apple PNG pasteboard type + Apple URL pasteboard type + CorePasteboardFlavorType 0x6D6F6F76 + NSColor pasteboard type + NSFilenamesPboardType + NSStringPboardType + NeXT Encapsulated PostScript v1.2 pasteboard type + NeXT RTFD pasteboard type + NeXT Rich Text Format v1.0 pasteboard type + NeXT TIFF v4.0 pasteboard type + NeXT font pasteboard type + NeXT ruler pasteboard type + WebURLsWithTitlesPboardType + public.url + + {240, 22} + + + + _NS:13 + + + + + + + + + + + + 182 + + + + 240 + 1 + + + 1140862831 + 0 + + + 1 + MC4wNzg5NTM1OTg0OCAwLjA3ODk1MzU5ODQ4IDAuMDc4OTUzNTk4NDgAA + + + + 6 + System + selectedTextBackgroundColor + + 3 + MC42NjY2NjY2NjY3AA + + + + 6 + System + selectedTextColor + + + + + 1 + MC45NjEzMzM2Nzc0IDEgMC4yOTE0OTkyNTU1AA + + + + 1 + MCAwIDEAA + + + {8, -8} + 13 + + + + + + 1 + + 6 + {463, 10000000} + {203, 22} + + + + {203, 22} + + + + _NS:11 + + + + {4, 5} + + 79691776 + + + + + + file://localhost/Applications/Xcode.app/Contents/SharedFrameworks/DVTKit.framework/Resources/DVTIbeamCursor.tiff + + + + + 3 + MCAwAA + + + + 4 + + + + 256 + {{187, 0}, {16, 22}} + + + + _NS:83 + NO + + _doScroller: + 0.90099009900990101 + + + + -2147483392 + {{-100, -100}, {87, 18}} + + + + _NS:33 + YES + NO + 1 + + _doScroller: + 1 + 0.94565218687057495 + + + {{0, 267}, {203, 22}} + + + + _NS:9 + 133264 + + + + 0.25 + 4 + 1 - - NO - - - - 274 - - Apple HTML pasteboard type - Apple PDF pasteboard type - Apple PICT pasteboard type - Apple URL pasteboard type - Apple Web Archive pasteboard type - NSColor pasteboard type - NSFilenamesPboardType - NSStringPboardType - NeXT RTFD pasteboard type - NeXT Rich Text Format v1.0 pasteboard type - NeXT TIFF v4.0 pasteboard type - WebURLsWithTitlesPboardType - public.png - public.url - public.url-name - - {143, 247} + + {203, 289} - - _NS:9 - - - - - - - - - - - YES - YES + + + 3 - {143, 273} + {203, 289} - + + _NS:9 NSView @@ -1416,8 +1617,9 @@ 18 - {{4, 4}, {437, 265}} + {{4, 4}, {315, 281}} + _NS:9 @@ -1491,10 +1693,7 @@ 6 System controlBackgroundColor - - 3 - MC42NjY2NjY2NjY3AA - + 6 @@ -1607,8 +1806,9 @@ Apple URL pasteboard type NSFilenamesPboardType - {{16, 197}, {253, 20}} + {{16, 213}, {253, 20}} + _NS:9 YES @@ -1667,8 +1867,9 @@ Apple URL pasteboard type NSFilenamesPboardType - {{154, 197}, {253, 20}} + {{154, 213}, {253, 20}} + _NS:9 YES @@ -1722,8 +1923,9 @@ Apple URL pasteboard type NSFilenamesPboardType - {{293, 197}, {253, 20}} + {{293, 213}, {253, 20}} + _NS:9 YES @@ -1774,16 +1976,42 @@ 274 + + + 289 + {{219, -7}, {82, 32}} + + + + _NS:9 + YES + + 67108864 + 134217728 + Button + + _NS:9 + + -2038284288 + 129 + + + 200 + 25 + + NO + - 295 - {{175, 11}, {66, 17}} + -2147483353 + {{122, 11}, {45, 17}} - + + _NS:9 YES - 67108864 + 603979776 134217728 Delete All @@ -1804,14 +2032,15 @@ - 294 - {{11, 11}, {65, 17}} + -2147483354 + {{11, 11}, {44, 17}} + _NS:9 YES - 67108864 + 603979776 134217728 Save @@ -1828,14 +2057,15 @@ - 295 - {{89, 11}, {66, 17}} + -2147483353 + {{62, 11}, {45, 17}} + _NS:9 YES - 67108864 + 603979776 134217728 Load Plist @@ -1852,14 +2082,15 @@ - 295 - {{262, 11}, {66, 17}} + -2147483353 + {{183, 11}, {45, 17}} - + + _NS:9 YES - 67108864 + 603979776 134217728 Copy @@ -1876,16 +2107,17 @@ - 291 - {{338, 10}, {65, 17}} + -2147483357 + {{175, 10}, {105, 17}} - + + _NS:9 YES - 67108864 + 603979776 134217728 - New + Add Assets _NS:9 @@ -1910,8 +2142,9 @@ 256 - {419, 137} + {415, 153} + _NS:13 YES @@ -1921,8 +2154,9 @@ 256 - {419, 17} + {415, 17} + _NS:16 @@ -2006,7 +2240,7 @@ Todo - 204 + 200 200 1000 @@ -2072,7 +2306,7 @@ MC4wNzg5NTM1OTg0OCAwLjA3ODk1MzU5ODQ4IDAuMDc4OTUzNTk4NDgAA 27 - -1237319680 + -1235222528 3 @@ -2084,8 +2318,9 @@ 1 - {{0, 17}, {419, 137}} + {{0, 17}, {297, 153}} + _NS:11 @@ -2100,6 +2335,7 @@ -2147483392 {{-100, -100}, {15, 102}} + _NS:58 NO @@ -2113,8 +2349,10 @@ -2147483392 {{-100, -100}, {501, 16}} + _NS:60 + YES NO 1 @@ -2127,8 +2365,9 @@ - {419, 17} + {297, 17} + _NS:15 @@ -2136,11 +2375,12 @@ 4 - {{-1, 36}, {419, 154}} + {{-1, 36}, {297, 170}} + _NS:9 - 154128 + 154256 @@ -2151,15 +2391,17 @@ 1 - {417, 190} + {295, 206} + _NS:9 NSView - {{10, 33}, {417, 219}} + {{10, 33}, {295, 235}} + _NS:28 @@ -2178,22 +2420,25 @@ - {{153, 0}, {445, 273}} + {{213, 0}, {323, 289}} + _NS:9 NSView - {598, 273} + {536, 289} + YES 3 - {598, 273} + {536, 289} + {{0, 0}, {1360, 746}} @@ -2877,22 +3122,6 @@ 620 - - - webView - - - - 544 - - - - urlField - - - - 543 - shortcuts @@ -2941,6 +3170,38 @@ 693 + + + urlField + + + + 543 + + + + webView + + + + 544 + + + + selectAssets: + + + + 723 + + + + stdOutView + + + + 724 + takeStringURLFrom: @@ -2959,19 +3220,19 @@ - UIDelegate + frameLoadDelegate - 542 + 540 - frameLoadDelegate + UIDelegate - 540 + 542 @@ -3132,6 +3393,26 @@ 689 + + + target: self + + + + + + target: self + target + self + + NSSelectorName + selectAssets: + + 2 + + + 729 + @@ -4153,29 +4434,10 @@ 622 - - + - - 536 - - - - - 537 - - - - - - - - 538 - - - 623 @@ -4318,33 +4580,10 @@ + - - 644 - - - - - - - - 645 - - - - - - - - 646 - - - - - - 647 @@ -4364,19 +4603,6 @@ - - 649 - - - - - - - - 650 - - - 651 @@ -4461,9 +4687,79 @@ - 664 - - + 667 + + + assetsController + + + 720 + + + + + + + + + 719 + + + + + + + + + 536 + + + + + 537 + + + + + + + + 538 + + + + + 714 + + + + + + + + + + 717 + + + + + 716 + + + + + 715 + + + + + 645 + + + + + 665 @@ -4471,15 +4767,56 @@ - 666 - - + 649 + + + + + - 667 - - - assetsController + 650 + + + + + 644 + + + + + + + + 666 + + + + + 646 + + + + + + + + 664 + + + + + 725 + + + + + + + + 726 + + @@ -4676,7 +5013,15 @@ com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin @@ -4692,13 +5037,24 @@ - 705 + 729 AppDelegate NSObject + + selectAssets: + id + + + selectAssets: + + selectAssets: + id + + NSArrayController NSTableView @@ -4707,6 +5063,7 @@ NSPathControl NSAC NSTV + NSTextView NSTXTF WebView NSW @@ -4740,6 +5097,10 @@ shortcuts NSTV + + stdOutView + NSTextView + urlField NSTXTF @@ -4759,21 +5120,997 @@ - WebView + DKDrawableObject + NSObject + + id + id + id + id + id + id + id + id + id + id + id + + + + copyDrawingStyle: + id + + + lock: + id + + + lockLocation: + id + + + logDescription: + id + + + pasteDrawingStyle: + id + + + toggleClipToBBox: + id + + + toggleShowBBox: + id + + + toggleShowPartcodes: + id + + + toggleShowTargets: + id + + + unlock: + id + + + unlockLocation: + id + + + + IBProjectSource + ./Classes/DKDrawableObject.h + + + + DKDrawablePath + DKDrawableObject + + id + id + id + id + id + id + id + id + id + id + id + id + id + + + + addRandomNoise: + id + + + breakApart: + id + + + closePath: + id + + + convertToOutline: + id + + + convertToShape: + id + + + curveFit: + id + + + parallelCopy: + id + + + reversePath: + id + + + roughenPath: + id + + + smoothPath: + id + + + smoothPathMore: + id + + + toggleHorizontalFlip: + id + + + toggleVerticalFlip: + id + + + + IBProjectSource + ./Classes/DKDrawablePath.h + + + + DKDrawableShape + DKDrawableObject + + id + id + id + id + id + id + id + id + + + + convertToPath: + id + + + pastePath: + id + + + resetBoundingBox: + id + + + rotate: + id + + + setDistortMode: + id + + + toggleHorizontalFlip: + id + + + toggleVerticalFlip: + id + + + unrotate: + id + + + + IBProjectSource + ./Classes/DKDrawableShape.h + + + + DKDrawingView + GCZoomView + + id + id + + + + toggleRuler: + id + + + toggleShowPageBreaks: + id + + + + IBProjectSource + ./Classes/DKDrawingView.h + + + + DKLayer + NSObject + + id + id + id + id + id + id + id + id + + + + copy: + id + + + hideLayer: + id + + + lockLayer: + id + + + logDescription: + id + + + showLayer: + id + + + toggleLayerLock: + id + + + toggleLayerVisible: + id + + + unlockLayer: + id + + + + IBProjectSource + ./Classes/DKLayer.h + + + + DKObjectDrawingLayer + DKObjectOwnerLayer + + id + id + id + id + id + id + id + id + id + id + id + id + id + id + id + id + id + id + id + id + id + id + id + id + id + id + id + id + id + id + id + id + id + id + id + id + id + id + id + id + id + id + id + id + id + id + id + id + id + + + + alignBottomEdges: + id + + + alignEdgesToGrid: + id + + + alignHorizontalCentres: + id + + + alignLeftEdges: + id + + + alignLocationToGrid: + id + + + alignRightEdges: + id + + + alignTopEdges: + id + + + alignVerticalCentres: + id + + + applyStyle: + id + + + assignKeyObject: + id + + + clusterObjects: + id + + + combineSelectedObjects: + id + + + copy: + id + + + cut: + id + + + delete: + id + + + deleteBackward: + id + + + diffSelectedObjects: + id + + + distributeHorizontalCentres: + id + + + distributeHorizontalSpace: + id + + + distributeVerticalCentres: + id + + + distributeVerticalSpace: + id + + + divideSelectedObjects: + id + + + duplicate: + id + + + ghostObjects: + id + + + groupObjects: + id + + + hideObject: + id + + + intersectionSelectedObjects: + id + + + joinPaths: + id + + + lockObject: + id + + + moveDown: + id + + + moveLeft: + id + + + moveRight: + id + + + moveUp: + id + + + objectBringForward: + id + + + objectBringToFront: + id + + + objectSendBackward: + id + + + objectSendToBack: + id + + + paste: + id + + + revealHiddenObjects: + id + + + selectAll: + id + + + selectMatchingStyle: + id + + + selectNone: + id + + + selectOthers: + id + + + setBooleanOpsFittingPolicy: + id + + + showObject: + id + + + unghostObjects: + id + + + unionSelectedObjects: + id + + + unlockObject: + id + + + xorSelectedObjects: + id + + + + IBProjectSource + ./Classes/DKObjectDrawingLayer.h + + + + DKObjectOwnerLayer + DKLayer + + id + id + + + + toggleShowStorageDebuggingPath: + id + + + toggleSnapToObjects: + id + + + + IBProjectSource + ./Classes/DKObjectOwnerLayer.h + + + + DKTextPath + DKDrawablePath + + id + id + id + id + id + id + id + id + id + id + id + id + id + id + id + id + id + id + id + id + id + id + id + id + id + id + id + id + + + + alignCenter: + id + + + alignJustified: + id + + + alignLeft: + id + + + alignRight: + id + + + capitalize: + id + + + changeAttributes: + id + + + changeFont: + id + + + changeFontSize: + id + + + changeLayoutMode: + id + + + convertToPath: + id + + + convertToShape: + id + + + convertToShapeGroup: + id + + + convertToTextShape: + id + + + editText: + id + + + loosenKerning: + id + + + lowerBaseline: + id + + + paste: + id + + + raiseBaseline: + id + + + subscript: + id + + + superscript: + id + + + takeTextAlignmentFromSender: + id + + + takeTextVerticalAlignmentFromSender: + id + + + tightenKerning: + id + + + turnOffKerning: + id + + + underline: + id + + + unscript: + id + + + useStandardKerning: + id + + + verticalAlign: + id + + + + IBProjectSource + ./Classes/DKTextPath.h + + + + DKTextShape + DKDrawableShape + + id + id + id + id + id + id + id + id + id + id + id + id + id + id + id + id + id + id + id + id + id + id + id + id + id + id + id + id + + + + alignCenter: + id + + + alignJustified: + id + + + alignLeft: + id + + + alignRight: + id + + + capitalize: + id + + + changeAttributes: + id + + + changeFont: + id + + + changeFontSize: + id + + + changeLayoutMode: + id + + + convertToShape: + id + + + convertToShapeGroup: + id + + + convertToTextPath: + id + + + editText: + id + + + fitToText: + id + + + loosenKerning: + id + + + lowerBaseline: + id + + + paste: + id + + + raiseBaseline: + id + + + subscript: + id + + + superscript: + id + + + takeTextAlignmentFromSender: + id + + + takeTextVerticalAlignmentFromSender: + id + + + tightenKerning: + id + + + turnOffKerning: + id + + + underline: + id + + + unscript: + id + + + useStandardKerning: + id + + + verticalAlign: + id + + + + IBProjectSource + ./Classes/DKTextShape.h + + + + GCZoomView + NSView + + id + id + id + id + id + id + id + + + + zoomFitInWindow: + id + + + zoomIn: + id + + + zoomMax: + id + + + zoomMin: + id + + + zoomOut: + id + + + zoomToActualSize: + id + + + zoomToPercentageWithTag: + id + + + + IBProjectSource + ./Classes/GCZoomView.h + + + + NSObject + + id + id + id + id + + + + increment: + id + + + performActionFromLabel: + id + + + performActionFromSegmentLabel: + id + + + setFromSegmentLabel: + id + + + + IBProjectSource + ./Classes/NSObject.h + + + + NSTableView + + scrollToStickyRow: + id + + + scrollToStickyRow: + + scrollToStickyRow: + id + + + + IBProjectSource + ./Classes/NSTableView.h + + + + NSTextView + + id + id + id + + + + decrementFontSize: + id + + + increaseFontSize: + id + + + incrementFontSize: + id + + + + IBProjectSource + ./Classes/NSTextView.h + + + + RBLPopover + NSResponder - reloadFromOrigin: + performClose: id - reloadFromOrigin: + performClose: - reloadFromOrigin: + performClose: id IBProjectSource - ./Classes/WebView.h + ./Classes/RBLPopover.h diff --git a/Example/RoutingHTTPServer/multipart/indexUp.html b/Example/RoutingHTTPServer/multipart/indexUp.html new file mode 100644 index 0000000..b039194 --- /dev/null +++ b/Example/RoutingHTTPServer/multipart/indexUp.html @@ -0,0 +1,15 @@ + + + + + + +
+
+
+ +
+ + + + \ No newline at end of file diff --git a/Example/RoutingHTTPServer/multipart/upload.html b/Example/RoutingHTTPServer/multipart/upload.html new file mode 100644 index 0000000..96e8df7 --- /dev/null +++ b/Example/RoutingHTTPServer/multipart/upload.html @@ -0,0 +1,9 @@ + + + + + + + %MyFiles% + + \ No newline at end of file diff --git a/Example/RoutingHTTPServer/record.js.js b/Example/RoutingHTTPServer/record.js.js new file mode 100644 index 0000000..484f8cf --- /dev/null +++ b/Example/RoutingHTTPServer/record.js.js @@ -0,0 +1,996 @@ +var Recorder = { +version: 1.13, +swfObject: null, +_callbacks: {}, +_events: {}, +_initialized: false, +_flashBlockCatched: false, +options: {}, +initialize: function(options) { + this.options = options || {}; + + if (window.location.protocol === 'file:') { + throw new Error('Due to Adobe Flash restrictions it is not possible to use the Recorder through the file:// protocol. Please use an http server.'); + } + + if (!this.options.flashContainer) { + this._setupFlashContainer(); + } + + this.bind('initialized', function() { + Recorder._initialized = true; + if (Recorder._flashBlockCatched) { + Recorder._defaultOnHideFlash(); + } + if (options.initialized) { + options.initialized(); + } + }); + + this.bind('showFlash', this.options.onFlashSecurity || this._defaultOnShowFlash); + this._loadFlash(); +}, + +clear: function() { + Recorder._events = {}; +}, + +record: function(options) { + options = options || {}; + this.clearBindings('recordingStart'); + this.clearBindings('recordingProgress'); + this.clearBindings('recordingCancel'); + + this.bind('recordingStart', this._defaultOnHideFlash); + this.bind('recordingCancel', this._defaultOnHideFlash); + // reload flash to allow mic permission dialog to show again + this.bind('recordingCancel', this._loadFlash); + + this.bind('recordingStart', options['start']); + this.bind('recordingProgress', options['progress']); + this.bind('recordingCancel', options['cancel']); + + this.flashInterface().record(); +}, + +stop: function() { + return this.flashInterface()._stop(); +}, + +play: function(options) { + options = options || {}; + this.clearBindings('playingProgress'); + this.bind('playingProgress', options['progress']); + this.bind('playingStop', options['finished']); + + this.flashInterface()._play(); +}, + +upload: function(options) { + options.audioParam = options.audioParam || 'audio'; + options.params = options.params || {}; + this.clearBindings('uploadSuccess'); + this.bind('uploadSuccess', function(responseText) { + options.success(Recorder._externalInterfaceDecode(responseText)); + }); + + this.flashInterface().upload(options.url, options.audioParam, options.params); +}, + +audioData: function(newData) { + var delimiter = ';', + newDataSerialized, stringData, data = [], + sample; + if (newData) { + newDataSerialized = newData.join(';'); + } + stringData = this.flashInterface().audioData(newDataSerialized).split(delimiter); + for (var i = 0; i < stringData.length; i++) { + sample = parseFloat(stringData[i]); + if (!isNaN(sample)) { + data.push(sample); + } + } + return data; +}, + +request: function(method, uri, contentType, data, callback) { + var callbackName = this.registerCallback(callback); + this.flashInterface().request(method, uri, contentType, data, callbackName); +}, + +clearBindings: function(eventName) { + Recorder._events[eventName] = []; +}, + +bind: function(eventName, fn) { + if (!Recorder._events[eventName]) { + Recorder._events[eventName] = [] + } + Recorder._events[eventName].push(fn); +}, + +triggerEvent: function(eventName, arg0, arg1) { + Recorder._executeInWindowContext(function() { + if (!Recorder._events[eventName]) { + return; + } + for (var i = 0, len = Recorder._events[eventName].length; i < len; i++) { + if (Recorder._events[eventName][i]) { + Recorder._events[eventName][i].apply(Recorder, [arg0, arg1]); + } + } + }); +}, + +triggerCallback: function(name, args) { + Recorder._executeInWindowContext(function() { + Recorder._callbacks[name].apply(null, args); + }); +}, + +registerCallback: function(fn) { + var name = 'CB' + parseInt(Math.random() * 999999, 10); + Recorder._callbacks[name] = fn; + return name; +}, + +flashInterface: function() { + if (!this.swfObject) { + return null; + } else if (this.swfObject.record) { + return this.swfObject; + } else if (this.swfObject.children[3].record) { + return this.swfObject.children[3]; + } +}, + +_executeInWindowContext: function(fn) { + window.setTimeout(fn, 1); +}, + +_setupFlashContainer: function() { + this.options.flashContainer = document.createElement('div'); + this.options.flashContainer.setAttribute('id', 'recorderFlashContainer'); + this.options.flashContainer.setAttribute('style', 'position: fixed; left: -9999px; top: -9999px; width: 230px; height: 140px; margin-left: 10px; border-top: 6px solid rgba(128, 128, 128, 0.6); border-bottom: 6px solid rgba(128, 128, 128, 0.6); border-radius: 5px 5px; padding-bottom: 1px; padding-right: 1px;'); + document.body.appendChild(this.options.flashContainer); +}, + +_clearFlash: function() { + var flashElement = this.options.flashContainer.children[0]; + if (flashElement) { + this.options.flashContainer.removeChild(flashElement); + } +}, + +_loadFlash: function() { + this._clearFlash(); + var flashElement = document.createElement('div'); + flashElement.setAttribute('id', 'recorderFlashObject'); + this.options.flashContainer.appendChild(flashElement); + swfobject.embedSWF(this.options.swfSrc, 'recorderFlashObject', '231', '141', '10.1.0', undefined, undefined, { + allowscriptaccess: 'always' + }, undefined, function(e) { + if (e.success) { + Recorder.swfObject = e.ref; + Recorder._checkForFlashBlock(); + } else { + Recorder._showFlashRequiredDialog(); + } + }); +}, + +_defaultOnShowFlash: function() { + var flashContainer = Recorder.options.flashContainer; + flashContainer.style.left = ((window.innerWidth || document.body.offsetWidth) / 2) - 115 + 'px'; + flashContainer.style.top = ((window.innerHeight || document.body.offsetHeight) / 2) - 70 + 'px'; +}, + +_defaultOnHideFlash: function() { + var flashContainer = Recorder.options.flashContainer; + flashContainer.style.left = '-9999px'; + flashContainer.style.top = '-9999px'; +}, + +_checkForFlashBlock: function() { + window.setTimeout(function() { + if (!Recorder._initialized) { + Recorder._flashBlockCatched = true; + Recorder.triggerEvent('showFlash'); + } + }, 500); +}, + +_showFlashRequiredDialog: function() { + Recorder.options.flashContainer.innerHTML = '

Adobe Flash Player 10.1 or newer is required to use this feature.

Get it on Adobe.com.

'; + Recorder.options.flashContainer.style.color = 'white'; + Recorder.options.flashContainer.style.backgroundColor = '#777'; + Recorder.options.flashContainer.style.textAlign = 'center'; + Recorder.triggerEvent('showFlash'); +}, + +_externalInterfaceDecode: function(data) { + return data.replace(/%22/g, '\'').replace(/%5c/g, '\\').replace(/%26/g, '&').replace(/%25/g, '%'); +} +}; + + +if (swfobject == undefined) { /* SWFObject v2.2 is released under the MIT License 0) { + for (var af = 0; af < ag; af++) { + var Y = o[af].id; + var ab = o[af].callbackFn; + var aa = { + success: false, + id: Y + }; + if (M.pv[0] > 0) { + var ae = c(Y); + if (ae) { + if (F(o[af].swfVersion) && !(M.wk && M.wk < 312)) { + w(Y, true); + if (ab) { + aa.success = true; + aa.ref = z(Y); + ab(aa) + } + } else { + if (o[af].expressInstall && A()) { + var ai = {}; + ai.data = o[af].expressInstall; + ai.width = ae.getAttribute('width') || '0'; + ai.height = ae.getAttribute('height') || '0'; + if (ae.getAttribute('class')) { + ai.styleclass = ae.getAttribute('class') + } + if (ae.getAttribute('align')) { + ai.align = ae.getAttribute('align') + } + var ah = {}; + var X = ae.getElementsByTagName('param'); + var ac = X.length; + for (var ad = 0; ad < ac; ad++) { + if (X[ad].getAttribute('name').toLowerCase() != 'movie') { + ah[X[ad].getAttribute('name')] = X[ad].getAttribute('value') + } + } + P(ai, ah, Y, ab) + } else { + p(ae); + if (ab) { + ab(aa) + } + } + } + } + } else { + w(Y, true); + if (ab) { + var Z = z(Y); + if (Z && typeof Z.SetVariable != D) { + aa.success = true; + aa.ref = Z + } + ab(aa) + } + } + } + } + } + function z(aa) { + var X = null; + var Y = c(aa); + if (Y && Y.nodeName == 'OBJECT') { + if (typeof Y.SetVariable != D) { + X = Y + } else { + var Z = Y.getElementsByTagName(r)[0]; + if (Z) { + X = Z + } + } + } + return X + } + function A() { + return !a && F('6.0.65') && (M.win || M.mac) && !(M.wk && M.wk < 312) + } + function P(aa, ab, X, Z) { + a = true; + E = Z || null; + B = { + success: false, + id: X + }; + var ae = c(X); + if (ae) { + if (ae.nodeName == 'OBJECT') { + l = g(ae); + Q = null + } else { + l = ae; + Q = X + } + aa.id = R; + if (typeof aa.width == D || (!/%$/.test(aa.width) && parseInt(aa.width, 10) < 310)) { + aa.width = '310' + } + if (typeof aa.height == D || (!/%$/.test(aa.height) && parseInt(aa.height, 10) < 137)) { + aa.height = '137' + } + j.title = j.title.slice(0, 47) + ' - Flash Player Installation'; + var ad = M.ie && M.win ? 'ActiveX' : 'PlugIn', + ac = 'MMredirectURL=' + encodeURI(O.location).toString().replace(/&/g, '%26') + '&MMplayerType=' + ad + '&MMdoctitle=' + j.title; + if (typeof ab.flashvars != D) { + ab.flashvars += '&' + ac + } else { + ab.flashvars = ac + } + if (M.ie && M.win && ae.readyState != 4) { + var Y = C('div'); + X += 'SWFObjectNew'; + Y.setAttribute('id', X); + ae.parentNode.insertBefore(Y, ae); + ae.style.display = 'none'; + (function() { + if (ae.readyState == 4) { + ae.parentNode.removeChild(ae) + } else { + setTimeout(arguments.callee, 10) + } + })() + } + u(aa, ab, X) + } + } + function p(Y) { + if (M.ie && M.win && Y.readyState != 4) { + var X = C('div'); + Y.parentNode.insertBefore(X, Y); + X.parentNode.replaceChild(g(Y), X); + Y.style.display = 'none'; + (function() { + if (Y.readyState == 4) { + Y.parentNode.removeChild(Y) + } else { + setTimeout(arguments.callee, 10) + } + })() + } else { + Y.parentNode.replaceChild(g(Y), Y) + } + } + function g(ab) { + var aa = C('div'); + if (M.win && M.ie) { + aa.innerHTML = ab.innerHTML + } else { + var Y = ab.getElementsByTagName(r)[0]; + if (Y) { + var ad = Y.childNodes; + if (ad) { + var X = ad.length; + for (var Z = 0; Z < X; Z++) { + if (!(ad[Z].nodeType == 1 && ad[Z].nodeName == 'PARAM') && !(ad[Z].nodeType == 8)) { + aa.appendChild(ad[Z].cloneNode(true)) + } + } + } + } + } + return aa + } + function u(ai, ag, Y) { + var X, aa = c(Y); + if (M.wk && M.wk < 312) { + return X + } + if (aa) { + if (typeof ai.id == D) { + ai.id = Y + } + if (M.ie && M.win) { + var ah = ''; + for (var ae in ai) { + if (ai[ae] != Object.prototype[ae]) { + if (ae.toLowerCase() == 'data') { + ag.movie = ai[ae] + } else { + if (ae.toLowerCase() == 'styleclass') { + ah += ' class=''+ai[ae]+''' + } else { + if (ae.toLowerCase() != 'classid') { + ah += ' ' + ae + '=''+ai[ae]+''' + } + } + } + } + } + var af = ''; + for (var ad in ag) { + if (ag[ad] != Object.prototype[ad]) { + af += '' + } + } + aa.outerHTML = '' + af + ''; + N[N.length] = ai.id; + X = c(ai.id) + } else { + var Z = C(r); + Z.setAttribute('type', q); + for (var ac in ai) { + if (ai[ac] != Object.prototype[ac]) { + if (ac.toLowerCase() == 'styleclass') { + Z.setAttribute('class', ai[ac]) + } else { + if (ac.toLowerCase() != 'classid') { + Z.setAttribute(ac, ai[ac]) + } + } + } + } + for (var ab in ag) { + if (ag[ab] != Object.prototype[ab] && ab.toLowerCase() != 'movie') { + e(Z, ab, ag[ab]) + } + } + aa.parentNode.replaceChild(Z, aa); + X = Z + } + } + return X + } + function e(Z, X, Y) { + var aa = C('param'); + aa.setAttribute('name', X); + aa.setAttribute('value', Y); + Z.appendChild(aa) + } + function y(Y) { + var X = c(Y); + if (X && X.nodeName == 'OBJECT') { + if (M.ie && M.win) { + X.style.display = 'none'; + (function() { + if (X.readyState == 4) { + b(Y) + } else { + setTimeout(arguments.callee, 10) + } + })() + } else { + X.parentNode.removeChild(X) + } + } + } + function b(Z) { + var Y = c(Z); + if (Y) { + for (var X in Y) { + if (typeof Y[X] == 'function') { + Y[X] = null + } + } + Y.parentNode.removeChild(Y) + } + } + function c(Z) { + var X = null; + try { + X = j.getElementById(Z) + } catch (Y) {} + return X + } + function C(X) { + return j.createElement(X) + } + function i(Z, X, Y) { + Z.attachEvent(X, Y); + I[I.length] = [Z, X, Y] + } + function F(Z) { + var Y = M.pv, + X = Z.split('.'); + X[0] = parseInt(X[0], 10); + X[1] = parseInt(X[1], 10) || 0; + X[2] = parseInt(X[2], 10) || 0; + return (Y[0] > X[0] || (Y[0] == X[0] && Y[1] > X[1]) || (Y[0] == X[0] && Y[1] == X[1] && Y[2] >= X[2])) ? true : false + } + function v(ac, Y, ad, ab) { + if (M.ie && M.mac) { + return + } + var aa = j.getElementsByTagName('head')[0]; + if (!aa) { + return + } + var X = (ad && typeof ad == 'string') ? ad : 'screen'; + if (ab) { + n = null; + G = null + } + if (!n || G != X) { + var Z = C('style'); + Z.setAttribute('type', 'text/css'); + Z.setAttribute('media', X); + n = aa.appendChild(Z); + if (M.ie && M.win && typeof j.styleSheets != D && j.styleSheets.length > 0) { + n = j.styleSheets[j.styleSheets.length - 1] + } + G = X + } + if (M.ie && M.win) { + if (n && typeof n.addRule == r) { + n.addRule(ac, Y) + } + } else { + if (n && typeof j.createTextNode != D) { + n.appendChild(j.createTextNode(ac + ' {' + Y + '}')) + } + } + } + function w(Z, X) { + if (!m) { + return + } + var Y = X ? 'visible' : 'hidden'; + if (J && c(Z)) { + c(Z).style.visibility = Y + } else { + v('#' + Z, 'visibility:' + Y) + } + } + function L(Y) { + var Z = /[\\\'<>\.;]/; + var X = Z.exec(Y) != null; + return X && typeof encodeURIComponent != D ? encodeURIComponent(Y) : Y + } + var d = function() { + if (M.ie && M.win) { + window.attachEvent('onunload', function() { + var ac = I.length; + for (var ab = 0; ab < ac; ab++) { + I[ab][0].detachEvent(I[ab][1], I[ab][2]) + } + var Z = N.length; + for (var aa = 0; aa < Z; aa++) { + y(N[aa]) + } + for (var Y in M) { + M[Y] = null + } + M = null; + for (var X in swfobject) { + swfobject[X] = null + } + swfobject = null + }) + } + }(); + return { + registerObject: function(ab, X, aa, Z) { + if (M.w3 && ab && X) { + var Y = {}; + Y.id = ab; + Y.swfVersion = X; + Y.expressInstall = aa; + Y.callbackFn = Z; + o[o.length] = Y; + w(ab, false) + } else { + if (Z) { + Z({ + success: false, + id: ab + }) + } + } + }, + getObjectById: function(X) { + if (M.w3) { + return z(X) + } + }, + embedSWF: function(ab, ah, ae, ag, Y, aa, Z, ad, af, ac) { + var X = { + success: false, + id: ah + }; + if (M.w3 && !(M.wk && M.wk < 312) && ab && ah && ae && ag && Y) { + w(ah, false); + K(function() { + ae += ''; + ag += ''; + var aj = {}; + if (af && typeof af === r) { + for (var al in af) { + aj[al] = af[al] + } + } + aj.data = ab; + aj.width = ae; + aj.height = ag; + var am = {}; + if (ad && typeof ad === r) { + for (var ak in ad) { + am[ak] = ad[ak] + } + } + if (Z && typeof Z === r) { + for (var ai in Z) { + if (typeof am.flashvars != D) { + am.flashvars += '&' + ai + '=' + Z[ai] + } else { + am.flashvars = ai + '=' + Z[ai] + } + } + } + if (F(Y)) { + var an = u(aj, am, ah); + if (aj.id == ah) { + w(ah, true) + } + X.success = true; + X.ref = an + } else { + if (aa && A()) { + aj.data = aa; + P(aj, am, ah, ac); + return + } else { + w(ah, true) + } + } + if (ac) { + ac(X) + } + }) + } else { + if (ac) { + ac(X) + } + } + }, + switchOffAutoHideShow: function() { + m = false + }, + ua: M, + getFlashPlayerVersion: function() { + return { + major: M.pv[0], + minor: M.pv[1], + release: M.pv[2] + } + }, + hasFlashPlayerVersion: F, + createSWF: function(Z, Y, X) { + if (M.w3) { + return u(Z, Y, X) + } else { + return undefined + } + }, + showExpressInstall: function(Z, aa, X, Y) { + if (M.w3 && A()) { + P(Z, aa, X, Y) + } + }, + removeSWF: function(X) { + if (M.w3) { + y(X) + } + }, + createCSS: function(aa, Z, Y, X) { + if (M.w3) { + v(aa, Z, Y, X) + } + }, + addDomLoadEvent: K, + addLoadEvent: s, + getQueryParamValue: function(aa) { + var Z = j.location.search || j.location.hash; + if (Z) { + if (/\?/.test(Z)) { + Z = Z.split('?')[1] + } + if (aa == null) { + return L(Z) + } + var Y = Z.split('&'); + for (var X = 0; X < Y.length; X++) { + if (Y[X].substring(0, Y[X].indexOf('=')) == aa) { + return L(Y[X].substring((Y[X].indexOf('=') + 1))) + } + } + } + return '' + }, + expressInstallCallback: function() { + if (a) { + var X = c(R); + if (X && l) { + X.parentNode.replaceChild(l, X); + if (Q) { + w(Q, true); + if (M.ie && M.win) { + l.style.display = 'block' + } + } + if (E) { + E(B) + } + } + a = false + } + } + } + }(); + } + + + + function timecode(ms) { + var hms = { + h: Math.floor(ms / (60 * 60 * 1000)), + m: Math.floor((ms / 60000) % 60), + s: Math.floor((ms / 1000) % 60) + }; + var tc = []; // Timecode array to be joined with '.' + if (hms.h > 0) { + tc.push(hms.h); + } + tc.push((hms.m < 10 && hms.h > 0 ? '0' + hms.m : hms.m)); + tc.push((hms.s < 10 ? '0' + hms.s : hms.s)); + return tc.join(':'); + } + + + Recorder.initialize({ + swfSrc: '../recorder.swf' + }); + + function record() { + Recorder.record({ + start: function() { + //alert('recording starts now. press stop when youre done. and then play or upload if you want.'); + }, + progress: function(milliseconds) { + document.getElementById('time').innerHTML = timecode(milliseconds); + } + }); + } + + function play() { + Recorder.stop(); + Recorder.play({ + progress: function(milliseconds) { + document.getElementById('time').innerHTML = timecode(milliseconds); + } + }); + } + + function stop() { + Recorder.stop(); + } + + function upload() { + SC.connect({ + client_id: 'aHT5HITYm8vlkWNabL0Qg', + redirect_uri: 'http://recorder.dev/examples/soundcloud-callback.html', + connected: function() { + Recorder.upload({ + url: 'https://api.soundcloud.com/tracks.json?oauth_token=' + SC.options.access_token, + audioParam: 'track[asset_data]', + params: { + 'track[title]': 'recorder.js track test', + 'track[sharing]': 'private' + }, + success: function(responseText) { + var track = $.parseJSON(responseText) + window.location = track.permalink_url; + } + }); + }, + error: function(err) { + alert(err) + } + }) + } diff --git a/External/.DS_Store b/External/.DS_Store deleted file mode 100644 index 716a6e3..0000000 Binary files a/External/.DS_Store and /dev/null differ diff --git a/Source/RoutingConnection.h b/Source/RoutingConnection.h index 3864259..1fbe6ed 100644 --- a/Source/RoutingConnection.h +++ b/Source/RoutingConnection.h @@ -1,6 +1,26 @@ #import #import "HTTPConnection.h" + +#import "AppDelegate.h" + +#define UPLOAD_FILE_PROGRESS @"uploadfileprogress" + + @class RoutingHTTPServer; +@class MultipartFormDataParser; + @interface RoutingConnection : HTTPConnection +{ + MultipartFormDataParser* parser; + NSFileHandle* storeFile; + NSMutableArray* uploadedFiles; + +// int dataStartIndex; +// NSMutableArray *multipartData; +// BOOL postHeaderOK; +} +//- (BOOL) isBeginOfOctetStream; + + @end diff --git a/Source/RoutingConnection.m b/Source/RoutingConnection.m index 376130e..a1d96ff 100644 --- a/Source/RoutingConnection.m +++ b/Source/RoutingConnection.m @@ -1,3 +1,17 @@ + +#import "HTTPMessage.h" +#import "HTTPDataResponse.h" +#import "DDNumber.h" +#import "HTTPLogging.h" + +#import "MultipartFormDataParser.h" +#import "MultipartMessageHeaderField.h" +#import "HTTPDynamicFileResponse.h" +#import "HTTPFileResponse.h" +///MULTIPART ABOVE + + + #import "RoutingConnection.h" #import "RoutingHTTPServer.h" #import "HTTPMessage.h" @@ -35,13 +49,14 @@ - (BOOL)shouldHandleRequestForMethod:(NSString *)method atPath:(NSString *)path return YES; } +/* - (void)processBodyData:(NSData *)postDataChunk { BOOL result = [request appendData:postDataChunk]; if (!result) { // TODO: Log } } - +*/ - (NSObject *)httpResponseForMethod:(NSString *)method URI:(NSString *)path { NSURL *url = [request url]; NSString *query = nil; @@ -134,4 +149,320 @@ - (BOOL)shouldDie { return shouldDie; } + +/** + //扩展HTTPServer支持的请求类型,默认支持GET,HEAD,不支持POST +- (BOOL)supportsMethod:(NSString *)method atPath:(NSString *)relativePath +{ + if ([@"POST" isEqualToString:method]) + { + return YES; + } + return [super supportsMethod:method atPath:relativePath]; +} + + //处量返回的response数据 +- (NSObject *)httpResponseForMethod:(NSString *)method URI:(NSString *)path +{ + return [super httpResponseForMethod:method URI:path]; +} + + + //处理POST请求提交的数据流(下面方法是改自 Andrew Davidson的类) +- (void)processDataChunk:(NSData *)postDataChunk +{ + NSLog(@"processDataChunk function called"); + //multipartData初始化不放在init函数中, 当前类似乎不经init函数初始化 + if (multipartData == nil) { + multipartData = [[NSMutableArray alloc] init]; + } + + //处理multipart/form-data的POST请求中Body数据集中的表单值域并创建文件 + if (!postHeaderOK) + { + //0x0A0D: 换行符 + UInt16 separatorBytes = 0x0A0D; + NSData* separatorData = [NSData dataWithBytes:&separatorBytes length:2]; + + int l = [separatorData length]; + for (int i = 0; i < [postDataChunk length] - l; i++) + { + //每次取两个字节 比对下看看是否是换行 + NSRange searchRange = {i, l}; + //如果是换行符则进行如下处理 + if ([[postDataChunk subdataWithRange:searchRange] isEqualToData:separatorData]) + { + //获取dataStartIndex标识的上一个换行位置到当前换行符之间的数据的Range + NSRange newDataRange = {dataStartIndex, i - dataStartIndex}; + //dataStartIndex标识的上一个换行位置到移到当前换行符位置 + dataStartIndex = i + l; + i += l - 1; + //获取dataStartIndex标识的上一个换行位置到当前换行符之间的数据 + NSData *newData = [postDataChunk subdataWithRange:newDataRange]; + //如果newData不为空或还没有处理完multipart/form-data中表单变量值域则继续处理剩下的表单值域数据 + if ([newData length] || ![self isBeginOfOctetStream]) + { + if ([newData length]) { + [multipartData addObject:newData]; + } + } + else + { + //将标识处理完multipart/form-data中表单变量值域的postHeaderOK变量设置为TRUE; + postHeaderOK = TRUE; + //这里暂时写成硬编码 弊端:每次增加表单变量都要改这里的数值 + //获取Content-Disposition: form-data; name="xxx"; filename="xxx" + NSString* postInfo = [[NSString alloc] initWithBytes:[[multipartData objectAtIndex:4] bytes] + length:[[multipartData objectAtIndex:4] length] + encoding:NSUTF8StringEncoding]; + NSLog(@"postInfo is:%@", postInfo); + NSArray* postInfoComponents = [postInfo componentsSeparatedByString:@"; filename="]; + postInfoComponents = [[postInfoComponents lastObject] componentsSeparatedByString:@"\""]; + NSLog(@"postInfoComponents0 :%@",postInfoComponents); + if ([postInfoComponents count]<2) + { + return; + } + + postInfoComponents = [[postInfoComponents objectAtIndex:1] componentsSeparatedByString:@"\\"]; + NSString *documentPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0]; + NSString* filename = [documentPath stringByAppendingPathComponent:[postInfoComponents lastObject]]; + NSLog(@"filename :%@",filename); + NSRange fileDataRange = {dataStartIndex, [postDataChunk length] - dataStartIndex}; + [[NSFileManager defaultManager] createFileAtPath:filename contents:[postDataChunk subdataWithRange:fileDataRange] attributes:nil]; + NSFileHandle *file = [NSFileHandle fileHandleForUpdatingAtPath:filename];// retain]; + if (file) + { + [file seekToEndOfFile]; + [multipartData addObject:file]; + } + +// [postInfo release]; + break; + } + } + } + } + else //表单值域已经处理过了 这之后的数据全是文件数据流 + { + [(NSFileHandle*)[multipartData lastObject] writeData:postDataChunk]; + } + + float uploadProgress = (double)requestContentLengthReceived / requestContentLength; + //实际应用时 当前类的实例是相当于单例一样被引用(因为只被实例化一次) + if (uploadProgress >= 1.0) { + postHeaderOK = NO; +// [multipartData release]; + multipartData = nil; + } + [[NSNotificationCenter defaultCenter] postNotificationName:UPLOAD_FILE_PROGRESS object:[NSNumber numberWithFloat:uploadProgress] userInfo:nil]; +} + + +//检查是否已经处理完了multipart/form-data表单中的表单变量 +- (BOOL) isBeginOfOctetStream +{ + NSString *octetStreamFlag = @"Content-Type: application/octet-stream"; + NSString *findData = [[NSString alloc] initWithData:(NSData *)[multipartData lastObject] encoding:NSUTF8StringEncoding]; + + for (NSData *d in multipartData) { + NSString *temp = [NSString.alloc initWithData:d encoding:NSUTF8StringEncoding];// autorelease] ; + NSLog(@"multipartData items: %@", temp); + } + //如果已经处理完了multipart/form-data表单中的表单变量 + if ( findData != nil && [findData length] > 0 ) + { + NSLog(@"findData is :%@\n octetStreamFlag is :%@", findData, octetStreamFlag); + if ([octetStreamFlag isEqualToString:findData]) { + NSLog(@"multipart/form-data 变量值域数据处理完毕"); +// [findData release]; + return YES; + } +// [findData release]; + return NO; + } + return NO; +} + + + +*/ +// Log levels : off, error, warn, info, verbose +// Other flags: trace +static const int httpLogLevel = HTTP_LOG_LEVEL_VERBOSE; // | HTTP_LOG_FLAG_TRACE; + + +/** + * All we have to do is override appropriate methods in HTTPConnection. + **/ + +//@implementation MyHTTPConnection + +//- (BOOL)supportsMethod:(NSString *)method atPath:(NSString *)path +//{ +// HTTPLogTrace(); +// +// // Add support for POST +// +// if ([method isEqualToString:@"POST"]) +// { +// if ([path isEqualToString:@"/upload.html"]) +// { +// return YES; +// } +// } +// +// return [super supportsMethod:method atPath:path]; +//} + +- (BOOL)expectsRequestBodyFromMethod:(NSString *)method atPath:(NSString *)path +{ + HTTPLogTrace(); + + // Inform HTTP server that we expect a body to accompany a POST request + + if([method isEqualToString:@"POST"] && [path isEqualToString:@"/upload.html"]) { + // here we need to make sure, boundary is set in header + NSString* contentType = [request headerField:@"Content-Type"]; + NSLog(@"%@, %@, %@", request.body, request.allHeaderFields, request);//req.connection ); + + int paramsSeparator = [contentType rangeOfString:@";"].location; + if( NSNotFound == paramsSeparator ) { + return NO; + } + if( paramsSeparator >= contentType.length - 1 ) { + return NO; + } + NSString* type = [contentType substringToIndex:paramsSeparator]; + if( ![type isEqualToString:@"multipart/form-data"] ) { + // we expect multipart/form-data content type + return NO; + } + + // enumerate all params in content-type, and find boundary there + NSArray* params = [[contentType substringFromIndex:paramsSeparator + 1] componentsSeparatedByString:@";"]; + for( NSString* param in params ) { + paramsSeparator = [param rangeOfString:@"="].location; + if( (NSNotFound == paramsSeparator) || paramsSeparator >= param.length - 1 ) { + continue; + } + NSString* paramName = [param substringWithRange:NSMakeRange(1, paramsSeparator-1)]; + NSString* paramValue = [param substringFromIndex:paramsSeparator+1]; + + if( [paramName isEqualToString: @"boundary"] ) { + // let's separate the boundary from content-type, to make it more handy to handle + [request setHeaderField:@"boundary" value:paramValue]; + } + } + // check if boundary specified + if( nil == [request headerField:@"boundary"] ) { + return NO; + } + return YES; + } + return [super expectsRequestBodyFromMethod:method atPath:path]; +} +/* +- (NSObject *)httpResponseForMethod:(NSString *)method URI:(NSString *)path +{ + HTTPLogTrace(); + + if ([method isEqualToString:@"POST"] && [path isEqualToString:@"/upload.html"]) + { + + // this method will generate response with links to uploaded file + NSMutableString* filesStr = [[NSMutableString alloc] init]; + + for( NSString* filePath in uploadedFiles ) { + //generate links + [filesStr appendFormat:@" %@
",filePath, [filePath lastPathComponent]]; + } + NSString* templatePath = [[config documentRoot] stringByAppendingPathComponent:@"upload.html"]; + NSDictionary* replacementDict = [NSDictionary dictionaryWithObject:filesStr forKey:@"MyFiles"]; + // use dynamic file response to apply our links to response template + return [[HTTPDynamicFileResponse alloc] initWithFilePath:templatePath forConnection:self separator:@"%" replacementDictionary:replacementDict]; + } + if( [method isEqualToString:@"GET"] && [path hasPrefix:@"/upload/"] ) { + // let download the uploaded files + return [[HTTPFileResponse alloc] initWithFilePath: [[config documentRoot] stringByAppendingString:path] forConnection:self]; + } + + return [super httpResponseForMethod:method URI:path]; +} +*/ +- (void)prepareForBodyWithSize:(UInt64)contentLength +{ + HTTPLogTrace(); + + // set up mime parser + NSString* boundary = [request headerField:@"boundary"]; + parser = [[MultipartFormDataParser alloc] initWithBoundary:boundary formEncoding:NSUTF8StringEncoding]; + parser.delegate = self; + + uploadedFiles = [[NSMutableArray alloc] init]; +} + + +- (void)processBodyData:(NSData *)postDataChunk +{ + HTTPLogTrace(); + // append data to the parser. It will invoke callbacks to let us handle + // parsed data. + [parser appendData:postDataChunk]; +} + + +//----------------------------------------------------------------- +#pragma mark multipart form data parser delegate + + +- (void) processStartOfPartWithHeader:(MultipartMessageHeader*) header { + // in this sample, we are not interested in parts, other then file parts. + // check content disposition to find out filename + + MultipartMessageHeaderField* disposition = [header.fields objectForKey:@"Content-Disposition"]; + NSString* filename = [[disposition.params objectForKey:@"filename"] lastPathComponent]; + + if ( (nil == filename) || [filename isEqualToString: @""] ) { + // it's either not a file part, or + // an empty form sent. we won't handle it. + return; + } + NSString* uploadDirPath = [NSTemporaryDirectory() withPath:NSS.UUIDString]; + //[[config documentRoot] stringByAppendingPathComponent:@"upload"]; + + + BOOL isDir = YES; + if (![[NSFileManager defaultManager]fileExistsAtPath:uploadDirPath isDirectory:&isDir ]) { + [[NSFileManager defaultManager]createDirectoryAtPath:uploadDirPath withIntermediateDirectories:YES attributes:nil error:nil]; + } + + NSString* filePath = [uploadDirPath stringByAppendingPathComponent: filename]; + if( [[NSFileManager defaultManager] fileExistsAtPath:filePath] ) { + storeFile = nil; + } + else { + HTTPLogVerbose(@"Saving file to %@", filePath); + [[NSFileManager defaultManager] createFileAtPath:filePath contents:nil attributes:nil]; + storeFile = [NSFileHandle fileHandleForWritingAtPath:filePath]; + [uploadedFiles addObject: [NSString stringWithFormat:@"/upload/%@", filename]]; + } +} + + +- (void) processContent:(NSData*) data WithHeader:(MultipartMessageHeader*) header +{ + // here we just write the output from parser to the file. + if( storeFile ) { + [storeFile writeData:data]; + } +} + +- (void) processEndOfPartWithHeader:(MultipartMessageHeader*) header +{ + // as the file part is over, we close the file. + [storeFile closeFile]; + storeFile = nil; +} + @end