Skip to content

Implement string un/escaping and "raw" values #22

New issue

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

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

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 63 additions & 3 deletions interwebz/api.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from shlex import split
import json
from shlex import shlex
from typing import Any

from redis import exceptions
Expand All @@ -9,7 +10,8 @@
max_batch_size = 20
max_arguments = 25
max_argument_size = 256

escapes = {'n': '\n', 'r': '\r', 't': '\t', 'b': '\b', 'a': '\a'}
unescapes = {v: k for k, v in escapes.items()}

def reply(value: Any, error: bool) -> dict:
return {
Expand All @@ -27,6 +29,58 @@ def snip(value: str, init: int = 0) -> str:
return value[:max_argument_size - init - len(signature)] + signature


def unescape_argument(argument: str) -> str:
if len(argument) < 2 or not (argument.startswith('"') or argument.startswith("'")):
return argument
value, reply = argument[1:-1], ''
if argument.startswith('"'):
result = []
i = 0
while i < len(value):
if value[i] == '\\':
if i + 1 >= len(value):
return value
if value[i + 1] == 'x' and i + 3 < len(value):
try:
hex_val = int(value[i+2:i+4], 16)
result.append(chr(hex_val))
i += 4
continue
except ValueError:
result.append(value[i])
i += 1
continue
result.append(escapes.get(value[i + 1], value[i + 1]))
i += 2
else:
result.append(value[i])
i += 1
reply = ''.join(result)
else:
reply = value.replace("\\'", "'")
return reply

def escape_strings(resp: Any) -> Any:
if type(resp) is str:
escaped = ''
for c in resp:
if c == '\\':
escaped += '\\\\'
elif c == '"':
escaped += '\\"'
elif not c.isprintable():
escaped += f'\\x{ord(c):02x}'
else:
escaped += unescapes.get(c, c)
if escaped != resp:
escaped = f'"{escaped}"'
return escaped
if type(resp) is list:
return [escape_strings(x) for x in resp]
if type(resp) is dict:
return {k: escape_strings(v) for k, v in resp.items()}
return resp

def sanitize_exceptions(argv: list) -> Any:
# TODO: potential "attack" vectors: append, bitfield, sadd, zadd, xadd, hset, lpush/lmove*, sunionstore, zunionstore, ...
cmd_name = argv[0].lower()
Expand Down Expand Up @@ -65,7 +119,10 @@ def execute_commands(client: NameSpacedRedis, session: PageSession, commands: li
rep = []
for command in commands:
try:
argv = split(command)
lex = shlex(command, posix=True)
lex.whitespace_split = True
lex.commenters = ''
argv = list(iter(shlex(command, True).get_token, ''))
except ValueError as e:
rep.append(reply(str(e), True))
continue
Expand All @@ -83,6 +140,7 @@ def execute_commands(client: NameSpacedRedis, session: PageSession, commands: li
stronly = type(argv[i]) is str
if not stronly:
break
argv[i] = unescape_argument(argv[i])
if len(argv[i]) > max_argument_size:
argv[i] = snip(argv[i])
if not stronly:
Expand All @@ -96,6 +154,8 @@ def execute_commands(client: NameSpacedRedis, session: PageSession, commands: li

try:
resp = client.execute_namespaced(session, argv)
if not (argv[0].lower() == 'info' or (argc > 1 and argv[0].lower() == 'client' and argv[1].lower() == 'info')):
resp = escape_strings(resp)
rep.append(reply(resp, False))
except Exception as e:
rep.append(reply(str(e), True))
Expand Down
2 changes: 1 addition & 1 deletion interwebz/static/js/cli.js
Original file line number Diff line number Diff line change
Expand Up @@ -247,7 +247,7 @@ function formatReply(reply, indent = '') {

const type = typeof reply;
if (type === 'string') {
return `"${reply}"`;
return reply;
} else if (type === 'number') {
return `(integer) ${reply}`;
} else if (Array.isArray(reply)) {
Expand Down