Subversion Repositories pub

Compare Revisions

Ignore whitespace Rev 209 → Rev 211

/relevation/branches/1.2/relevation.py
99,6 → 99,8
'generic-pin': 'PIN',
'generic-port': 'Port'
}
MODE_AND='and'
MODE_OR='or'
 
def printe(s):
' Print to stderr '
114,6 → 116,8
channel.write(s)
p('%s {-f passwordfile} {-p password | -0} [search] [search2] [...]\n' % sys.argv[0])
p('\nOptions:\n')
# Reference: 80 characters
# -------------------------------------------------------------------------------
p(' -f FILE, --file=FILE Revelation password file.\n')
p(' -p PASS, --password=PASS Master password.\n')
p(' -s SEARCH, --search=SEARCH Search for string.\n')
125,6 → 129,10
p(' -t TYPE, --type=TYPE Print only entries of type TYPE.\n')
p(' With no search string, prints all entries of\n')
p(' type TYPE.\n')
p(' -A, --and When multiple search terms are used, use an AND\n')
p(' operator to combine them.\n')
p(' -O, --or When multiple search terms are used, use an OR\n')
p(' operator to combine them.\n')
p(' -x, --xml Dump unencrypted XML document.\n')
p(' -0, --stdin Read password from standard input.\n')
p(' -h, --help Print help (this message).\n')
134,6 → 142,11
def make_xpath_query(search_text=None, type_filter=None, ignore_case=True, negate_filter=False):
''' Construct the actual XPath expression
make_xpath_query(str, str, bool, bool) -> str
or
make_xpath_query(list, str, bool, bool) -> str
 
Passing a list as the second argument implies combining its elements
in the search (AND)
'''
xpath = '/revelationdata//entry'
if type_filter:
142,13 → 155,24
sign = '!='
xpath = '%s[@type%s"%s"]' % ( xpath, sign, type_filter )
if search_text:
xpath = xpath + '//text()'
if ignore_case:
# must pass lowercase to actually be case insensitive
search_text = string.lower(search_text)
xpath = '%s[contains(translate(., "ABCDEFGHIJKLMNOPQRSTUVWXYZ", "abcdefghijklmnopqrstuvwxyz"), "%s")]/../..' % ( xpath, search_text )
#xpath = xpath + '//text()'
needles = []
if type(search_text) == str:
needles = [ search_text, ]
else:
xpath = '%s[contains(., "%s")]/../..' % ( xpath, search_text )
needles = search_text
selector = ''
for search in needles:
if ignore_case:
# must pass lowercase to actually be case insensitive
search = string.lower(search)
# XPath 2.0 has lower-case, upper-case, matches(..., -i) etc.
selector += '//text()[contains(translate(., "ABCDEFGHIJKLMNOPQRSTUVWXYZ", "abcdefghijklmnopqrstuvwxyz"), "%s")]/../..' % search
else:
selector += '//text()[contains(., "%s")]/../..' % search
xpath = '%s%s' % ( xpath, selector )
if not RELEASE:
printe("> Xpath: %s\n" % xpath)
return xpath
 
def dump_all_entries(xmldata):
158,7 → 182,11
return dump_result(res, 'all')
 
def dump_entries(xmldata, search_text=None, type_filter=None, ignore_case=True, negate_filter=False):
' Dump entries from xmldata that match criteria '
''' Dump entries from xmldata that match criteria
dump_entries(str, str, str, bool, bool) -> int
or
dump_entries(str, list, str, bool, bool) -> int
'''
tree = etree.fromstring(xmldata)
xpath = make_xpath_query(search_text, type_filter, ignore_case, negate_filter)
try:
181,10 → 209,19
nr = dump_result(res, query_desc)
return nr
 
def print_wrapper(s):
def dump_single_result(typeName, name, descr, notes, fields):
printe('-------------------------------------------------------------------------------')
s = '\n'
s += 'Type: %s\n' % typeName
s += 'Name: %s\n' % name
s += 'Description: %s\n' % descr
s += 'Notes: %s\n' % notes
for field in fields:
s += '%s %s\n' % field # field, value
#s += '\n'
print s
 
def dump_result(res, query_desc, printfn=print_wrapper):
def dump_result(res, query_desc, dumpfn=dump_single_result):
''' Print query results.
dump_result(list of entries, query description) -> int
'''
194,9 → 231,11
return False
print '%d matches' % len(res)
for x in res:
printe('-------------------------------------------------------------------------------')
s = '\n'
s += 'Type: %s\n' % x.get('type')
typeName = x.get('type')
name = None
descr = None
fields = []
notes = None
for chld in x.getchildren():
n = chld.tag
val = chld.text
203,9 → 242,9
if val is None:
val = ''
if n == 'name':
s += 'Name: %s\n' % val
name = val
elif n == 'description':
s += 'Description: %s\n' % val
descr = val
elif n == 'field':
idv = chld.get('id')
if idv in TAGNAMES:
213,11 → 252,11
val = chld.text
if val is None:
val = ''
s += '%s %s\n' % ( idv, val )
# Maintain order => list
fields += [ ( idv, val ), ]
elif n == 'notes':
s += 'Notes: %s\n' % val
#s += '\n'
printfn(s)
notes = val
dumpfn(typeName, name, descr, notes, fields)
# / for chld in x.children
nr = len(res)
plural = ''
242,6 → 281,7
cfg = os.path.join(os.path.expanduser('~'), '.relevation.conf')
pw = None
fl = None
mode = MODE_OR
if os.path.isfile(cfg):
if os.access(cfg, os.R_OK):
wr = world_readable(cfg)
256,9 → 296,14
if wr: # TODO: how to check in windows?
printe('Your password can be read by anyone!!!')
pw = parser.get('relevation', 'password')
if 'mode' in ops:
mode = parser.get('relevation', 'mode')
if mode not in [ MODE_AND, MODE_OR ]:
printe('Warning: Unknown mode \'%s\' set in configuration' % mode)
mode=MODE_OR
else: # exists but not readable
printe('Configuration file (~/.relevation.conf) is not readable!')
return ( fl, pw )
return ( fl, pw, mode )
 
def decrypt_gz(key, cipher_text):
''' Decrypt cipher_text using key.
292,17 → 337,19
# individual search: ( 'value to search', 'type of search', 'type of entry to filter' )
searchTypes = []
dump_xml = False
mode = None
 
printe('Relevation v%s, (c) 2011-2012 Toni Corvera\n' % __version__)
printe('Relevation v%s, (c) 2011-2013 Toni Corvera\n' % __version__)
 
# ---------- OPTIONS ---------- #
( datafile, password ) = load_config()
( datafile, password, mode ) = load_config()
try:
# gnu_getopt requires py >= 2.3
ops, args = getopt.gnu_getopt(argv, 'f:p:s:0ciaht:x',
ops, args = getopt.gnu_getopt(argv, 'f:p:s:0ciaht:xAO',
[ 'file=', 'password=', 'search=', 'stdin',
'case-sensitive', 'case-insensitive', 'ask',
'help', 'version', 'type=', 'xml' ])
'help', 'version', 'type=', 'xml',
'and', 'or' ])
except getopt.GetoptError, err:
print str(err)
usage(sys.stderr)
355,6 → 402,10
searchTypes.append( ( iarg, neg ) )
elif opt in ( '-x', '--xml' ):
dump_xml = True
elif opt in ( '-A', '--and' ):
mode = MODE_AND
elif opt in ( '-O', '--or' ):
mode = MODE_OR
else:
printe('Unhandled option: %s' % opt)
assert False, "internal error parsing options"
396,13 → 447,23
if not ( needles or searchTypes ): # No search nor filters, print all
numhits = dump_all_entries(xmldata)
elif not searchTypes: # Simple case, all searches are text searches
for text in needles:
numhits += dump_entries(xmldata, text, 'folder', caseInsensitive, True)
if mode == MODE_OR:
for text in needles:
numhits += dump_entries(xmldata, text, 'folder', caseInsensitive, True)
else:
assert mode == MODE_AND, "Unknown boolean operation mode"
numhits += dump_entries(xmldata, needles, 'folder', caseInsensitive, True)
elif needles: # Do a search filtered for each type
for text in needles:
if mode == MODE_OR:
for text in needles:
for ( sfilter, negate ) in searchTypes:
numhits += dump_entries(xmldata, text, sfilter, caseInsensitive,
negate_filter=negate)
else:
assert mode == MODE_AND, "Unknown boolean operation mode"
for ( sfilter, negate ) in searchTypes:
numhits += dump_entries(xmldata, text, sfilter, caseInsensitive,
negate_filter=negate)
numhits += dump_entries(xmldata, needles, sfilter, caseInsensitive,
negate_filter=negate)
else: # Do a search only of types
for ( sfilter, negate ) in searchTypes:
numhits += dump_entries(xmldata, None, sfilter, negate_filter=negate)