blob: 34bcdf0cc21f47eb8d94e170e66143b0b91dc831 [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
7import string
8import sys
9import os
10import socket
11
12# Default selector, host and port
13DEF_SELECTOR = ''
14DEF_HOST = 'gopher.micro.umn.edu'
15DEF_PORT = 70
16
17# Recognized file types
18T_TEXTFILE = '0'
19T_MENU = '1'
20T_CSO = '2'
21T_ERROR = '3'
22T_BINHEX = '4'
23T_DOS = '5'
24T_UUENCODE = '6'
25T_SEARCH = '7'
26T_TELNET = '8'
27T_BINARY = '9'
28T_REDUNDANT = '+'
29T_SOUND = 's'
30
31# Dictionary mapping types to strings
32typename = {'0': '<TEXT>', '1': '<DIR>', '2': '<CSO>', '3': '<ERROR>', \
Tim Peterse6ddc8b2004-07-18 05:56:09 +000033 '4': '<BINHEX>', '5': '<DOS>', '6': '<UUENCODE>', '7': '<SEARCH>', \
34 '8': '<TELNET>', '9': '<BINARY>', '+': '<REDUNDANT>', 's': '<SOUND>'}
Guido van Rossum6b8d6991992-10-25 19:18:11 +000035
36# Oft-used characters and strings
37CRLF = '\r\n'
38TAB = '\t'
39
40# Open a TCP connection to a given host and port
41def open_socket(host, port):
Tim Peterse6ddc8b2004-07-18 05:56:09 +000042 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
Guido van Rossum6b8d6991992-10-25 19:18:11 +000049
50# Send a selector to a given host and port, return a file with the reply
51def send_request(selector, host, port):
Tim Peterse6ddc8b2004-07-18 05:56:09 +000052 s = open_socket(host, port)
53 s.send(selector + CRLF)
54 s.shutdown(1)
55 return s.makefile('r')
Guido van Rossum6b8d6991992-10-25 19:18:11 +000056
57# Get a menu in the form of a list of entries
58def get_menu(selector, host, port):
Tim Peterse6ddc8b2004-07-18 05:56:09 +000059 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
Guido van Rossum6b8d6991992-10-25 19:18:11 +000086
87# Get a text file as a list of lines, with trailing CRLF stripped
88def get_textfile(selector, host, port):
Tim Peterse6ddc8b2004-07-18 05:56:09 +000089 list = []
90 get_alt_textfile(selector, host, port, list.append)
91 return list
Guido van Rossum6b8d6991992-10-25 19:18:11 +000092
93# Get a text file and pass each line to a function, with trailing CRLF stripped
94def get_alt_textfile(selector, host, port, func):
Tim Peterse6ddc8b2004-07-18 05:56:09 +000095 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()
Guido van Rossum6b8d6991992-10-25 19:18:11 +0000111
112# Get a binary file as one solid data block
113def get_binary(selector, host, port):
Tim Peterse6ddc8b2004-07-18 05:56:09 +0000114 f = send_request(selector, host, port)
115 data = f.read()
116 f.close()
117 return data
Guido van Rossum6b8d6991992-10-25 19:18:11 +0000118
119# Get a binary file and pass each block to a function
120def get_alt_binary(selector, host, port, func, blocksize):
Tim Peterse6ddc8b2004-07-18 05:56:09 +0000121 f = send_request(selector, host, port)
122 while 1:
123 data = f.read(blocksize)
124 if not data:
125 break
126 func(data)
Guido van Rossum6b8d6991992-10-25 19:18:11 +0000127
128# A *very* simple interactive browser
129
130# Browser main command, has default arguments
131def browser(*args):
Tim Peterse6ddc8b2004-07-18 05:56:09 +0000132 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]'
Guido van Rossum6b8d6991992-10-25 19:18:11 +0000151
152# Browse a menu
153def browse_menu(selector, host, port):
Tim Peterse6ddc8b2004-07-18 05:56:09 +0000154 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):
Neal Norwitzac3625f2006-03-17 05:49:33 +0000194 t, v, tb = sys.exc_info()
195 print '***', t, ':', v
Tim Peterse6ddc8b2004-07-18 05:56:09 +0000196 else:
197 print 'Unsupported object type'
Guido van Rossum6b8d6991992-10-25 19:18:11 +0000198
199# Browse a text file
200def browse_textfile(selector, host, port):
Tim Peterse6ddc8b2004-07-18 05:56:09 +0000201 x = None
202 try:
203 p = os.popen('${PAGER-more}', 'w')
204 x = SaveLines(p)
205 get_alt_textfile(selector, host, port, x.writeln)
206 except IOError, msg:
207 print 'IOError:', msg
208 if x:
209 x.close()
210 f = open_savefile()
211 if not f:
212 return
213 x = SaveLines(f)
214 try:
215 get_alt_textfile(selector, host, port, x.writeln)
216 print 'Done.'
217 except IOError, msg:
218 print 'IOError:', msg
219 x.close()
Guido van Rossum6b8d6991992-10-25 19:18:11 +0000220
221# Browse a search index
222def browse_search(selector, host, port):
Tim Peterse6ddc8b2004-07-18 05:56:09 +0000223 while 1:
224 print '----- SEARCH -----'
225 print 'Selector:', repr(selector)
226 print 'Host:', host, ' Port:', port
227 print
228 try:
229 query = raw_input('Query [CR == up a level]: ')
230 except EOFError:
231 print
232 break
233 query = string.strip(query)
234 if not query:
235 break
236 if '\t' in query:
237 print 'Sorry, queries cannot contain tabs'
238 continue
239 browse_menu(selector + TAB + query, host, port)
Guido van Rossum6b8d6991992-10-25 19:18:11 +0000240
241# "Browse" telnet-based information, i.e. open a telnet session
242def browse_telnet(selector, host, port):
Tim Peterse6ddc8b2004-07-18 05:56:09 +0000243 if selector:
244 print 'Log in as', repr(selector)
245 if type(port) <> type(''):
246 port = repr(port)
247 sts = os.system('set -x; exec telnet ' + host + ' ' + port)
248 if sts:
249 print 'Exit status:', sts
Guido van Rossum6b8d6991992-10-25 19:18:11 +0000250
251# "Browse" a binary file, i.e. save it to a file
252def browse_binary(selector, host, port):
Tim Peterse6ddc8b2004-07-18 05:56:09 +0000253 f = open_savefile()
254 if not f:
255 return
256 x = SaveWithProgress(f)
257 get_alt_binary(selector, host, port, x.write, 8*1024)
258 x.close()
Guido van Rossum6b8d6991992-10-25 19:18:11 +0000259
260# "Browse" a sound file, i.e. play it or save it
261def browse_sound(selector, host, port):
Tim Peterse6ddc8b2004-07-18 05:56:09 +0000262 browse_binary(selector, host, port)
Guido van Rossum6b8d6991992-10-25 19:18:11 +0000263
264# Dictionary mapping types to browser functions
265typebrowser = {'0': browse_textfile, '1': browse_menu, \
Tim Peterse6ddc8b2004-07-18 05:56:09 +0000266 '4': browse_binary, '5': browse_binary, '6': browse_textfile, \
267 '7': browse_search, \
268 '8': browse_telnet, '9': browse_binary, 's': browse_sound}
Guido van Rossum6b8d6991992-10-25 19:18:11 +0000269
270# Class used to save lines, appending a newline to each line
271class SaveLines:
Tim Peterse6ddc8b2004-07-18 05:56:09 +0000272 def __init__(self, f):
273 self.f = f
274 def writeln(self, line):
275 self.f.write(line + '\n')
276 def close(self):
277 sts = self.f.close()
278 if sts:
279 print 'Exit status:', sts
Guido van Rossum6b8d6991992-10-25 19:18:11 +0000280
281# Class used to save data while showing progress
282class SaveWithProgress:
Tim Peterse6ddc8b2004-07-18 05:56:09 +0000283 def __init__(self, f):
284 self.f = f
285 def write(self, data):
286 sys.stdout.write('#')
287 sys.stdout.flush()
288 self.f.write(data)
289 def close(self):
290 print
291 sts = self.f.close()
292 if sts:
293 print 'Exit status:', sts
Guido van Rossum6b8d6991992-10-25 19:18:11 +0000294
295# Ask for and open a save file, or return None if not to save
296def open_savefile():
Tim Peterse6ddc8b2004-07-18 05:56:09 +0000297 try:
298 savefile = raw_input( \
299 'Save as file [CR == don\'t save; |pipeline or ~user/... OK]: ')
300 except EOFError:
301 print
302 return None
303 savefile = string.strip(savefile)
304 if not savefile:
305 return None
306 if savefile[0] == '|':
307 cmd = string.strip(savefile[1:])
308 try:
309 p = os.popen(cmd, 'w')
310 except IOError, msg:
311 print repr(cmd), ':', msg
312 return None
313 print 'Piping through', repr(cmd), '...'
314 return p
315 if savefile[0] == '~':
316 savefile = os.path.expanduser(savefile)
317 try:
318 f = open(savefile, 'w')
319 except IOError, msg:
320 print repr(savefile), ':', msg
321 return None
322 print 'Saving to', repr(savefile), '...'
323 return f
Guido van Rossum6b8d6991992-10-25 19:18:11 +0000324
325# Test program
326def test():
Tim Peterse6ddc8b2004-07-18 05:56:09 +0000327 if sys.argv[4:]:
328 print 'usage: gopher [ [selector] host [port] ]'
329 sys.exit(2)
330 elif sys.argv[3:]:
331 browser(sys.argv[1], sys.argv[2], sys.argv[3])
332 elif sys.argv[2:]:
333 try:
334 port = string.atoi(sys.argv[2])
335 selector = ''
336 host = sys.argv[1]
337 except string.atoi_error:
338 selector = sys.argv[1]
339 host = sys.argv[2]
340 port = ''
341 browser(selector, host, port)
342 elif sys.argv[1:]:
343 browser('', sys.argv[1])
344 else:
345 browser()
Guido van Rossum6b8d6991992-10-25 19:18:11 +0000346
347# Call the test program as a main program
348test()