diff options
author | Abhi Beckert <abhi@abhibeckert.com> | 2017-08-04 13:20:12 +1000 |
---|---|---|
committer | Abhi Beckert <abhi@abhibeckert.com> | 2017-08-04 13:20:12 +1000 |
commit | ebf7d8b7db4144d304bf2224db19d787d631eda0 (patch) | |
tree | 5b1481d8ded07101891b3acce80b385a204f1ef8 /Source/SPDataAdditions.m | |
parent | ff1db69283f69b8e9dc7fc373db242c37698c7c2 (diff) | |
parent | 1cbc8f7ca081a6538a2df484d89723cf441acb3c (diff) | |
download | sequelpro-ebf7d8b7db4144d304bf2224db19d787d631eda0.tar.gz sequelpro-ebf7d8b7db4144d304bf2224db19d787d631eda0.tar.bz2 sequelpro-ebf7d8b7db4144d304bf2224db19d787d631eda0.zip |
Merge remote-tracking branch 'sequelpro/master'
Diffstat (limited to 'Source/SPDataAdditions.m')
-rw-r--r-- | Source/SPDataAdditions.m | 134 |
1 files changed, 134 insertions, 0 deletions
diff --git a/Source/SPDataAdditions.m b/Source/SPDataAdditions.m index 53d18274..19539cd0 100644 --- a/Source/SPDataAdditions.m +++ b/Source/SPDataAdditions.m @@ -344,6 +344,140 @@ uint32_t LimitUInt32(NSUInteger i); } /** + * Returns the integer value for a single hex-encoded nibble or -1 for invalid values. + * Supported characters: 0-9,a-f,A-F + * + * Note: You usually would call this method like ((hexchar2nibble(highByte) << 4) + hexchar2nibble(lowByte)) to decode a single hex-encoded byte. + */ +static int hexchar2nibble(char c) +{ + if (c >= '0' && c <= '9') return c - '0'; + if (c >= 'a' && c <= 'f') return c - 'a' + 10; + if (c >= 'A' && c <= 'F') return c - 'A' + 10; + return -1; +} + +/** + * Decodes a sequence of hex digits to raw byte values. + * This function is very strict about the allowed inputs and must only be used for validated inputs! + * + * - If numRawBytes != 0 and inBuffer == NULL or outBuffer == NULL, this will crash + * - The hex sequence must ONLY contain chars 0-9,a-f,A-F or the result will be undefined + * - The sequence must be padded to have an even length. numRawBytes is the number of bytes AFTER decoding, so inBuffer must be exactly 2x as large + * - inBuffer and outBuffer may be the same pointer + */ +static void decodeValidHexSequence(const char *inBuffer,uint8_t *outBuffer, NSUInteger numRawBytes) +{ + NSUInteger outIndex = 0; + NSUInteger srcIndex = 0; + while (outIndex < numRawBytes) { + uint8_t v = (hexchar2nibble(inBuffer[srcIndex]) << 4) + hexchar2nibble(inBuffer[srcIndex+1]); + outBuffer[outIndex++] = v; + srcIndex += 2; + } +} + +/** + * Interpret a string of hex digits in 'hex' as hex data, and return + * an NSData representation of the data. Spaces are permitted within + * the string and an initial '0x' will be ignored. If bad input + * is detected, nil is returned. + * + * Alternatively the MySQL-style X'val' syntax is also supported, + * with the same restrictions as in MySQL: + * - val must always be an even number of characters + * - val cannot contain whitespace (whitespace before/after is ok) + * - The leading x is case-INsensitive + */ ++ (NSData *)dataWithHexString:(NSString *)hex +{ + if(!hex) return nil; // no string + const char *sourceBytes = [hex UTF8String]; + + size_t length = strlen(sourceBytes); // keep in mind that [hex length] is the number of Unicode characters, not the number of bytes + if (length < 1) return [NSData data]; // empty string + + NSUInteger srcIndex = 0; + NSData *data = nil; + NSUInteger nbytes; + + //skip leading whitespace (in order to properly check for leading "0x") + while(srcIndex < length && (sourceBytes[srcIndex] == ' ' || sourceBytes[srcIndex] == '\t')) srcIndex++; + + // bypass initial 0x + if(srcIndex+1 < length && sourceBytes[srcIndex] == '0' && sourceBytes[srcIndex+1] == 'x' ) { + srcIndex += 2; + } + //check for mysql syntax + else if(srcIndex+2 < length && (sourceBytes[srcIndex] == 'x' || sourceBytes[srcIndex] == 'X') && sourceBytes[srcIndex+1] == '\'') { + srcIndex += 2; + //look for the terminating quote + NSUInteger startIndex = srcIndex; + NSUInteger endIndex = startIndex; //startIndex points to the first character inside the quotes, which may already be the terminating quote + while(endIndex < length) { + char c = sourceBytes[endIndex]; + //if we've hit the terminator, verify that only whitespace follows and stop reading + if(c == '\'') { + NSUInteger afterIndex = endIndex+1; + while (afterIndex < length) { + c = sourceBytes[afterIndex++]; + if(c != ' ' && c != '\t') return nil; + } + break; + } + endIndex++; + // Check for non-hex characters + if (hexchar2nibble(c) < 0) return nil; + } + // Check for unterminated sequence and uneven number of bytes + NSUInteger n = endIndex - startIndex; + if(endIndex == length || ((n % 2) != 0)) return nil; + // shortcut + if(n == 0) return [NSData data]; + //looks good, create the output buffer and decode + nbytes = n / 2; + unsigned char *outBuf = malloc(nbytes); + decodeValidHexSequence(&sourceBytes[startIndex], outBuf, nbytes); + return [NSData dataWithBytesNoCopy:outBuf length:nbytes freeWhenDone:YES]; + } + + // Copy input while removing spaces and tabs. + char *trimmedFull = (char *)malloc(length + 1); + char *trimmed = (trimmedFull + 1); //we'll use the first byte in case we have to fill in a leading '0' + NSUInteger trimIndex = 0; + NSUInteger n = 0; // n = # of hex digits + while(srcIndex < length) { + char c = sourceBytes[srcIndex++]; + if(c == ' ' || c == '\t') continue; + trimmed[trimIndex++] = c; + if(!c) break; + n++; + // Check for non-hex characters + if (hexchar2nibble(c) < 0) goto fail_cleanup; + } + //shortcut + if(n == 0) { + data = [NSData data]; + goto fail_cleanup; + } + + BOOL isEven = ((n % 2) == 0); + nbytes = !isEven ? (n + 1) / 2 : n / 2; //adjust for cases where "0aff" is written as "aff" (e.g.) + if(!isEven) { + trimmed--; + trimmed[0] = '0'; + } + + //we'll just decode the data in-place since the raw values have to be shorter by definition, anyway + decodeValidHexSequence(trimmed, (uint8_t *)trimmedFull, nbytes); + return [NSData dataWithBytesNoCopy:trimmedFull length:nbytes freeWhenDone:YES]; + +fail_cleanup: + free(trimmedFull); + return data; +} + +/** * Returns the hex representation of the given data. */ - (NSString *)dataToFormattedHexString |