3 # A simple gopher client.
5 # Usage: gopher [ [selector] host [port] ]
12 # Default selector, host and port
14 DEF_HOST
= 'gopher.micro.umn.edu'
17 # Recognized file types
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>'}
36 # Oft-used characters and strings
40 # Open a TCP connection to a given host and port
41 def open_socket(host
, 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
))
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
)
55 return s
.makefile('r')
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
)
64 print '(Unexpected EOF from server)'
68 elif line
[-1:] in CRLF
:
73 print '(Empty line from server)'
76 parts
= string
.splitfields(line
[1:], TAB
)
78 print '(Bad line from server: %r)' % (line
,)
81 print '(Extra info from server: %r)' % (parts
[4:],)
82 parts
.insert(0, typechar
)
87 # Get a text file as a list of lines, with trailing CRLF stripped
88 def get_textfile(selector
, host
, port
):
90 get_alt_textfile(selector
, host
, port
, list.append
)
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
)
99 print '(Unexpected EOF from server)'
101 if line
[-2:] == CRLF
:
103 elif line
[-1:] in CRLF
:
112 # Get a binary file as one solid data block
113 def get_binary(selector
, host
, port
):
114 f
= send_request(selector
, host
, port
)
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
)
123 data
= f
.read(blocksize
)
128 # A *very* simple interactive browser
130 # Browser main command, has default arguments
132 selector
= DEF_SELECTOR
136 if n
> 0 and args
[0]:
138 if n
> 1 and args
[1]:
140 if n
> 2 and args
[2]:
143 raise RuntimeError, 'too many args'
145 browse_menu(selector
, host
, port
)
146 except socket
.error
, msg
:
147 print 'Socket error:', msg
149 except KeyboardInterrupt:
153 def browse_menu(selector
, host
, port
):
154 list = get_menu(selector
, host
, port
)
156 print '----- MENU -----'
157 print 'Selector:', repr(selector
)
158 print 'Host:', host
, ' Port:', port
160 for i
in range(len(list)):
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
]
167 print '<TYPE=' + repr(typechar
) + '>'
171 str = raw_input('Choice [CR == up a level]: ')
178 choice
= string
.atoi(str)
179 except string
.atoi_error
:
180 print 'Choice must be a number; try again:'
182 if not 0 < choice
<= len(list):
183 print 'Choice out of range; try again:'
186 item
= list[choice
-1]
188 [i_selector
, i_host
, i_port
] = item
[2:5]
189 if typebrowser
.has_key(typechar
):
190 browserfunc
= typebrowser
[typechar
]
192 browserfunc(i_selector
, i_host
, i_port
)
193 except (IOError, socket
.error
):
194 print '***', sys
.exc_type
, ':', sys
.exc_value
196 print 'Unsupported object type'
199 def browse_textfile(selector
, host
, port
):
202 p
= os
.popen('${PAGER-more}', 'w')
204 get_alt_textfile(selector
, host
, port
, x
.writeln
)
206 print 'IOError:', msg
214 get_alt_textfile(selector
, host
, port
, x
.writeln
)
217 print 'IOError:', msg
220 # Browse a search index
221 def browse_search(selector
, host
, port
):
223 print '----- SEARCH -----'
224 print 'Selector:', repr(selector
)
225 print 'Host:', host
, ' Port:', port
228 query
= raw_input('Query [CR == up a level]: ')
232 query
= string
.strip(query
)
236 print 'Sorry, queries cannot contain tabs'
238 browse_menu(selector
+ TAB
+ query
, host
, port
)
240 # "Browse" telnet-based information, i.e. open a telnet session
241 def browse_telnet(selector
, host
, port
):
243 print 'Log in as', repr(selector
)
244 if type(port
) <> type(''):
246 sts
= os
.system('set -x; exec telnet ' + host
+ ' ' + port
)
248 print 'Exit status:', sts
250 # "Browse" a binary file, i.e. save it to a file
251 def browse_binary(selector
, host
, port
):
255 x
= SaveWithProgress(f
)
256 get_alt_binary(selector
, host
, port
, x
.write
, 8*1024)
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
)
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
}
269 # Class used to save lines, appending a newline to each line
271 def __init__(self
, f
):
273 def writeln(self
, line
):
274 self
.f
.write(line
+ '\n')
278 print 'Exit status:', sts
280 # Class used to save data while showing progress
281 class SaveWithProgress
:
282 def __init__(self
, f
):
284 def write(self
, data
):
285 sys
.stdout
.write('#')
292 print 'Exit status:', sts
294 # Ask for and open a save file, or return None if not to save
297 savefile
= raw_input( \
298 'Save as file [CR == don\'t save; |pipeline or ~user/... OK]: ')
302 savefile
= string
.strip(savefile
)
305 if savefile
[0] == '|':
306 cmd
= string
.strip(savefile
[1:])
308 p
= os
.popen(cmd
, 'w')
310 print repr(cmd
), ':', msg
312 print 'Piping through', repr(cmd
), '...'
314 if savefile
[0] == '~':
315 savefile
= os
.path
.expanduser(savefile
)
317 f
= open(savefile
, 'w')
319 print repr(savefile
), ':', msg
321 print 'Saving to', repr(savefile
), '...'
327 print 'usage: gopher [ [selector] host [port] ]'
330 browser(sys
.argv
[1], sys
.argv
[2], sys
.argv
[3])
333 port
= string
.atoi(sys
.argv
[2])
336 except string
.atoi_error
:
337 selector
= sys
.argv
[1]
340 browser(selector
, host
, port
)
342 browser('', sys
.argv
[1])
346 # Call the test program as a main program