Home | History | Annotate | Download | only in sockets
      1 #! /usr/bin/env python

      2 
      3 # A simple gopher client.

      4 #

      5 # Usage: gopher [ [selector] host [port] ]

      6 
      7 import string
      8 import sys
      9 import os
     10 import socket
     11 
     12 # Default selector, host and port

     13 DEF_SELECTOR = ''
     14 DEF_HOST     = 'gopher.micro.umn.edu'
     15 DEF_PORT     = 70
     16 
     17 # Recognized file types

     18 T_TEXTFILE  = '0'
     19 T_MENU      = '1'
     20 T_CSO       = '2'
     21 T_ERROR     = '3'
     22 T_BINHEX    = '4'
     23 T_DOS       = '5'
     24 T_UUENCODE  = '6'
     25 T_SEARCH    = '7'
     26 T_TELNET    = '8'
     27 T_BINARY    = '9'
     28 T_REDUNDANT = '+'
     29 T_SOUND     = 's'
     30 
     31 # Dictionary mapping types to strings

     32 typename = {'0': '<TEXT>', '1': '<DIR>', '2': '<CSO>', '3': '<ERROR>', \
     33         '4': '<BINHEX>', '5': '<DOS>', '6': '<UUENCODE>', '7': '<SEARCH>', \
     34         '8': '<TELNET>', '9': '<BINARY>', '+': '<REDUNDANT>', 's': '<SOUND>'}
     35 
     36 # Oft-used characters and strings

     37 CRLF = '\r\n'
     38 TAB = '\t'
     39 
     40 # Open a TCP connection to a given host and port

     41 def open_socket(host, port):
     42     if not port:
     43         port = DEF_PORT
     44     elif type(port) == type(''):
     45         port = string.atoi(port)
     46     s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
     47     s.connect((host, port))
     48     return s
     49 
     50 # Send a selector to a given host and port, return a file with the reply

     51 def send_request(selector, host, port):
     52     s = open_socket(host, port)
     53     s.send(selector + CRLF)
     54     s.shutdown(1)
     55     return s.makefile('r')
     56 
     57 # Get a menu in the form of a list of entries

     58 def get_menu(selector, host, port):
     59     f = send_request(selector, host, port)
     60     list = []
     61     while 1:
     62         line = f.readline()
     63         if not line:
     64             print '(Unexpected EOF from server)'
     65             break
     66         if line[-2:] == CRLF:
     67             line = line[:-2]
     68         elif line[-1:] in CRLF:
     69             line = line[:-1]
     70         if line == '.':
     71             break
     72         if not line:
     73             print '(Empty line from server)'
     74             continue
     75         typechar = line[0]
     76         parts = string.splitfields(line[1:], TAB)
     77         if len(parts) < 4:
     78             print '(Bad line from server: %r)' % (line,)
     79             continue
     80         if len(parts) > 4:
     81             print '(Extra info from server: %r)' % (parts[4:],)
     82         parts.insert(0, typechar)
     83         list.append(parts)
     84     f.close()
     85     return list
     86 
     87 # Get a text file as a list of lines, with trailing CRLF stripped

     88 def get_textfile(selector, host, port):
     89     list = []
     90     get_alt_textfile(selector, host, port, list.append)
     91     return list
     92 
     93 # Get a text file and pass each line to a function, with trailing CRLF stripped

     94 def get_alt_textfile(selector, host, port, func):
     95     f = send_request(selector, host, port)
     96     while 1:
     97         line = f.readline()
     98         if not line:
     99             print '(Unexpected EOF from server)'
    100             break
    101         if line[-2:] == CRLF:
    102             line = line[:-2]
    103         elif line[-1:] in CRLF:
    104             line = line[:-1]
    105         if line == '.':
    106             break
    107         if line[:2] == '..':
    108             line = line[1:]
    109         func(line)
    110     f.close()
    111 
    112 # Get a binary file as one solid data block

    113 def get_binary(selector, host, port):
    114     f = send_request(selector, host, port)
    115     data = f.read()
    116     f.close()
    117     return data
    118 
    119 # Get a binary file and pass each block to a function

    120 def get_alt_binary(selector, host, port, func, blocksize):
    121     f = send_request(selector, host, port)
    122     while 1:
    123         data = f.read(blocksize)
    124         if not data:
    125             break
    126         func(data)
    127 
    128 # A *very* simple interactive browser

    129 
    130 # Browser main command, has default arguments

    131 def browser(*args):
    132     selector = DEF_SELECTOR
    133     host = DEF_HOST
    134     port = DEF_PORT
    135     n = len(args)
    136     if n > 0 and args[0]:
    137         selector = args[0]
    138     if n > 1 and args[1]:
    139         host = args[1]
    140     if n > 2 and args[2]:
    141         port = args[2]
    142     if n > 3:
    143         raise RuntimeError, 'too many args'
    144     try:
    145         browse_menu(selector, host, port)
    146     except socket.error, msg:
    147         print 'Socket error:', msg
    148         sys.exit(1)
    149     except KeyboardInterrupt:
    150         print '\n[Goodbye]'
    151 
    152 # Browse a menu

    153 def browse_menu(selector, host, port):
    154     list = get_menu(selector, host, port)
    155     while 1:
    156         print '----- MENU -----'
    157         print 'Selector:', repr(selector)
    158         print 'Host:', host, ' Port:', port
    159         print
    160         for i in range(len(list)):
    161             item = list[i]
    162             typechar, description = item[0], item[1]
    163             print string.rjust(repr(i+1), 3) + ':', description,
    164             if typename.has_key(typechar):
    165                 print typename[typechar]
    166             else:
    167                 print '<TYPE=' + repr(typechar) + '>'
    168         print
    169         while 1:
    170             try:
    171                 str = raw_input('Choice [CR == up a level]: ')
    172             except EOFError:
    173                 print
    174                 return
    175             if not str:
    176                 return
    177             try:
    178                 choice = string.atoi(str)
    179             except string.atoi_error:
    180                 print 'Choice must be a number; try again:'
    181                 continue
    182             if not 0 < choice <= len(list):
    183                 print 'Choice out of range; try again:'
    184                 continue
    185             break
    186         item = list[choice-1]
    187         typechar = item[0]
    188         [i_selector, i_host, i_port] = item[2:5]
    189         if typebrowser.has_key(typechar):
    190             browserfunc = typebrowser[typechar]
    191             try:
    192                 browserfunc(i_selector, i_host, i_port)
    193             except (IOError, socket.error):
    194                 print '***', sys.exc_type, ':', sys.exc_value
    195         else:
    196             print 'Unsupported object type'
    197 
    198 # Browse a text file

    199 def browse_textfile(selector, host, port):
    200     x = None
    201     try:
    202         p = os.popen('${PAGER-more}', 'w')
    203         x = SaveLines(p)
    204         get_alt_textfile(selector, host, port, x.writeln)
    205     except IOError, msg:
    206         print 'IOError:', msg
    207     if x:
    208         x.close()
    209     f = open_savefile()
    210     if not f:
    211         return
    212     x = SaveLines(f)
    213     try:
    214         get_alt_textfile(selector, host, port, x.writeln)
    215         print 'Done.'
    216     except IOError, msg:
    217         print 'IOError:', msg
    218     x.close()
    219 
    220 # Browse a search index

    221 def browse_search(selector, host, port):
    222     while 1:
    223         print '----- SEARCH -----'
    224         print 'Selector:', repr(selector)
    225         print 'Host:', host, ' Port:', port
    226         print
    227         try:
    228             query = raw_input('Query [CR == up a level]: ')
    229         except EOFError:
    230             print
    231             break
    232         query = string.strip(query)
    233         if not query:
    234             break
    235         if '\t' in query:
    236             print 'Sorry, queries cannot contain tabs'
    237             continue
    238         browse_menu(selector + TAB + query, host, port)
    239 
    240 # "Browse" telnet-based information, i.e. open a telnet session

    241 def browse_telnet(selector, host, port):
    242     if selector:
    243         print 'Log in as', repr(selector)
    244     if type(port) <> type(''):
    245         port = repr(port)
    246     sts = os.system('set -x; exec telnet ' + host + ' ' + port)
    247     if sts:
    248         print 'Exit status:', sts
    249 
    250 # "Browse" a binary file, i.e. save it to a file

    251 def browse_binary(selector, host, port):
    252     f = open_savefile()
    253     if not f:
    254         return
    255     x = SaveWithProgress(f)
    256     get_alt_binary(selector, host, port, x.write, 8*1024)
    257     x.close()
    258 
    259 # "Browse" a sound file, i.e. play it or save it

    260 def browse_sound(selector, host, port):
    261     browse_binary(selector, host, port)
    262 
    263 # Dictionary mapping types to browser functions

    264 typebrowser = {'0': browse_textfile, '1': browse_menu, \
    265         '4': browse_binary, '5': browse_binary, '6': browse_textfile, \
    266         '7': browse_search, \
    267         '8': browse_telnet, '9': browse_binary, 's': browse_sound}
    268 
    269 # Class used to save lines, appending a newline to each line

    270 class SaveLines:
    271     def __init__(self, f):
    272         self.f = f
    273     def writeln(self, line):
    274         self.f.write(line + '\n')
    275     def close(self):
    276         sts = self.f.close()
    277         if sts:
    278             print 'Exit status:', sts
    279 
    280 # Class used to save data while showing progress

    281 class SaveWithProgress:
    282     def __init__(self, f):
    283         self.f = f
    284     def write(self, data):
    285         sys.stdout.write('#')
    286         sys.stdout.flush()
    287         self.f.write(data)
    288     def close(self):
    289         print
    290         sts = self.f.close()
    291         if sts:
    292             print 'Exit status:', sts
    293 
    294 # Ask for and open a save file, or return None if not to save

    295 def open_savefile():
    296     try:
    297         savefile = raw_input( \
    298     'Save as file [CR == don\'t save; |pipeline or ~user/... OK]: ')
    299     except EOFError:
    300         print
    301         return None
    302     savefile = string.strip(savefile)
    303     if not savefile:
    304         return None
    305     if savefile[0] == '|':
    306         cmd = string.strip(savefile[1:])
    307         try:
    308             p = os.popen(cmd, 'w')
    309         except IOError, msg:
    310             print repr(cmd), ':', msg
    311             return None
    312         print 'Piping through', repr(cmd), '...'
    313         return p
    314     if savefile[0] == '~':
    315         savefile = os.path.expanduser(savefile)
    316     try:
    317         f = open(savefile, 'w')
    318     except IOError, msg:
    319         print repr(savefile), ':', msg
    320         return None
    321     print 'Saving to', repr(savefile), '...'
    322     return f
    323 
    324 # Test program

    325 def test():
    326     if sys.argv[4:]:
    327         print 'usage: gopher [ [selector] host [port] ]'
    328         sys.exit(2)
    329     elif sys.argv[3:]:
    330         browser(sys.argv[1], sys.argv[2], sys.argv[3])
    331     elif sys.argv[2:]:
    332         try:
    333             port = string.atoi(sys.argv[2])
    334             selector = ''
    335             host = sys.argv[1]
    336         except string.atoi_error:
    337             selector = sys.argv[1]
    338             host = sys.argv[2]
    339             port = ''
    340         browser(selector, host, port)
    341     elif sys.argv[1:]:
    342         browser('', sys.argv[1])
    343     else:
    344         browser()
    345 
    346 # Call the test program as a main program

    347 test()
    348