14710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm#! /usr/bin/env python
24710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
34710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm# A simple gopher client.
44710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm#
54710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm# Usage: gopher [ [selector] host [port] ]
64710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
74710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylmimport string
84710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylmimport sys
94710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylmimport os
104710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylmimport socket
114710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
124710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm# Default selector, host and port
134710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylmDEF_SELECTOR = ''
144710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylmDEF_HOST     = 'gopher.micro.umn.edu'
154710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylmDEF_PORT     = 70
164710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
174710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm# Recognized file types
184710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylmT_TEXTFILE  = '0'
194710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylmT_MENU      = '1'
204710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylmT_CSO       = '2'
214710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylmT_ERROR     = '3'
224710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylmT_BINHEX    = '4'
234710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylmT_DOS       = '5'
244710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylmT_UUENCODE  = '6'
254710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylmT_SEARCH    = '7'
264710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylmT_TELNET    = '8'
274710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylmT_BINARY    = '9'
284710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylmT_REDUNDANT = '+'
294710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylmT_SOUND     = 's'
304710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
314710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm# Dictionary mapping types to strings
324710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylmtypename = {'0': '<TEXT>', '1': '<DIR>', '2': '<CSO>', '3': '<ERROR>', \
334710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        '4': '<BINHEX>', '5': '<DOS>', '6': '<UUENCODE>', '7': '<SEARCH>', \
344710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        '8': '<TELNET>', '9': '<BINARY>', '+': '<REDUNDANT>', 's': '<SOUND>'}
354710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
364710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm# Oft-used characters and strings
374710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylmCRLF = '\r\n'
384710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylmTAB = '\t'
394710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
404710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm# Open a TCP connection to a given host and port
414710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylmdef open_socket(host, port):
424710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    if not port:
434710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        port = DEF_PORT
444710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    elif type(port) == type(''):
454710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        port = string.atoi(port)
464710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
474710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    s.connect((host, port))
484710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    return s
494710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
504710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm# Send a selector to a given host and port, return a file with the reply
514710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylmdef send_request(selector, host, port):
524710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    s = open_socket(host, port)
534710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    s.send(selector + CRLF)
544710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    s.shutdown(1)
554710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    return s.makefile('r')
564710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
574710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm# Get a menu in the form of a list of entries
584710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylmdef get_menu(selector, host, port):
594710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    f = send_request(selector, host, port)
604710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    list = []
614710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    while 1:
624710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        line = f.readline()
634710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        if not line:
644710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            print '(Unexpected EOF from server)'
654710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            break
664710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        if line[-2:] == CRLF:
674710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            line = line[:-2]
684710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        elif line[-1:] in CRLF:
694710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            line = line[:-1]
704710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        if line == '.':
714710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            break
724710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        if not line:
734710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            print '(Empty line from server)'
744710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            continue
754710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        typechar = line[0]
764710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        parts = string.splitfields(line[1:], TAB)
774710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        if len(parts) < 4:
784710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            print '(Bad line from server: %r)' % (line,)
794710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            continue
804710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        if len(parts) > 4:
814710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            print '(Extra info from server: %r)' % (parts[4:],)
824710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        parts.insert(0, typechar)
834710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        list.append(parts)
844710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    f.close()
854710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    return list
864710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
874710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm# Get a text file as a list of lines, with trailing CRLF stripped
884710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylmdef get_textfile(selector, host, port):
894710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    list = []
904710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    get_alt_textfile(selector, host, port, list.append)
914710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    return list
924710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
934710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm# Get a text file and pass each line to a function, with trailing CRLF stripped
944710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylmdef get_alt_textfile(selector, host, port, func):
954710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    f = send_request(selector, host, port)
964710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    while 1:
974710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        line = f.readline()
984710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        if not line:
994710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            print '(Unexpected EOF from server)'
1004710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            break
1014710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        if line[-2:] == CRLF:
1024710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            line = line[:-2]
1034710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        elif line[-1:] in CRLF:
1044710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            line = line[:-1]
1054710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        if line == '.':
1064710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            break
1074710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        if line[:2] == '..':
1084710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            line = line[1:]
1094710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        func(line)
1104710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    f.close()
1114710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
1124710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm# Get a binary file as one solid data block
1134710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylmdef get_binary(selector, host, port):
1144710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    f = send_request(selector, host, port)
1154710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    data = f.read()
1164710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    f.close()
1174710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    return data
1184710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
1194710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm# Get a binary file and pass each block to a function
1204710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylmdef get_alt_binary(selector, host, port, func, blocksize):
1214710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    f = send_request(selector, host, port)
1224710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    while 1:
1234710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        data = f.read(blocksize)
1244710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        if not data:
1254710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            break
1264710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        func(data)
1274710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
1284710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm# A *very* simple interactive browser
1294710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
1304710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm# Browser main command, has default arguments
1314710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylmdef browser(*args):
1324710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    selector = DEF_SELECTOR
1334710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    host = DEF_HOST
1344710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    port = DEF_PORT
1354710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    n = len(args)
1364710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    if n > 0 and args[0]:
1374710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        selector = args[0]
1384710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    if n > 1 and args[1]:
1394710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        host = args[1]
1404710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    if n > 2 and args[2]:
1414710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        port = args[2]
1424710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    if n > 3:
1434710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        raise RuntimeError, 'too many args'
1444710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    try:
1454710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        browse_menu(selector, host, port)
1464710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    except socket.error, msg:
1474710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        print 'Socket error:', msg
1484710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        sys.exit(1)
1494710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    except KeyboardInterrupt:
1504710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        print '\n[Goodbye]'
1514710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
1524710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm# Browse a menu
1534710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylmdef browse_menu(selector, host, port):
1544710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    list = get_menu(selector, host, port)
1554710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    while 1:
1564710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        print '----- MENU -----'
1574710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        print 'Selector:', repr(selector)
1584710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        print 'Host:', host, ' Port:', port
1594710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        print
1604710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        for i in range(len(list)):
1614710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            item = list[i]
1624710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            typechar, description = item[0], item[1]
1634710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            print string.rjust(repr(i+1), 3) + ':', description,
1644710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            if typename.has_key(typechar):
1654710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm                print typename[typechar]
1664710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            else:
1674710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm                print '<TYPE=' + repr(typechar) + '>'
1684710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        print
1694710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        while 1:
1704710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            try:
1714710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm                str = raw_input('Choice [CR == up a level]: ')
1724710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            except EOFError:
1734710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm                print
1744710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm                return
1754710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            if not str:
1764710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm                return
1774710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            try:
1784710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm                choice = string.atoi(str)
1794710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            except string.atoi_error:
1804710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm                print 'Choice must be a number; try again:'
1814710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm                continue
1824710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            if not 0 < choice <= len(list):
1834710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm                print 'Choice out of range; try again:'
1844710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm                continue
1854710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            break
1864710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        item = list[choice-1]
1874710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        typechar = item[0]
1884710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        [i_selector, i_host, i_port] = item[2:5]
1894710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        if typebrowser.has_key(typechar):
1904710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            browserfunc = typebrowser[typechar]
1914710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            try:
1924710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm                browserfunc(i_selector, i_host, i_port)
1934710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            except (IOError, socket.error):
1944710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm                print '***', sys.exc_type, ':', sys.exc_value
1954710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        else:
1964710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            print 'Unsupported object type'
1974710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
1984710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm# Browse a text file
1994710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylmdef browse_textfile(selector, host, port):
2004710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    x = None
2014710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    try:
2024710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        p = os.popen('${PAGER-more}', 'w')
2034710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        x = SaveLines(p)
2044710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        get_alt_textfile(selector, host, port, x.writeln)
2054710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    except IOError, msg:
2064710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        print 'IOError:', msg
2074710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    if x:
2084710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        x.close()
2094710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    f = open_savefile()
2104710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    if not f:
2114710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        return
2124710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    x = SaveLines(f)
2134710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    try:
2144710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        get_alt_textfile(selector, host, port, x.writeln)
2154710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        print 'Done.'
2164710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    except IOError, msg:
2174710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        print 'IOError:', msg
2184710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    x.close()
2194710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
2204710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm# Browse a search index
2214710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylmdef browse_search(selector, host, port):
2224710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    while 1:
2234710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        print '----- SEARCH -----'
2244710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        print 'Selector:', repr(selector)
2254710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        print 'Host:', host, ' Port:', port
2264710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        print
2274710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        try:
2284710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            query = raw_input('Query [CR == up a level]: ')
2294710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        except EOFError:
2304710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            print
2314710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            break
2324710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        query = string.strip(query)
2334710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        if not query:
2344710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            break
2354710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        if '\t' in query:
2364710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            print 'Sorry, queries cannot contain tabs'
2374710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            continue
2384710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        browse_menu(selector + TAB + query, host, port)
2394710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
2404710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm# "Browse" telnet-based information, i.e. open a telnet session
2414710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylmdef browse_telnet(selector, host, port):
2424710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    if selector:
2434710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        print 'Log in as', repr(selector)
2444710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    if type(port) <> type(''):
2454710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        port = repr(port)
2464710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    sts = os.system('set -x; exec telnet ' + host + ' ' + port)
2474710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    if sts:
2484710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        print 'Exit status:', sts
2494710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
2504710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm# "Browse" a binary file, i.e. save it to a file
2514710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylmdef browse_binary(selector, host, port):
2524710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    f = open_savefile()
2534710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    if not f:
2544710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        return
2554710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    x = SaveWithProgress(f)
2564710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    get_alt_binary(selector, host, port, x.write, 8*1024)
2574710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    x.close()
2584710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
2594710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm# "Browse" a sound file, i.e. play it or save it
2604710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylmdef browse_sound(selector, host, port):
2614710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    browse_binary(selector, host, port)
2624710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
2634710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm# Dictionary mapping types to browser functions
2644710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylmtypebrowser = {'0': browse_textfile, '1': browse_menu, \
2654710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        '4': browse_binary, '5': browse_binary, '6': browse_textfile, \
2664710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        '7': browse_search, \
2674710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        '8': browse_telnet, '9': browse_binary, 's': browse_sound}
2684710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
2694710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm# Class used to save lines, appending a newline to each line
2704710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylmclass SaveLines:
2714710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    def __init__(self, f):
2724710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        self.f = f
2734710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    def writeln(self, line):
2744710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        self.f.write(line + '\n')
2754710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    def close(self):
2764710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        sts = self.f.close()
2774710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        if sts:
2784710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            print 'Exit status:', sts
2794710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
2804710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm# Class used to save data while showing progress
2814710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylmclass SaveWithProgress:
2824710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    def __init__(self, f):
2834710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        self.f = f
2844710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    def write(self, data):
2854710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        sys.stdout.write('#')
2864710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        sys.stdout.flush()
2874710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        self.f.write(data)
2884710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    def close(self):
2894710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        print
2904710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        sts = self.f.close()
2914710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        if sts:
2924710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            print 'Exit status:', sts
2934710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
2944710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm# Ask for and open a save file, or return None if not to save
2954710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylmdef open_savefile():
2964710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    try:
2974710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        savefile = raw_input( \
2984710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    'Save as file [CR == don\'t save; |pipeline or ~user/... OK]: ')
2994710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    except EOFError:
3004710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        print
3014710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        return None
3024710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    savefile = string.strip(savefile)
3034710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    if not savefile:
3044710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        return None
3054710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    if savefile[0] == '|':
3064710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        cmd = string.strip(savefile[1:])
3074710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        try:
3084710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            p = os.popen(cmd, 'w')
3094710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        except IOError, msg:
3104710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            print repr(cmd), ':', msg
3114710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            return None
3124710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        print 'Piping through', repr(cmd), '...'
3134710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        return p
3144710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    if savefile[0] == '~':
3154710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        savefile = os.path.expanduser(savefile)
3164710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    try:
3174710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        f = open(savefile, 'w')
3184710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    except IOError, msg:
3194710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        print repr(savefile), ':', msg
3204710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        return None
3214710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    print 'Saving to', repr(savefile), '...'
3224710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    return f
3234710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
3244710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm# Test program
3254710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylmdef test():
3264710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    if sys.argv[4:]:
3274710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        print 'usage: gopher [ [selector] host [port] ]'
3284710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        sys.exit(2)
3294710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    elif sys.argv[3:]:
3304710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        browser(sys.argv[1], sys.argv[2], sys.argv[3])
3314710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    elif sys.argv[2:]:
3324710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        try:
3334710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            port = string.atoi(sys.argv[2])
3344710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            selector = ''
3354710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            host = sys.argv[1]
3364710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        except string.atoi_error:
3374710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            selector = sys.argv[1]
3384710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            host = sys.argv[2]
3394710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm            port = ''
3404710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        browser(selector, host, port)
3414710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    elif sys.argv[1:]:
3424710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        browser('', sys.argv[1])
3434710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm    else:
3444710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm        browser()
3454710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm
3464710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylm# Call the test program as a main program
3474710c53dcad1ebf3755f3efb9e80ac24bd72a9b2darylmtest()
348