3 # A simple gopher client.
5 # Usage: gopher [ [selector] host [port] ]
11 # Default selector, host and port
13 DEF_HOST
= 'gopher.micro.umn.edu'
16 # Recognized file types
30 # Dictionary mapping types to strings
31 typename
= {'0': '<TEXT>', '1': '<DIR>', '2': '<CSO>', '3': '<ERROR>', \
32 '4': '<BINHEX>', '5': '<DOS>', '6': '<UUENCODE>', '7': '<SEARCH>', \
33 '8': '<TELNET>', '9': '<BINARY>', '+': '<REDUNDANT>', 's': '<SOUND>'}
35 # Oft-used characters and strings
39 # Open a TCP connection to a given host and port
40 def open_socket(host
, port
):
43 elif type(port
) == type(''):
45 s
= socket
.socket(socket
.AF_INET
, socket
.SOCK_STREAM
)
46 s
.connect((host
, port
))
49 # Send a selector to a given host and port, return a file with the reply
50 def send_request(selector
, host
, port
):
51 s
= open_socket(host
, port
)
52 s
.send(selector
+ CRLF
)
54 return s
.makefile('r')
56 # Get a menu in the form of a list of entries
57 def get_menu(selector
, host
, port
):
58 f
= send_request(selector
, host
, port
)
63 print('(Unexpected EOF from server)')
67 elif line
[-1:] in CRLF
:
72 print('(Empty line from server)')
75 parts
= line
[1:].split(TAB
)
77 print('(Bad line from server: %r)' % (line
,))
80 print('(Extra info from server: %r)' % (parts
[4:],))
81 parts
.insert(0, typechar
)
86 # Get a text file as a list of lines, with trailing CRLF stripped
87 def get_textfile(selector
, host
, port
):
89 get_alt_textfile(selector
, host
, port
, list.append
)
92 # Get a text file and pass each line to a function, with trailing CRLF stripped
93 def get_alt_textfile(selector
, host
, port
, func
):
94 f
= send_request(selector
, host
, port
)
98 print('(Unexpected EOF from server)')
100 if line
[-2:] == CRLF
:
102 elif line
[-1:] in CRLF
:
111 # Get a binary file as one solid data block
112 def get_binary(selector
, host
, port
):
113 f
= send_request(selector
, host
, port
)
118 # Get a binary file and pass each block to a function
119 def get_alt_binary(selector
, host
, port
, func
, blocksize
):
120 f
= send_request(selector
, host
, port
)
122 data
= f
.read(blocksize
)
127 # A *very* simple interactive browser
129 # Browser main command, has default arguments
131 selector
= DEF_SELECTOR
135 if n
> 0 and args
[0]:
137 if n
> 1 and args
[1]:
139 if n
> 2 and args
[2]:
142 raise RuntimeError('too many args')
144 browse_menu(selector
, host
, port
)
145 except socket
.error
as msg
:
146 print('Socket error:', msg
)
148 except KeyboardInterrupt:
152 def browse_menu(selector
, host
, port
):
153 list = get_menu(selector
, host
, port
)
155 print('----- MENU -----')
156 print('Selector:', repr(selector
))
157 print('Host:', host
, ' Port:', port
)
159 for i
in range(len(list)):
161 typechar
, description
= item
[0], item
[1]
162 print(repr(i
+1).rjust(3) + ':', description
, end
=' ')
163 if typechar
in typename
:
164 print(typename
[typechar
])
166 print('<TYPE=' + repr(typechar
) + '>')
170 str = input('Choice [CR == up a level]: ')
179 print('Choice must be a number; try again:')
181 if not 0 < choice
<= len(list):
182 print('Choice out of range; try again:')
185 item
= list[choice
-1]
187 [i_selector
, i_host
, i_port
] = item
[2:5]
188 if typechar
in typebrowser
:
189 browserfunc
= typebrowser
[typechar
]
191 browserfunc(i_selector
, i_host
, i_port
)
192 except (IOError, socket
.error
):
193 t
, v
, tb
= sys
.exc_info()
194 print('***', t
, ':', v
)
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
)
205 except IOError as msg
:
206 print('IOError:', msg
)
214 get_alt_textfile(selector
, host
, port
, x
.writeln
)
216 except IOError as msg
:
217 print('IOError:', msg
)
220 def raw_input(prompt
):
221 sys
.stdout
.write(prompt
)
223 return sys
.stdin
.readline()
225 # Browse a search index
226 def browse_search(selector
, host
, port
):
228 print('----- SEARCH -----')
229 print('Selector:', repr(selector
))
230 print('Host:', host
, ' Port:', port
)
233 query
= input('Query [CR == up a level]: ')
237 query
= query
.strip()
241 print('Sorry, queries cannot contain tabs')
243 browse_menu(selector
+ TAB
+ query
, host
, port
)
245 # "Browse" telnet-based information, i.e. open a telnet session
246 def browse_telnet(selector
, host
, port
):
248 print('Log in as', repr(selector
))
249 if type(port
) != type(''):
251 sts
= os
.system('set -x; exec telnet ' + host
+ ' ' + port
)
253 print('Exit status:', sts
)
255 # "Browse" a binary file, i.e. save it to a file
256 def browse_binary(selector
, host
, port
):
260 x
= SaveWithProgress(f
)
261 get_alt_binary(selector
, host
, port
, x
.write
, 8*1024)
264 # "Browse" a sound file, i.e. play it or save it
265 def browse_sound(selector
, host
, port
):
266 browse_binary(selector
, host
, port
)
268 # Dictionary mapping types to browser functions
269 typebrowser
= {'0': browse_textfile
, '1': browse_menu
, \
270 '4': browse_binary
, '5': browse_binary
, '6': browse_textfile
, \
271 '7': browse_search
, \
272 '8': browse_telnet
, '9': browse_binary
, 's': browse_sound
}
274 # Class used to save lines, appending a newline to each line
276 def __init__(self
, f
):
278 def writeln(self
, line
):
279 self
.f
.write(line
+ '\n')
283 print('Exit status:', sts
)
285 # Class used to save data while showing progress
286 class SaveWithProgress
:
287 def __init__(self
, f
):
289 def write(self
, data
):
290 sys
.stdout
.write('#')
297 print('Exit status:', sts
)
299 # Ask for and open a save file, or return None if not to save
303 'Save as file [CR == don\'t save; |pipeline or ~user/... OK]: ')
307 savefile
= savefile
.strip()
310 if savefile
[0] == '|':
311 cmd
= savefile
[1:].strip()
313 p
= os
.popen(cmd
, 'w')
314 except IOError as msg
:
315 print(repr(cmd
), ':', msg
)
317 print('Piping through', repr(cmd
), '...')
319 if savefile
[0] == '~':
320 savefile
= os
.path
.expanduser(savefile
)
322 f
= open(savefile
, 'w')
323 except IOError as msg
:
324 print(repr(savefile
), ':', msg
)
326 print('Saving to', repr(savefile
), '...')
332 print('usage: gopher [ [selector] host [port] ]')
335 browser(sys
.argv
[1], sys
.argv
[2], sys
.argv
[3])
338 port
= int(sys
.argv
[2])
342 selector
= sys
.argv
[1]
345 browser(selector
, host
, port
)
347 browser('', sys
.argv
[1])
351 # Call the test program as a main program