Created
July 4, 2024 18:42
-
-
Save mble/505225ff16fe3ce30a5f372a95057081 to your computer and use it in GitHub Desktop.
Debugging how Redis CLI parses URIs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
○ ~/dev/scratch/redis-cli-parse $ clang redis-cli-parser.c sds.c -o redis-cli-parser.out | |
○ ~/dev/scratch/redis-cli-parse $ ./redis-cli-parser.out | |
strlen(user): 0 | |
scheme: rediss | |
tls_flag: 1 | |
hostip: localhost | |
hostport: 6379 | |
input_dbnum: 0 | |
auth: password | |
user: |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <ctype.h> | |
#include <stdio.h> | |
#include <stdlib.h> | |
#include <unistd.h> | |
#include <string.h> | |
#include "sds.h" | |
typedef struct cliConnInfo { | |
char *hostip; | |
int hostport; | |
int input_dbnum; | |
char *auth; | |
char *user; | |
} cliConnInfo; | |
#define isHexChar(c) (isdigit(c) || ((c) >= 'a' && (c) <= 'f')) | |
#define decodeHexChar(c) (isdigit(c) ? (c) - '0' : (c) - 'a' + 10) | |
#define decodeHex(h, l) ((decodeHexChar(h) << 4) + decodeHexChar(l)) | |
static sds percentDecode(const char *pe, size_t len) { | |
const char *end = pe + len; | |
sds ret = sdsempty(); | |
const char *curr = pe; | |
while (curr < end) { | |
if (*curr == '%') { | |
if ((end - curr) < 2) { | |
fprintf(stderr, "Incomplete URI encoding\n"); | |
exit(1); | |
} | |
char h = tolower(*(++curr)); | |
char l = tolower(*(++curr)); | |
if (!isHexChar(h) || !isHexChar(l)) { | |
fprintf(stderr, "Illegal character in URI encoding\n"); | |
exit(1); | |
} | |
char c = decodeHex(h, l); | |
ret = sdscatlen(ret, &c, 1); | |
curr++; | |
} else { | |
ret = sdscatlen(ret, curr++, 1); | |
} | |
} | |
return ret; | |
} | |
void parseRedisUri(cliConnInfo *connInfo, const char *uri, int *tls_flag) { | |
const char *scheme = "redis://"; | |
const char *tlsscheme = "rediss://"; | |
const char *curr = uri; | |
const char *end = uri + strlen(uri); | |
const char *userinfo, *username, *port, *host, *path; | |
/* URI must start with a valid scheme. */ | |
if (!strncasecmp(tlsscheme, curr, strlen(tlsscheme))) { | |
*tls_flag = 1; | |
curr += strlen(tlsscheme); | |
} else if (!strncasecmp(scheme, curr, strlen(scheme))) { | |
*tls_flag = 0; | |
curr += strlen(scheme); | |
} else { | |
fprintf(stderr, "Invalid URI scheme\n"); | |
exit(1); | |
} | |
if (curr == end) return; | |
/* Extract user info. */ | |
if ((userinfo = strchr(curr,'@'))) { | |
if ((username = strchr(curr, ':')) && username < userinfo) { | |
connInfo->user = percentDecode(curr, username - curr); | |
curr = username + 1; | |
} | |
connInfo->auth = percentDecode(curr, userinfo - curr); | |
curr = userinfo + 1; | |
} | |
if (curr == end) return; | |
/* Extract host and port. */ | |
path = strchr(curr, '/'); | |
if (*curr != '/') { | |
host = path ? path - 1 : end; | |
if (*curr == '[') { | |
curr += 1; | |
if ((port = strchr(curr, ']'))) { | |
if (*(port+1) == ':') { | |
connInfo->hostport = atoi(port + 2); | |
} | |
host = port - 1; | |
} | |
} else { | |
if ((port = strchr(curr, ':'))) { | |
connInfo->hostport = atoi(port + 1); | |
host = port - 1; | |
} | |
} | |
sdsfree(connInfo->hostip); | |
connInfo->hostip = sdsnewlen(curr, host - curr + 1); | |
} | |
curr = path ? path + 1 : end; | |
if (curr == end) return; | |
/* Extract database number. */ | |
connInfo->input_dbnum = atoi(curr); | |
} | |
int main() { | |
cliConnInfo connInfo; | |
connInfo.hostip = sdsnew("127.0.0.1"); | |
connInfo.hostport = 6379; | |
connInfo.input_dbnum = 0; | |
connInfo.auth = NULL; | |
connInfo.user = NULL; | |
int tls_flag; | |
const char *uri = "rediss://:password@localhost:6379/0"; | |
parseRedisUri(&connInfo, uri, &tls_flag); | |
if (connInfo.user != NULL) { | |
printf("strlen(user): %lu\n", strlen(connInfo.user)); | |
} | |
printf("scheme: %s\n", tls_flag ? "rediss" : "redis"); | |
printf("tls_flag: %d\n", tls_flag); | |
printf("hostip: %s\n", connInfo.hostip); | |
printf("hostport: %d\n", connInfo.hostport); | |
printf("input_dbnum: %d\n", connInfo.input_dbnum); | |
printf("auth: %s\n", connInfo.auth); | |
printf("user: %s\n", connInfo.user); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
TL;DR empty usernames are not treated as
NULL
, so triggers theAUTH/2
behaviour, instead ofAUTH/1
.