Commit | Line | Data |
---|---|---|
920dae64 AT |
1 | """Gopher protocol client interface.""" |
2 | ||
3 | __all__ = ["send_selector","send_query"] | |
4 | ||
5 | # Default selector, host and port | |
6 | DEF_SELECTOR = '1/' | |
7 | DEF_HOST = 'gopher.micro.umn.edu' | |
8 | DEF_PORT = 70 | |
9 | ||
10 | # Recognized file types | |
11 | A_TEXT = '0' | |
12 | A_MENU = '1' | |
13 | A_CSO = '2' | |
14 | A_ERROR = '3' | |
15 | A_MACBINHEX = '4' | |
16 | A_PCBINHEX = '5' | |
17 | A_UUENCODED = '6' | |
18 | A_INDEX = '7' | |
19 | A_TELNET = '8' | |
20 | A_BINARY = '9' | |
21 | A_DUPLICATE = '+' | |
22 | A_SOUND = 's' | |
23 | A_EVENT = 'e' | |
24 | A_CALENDAR = 'c' | |
25 | A_HTML = 'h' | |
26 | A_TN3270 = 'T' | |
27 | A_MIME = 'M' | |
28 | A_IMAGE = 'I' | |
29 | A_WHOIS = 'w' | |
30 | A_QUERY = 'q' | |
31 | A_GIF = 'g' | |
32 | A_HTML = 'h' # HTML file | |
33 | A_WWW = 'w' # WWW address | |
34 | A_PLUS_IMAGE = ':' | |
35 | A_PLUS_MOVIE = ';' | |
36 | A_PLUS_SOUND = '<' | |
37 | ||
38 | ||
39 | _names = dir() | |
40 | _type_to_name_map = {} | |
41 | def type_to_name(gtype): | |
42 | """Map all file types to strings; unknown types become TYPE='x'.""" | |
43 | global _type_to_name_map | |
44 | if _type_to_name_map=={}: | |
45 | for name in _names: | |
46 | if name[:2] == 'A_': | |
47 | _type_to_name_map[eval(name)] = name[2:] | |
48 | if gtype in _type_to_name_map: | |
49 | return _type_to_name_map[gtype] | |
50 | return 'TYPE=%r' % (gtype,) | |
51 | ||
52 | # Names for characters and strings | |
53 | CRLF = '\r\n' | |
54 | TAB = '\t' | |
55 | ||
56 | def send_selector(selector, host, port = 0): | |
57 | """Send a selector to a given host and port, return a file with the reply.""" | |
58 | import socket | |
59 | if not port: | |
60 | i = host.find(':') | |
61 | if i >= 0: | |
62 | host, port = host[:i], int(host[i+1:]) | |
63 | if not port: | |
64 | port = DEF_PORT | |
65 | elif type(port) == type(''): | |
66 | port = int(port) | |
67 | s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) | |
68 | s.connect((host, port)) | |
69 | s.sendall(selector + CRLF) | |
70 | s.shutdown(1) | |
71 | return s.makefile('rb') | |
72 | ||
73 | def send_query(selector, query, host, port = 0): | |
74 | """Send a selector and a query string.""" | |
75 | return send_selector(selector + '\t' + query, host, port) | |
76 | ||
77 | def path_to_selector(path): | |
78 | """Takes a path as returned by urlparse and returns the appropriate selector.""" | |
79 | if path=="/": | |
80 | return "/" | |
81 | else: | |
82 | return path[2:] # Cuts initial slash and data type identifier | |
83 | ||
84 | def path_to_datatype_name(path): | |
85 | """Takes a path as returned by urlparse and maps it to a string. | |
86 | See section 3.4 of RFC 1738 for details.""" | |
87 | if path=="/": | |
88 | # No way to tell, although "INDEX" is likely | |
89 | return "TYPE='unknown'" | |
90 | else: | |
91 | return type_to_name(path[1]) | |
92 | ||
93 | # The following functions interpret the data returned by the gopher | |
94 | # server according to the expected type, e.g. textfile or directory | |
95 | ||
96 | def get_directory(f): | |
97 | """Get a directory in the form of a list of entries.""" | |
98 | entries = [] | |
99 | while 1: | |
100 | line = f.readline() | |
101 | if not line: | |
102 | print '(Unexpected EOF from server)' | |
103 | break | |
104 | if line[-2:] == CRLF: | |
105 | line = line[:-2] | |
106 | elif line[-1:] in CRLF: | |
107 | line = line[:-1] | |
108 | if line == '.': | |
109 | break | |
110 | if not line: | |
111 | print '(Empty line from server)' | |
112 | continue | |
113 | gtype = line[0] | |
114 | parts = line[1:].split(TAB) | |
115 | if len(parts) < 4: | |
116 | print '(Bad line from server: %r)' % (line,) | |
117 | continue | |
118 | if len(parts) > 4: | |
119 | if parts[4:] != ['+']: | |
120 | print '(Extra info from server:', | |
121 | print parts[4:], ')' | |
122 | else: | |
123 | parts.append('') | |
124 | parts.insert(0, gtype) | |
125 | entries.append(parts) | |
126 | return entries | |
127 | ||
128 | def get_textfile(f): | |
129 | """Get a text file as a list of lines, with trailing CRLF stripped.""" | |
130 | lines = [] | |
131 | get_alt_textfile(f, lines.append) | |
132 | return lines | |
133 | ||
134 | def get_alt_textfile(f, func): | |
135 | """Get a text file and pass each line to a function, with trailing CRLF stripped.""" | |
136 | while 1: | |
137 | line = f.readline() | |
138 | if not line: | |
139 | print '(Unexpected EOF from server)' | |
140 | break | |
141 | if line[-2:] == CRLF: | |
142 | line = line[:-2] | |
143 | elif line[-1:] in CRLF: | |
144 | line = line[:-1] | |
145 | if line == '.': | |
146 | break | |
147 | if line[:2] == '..': | |
148 | line = line[1:] | |
149 | func(line) | |
150 | ||
151 | def get_binary(f): | |
152 | """Get a binary file as one solid data block.""" | |
153 | data = f.read() | |
154 | return data | |
155 | ||
156 | def get_alt_binary(f, func, blocksize): | |
157 | """Get a binary file and pass each block to a function.""" | |
158 | while 1: | |
159 | data = f.read(blocksize) | |
160 | if not data: | |
161 | break | |
162 | func(data) | |
163 | ||
164 | def test(): | |
165 | """Trivial test program.""" | |
166 | import sys | |
167 | import getopt | |
168 | opts, args = getopt.getopt(sys.argv[1:], '') | |
169 | selector = DEF_SELECTOR | |
170 | type = selector[0] | |
171 | host = DEF_HOST | |
172 | if args: | |
173 | host = args[0] | |
174 | args = args[1:] | |
175 | if args: | |
176 | type = args[0] | |
177 | args = args[1:] | |
178 | if len(type) > 1: | |
179 | type, selector = type[0], type | |
180 | else: | |
181 | selector = '' | |
182 | if args: | |
183 | selector = args[0] | |
184 | args = args[1:] | |
185 | query = '' | |
186 | if args: | |
187 | query = args[0] | |
188 | args = args[1:] | |
189 | if type == A_INDEX: | |
190 | f = send_query(selector, query, host) | |
191 | else: | |
192 | f = send_selector(selector, host) | |
193 | if type == A_TEXT: | |
194 | lines = get_textfile(f) | |
195 | for item in lines: print item | |
196 | elif type in (A_MENU, A_INDEX): | |
197 | entries = get_directory(f) | |
198 | for item in entries: print item | |
199 | else: | |
200 | data = get_binary(f) | |
201 | print 'binary data:', len(data), 'bytes:', repr(data[:100])[:40] | |
202 | ||
203 | # Run the test when run as script | |
204 | if __name__ == '__main__': | |
205 | test() |