/usr/share/arm/cli/connections/descriptorPopup.py is in tor-arm 1.4.5.0-1.
This file is owned by root:root, with mode 0o644.
The actual contents of the file can be viewed below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 | """
Popup providing the raw descriptor and consensus information for a relay.
"""
import math
import curses
import cli.popups
import cli.connections.connEntry
from util import panel, torTools, uiTools
# field keywords used to identify areas for coloring
LINE_NUM_COLOR = "yellow"
HEADER_COLOR = "cyan"
HEADER_PREFIX = ["ns/id/", "desc/id/"]
SIG_COLOR = "red"
SIG_START_KEYS = ["-----BEGIN RSA PUBLIC KEY-----", "-----BEGIN SIGNATURE-----"]
SIG_END_KEYS = ["-----END RSA PUBLIC KEY-----", "-----END SIGNATURE-----"]
UNRESOLVED_MSG = "No consensus data available"
ERROR_MSG = "Unable to retrieve data"
def showDescriptorPopup(connPanel):
"""
Presents consensus descriptor in popup window with the following controls:
Up, Down, Page Up, Page Down - scroll descriptor
Right, Left - next / previous connection
Enter, Space, d, D - close popup
Arguments:
connPanel - connection panel providing the dialog
"""
# hides the title of the connection panel
connPanel.setTitleVisible(False)
connPanel.redraw(True)
control = cli.controller.getController()
panel.CURSES_LOCK.acquire()
isDone = False
try:
while not isDone:
selection = connPanel.getSelection()
if not selection: break
fingerprint = selection.foreign.getFingerprint()
if fingerprint == "UNKNOWN": fingerprint = None
displayText = getDisplayText(fingerprint)
displayColor = cli.connections.connEntry.CATEGORY_COLOR[selection.getType()]
showLineNumber = fingerprint != None
# determines the maximum popup size the displayText can fill
pHeight, pWidth = getPreferredSize(displayText, connPanel.maxX, showLineNumber)
popup, _, height = cli.popups.init(pHeight, pWidth)
if not popup: break
scroll, isChanged = 0, True
try:
while not isDone:
if isChanged:
draw(popup, fingerprint, displayText, displayColor, scroll, showLineNumber)
isChanged = False
key = control.getScreen().getch()
if uiTools.isScrollKey(key):
# TODO: This is a bit buggy in that scrolling is by displayText
# lines rather than the displayed lines, causing issues when
# content wraps. The result is that we can't have a scrollbar and
# can't scroll to the bottom if there's a multi-line being
# displayed. However, trying to correct this introduces a big can
# of worms and after hours decided that this isn't worth the
# effort...
newScroll = uiTools.getScrollPosition(key, scroll, height - 2, len(displayText))
if scroll != newScroll:
scroll, isChanged = newScroll, True
elif uiTools.isSelectionKey(key) or key in (ord('d'), ord('D')):
isDone = True # closes popup
elif key in (curses.KEY_LEFT, curses.KEY_RIGHT):
# navigation - pass on to connPanel and recreate popup
connPanel.handleKey(curses.KEY_UP if key == curses.KEY_LEFT else curses.KEY_DOWN)
break
finally: cli.popups.finalize()
finally:
connPanel.setTitleVisible(True)
connPanel.redraw(True)
panel.CURSES_LOCK.release()
def getDisplayText(fingerprint):
"""
Provides the descriptor and consensus entry for a relay. This is a list of
lines to be displayed by the dialog.
"""
if not fingerprint: return [UNRESOLVED_MSG]
conn, description = torTools.getConn(), []
description.append("ns/id/%s" % fingerprint)
consensusEntry = conn.getConsensusEntry(fingerprint)
if consensusEntry: description += consensusEntry.split("\n")
else: description += [ERROR_MSG, ""]
description.append("desc/id/%s" % fingerprint)
descriptorEntry = conn.getDescriptorEntry(fingerprint)
if descriptorEntry: description += descriptorEntry.split("\n")
else: description += [ERROR_MSG]
return description
def getPreferredSize(text, maxWidth, showLineNumber):
"""
Provides the (height, width) tuple for the preferred size of the given text.
"""
width, height = 0, len(text) + 2
lineNumWidth = int(math.log10(len(text))) + 1
for line in text:
# width includes content, line number field, and border
lineWidth = len(line) + 5
if showLineNumber: lineWidth += lineNumWidth
width = max(width, lineWidth)
# tracks number of extra lines that will be taken due to text wrap
height += (lineWidth - 2) / maxWidth
return (height, width)
def draw(popup, fingerprint, displayText, displayColor, scroll, showLineNumber):
popup.win.erase()
popup.win.box()
xOffset = 2
if fingerprint: title = "Consensus Descriptor (%s):" % fingerprint
else: title = "Consensus Descriptor:"
popup.addstr(0, 0, title, curses.A_STANDOUT)
lineNumWidth = int(math.log10(len(displayText))) + 1
isEncryptionBlock = False # flag indicating if we're currently displaying a key
# checks if first line is in an encryption block
for i in range(0, scroll):
lineText = displayText[i].strip()
if lineText in SIG_START_KEYS: isEncryptionBlock = True
elif lineText in SIG_END_KEYS: isEncryptionBlock = False
drawLine, pageHeight = 1, popup.maxY - 2
for i in range(scroll, scroll + pageHeight):
lineText = displayText[i].strip()
xOffset = 2
if showLineNumber:
lineNumLabel = ("%%%ii" % lineNumWidth) % (i + 1)
lineNumFormat = curses.A_BOLD | uiTools.getColor(LINE_NUM_COLOR)
popup.addstr(drawLine, xOffset, lineNumLabel, lineNumFormat)
xOffset += lineNumWidth + 1
# Most consensus and descriptor lines are keyword/value pairs. Both are
# shown with the same color, but the keyword is bolded.
keyword, value = lineText, ""
drawFormat = uiTools.getColor(displayColor)
if lineText.startswith(HEADER_PREFIX[0]) or lineText.startswith(HEADER_PREFIX[1]):
keyword, value = lineText, ""
drawFormat = uiTools.getColor(HEADER_COLOR)
elif lineText == UNRESOLVED_MSG or lineText == ERROR_MSG:
keyword, value = lineText, ""
elif lineText in SIG_START_KEYS:
keyword, value = lineText, ""
isEncryptionBlock = True
drawFormat = uiTools.getColor(SIG_COLOR)
elif lineText in SIG_END_KEYS:
keyword, value = lineText, ""
isEncryptionBlock = False
drawFormat = uiTools.getColor(SIG_COLOR)
elif isEncryptionBlock:
keyword, value = "", lineText
drawFormat = uiTools.getColor(SIG_COLOR)
elif " " in lineText:
divIndex = lineText.find(" ")
keyword, value = lineText[:divIndex], lineText[divIndex:]
displayQueue = [(keyword, drawFormat | curses.A_BOLD), (value, drawFormat)]
cursorLoc = xOffset
while displayQueue:
msg, format = displayQueue.pop(0)
if not msg: continue
maxMsgSize = popup.maxX - 1 - cursorLoc
if len(msg) >= maxMsgSize:
# needs to split up the line
msg, remainder = uiTools.cropStr(msg, maxMsgSize, None, endType = None, getRemainder = True)
if xOffset == cursorLoc and msg == "":
# first word is longer than the line
msg = uiTools.cropStr(remainder, maxMsgSize)
if " " in remainder:
remainder = remainder.split(" ", 1)[1]
else: remainder = ""
popup.addstr(drawLine, cursorLoc, msg, format)
cursorLoc = xOffset
if remainder:
displayQueue.insert(0, (remainder.strip(), format))
drawLine += 1
else:
popup.addstr(drawLine, cursorLoc, msg, format)
cursorLoc += len(msg)
if drawLine > pageHeight: break
drawLine += 1
if drawLine > pageHeight: break
popup.win.refresh()
|