console: first pass of a Set-cookie editor for responses

This commit is contained in:
Aldo Cortesi 2015-04-16 10:57:12 +12:00
parent 850a50262b
commit 52716e3439
3 changed files with 195 additions and 65 deletions

View File

@ -66,20 +66,26 @@ def format_keyvals(lst, key="key", val="text", indent=0):
if kv is None: if kv is None:
ret.append(urwid.Text("")) ret.append(urwid.Text(""))
else: else:
cols = [] if isinstance(kv[1], urwid.Widget):
# This cumbersome construction process is here for a reason: v = kv[1]
# Urwid < 1.0 barfs if given a fixed size column of size zero. elif kv[1] is None:
if indent: v = urwid.Text("")
cols.append(("fixed", indent, urwid.Text(""))) else:
cols.extend([ v = urwid.Text([(val, kv[1])])
ret.append(
urwid.Columns(
[
("fixed", indent, urwid.Text("")),
( (
"fixed", "fixed",
maxk, maxk,
urwid.Text([(key, kv[0] or "")]) urwid.Text([(key, kv[0] or "")])
), ),
kv[1] if isinstance(kv[1], urwid.Widget) else urwid.Text([(val, kv[1])]) v
]) ],
ret.append(urwid.Columns(cols, dividechars = 2)) dividechars = 2
)
)
return ret return ret

View File

@ -316,6 +316,19 @@ class FlowView(tabs.Tabs):
conn.set_cookies(od) conn.set_cookies(od)
signals.flow_change.send(self, flow = self.flow) signals.flow_change.send(self, flow = self.flow)
def set_setcookies(self, lst, conn):
vals = []
for i in lst:
vals.append(
[
i[0],
[i[1], odict.ODictCaseless(i[2])]
]
)
od = odict.ODict(vals)
conn.set_cookies(od)
signals.flow_change.send(self, flow = self.flow)
def edit(self, part): def edit(self, part):
if self.tab_offset == TAB_REQ: if self.tab_offset == TAB_REQ:
message = self.flow.request message = self.flow.request
@ -338,7 +351,18 @@ class FlowView(tabs.Tabs):
message message
) )
) )
pass if message == self.flow.response and part == "c":
flattened = []
for k, v in message.get_cookies().items():
flattened.append([k, v[0], v[1].lst])
self.master.view_grideditor(
grideditor.SetCookieEditor(
self.master,
flattened,
self.set_setcookies,
message
)
)
if part == "r": if part == "r":
with decoded(message): with decoded(message):
# Fix an issue caused by some editors when editing a # Fix an issue caused by some editors when editing a
@ -541,7 +565,7 @@ class FlowView(tabs.Tabs):
signals.status_prompt_onekey.send( signals.status_prompt_onekey.send(
prompt = "Edit request", prompt = "Edit request",
keys = ( keys = (
("cookie", "c"), ("cookies", "c"),
("query", "q"), ("query", "q"),
("path", "p"), ("path", "p"),
("url", "u"), ("url", "u"),
@ -556,6 +580,7 @@ class FlowView(tabs.Tabs):
signals.status_prompt_onekey.send( signals.status_prompt_onekey.send(
prompt = "Edit response", prompt = "Edit response",
keys = ( keys = (
("cookies", "c"),
("code", "o"), ("code", "o"),
("message", "m"), ("message", "m"),
("header", "h"), ("header", "h"),

View File

@ -7,7 +7,7 @@ import urwid
from . import common, signals from . import common, signals
from .. import utils, filt, script from .. import utils, filt, script
from netlib import http_uastrings from netlib import http_uastrings, http_cookies
FOOTER = [ FOOTER = [
@ -19,17 +19,36 @@ FOOTER_EDITING = [
] ]
class SText(urwid.WidgetWrap): class TextColumn:
def __init__(self, txt, focused, error): subeditor = None
def __init__(self, heading):
self.heading = heading
def text(self, obj):
return SEscaped(obj or "")
def blank(self):
return ""
class SubgridColumn:
def __init__(self, heading, subeditor):
self.heading = heading
self.subeditor = subeditor
def text(self, obj):
p = http_cookies._format_pairs(obj, sep="\n")
return urwid.Text(p)
def blank(self):
return []
class SEscaped(urwid.WidgetWrap):
def __init__(self, txt):
txt = txt.encode("string-escape") txt = txt.encode("string-escape")
w = urwid.Text(txt, wrap="any") w = urwid.Text(txt, wrap="any")
if focused:
if error:
w = urwid.AttrWrap(w, "focusfield_error")
else:
w = urwid.AttrWrap(w, "focusfield")
elif error:
w = urwid.AttrWrap(w, "field_error")
urwid.WidgetWrap.__init__(self, w) urwid.WidgetWrap.__init__(self, w)
def get_text(self): def get_text(self):
@ -50,7 +69,7 @@ class SEdit(urwid.WidgetWrap):
urwid.WidgetWrap.__init__(self, w) urwid.WidgetWrap.__init__(self, w)
def get_text(self): def get_text(self):
return self._w.get_text()[0] return self._w.get_text()[0].strip()
def selectable(self): def selectable(self):
return True return True
@ -67,9 +86,15 @@ class GridRow(urwid.WidgetWrap):
self.editing = SEdit(v) self.editing = SEdit(v)
self.fields.append(self.editing) self.fields.append(self.editing)
else: else:
self.fields.append( w = self.editor.columns[i].text(v)
SText(v, True if focused == i else False, i in errors) if focused == i:
) if i in errors:
w = urwid.AttrWrap(w, "focusfield_error")
else:
w = urwid.AttrWrap(w, "focusfield")
elif i in errors:
w = urwid.AttrWrap(w, "field_error")
self.fields.append(w)
fspecs = self.fields[:] fspecs = self.fields[:]
if len(self.fields) > 1: if len(self.fields) > 1:
@ -126,7 +151,9 @@ class GridWalker(urwid.ListWalker):
val = val.decode("string-escape") val = val.decode("string-escape")
except ValueError: except ValueError:
signals.status_message.send( signals.status_message.send(
self, message = "Invalid Python-style string encoding.", expure = 1000 self,
message = "Invalid Python-style string encoding.",
expire = 1000
) )
return return
errors = self.lst[self.focus][1] errors = self.lst[self.focus][1]
@ -136,10 +163,15 @@ class GridWalker(urwid.ListWalker):
errors.add(self.focus_col) errors.add(self.focus_col)
else: else:
errors.discard(self.focus_col) errors.discard(self.focus_col)
self.set_value(val, self.focus, self.focus_col, errors)
row = list(self.lst[self.focus][0]) def set_value(self, val, focus, focus_col, errors=None):
row[self.focus_col] = val if not errors:
self.lst[self.focus] = [tuple(row), errors] errors = set([])
row = list(self.lst[focus][0])
row[focus_col] = val
self.lst[focus] = [tuple(row), errors]
self._modified()
def delete_focus(self): def delete_focus(self):
if self.lst: if self.lst:
@ -149,7 +181,10 @@ class GridWalker(urwid.ListWalker):
def _insert(self, pos): def _insert(self, pos):
self.focus = pos self.focus = pos
self.lst.insert(self.focus, [[""]*self.editor.columns, set([])]) self.lst.insert(
self.focus, [
[c.blank() for c in self.editor.columns], set([])]
)
self.focus_col = 0 self.focus_col = 0
self.start_edit() self.start_edit()
@ -179,12 +214,12 @@ class GridWalker(urwid.ListWalker):
self._modified() self._modified()
def right(self): def right(self):
self.focus_col = min(self.focus_col + 1, self.editor.columns-1) self.focus_col = min(self.focus_col + 1, len(self.editor.columns)-1)
self._modified() self._modified()
def tab_next(self): def tab_next(self):
self.stop_edit() self.stop_edit()
if self.focus_col < self.editor.columns-1: if self.focus_col < len(self.editor.columns)-1:
self.focus_col += 1 self.focus_col += 1
elif self.focus != len(self.lst)-1: elif self.focus != len(self.lst)-1:
self.focus_col = 0 self.focus_col = 0
@ -231,7 +266,6 @@ FIRST_WIDTH_MIN = 20
class GridEditor(urwid.WidgetWrap): class GridEditor(urwid.WidgetWrap):
title = None title = None
columns = None columns = None
headings = None
def __init__(self, master, value, callback, *cb_args, **cb_kwargs): def __init__(self, master, value, callback, *cb_args, **cb_kwargs):
value = copy.deepcopy(value) value = copy.deepcopy(value)
@ -241,7 +275,7 @@ class GridEditor(urwid.WidgetWrap):
first_width = 20 first_width = 20
if value: if value:
for r in value: for r in value:
assert len(r) == self.columns assert len(r) == len(self.columns)
first_width = max(len(r), first_width) first_width = max(len(r), first_width)
self.first_width = min(first_width, FIRST_WIDTH_MAX) self.first_width = min(first_width, FIRST_WIDTH_MAX)
@ -250,9 +284,9 @@ class GridEditor(urwid.WidgetWrap):
title = urwid.AttrWrap(title, "heading") title = urwid.AttrWrap(title, "heading")
headings = [] headings = []
for i, h in enumerate(self.headings): for i, col in enumerate(self.columns):
c = urwid.Text(h) c = urwid.Text(col.heading)
if i == 0 and len(self.headings) > 1: if i == 0 and len(self.columns) > 1:
headings.append(("fixed", first_width + 2, c)) headings.append(("fixed", first_width + 2, c))
else: else:
headings.append(c) headings.append(c)
@ -303,6 +337,9 @@ class GridEditor(urwid.WidgetWrap):
except IOError, v: except IOError, v:
return str(v) return str(v)
def set_subeditor_value(self, val, focus, focus_col):
self.walker.set_value(val, focus, focus_col)
def keypress(self, size, key): def keypress(self, size, key):
if self.walker.editing: if self.walker.editing:
if key in ["esc"]: if key in ["esc"]:
@ -317,10 +354,11 @@ class GridEditor(urwid.WidgetWrap):
return None return None
key = common.shortcuts(key) key = common.shortcuts(key)
column = self.columns[self.walker.focus_col]
if key in ["q", "esc"]: if key in ["q", "esc"]:
res = [] res = []
for i in self.walker.lst: for i in self.walker.lst:
if not i[1] and any([x.strip() for x in i[0]]): if not i[1] and any([x for x in i[0]]):
res.append(i[0]) res.append(i[0])
self.callback(res, *self.cb_args, **self.cb_kwargs) self.callback(res, *self.cb_args, **self.cb_kwargs)
signals.pop_view_state.send(self) signals.pop_view_state.send(self)
@ -337,6 +375,13 @@ class GridEditor(urwid.WidgetWrap):
elif key == "d": elif key == "d":
self.walker.delete_focus() self.walker.delete_focus()
elif key == "r": elif key == "r":
if column.subeditor:
signals.status_message.send(
self,
message = "Press enter to edit this field.",
expire = 1000
)
return
if self.walker.get_current_value() is not None: if self.walker.get_current_value() is not None:
signals.status_prompt_path.send( signals.status_prompt_path.send(
self, self,
@ -344,6 +389,13 @@ class GridEditor(urwid.WidgetWrap):
callback = self.read_file callback = self.read_file
) )
elif key == "R": elif key == "R":
if column.subeditor:
signals.status_message.send(
self,
message = "Press enter to edit this field.",
expire = 1000
)
return
if self.walker.get_current_value() is not None: if self.walker.get_current_value() is not None:
signals.status_prompt_path.send( signals.status_prompt_path.send(
self, self,
@ -352,6 +404,13 @@ class GridEditor(urwid.WidgetWrap):
args = (True,) args = (True,)
) )
elif key == "e": elif key == "e":
if column.subeditor:
signals.status_message.send(
self,
message = "Press enter to edit this field.",
expire = 1000
)
return
o = self.walker.get_current_value() o = self.walker.get_current_value()
if o is not None: if o is not None:
n = self.master.spawn_editor(o.encode("string-escape")) n = self.master.spawn_editor(o.encode("string-escape"))
@ -359,7 +418,19 @@ class GridEditor(urwid.WidgetWrap):
self.walker.set_current_value(n, False) self.walker.set_current_value(n, False)
self.walker._modified() self.walker._modified()
elif key in ["enter"]: elif key in ["enter"]:
if column.subeditor:
self.master.view_grideditor(
self.columns[self.walker.focus_col].subeditor(
self.master,
self.walker.get_current_value(),
self.set_subeditor_value,
self.walker.focus,
self.walker.focus_col
)
)
else:
self.walker.start_edit() self.walker.start_edit()
elif not self.handle_key(key): elif not self.handle_key(key):
return self._w.keypress(size, key) return self._w.keypress(size, key)
@ -403,14 +474,18 @@ class GridEditor(urwid.WidgetWrap):
class QueryEditor(GridEditor): class QueryEditor(GridEditor):
title = "Editing query" title = "Editing query"
columns = 2 columns = [
headings = ("Key", "Value") TextColumn("Key"),
TextColumn("Value")
]
class HeaderEditor(GridEditor): class HeaderEditor(GridEditor):
title = "Editing headers" title = "Editing headers"
columns = 2 columns = [
headings = ("Key", "Value") TextColumn("Key"),
TextColumn("Value")
]
def make_help(self): def make_help(self):
h = GridEditor.make_help(self) h = GridEditor.make_help(self)
@ -448,14 +523,19 @@ class HeaderEditor(GridEditor):
class URLEncodedFormEditor(GridEditor): class URLEncodedFormEditor(GridEditor):
title = "Editing URL-encoded form" title = "Editing URL-encoded form"
columns = 2 columns = [
headings = ("Key", "Value") TextColumn("Key"),
TextColumn("Value")
]
class ReplaceEditor(GridEditor): class ReplaceEditor(GridEditor):
title = "Editing replacement patterns" title = "Editing replacement patterns"
columns = 3 columns = [
headings = ("Filter", "Regex", "Replacement") TextColumn("Filter"),
TextColumn("Regex"),
TextColumn("Replacement"),
]
def is_error(self, col, val): def is_error(self, col, val):
if col == 0: if col == 0:
@ -471,8 +551,11 @@ class ReplaceEditor(GridEditor):
class SetHeadersEditor(GridEditor): class SetHeadersEditor(GridEditor):
title = "Editing header set patterns" title = "Editing header set patterns"
columns = 3 columns = [
headings = ("Filter", "Header", "Value") TextColumn("Filter"),
TextColumn("Header"),
TextColumn("Value"),
]
def is_error(self, col, val): def is_error(self, col, val):
if col == 0: if col == 0:
@ -517,14 +600,16 @@ class SetHeadersEditor(GridEditor):
class PathEditor(GridEditor): class PathEditor(GridEditor):
title = "Editing URL path components" title = "Editing URL path components"
columns = 1 columns = [
headings = ("Component",) TextColumn("Component"),
]
class ScriptEditor(GridEditor): class ScriptEditor(GridEditor):
title = "Editing scripts" title = "Editing scripts"
columns = 1 columns = [
headings = ("Command",) TextColumn("Command"),
]
def is_error(self, col, val): def is_error(self, col, val):
try: try:
@ -535,8 +620,9 @@ class ScriptEditor(GridEditor):
class HostPatternEditor(GridEditor): class HostPatternEditor(GridEditor):
title = "Editing host patterns" title = "Editing host patterns"
columns = 1 columns = [
headings = ("Regex (matched on hostname:port / ip:port)",) TextColumn("Regex (matched on hostname:port / ip:port)")
]
def is_error(self, col, val): def is_error(self, col, val):
try: try:
@ -547,11 +633,24 @@ class HostPatternEditor(GridEditor):
class CookieEditor(GridEditor): class CookieEditor(GridEditor):
title = "Editing request Cookie header" title = "Editing request Cookie header"
columns = 2 columns = [
headings = ("Name", "Value") TextColumn("Name"),
TextColumn("Value"),
]
class CookieAttributeEditor(GridEditor):
title = "Editing Set-Cookie attributes"
columns = [
TextColumn("Name"),
TextColumn("Value"),
]
class SetCookieEditor(GridEditor): class SetCookieEditor(GridEditor):
title = "Editing request SetCookie header" title = "Editing response SetCookie header"
columns = 2 columns = [
headings = ("Name", "Value") TextColumn("Name"),
TextColumn("Value"),
SubgridColumn("Attributes", CookieAttributeEditor),
]