347 lines · 9.6 KB · Python
RawDownload
#! /usr/bin/env python # A simple gopher client.## Usage: gopher [ [selector] host [port] ] import stringimport sysimport osimport socket # Default selector, host and portDEF_SELECTOR = ''DEF_HOST     = 'gopher.micro.umn.edu'DEF_PORT     = 70 # Recognized file typesT_TEXTFILE  = '0'T_MENU      = '1'T_CSO       = '2'T_ERROR     = '3'T_BINHEX    = '4'T_DOS       = '5'T_UUENCODE  = '6'T_SEARCH    = '7'T_TELNET    = '8'T_BINARY    = '9'T_REDUNDANT = '+'T_SOUND     = 's' # Dictionary mapping types to stringstypename = {'0': '<TEXT>', '1': '<DIR>', '2': '<CSO>', '3': '<ERROR>', \        '4': '<BINHEX>', '5': '<DOS>', '6': '<UUENCODE>', '7': '<SEARCH>', \        '8': '<TELNET>', '9': '<BINARY>', '+': '<REDUNDANT>', 's': '<SOUND>'} # Oft-used characters and stringsCRLF = '\r\n'TAB = '\t' # Open a TCP connection to a given host and portdef open_socket(host, port):    if not port:        port = DEF_PORT    elif type(port) == type(''):        port = string.atoi(port)    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)    s.connect((host, port))    return s # Send a selector to a given host and port, return a file with the replydef send_request(selector, host, port):    s = open_socket(host, port)    s.send(selector + CRLF)    s.shutdown(1)    return s.makefile('r') # Get a menu in the form of a list of entriesdef get_menu(selector, host, port):    f = send_request(selector, host, port)    list = []    while 1:        line = f.readline()        if not line:            print '(Unexpected EOF from server)'            break        if line[-2:] == CRLF:            line = line[:-2]        elif line[-1:] in CRLF:            line = line[:-1]        if line == '.':            break        if not line:            print '(Empty line from server)'            continue        typechar = line[0]        parts = string.splitfields(line[1:], TAB)        if len(parts) < 4:            print '(Bad line from server: %r)' % (line,)            continue        if len(parts) > 4:            print '(Extra info from server: %r)' % (parts[4:],)        parts.insert(0, typechar)        list.append(parts)    f.close()    return list # Get a text file as a list of lines, with trailing CRLF strippeddef get_textfile(selector, host, port):    list = []    get_alt_textfile(selector, host, port, list.append)    return list # Get a text file and pass each line to a function, with trailing CRLF strippeddef get_alt_textfile(selector, host, port, func):    f = send_request(selector, host, port)    while 1:        line = f.readline()        if not line:            print '(Unexpected EOF from server)'            break        if line[-2:] == CRLF:            line = line[:-2]        elif line[-1:] in CRLF:            line = line[:-1]        if line == '.':            break        if line[:2] == '..':            line = line[1:]        func(line)    f.close() # Get a binary file as one solid data blockdef get_binary(selector, host, port):    f = send_request(selector, host, port)    data = f.read()    f.close()    return data # Get a binary file and pass each block to a functiondef get_alt_binary(selector, host, port, func, blocksize):    f = send_request(selector, host, port)    while 1:        data = f.read(blocksize)        if not data:            break        func(data) # A *very* simple interactive browser # Browser main command, has default argumentsdef browser(*args):    selector = DEF_SELECTOR    host = DEF_HOST    port = DEF_PORT    n = len(args)    if n > 0 and args[0]:        selector = args[0]    if n > 1 and args[1]:        host = args[1]    if n > 2 and args[2]:        port = args[2]    if n > 3:        raise RuntimeError, 'too many args'    try:        browse_menu(selector, host, port)    except socket.error, msg:        print 'Socket error:', msg        sys.exit(1)    except KeyboardInterrupt:        print '\n[Goodbye]' # Browse a menudef browse_menu(selector, host, port):    list = get_menu(selector, host, port)    while 1:        print '----- MENU -----'        print 'Selector:', repr(selector)        print 'Host:', host, ' Port:', port        print        for i in range(len(list)):            item = list[i]            typechar, description = item[0], item[1]            print string.rjust(repr(i+1), 3) + ':', description,            if typename.has_key(typechar):                print typename[typechar]            else:                print '<TYPE=' + repr(typechar) + '>'        print        while 1:            try:                str = raw_input('Choice [CR == up a level]: ')            except EOFError:                print                return            if not str:                return            try:                choice = string.atoi(str)            except string.atoi_error:                print 'Choice must be a number; try again:'                continue            if not 0 < choice <= len(list):                print 'Choice out of range; try again:'                continue            break        item = list[choice-1]        typechar = item[0]        [i_selector, i_host, i_port] = item[2:5]        if typebrowser.has_key(typechar):            browserfunc = typebrowser[typechar]            try:                browserfunc(i_selector, i_host, i_port)            except (IOError, socket.error):                print '***', sys.exc_type, ':', sys.exc_value        else:            print 'Unsupported object type' # Browse a text filedef browse_textfile(selector, host, port):    x = None    try:        p = os.popen('${PAGER-more}', 'w')        x = SaveLines(p)        get_alt_textfile(selector, host, port, x.writeln)    except IOError, msg:        print 'IOError:', msg    if x:        x.close()    f = open_savefile()    if not f:        return    x = SaveLines(f)    try:        get_alt_textfile(selector, host, port, x.writeln)        print 'Done.'    except IOError, msg:        print 'IOError:', msg    x.close() # Browse a search indexdef browse_search(selector, host, port):    while 1:        print '----- SEARCH -----'        print 'Selector:', repr(selector)        print 'Host:', host, ' Port:', port        print        try:            query = raw_input('Query [CR == up a level]: ')        except EOFError:            print            break        query = string.strip(query)        if not query:            break        if '\t' in query:            print 'Sorry, queries cannot contain tabs'            continue        browse_menu(selector + TAB + query, host, port) # "Browse" telnet-based information, i.e. open a telnet sessiondef browse_telnet(selector, host, port):    if selector:        print 'Log in as', repr(selector)    if type(port) <> type(''):        port = repr(port)    sts = os.system('set -x; exec telnet ' + host + ' ' + port)    if sts:        print 'Exit status:', sts # "Browse" a binary file, i.e. save it to a filedef browse_binary(selector, host, port):    f = open_savefile()    if not f:        return    x = SaveWithProgress(f)    get_alt_binary(selector, host, port, x.write, 8*1024)    x.close() # "Browse" a sound file, i.e. play it or save itdef browse_sound(selector, host, port):    browse_binary(selector, host, port) # Dictionary mapping types to browser functionstypebrowser = {'0': browse_textfile, '1': browse_menu, \        '4': browse_binary, '5': browse_binary, '6': browse_textfile, \        '7': browse_search, \        '8': browse_telnet, '9': browse_binary, 's': browse_sound} # Class used to save lines, appending a newline to each lineclass SaveLines:    def __init__(self, f):        self.f = f    def writeln(self, line):        self.f.write(line + '\n')    def close(self):        sts = self.f.close()        if sts:            print 'Exit status:', sts # Class used to save data while showing progressclass SaveWithProgress:    def __init__(self, f):        self.f = f    def write(self, data):        sys.stdout.write('#')        sys.stdout.flush()        self.f.write(data)    def close(self):        print        sts = self.f.close()        if sts:            print 'Exit status:', sts # Ask for and open a save file, or return None if not to savedef open_savefile():    try:        savefile = raw_input( \    'Save as file [CR == don\'t save; |pipeline or ~user/... OK]: ')    except EOFError:        print        return None    savefile = string.strip(savefile)    if not savefile:        return None    if savefile[0] == '|':        cmd = string.strip(savefile[1:])        try:            p = os.popen(cmd, 'w')        except IOError, msg:            print repr(cmd), ':', msg            return None        print 'Piping through', repr(cmd), '...'        return p    if savefile[0] == '~':        savefile = os.path.expanduser(savefile)    try:        f = open(savefile, 'w')    except IOError, msg:        print repr(savefile), ':', msg        return None    print 'Saving to', repr(savefile), '...'    return f # Test programdef test():    if sys.argv[4:]:        print 'usage: gopher [ [selector] host [port] ]'        sys.exit(2)    elif sys.argv[3:]:        browser(sys.argv[1], sys.argv[2], sys.argv[3])    elif sys.argv[2:]:        try:            port = string.atoi(sys.argv[2])            selector = ''            host = sys.argv[1]        except string.atoi_error:            selector = sys.argv[1]            host = sys.argv[2]            port = ''        browser(selector, host, port)    elif sys.argv[1:]:        browser('', sys.argv[1])    else:        browser() # Call the test program as a main programtest()