blob: 0635ac17c3a5943ecef200eb07da0dc2996d2e7d [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:
63 print '(Unexpected EOF from server)'
64 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:
72 print '(Empty line from server)'
73 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:
77 print '(Bad line from server: %r)' % (line,)
78 continue
79 if len(parts) > 4:
80 print '(Extra info from server: %r)' % (parts[4:],)
81 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:
98 print '(Unexpected EOF from server)'
99 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:
142 raise RuntimeError, 'too many args'
143 try:
144 browse_menu(selector, host, port)
Guido van Rossumb940e112007-01-10 16:19:56 +0000145 except socket.error as msg:
Tim Peterse6ddc8b2004-07-18 05:56:09 +0000146 print 'Socket error:', msg
147 sys.exit(1)
148 except KeyboardInterrupt:
149 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:
155 print '----- MENU -----'
156 print 'Selector:', repr(selector)
157 print 'Host:', host, ' Port:', port
158 print
159 for i in range(len(list)):
160 item = list[i]
161 typechar, description = item[0], item[1]
Neal Norwitzce96f692006-03-17 06:49:51 +0000162 print repr(i+1).rjust(3) + ':', description,
Tim Peterse6ddc8b2004-07-18 05:56:09 +0000163 if typename.has_key(typechar):
164 print typename[typechar]
165 else:
166 print '<TYPE=' + repr(typechar) + '>'
167 print
168 while 1:
169 try:
170 str = raw_input('Choice [CR == up a level]: ')
171 except EOFError:
172 print
173 return
174 if not str:
175 return
176 try:
Neal Norwitzce96f692006-03-17 06:49:51 +0000177 choice = int(str)
178 except ValueError:
Tim Peterse6ddc8b2004-07-18 05:56:09 +0000179 print 'Choice must be a number; try again:'
180 continue
181 if not 0 < choice <= len(list):
182 print 'Choice out of range; try again:'
183 continue
184 break
185 item = list[choice-1]
186 typechar = item[0]
187 [i_selector, i_host, i_port] = item[2:5]
188 if typebrowser.has_key(typechar):
189 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()
194 print '***', t, ':', v
Tim Peterse6ddc8b2004-07-18 05:56:09 +0000195 else:
196 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:
Tim Peterse6ddc8b2004-07-18 05:56:09 +0000206 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.'
Guido van Rossumb940e112007-01-10 16:19:56 +0000216 except IOError as msg:
Tim Peterse6ddc8b2004-07-18 05:56:09 +0000217 print 'IOError:', msg
218 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:
228 print '----- SEARCH -----'
229 print 'Selector:', repr(selector)
230 print 'Host:', host, ' Port:', port
231 print
232 try:
233 query = raw_input('Query [CR == up a level]: ')
234 except EOFError:
235 print
236 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:
241 print 'Sorry, queries cannot contain tabs'
242 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:
248 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:
253 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:
283 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):
294 print
295 sts = self.f.close()
296 if sts:
297 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:
302 savefile = raw_input( \
303 'Save as file [CR == don\'t save; |pipeline or ~user/... OK]: ')
304 except EOFError:
305 print
306 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:
Tim Peterse6ddc8b2004-07-18 05:56:09 +0000315 print repr(cmd), ':', msg
316 return None
317 print 'Piping through', repr(cmd), '...'
318 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:
Tim Peterse6ddc8b2004-07-18 05:56:09 +0000324 print repr(savefile), ':', msg
325 return None
326 print 'Saving to', repr(savefile), '...'
327 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:]:
332 print 'usage: gopher [ [selector] host [port] ]'
333 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()