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