home *** CD-ROM | disk | FTP | other *** search
/ OS/2 Shareware BBS: 10 Tools / 10-Tools.zip / pyos2bin.zip / Demo / sockets / gopher.py < prev    next >
Text File  |  1996-11-27  |  8KB  |  348 lines

  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:', `line`, ')'
  79.             continue
  80.         if len(parts) > 4:
  81.             print '(Extra info from server:', 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:', `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(`i+1`, 3) + ':', description,
  164.             if typename.has_key(typechar):
  165.                 print typename[typechar]
  166.             else:
  167.                 print '<TYPE=' + `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:', `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', `selector`
  244.     if type(port) <> type(''):
  245.         port = `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 `cmd`, ':', msg
  311.             return None
  312.         print 'Piping through', `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 `savefile`, ':', msg
  320.         return None
  321.     print 'Saving to', `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.