summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--read.c88
1 files changed, 71 insertions, 17 deletions
diff --git a/read.c b/read.c
index 39c21b8..ef483f7 100644
--- a/read.c
+++ b/read.c
@@ -142,24 +142,78 @@ static char *seekNewline(char *s, size_t len) {
return NULL;
}
-/* Read a long long value starting at *s, under the assumption that it will be
- * terminated by \r\n. Returns REDIS_ERR for unexpected inputs or if the
- * resulting value would be greater than LLONG_MAX. */
-static int readLongLong(char *s, long long* val) {
- char* end;
- errno = 0;
+/* Convert a string into a long long. Returns REDIS_OK if the string could be
+ * parsed into a (non-overflowing) long long, REDIS_ERR otherwise. The value
+ * will be set to the parsed value when appropriate.
+ *
+ * Note that this function demands that the string strictly represents
+ * a long long: no spaces or other characters before or after the string
+ * representing the number are accepted, nor zeroes at the start if not
+ * for the string "0" representing the zero number.
+ *
+ * Because of its strictness, it is safe to use this function to check if
+ * you can convert a string into a long long, and obtain back the string
+ * from the number without any loss in the string representation. */
+int string2ll(const char *s, size_t slen, long long *value) {
+ const char *p = s;
+ size_t plen = 0;
+ int negative = 0;
+ unsigned long long v;
+
+ if (plen == slen)
+ return REDIS_ERR;
+
+ /* Special case: first and only digit is 0. */
+ if (slen == 1 && p[0] == '0') {
+ if (value != NULL) *value = 0;
+ return REDIS_OK;
+ }
- long long v = strtoll(s, &end, 10);
+ if (p[0] == '-') {
+ negative = 1;
+ p++; plen++;
- if(s == end || ((v == LLONG_MAX || v == LLONG_MIN) && errno == ERANGE)
- || (*end != '\r' || *(end+1) != '\n')) {
+ /* Abort on only a negative sign. */
+ if (plen == slen)
+ return REDIS_ERR;
+ }
+
+ /* First digit should be 1-9, otherwise the string should just be 0. */
+ if (p[0] >= '1' && p[0] <= '9') {
+ v = p[0]-'0';
+ p++; plen++;
+ } else if (p[0] == '0' && slen == 1) {
+ *value = 0;
+ return REDIS_OK;
+ } else {
return REDIS_ERR;
}
- if(val) {
- *val = v;
+ while (plen < slen && p[0] >= '0' && p[0] <= '9') {
+ if (v > (ULLONG_MAX / 10)) /* Overflow. */
+ return REDIS_ERR;
+ v *= 10;
+
+ if (v > (ULLONG_MAX - (p[0]-'0'))) /* Overflow. */
+ return REDIS_ERR;
+ v += p[0]-'0';
+
+ p++; plen++;
}
+ /* Return if not all bytes were used. */
+ if (plen < slen)
+ return REDIS_ERR;
+
+ if (negative) {
+ if (v > ((unsigned long long)(-(LLONG_MIN+1))+1)) /* Overflow. */
+ return REDIS_ERR;
+ if (value != NULL) *value = -v;
+ } else {
+ if (v > LLONG_MAX) /* Overflow. */
+ return REDIS_ERR;
+ if (value != NULL) *value = v;
+ }
return REDIS_OK;
}
@@ -213,7 +267,7 @@ static int processLineItem(redisReader *r) {
if (cur->type == REDIS_REPLY_INTEGER) {
if (r->fn && r->fn->createInteger) {
long long v;
- if(readLongLong(p, &v) == REDIS_ERR) {
+ if (string2ll(p, len, &v) == REDIS_ERR) {
__redisReaderSetError(r,REDIS_ERR_PROTOCOL,
"Bad integer value");
return REDIS_ERR;
@@ -258,7 +312,7 @@ static int processBulkItem(redisReader *r) {
p = r->buf+r->pos;
bytelen = s-(r->buf+r->pos)+2; /* include \r\n */
- if (readLongLong(p, &len) == REDIS_ERR) {
+ if (string2ll(p, bytelen - 2, &len) == REDIS_ERR) {
__redisReaderSetError(r,REDIS_ERR_PROTOCOL,
"Bad bulk string length");
return REDIS_ERR;
@@ -313,7 +367,7 @@ static int processMultiBulkItem(redisReader *r) {
void *obj;
char *p;
long long elements;
- int root = 0;
+ int root = 0, len;
/* Set error for nested multi bulks with depth > 7 */
if (r->ridx == 8) {
@@ -322,8 +376,8 @@ static int processMultiBulkItem(redisReader *r) {
return REDIS_ERR;
}
- if ((p = readLine(r,NULL)) != NULL) {
- if(readLongLong(p, &elements) == REDIS_ERR) {
+ if ((p = readLine(r,&len)) != NULL) {
+ if (string2ll(p, len, &elements) == REDIS_ERR) {
__redisReaderSetError(r,REDIS_ERR_PROTOCOL,
"Bad multi-bulk length");
return REDIS_ERR;
@@ -331,7 +385,7 @@ static int processMultiBulkItem(redisReader *r) {
root = (r->ridx == 0);
- if(elements < -1 || elements > INT_MAX) {
+ if (elements < -1 || elements > INT_MAX) {
__redisReaderSetError(r,REDIS_ERR_PROTOCOL,
"Multi-bulk length out of range");
return REDIS_ERR;