blob: c287319c2ea1869c651ba06886f92c1017d7394a [file] [log] [blame]
Guido van Rossumf06ee5f1996-11-27 19:52:01 +00001#! /usr/bin/env python
Guido van Rossum6b8d6991992-10-25 19:18:11 +00002
3# A simple gopher client.
4#
5# Usage: gopher [ [selector] host [port] ]
6
Guido van Rossum6b8d6991992-10-25 19:18:11 +00007import sys
8import os
9import socket
10
11# Default selector, host and port
12DEF_SELECTOR = ''
13DEF_HOST = 'gopher.micro.umn.edu'
14DEF_PORT = 70
15
16# Recognized file types
17T_TEXTFILE = '0'
18T_MENU = '1'
19T_CSO = '2'
20T_ERROR = '3'
21T_BINHEX = '4'
22T_DOS = '5'
23T_UUENCODE = '6'
24T_SEARCH = '7'
25T_TELNET = '8'
26T_BINARY = '9'
27T_REDUNDANT = '+'
28T_SOUND = 's'
29
30# Dictionary mapping types to strings
31typename = {'0': '<TEXT>', '1': '<DIR>', '2': '<CSO>', '3': '<ERROR>', \
Tim Peterse6ddc8b2004-07-18 05:56:09 +000032 '4': '<BINHEX>', '5': '<DOS>', '6': '<UUENCODE>', '7': '<SEARCH>', \
33 '8': '<TELNET>', '9': '<BINARY>', '+': '<REDUNDANT>', 's': '<SOUND>'}
Guido van Rossum6b8d6991992-10-25 19:18:11 +000034
35# Oft-used characters and strings
36CRLF = '\r\n'
37TAB = '\t'
38
39# Open a TCP connection to a given host and port
40def open_socket(host, port):
Tim Peterse6ddc8b2004-07-18 05:56:09 +000041 if not port:
42 port = DEF_PORT
43 elif type(port) == type(''):
Neal Norwitzce96f692006-03-17 06:49:51 +000044 port = int(port)
Tim Peterse6ddc8b2004-07-18 05:56:09 +000045 s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
46 s.connect((host, port))
47 return s
Guido van Rossum6b8d6991992-10-25 19:18:11 +000048
49# Send a selector to a given host and port, return a file with the reply
50def send_request(selector, host, port):
Tim Peterse6ddc8b2004-07-18 05:56:09 +000051 s = open_socket(host, port)
52 s.send(selector + CRLF)
53 s.shutdown(1)
54 return s.makefile('r')
Guido van Rossum6b8d6991992-10-25 19:18:11 +000055
56# Get a menu in the form of a list of entries
57def get_menu(selector, host, port):
Tim Peterse6ddc8b2004-07-18 05:56:09 +000058 f = send_request(selector, host, port)
59 list = []
60 while 1:
61 line = f.readline()
62 if not line:
Collin Winter6f2df4d2007-07-17 20:59:35 +000063 print('(Unexpected EOF from server)')
Tim Peterse6ddc8b2004-07-18 05:56:09 +000064 break
65 if line[-2:] == CRLF:
66 line = line[:-2]
67 elif line[-1:] in CRLF:
68 line = line[:-1]
69 if line == '.':
70 break
71 if not line:
Collin Winter6f2df4d2007-07-17 20:59:35 +000072 print('(Empty line from server)')
Tim Peterse6ddc8b2004-07-18 05:56:09 +000073 continue
74 typechar = line[0]
Neal Norwitzce96f692006-03-17 06:49:51 +000075 parts = line[1:].split(TAB)
Tim Peterse6ddc8b2004-07-18 05:56:09 +000076 if len(parts) < 4:
Collin Winter6f2df4d2007-07-17 20:59:35 +000077 print('(Bad line from server: %r)' % (line,))
Tim Peterse6ddc8b2004-07-18 05:56:09 +000078 continue
79 if len(parts) > 4:
Collin Winter6f2df4d2007-07-17 20:59:35 +000080 print('(Extra info from server: %r)' % (parts[4:],))
Tim Peterse6ddc8b2004-07-18 05:56:09 +000081 parts.insert(0, typechar)
82 list.append(parts)
83 f.close()
84 return list
Guido van Rossum6b8d6991992-10-25 19:18:11 +000085
86# Get a text file as a list of lines, with trailing CRLF stripped
87def get_textfile(selector, host, port):
Tim Peterse6ddc8b2004-07-18 05:56:09 +000088 list = []
89 get_alt_textfile(selector, host, port, list.append)
90 return list
Guido van Rossum6b8d6991992-10-25 19:18:11 +000091
92# Get a text file and pass each line to a function, with trailing CRLF stripped
93def get_alt_textfile(selector, host, port, func):
Tim Peterse6ddc8b2004-07-18 05:56:09 +000094 f = send_request(selector, host, port)
95 while 1:
96 line = f.readline()
97 if not line:
Collin Winter6f2df4d2007-07-17 20:59:35 +000098 print('(Unexpected EOF from server)')
Tim Peterse6ddc8b2004-07-18 05:56:09 +000099 break
100 if line[-2:] == CRLF:
101 line = line[:-2]
102 elif line[-1:] in CRLF:
103 line = line[:-1]
104 if line == '.':
105 break
106 if line[:2] == '..':
107 line = line[1:]
108 func(line)
109 f.close()
Guido van Rossum6b8d6991992-10-25 19:18:11 +0000110
111# Get a binary file as one solid data block
112def get_binary(selector, host, port):
Tim Peterse6ddc8b2004-07-18 05:56:09 +0000113 f = send_request(selector, host, port)
114 data = f.read()
115 f.close()
116 return data
Guido van Rossum6b8d6991992-10-25 19:18:11 +0000117
118# Get a binary file and pass each block to a function
119def get_alt_binary(selector, host, port, func, blocksize):
Tim Peterse6ddc8b2004-07-18 05:56:09 +0000120 f = send_request(selector, host, port)
121 while 1:
122 data = f.read(blocksize)
123 if not data:
124 break
125 func(data)
Guido van Rossum6b8d6991992-10-25 19:18:11 +0000126
127# A *very* simple interactive browser
128
129# Browser main command, has default arguments
130def browser(*args):
Tim Peterse6ddc8b2004-07-18 05:56:09 +0000131 selector = DEF_SELECTOR
132 host = DEF_HOST
133 port = DEF_PORT
134 n = len(args)
135 if n > 0 and args[0]:
136 selector = args[0]
137 if n > 1 and args[1]:
138 host = args[1]
139 if n > 2 and args[2]:
140 port = args[2]
141 if n > 3:
Collin Winter6f2df4d2007-07-17 20:59:35 +0000142 raise RuntimeError('too many args')
Tim Peterse6ddc8b2004-07-18 05:56:09 +0000143 try:
144 browse_menu(selector, host, port)
Guido van Rossumb940e112007-01-10 16:19:56 +0000145 except socket.error as msg:
Collin Winter6f2df4d2007-07-17 20:59:35 +0000146 print('Socket error:', msg)
Tim Peterse6ddc8b2004-07-18 05:56:09 +0000147 sys.exit(1)
148 except KeyboardInterrupt:
Collin Winter6f2df4d2007-07-17 20:59:35 +0000149 print('\n[Goodbye]')
Guido van Rossum6b8d6991992-10-25 19:18:11 +0000150
151# Browse a menu
152def browse_menu(selector, host, port):
Tim Peterse6ddc8b2004-07-18 05:56:09 +0000153 list = get_menu(selector, host, port)
154 while 1:
Collin Winter6f2df4d2007-07-17 20:59:35 +0000155 print('----- MENU -----')
156 print('Selector:', repr(selector))
157 print('Host:', host, ' Port:', port)
158 print()
Tim Peterse6ddc8b2004-07-18 05:56:09 +0000159 for i in range(len(list)):
160 item = list[i]
161 typechar, description = item[0], item[1]
Collin Winter6f2df4d2007-07-17 20:59:35 +0000162 print(repr(i+1).rjust(3) + ':', description, end=' ')
163 if typechar in typename:
164 print(typename[typechar])
Tim Peterse6ddc8b2004-07-18 05:56:09 +0000165 else:
Collin Winter6f2df4d2007-07-17 20:59:35 +0000166 print('<TYPE=' + repr(typechar) + '>')
167 print()
Tim Peterse6ddc8b2004-07-18 05:56:09 +0000168 while 1:
169 try:
Collin Winter6f2df4d2007-07-17 20:59:35 +0000170 str = input('Choice [CR == up a level]: ')
Tim Peterse6ddc8b2004-07-18 05:56:09 +0000171 except EOFError:
Collin Winter6f2df4d2007-07-17 20:59:35 +0000172 print()
Tim Peterse6ddc8b2004-07-18 05:56:09 +0000173 return
174 if not str:
175 return
176 try:
Neal Norwitzce96f692006-03-17 06:49:51 +0000177 choice = int(str)
178 except ValueError:
Collin Winter6f2df4d2007-07-17 20:59:35 +0000179 print('Choice must be a number; try again:')
Tim Peterse6ddc8b2004-07-18 05:56:09 +0000180 continue
181 if not 0 < choice <= len(list):
Collin Winter6f2df4d2007-07-17 20:59:35 +0000182 print('Choice out of range; try again:')
Tim Peterse6ddc8b2004-07-18 05:56:09 +0000183 continue
184 break
185 item = list[choice-1]
186 typechar = item[0]
187 [i_selector, i_host, i_port] = item[2:5]
Collin Winter6f2df4d2007-07-17 20:59:35 +0000188 if typechar in typebrowser:
Tim Peterse6ddc8b2004-07-18 05:56:09 +0000189 browserfunc = typebrowser[typechar]
190 try:
191 browserfunc(i_selector, i_host, i_port)
192 except (IOError, socket.error):
Neal Norwitzac3625f2006-03-17 05:49:33 +0000193 t, v, tb = sys.exc_info()
Collin Winter6f2df4d2007-07-17 20:59:35 +0000194 print('***', t, ':', v)
Tim Peterse6ddc8b2004-07-18 05:56:09 +0000195 else:
Collin Winter6f2df4d2007-07-17 20:59:35 +0000196 print('Unsupported object type')
Guido van Rossum6b8d6991992-10-25 19:18:11 +0000197
198# Browse a text file
199def browse_textfile(selector, host, port):
Tim Peterse6ddc8b2004-07-18 05:56:09 +0000200 x = None
201 try:
202 p = os.popen('${PAGER-more}', 'w')
203 x = SaveLines(p)
204 get_alt_textfile(selector, host, port, x.writeln)
Guido van Rossumb940e112007-01-10 16:19:56 +0000205 except IOError as msg:
Collin Winter6f2df4d2007-07-17 20:59:35 +0000206 print('IOError:', msg)
Tim Peterse6ddc8b2004-07-18 05:56:09 +0000207 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)
Collin Winter6f2df4d2007-07-17 20:59:35 +0000215 print('Done.')
Guido van Rossumb940e112007-01-10 16:19:56 +0000216 except IOError as msg:
Collin Winter6f2df4d2007-07-17 20:59:35 +0000217 print('IOError:', msg)
Tim Peterse6ddc8b2004-07-18 05:56:09 +0000218 x.close()
Guido van Rossum6b8d6991992-10-25 19:18:11 +0000219
Neal Norwitzce96f692006-03-17 06:49:51 +0000220def raw_input(prompt):
221 sys.stdout.write(prompt)
222 sys.stdout.flush()
223 return sys.stdin.readline()
224
Guido van Rossum6b8d6991992-10-25 19:18:11 +0000225# Browse a search index
226def browse_search(selector, host, port):
Tim Peterse6ddc8b2004-07-18 05:56:09 +0000227 while 1:
Collin Winter6f2df4d2007-07-17 20:59:35 +0000228 print('----- SEARCH -----')
229 print('Selector:', repr(selector))
230 print('Host:', host, ' Port:', port)
231 print()
Tim Peterse6ddc8b2004-07-18 05:56:09 +0000232 try:
Collin Winter6f2df4d2007-07-17 20:59:35 +0000233 query = input('Query [CR == up a level]: ')
Tim Peterse6ddc8b2004-07-18 05:56:09 +0000234 except EOFError:
Collin Winter6f2df4d2007-07-17 20:59:35 +0000235 print()
Tim Peterse6ddc8b2004-07-18 05:56:09 +0000236 break
Neal Norwitzce96f692006-03-17 06:49:51 +0000237 query = query.strip()
Tim Peterse6ddc8b2004-07-18 05:56:09 +0000238 if not query:
239 break
240 if '\t' in query:
Collin Winter6f2df4d2007-07-17 20:59:35 +0000241 print('Sorry, queries cannot contain tabs')
Tim Peterse6ddc8b2004-07-18 05:56:09 +0000242 continue
243 browse_menu(selector + TAB + query, host, port)
Guido van Rossum6b8d6991992-10-25 19:18:11 +0000244
245# "Browse" telnet-based information, i.e. open a telnet session
246def browse_telnet(selector, host, port):
Tim Peterse6ddc8b2004-07-18 05:56:09 +0000247 if selector:
Collin Winter6f2df4d2007-07-17 20:59:35 +0000248 print('Log in as', repr(selector))
Neal Norwitz3bd844e2006-08-29 04:39:12 +0000249 if type(port) != type(''):
Tim Peterse6ddc8b2004-07-18 05:56:09 +0000250 port = repr(port)
251 sts = os.system('set -x; exec telnet ' + host + ' ' + port)
252 if sts:
Collin Winter6f2df4d2007-07-17 20:59:35 +0000253 print('Exit status:', sts)
Guido van Rossum6b8d6991992-10-25 19:18:11 +0000254
255# "Browse" a binary file, i.e. save it to a file
256def browse_binary(selector, host, port):
Tim Peterse6ddc8b2004-07-18 05:56:09 +0000257 f = open_savefile()
258 if not f:
259 return
260 x = SaveWithProgress(f)
261 get_alt_binary(selector, host, port, x.write, 8*1024)
262 x.close()
Guido van Rossum6b8d6991992-10-25 19:18:11 +0000263
264# "Browse" a sound file, i.e. play it or save it
265def browse_sound(selector, host, port):
Tim Peterse6ddc8b2004-07-18 05:56:09 +0000266 browse_binary(selector, host, port)
Guido van Rossum6b8d6991992-10-25 19:18:11 +0000267
268# Dictionary mapping types to browser functions
269typebrowser = {'0': browse_textfile, '1': browse_menu, \
Tim Peterse6ddc8b2004-07-18 05:56:09 +0000270 '4': browse_binary, '5': browse_binary, '6': browse_textfile, \
271 '7': browse_search, \
272 '8': browse_telnet, '9': browse_binary, 's': browse_sound}
Guido van Rossum6b8d6991992-10-25 19:18:11 +0000273
274# Class used to save lines, appending a newline to each line
275class SaveLines:
Tim Peterse6ddc8b2004-07-18 05:56:09 +0000276 def __init__(self, f):
277 self.f = f
278 def writeln(self, line):
279 self.f.write(line + '\n')
280 def close(self):
281 sts = self.f.close()
282 if sts:
Collin Winter6f2df4d2007-07-17 20:59:35 +0000283 print('Exit status:', sts)
Guido van Rossum6b8d6991992-10-25 19:18:11 +0000284
285# Class used to save data while showing progress
286class SaveWithProgress:
Tim Peterse6ddc8b2004-07-18 05:56:09 +0000287 def __init__(self, f):
288 self.f = f
289 def write(self, data):
290 sys.stdout.write('#')
291 sys.stdout.flush()
292 self.f.write(data)
293 def close(self):
Collin Winter6f2df4d2007-07-17 20:59:35 +0000294 print()
Tim Peterse6ddc8b2004-07-18 05:56:09 +0000295 sts = self.f.close()
296 if sts:
Collin Winter6f2df4d2007-07-17 20:59:35 +0000297 print('Exit status:', sts)
Guido van Rossum6b8d6991992-10-25 19:18:11 +0000298
299# Ask for and open a save file, or return None if not to save
300def open_savefile():
Tim Peterse6ddc8b2004-07-18 05:56:09 +0000301 try:
Collin Winter6f2df4d2007-07-17 20:59:35 +0000302 savefile = input( \
Tim Peterse6ddc8b2004-07-18 05:56:09 +0000303 'Save as file [CR == don\'t save; |pipeline or ~user/... OK]: ')
304 except EOFError:
Collin Winter6f2df4d2007-07-17 20:59:35 +0000305 print()
Tim Peterse6ddc8b2004-07-18 05:56:09 +0000306 return None
Neal Norwitzce96f692006-03-17 06:49:51 +0000307 savefile = savefile.strip()
Tim Peterse6ddc8b2004-07-18 05:56:09 +0000308 if not savefile:
309 return None
310 if savefile[0] == '|':
Neal Norwitzce96f692006-03-17 06:49:51 +0000311 cmd = savefile[1:].strip()
Tim Peterse6ddc8b2004-07-18 05:56:09 +0000312 try:
313 p = os.popen(cmd, 'w')
Guido van Rossumb940e112007-01-10 16:19:56 +0000314 except IOError as msg:
Collin Winter6f2df4d2007-07-17 20:59:35 +0000315 print(repr(cmd), ':', msg)
Tim Peterse6ddc8b2004-07-18 05:56:09 +0000316 return None
Collin Winter6f2df4d2007-07-17 20:59:35 +0000317 print('Piping through', repr(cmd), '...')
Tim Peterse6ddc8b2004-07-18 05:56:09 +0000318 return p
319 if savefile[0] == '~':
320 savefile = os.path.expanduser(savefile)
321 try:
322 f = open(savefile, 'w')
Guido van Rossumb940e112007-01-10 16:19:56 +0000323 except IOError as msg:
Collin Winter6f2df4d2007-07-17 20:59:35 +0000324 print(repr(savefile), ':', msg)
Tim Peterse6ddc8b2004-07-18 05:56:09 +0000325 return None
Collin Winter6f2df4d2007-07-17 20:59:35 +0000326 print('Saving to', repr(savefile), '...')
Tim Peterse6ddc8b2004-07-18 05:56:09 +0000327 return f
Guido van Rossum6b8d6991992-10-25 19:18:11 +0000328
329# Test program
330def test():
Tim Peterse6ddc8b2004-07-18 05:56:09 +0000331 if sys.argv[4:]:
Collin Winter6f2df4d2007-07-17 20:59:35 +0000332 print('usage: gopher [ [selector] host [port] ]')
Tim Peterse6ddc8b2004-07-18 05:56:09 +0000333 sys.exit(2)
334 elif sys.argv[3:]:
335 browser(sys.argv[1], sys.argv[2], sys.argv[3])
336 elif sys.argv[2:]:
337 try:
Neal Norwitzce96f692006-03-17 06:49:51 +0000338 port = int(sys.argv[2])
Tim Peterse6ddc8b2004-07-18 05:56:09 +0000339 selector = ''
340 host = sys.argv[1]
Neal Norwitzce96f692006-03-17 06:49:51 +0000341 except ValueError:
Tim Peterse6ddc8b2004-07-18 05:56:09 +0000342 selector = sys.argv[1]
343 host = sys.argv[2]
344 port = ''
345 browser(selector, host, port)
346 elif sys.argv[1:]:
347 browser('', sys.argv[1])
348 else:
349 browser()
Guido van Rossum6b8d6991992-10-25 19:18:11 +0000350
351# Call the test program as a main program
352test()