blob: d06c78a831b6c8159745dace55d27f2a4d9635cb [file] [log] [blame]
Guido van Rossum6b8d6991992-10-25 19:18:11 +00001#! /usr/local/bin/python
2
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>', \
33 '4': '<BINHEX>', '5': '<DOS>', '6': '<UUENCODE>', '7': '<SEARCH>', \
34 '8': '<TELNET>', '9': '<BINARY>', '+': '<REDUNDANT>', 's': '<SOUND>'}
35
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):
42 if type(port) == type(''):
43 port = string.atoi(port)
44 s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
45 s.connect((host, port))
46 return s
47
48# Send a selector to a given host and port, return a file with the reply
49def send_request(selector, host, port):
50 s = open_socket(host, port)
51 s.send(selector + CRLF)
52 s.shutdown(1)
53 return s.makefile('r')
54
55# Get a menu in the form of a list of entries
56def get_menu(selector, host, port):
57 f = send_request(selector, host, port)
58 list = []
59 while 1:
60 line = f.readline()
61 if not line:
62 print '(Unexpected EOF from server)'
63 break
64 if line[-2:] == CRLF:
65 line = line[:-2]
66 elif line[-1:] in CRLF:
67 line = line[:-1]
68 if line == '.':
69 break
70 if not line:
71 print '(Empty line from server)'
72 continue
73 typechar = line[0]
74 parts = string.splitfields(line[1:], TAB)
75 if len(parts) < 4:
76 print '(Bad line from server:', `line`, ')'
77 continue
78 if len(parts) > 4:
79 print '(Extra info from server:', parts[4:], ')'
80 parts.insert(0, typechar)
81 list.append(parts)
82 f.close()
83 return list
84
85# Get a text file as a list of lines, with trailing CRLF stripped
86def get_textfile(selector, host, port):
87 list = []
88 get_alt_textfile(selector, host, port, list.append)
89 return list
90
91# Get a text file and pass each line to a function, with trailing CRLF stripped
92def get_alt_textfile(selector, host, port, func):
93 f = send_request(selector, host, port)
94 while 1:
95 line = f.readline()
96 if not line:
97 print '(Unexpected EOF from server)'
98 break
99 if line[-2:] == CRLF:
100 line = line[:-2]
101 elif line[-1:] in CRLF:
102 line = line[:-1]
103 if line == '.':
104 break
105 if line[:2] == '..':
106 line = line[1:]
107 func(line)
108 f.close()
109
110# Get a binary file as one solid data block
111def get_binary(selector, host, port):
112 f = send_request(selector, host, port)
113 data = f.read()
114 f.close()
115 return data
116
117# Get a binary file and pass each block to a function
118def get_alt_binary(selector, host, port, func, blocksize):
119 f = send_request(selector, host, port)
120 while 1:
121 data = f.read(blocksize)
122 if not data:
123 break
124 func(data)
125
126# A *very* simple interactive browser
127
128# Browser main command, has default arguments
129def browser(*args):
130 selector = DEF_SELECTOR
131 host = DEF_HOST
132 port = DEF_PORT
133 n = len(args)
134 if n > 0 and args[0]:
135 selector = args[0]
136 if n > 1 and args[1]:
137 host = args[1]
138 if n > 2 and args[2]:
139 port = args[2]
140 if n > 3:
141 raise RuntimeError, 'too many args'
142 try:
143 browse_menu(selector, host, port)
144 except socket.error, msg:
145 print 'Socket error:', msg
146 sys.exit(1)
147 except KeyboardInterrupt:
148 print '\n[Goodbye]'
149
150# Browse a menu
151def browse_menu(selector, host, port):
152 list = get_menu(selector, host, port)
153 while 1:
154 print '----- MENU -----'
155 print 'Selector:', `selector`
156 print 'Host:', host, ' Port:', port
157 print
158 for i in range(len(list)):
159 item = list[i]
160 typechar, description = item[0], item[1]
161 print string.rjust(`i+1`, 3) + ':', description,
162 if typename.has_key(typechar):
163 print typename[typechar]
164 else:
165 print '<TYPE=' + `typechar` + '>'
166 print
167 while 1:
168 try:
169 str = raw_input('Choice [CR == up a level]: ')
170 except EOFError:
171 print
172 return
173 if not str:
174 return
175 try:
176 choice = string.atoi(str)
177 except string.atoi_error:
178 print 'Choice must be a number; try again:'
179 continue
180 if not 0 < choice <= len(list):
181 print 'Choice out of range; try again:'
182 continue
183 break
184 item = list[choice-1]
185 typechar = item[0]
186 [i_selector, i_host, i_port] = item[2:5]
187 if typebrowser.has_key(typechar):
188 browserfunc = typebrowser[typechar]
189 try:
190 browserfunc(i_selector, i_host, i_port)
191 except (IOError, socket.error):
192 print '***', sys.exc_type, ':', sys.exc_value
193 else:
194 print 'Unsupported object type'
195
196# Browse a text file
197def browse_textfile(selector, host, port):
198 x = None
199 try:
200 p = os.popen('${PAGER-more}', 'w')
201 x = SaveLines().init(p)
202 get_alt_textfile(selector, host, port, x.writeln)
203 except IOError, msg:
204 print 'IOError:', msg
205 if x:
206 x.close()
207 f = open_savefile()
208 if not f:
209 return
210 x = SaveLines().init(f)
211 try:
212 get_alt_textfile(selector, host, port, x.writeln)
213 print 'Done.'
214 except IOError, msg:
215 print 'IOError:', msg
216 x.close()
217
218# Browse a search index
219def browse_search(selector, host, port):
220 while 1:
221 print '----- SEARCH -----'
222 print 'Selector:', `selector`
223 print 'Host:', host, ' Port:', port
224 print
225 try:
226 query = raw_input('Query [CR == up a level]: ')
227 except EOFError:
228 print
229 break
230 query = string.strip(query)
231 if not query:
232 break
233 if '\t' in query:
234 print 'Sorry, queries cannot contain tabs'
235 continue
236 browse_menu(selector + TAB + query, host, port)
237
238# "Browse" telnet-based information, i.e. open a telnet session
239def browse_telnet(selector, host, port):
240 if selector:
241 print 'Log in as', `selector`
242 if type(port) <> type(''):
243 port = `port`
244 sts = os.system('set -x; exec telnet ' + host + ' ' + port)
245 if sts:
246 print 'Exit status:', sts
247
248# "Browse" a binary file, i.e. save it to a file
249def browse_binary(selector, host, port):
250 f = open_savefile()
251 if not f:
252 return
253 x = SaveWithProgress().init(f)
254 get_alt_binary(selector, host, port, x.write, 8*1024)
255 x.close()
256
257# "Browse" a sound file, i.e. play it or save it
258def browse_sound(selector, host, port):
259 browse_binary(selector, host, port)
260
261# Dictionary mapping types to browser functions
262typebrowser = {'0': browse_textfile, '1': browse_menu, \
263 '4': browse_binary, '5': browse_binary, '6': browse_textfile, \
264 '7': browse_search, \
265 '8': browse_telnet, '9': browse_binary, 's': browse_sound}
266
267# Class used to save lines, appending a newline to each line
268class SaveLines:
269 def init(self, f):
270 self.f = f
271 return self
272 def writeln(self, line):
273 self.f.write(line + '\n')
274 def close(self):
275 sts = self.f.close()
276 if sts:
277 print 'Exit status:', sts
278
279# Class used to save data while showing progress
280class SaveWithProgress:
281 def init(self, f):
282 self.f = f
283 return self
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
295def 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
325def 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
347test()