fix picture fetching
[rofl0r-twatscrape.git] / httpsrv.py
blobdfe028d9814095e5c54f8379b78a82a5140c3515
1 # httpsrv library routines for python.
2 # Copyright (C) 2018 rofl0r
4 # This library is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU Lesser General Public
6 # License as published by the Free Software Foundation; either
7 # version 2.1 of the License, or (at your option) any later version.
9 # This library is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 # Lesser General Public License for more details.
14 # You should have received a copy of the GNU Lesser General Public
15 # License along with this library; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
18 # you can find the full license text at
19 # https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
22 import socket, urllib
24 def _parse_req(line):
25 r = line.find(' ')
26 if r == -1:
27 return '', '', ''
28 method = line[:r]
29 rest = line[r+1:]
30 r = rest.find(' ')
31 if r == -1:
32 return method, '', ''
33 else:
34 ver = rest[r+1:]
35 url = rest[:r]
36 return method, url, ver
38 class HttpClient():
39 def __init__(self, addr, conn):
40 self.addr = addr
41 self.conn = conn
42 self.active = True
43 self.debugreq = False
45 def _send_i(self, data):
46 self.conn.send(data)
47 if self.debugreq and len(data): print ">>>\n", data
49 def send(self, code, msg, response, headers=None):
50 r = ''
51 r += "HTTP/1.1 %d %s\r\n"%(code, msg)
52 if headers:
53 for h in headers:
54 r += "%s: %s\r\n"%(h, headers[h])
55 r += "Content-Length: %d\r\n" % len(response)
56 r += "\r\n"
57 try:
58 self._send_i(r)
59 self._send_i(response)
60 except:
61 self.disconnect()
63 def serve_file(self, filename):
64 self.send(200, "OK", open(filename, 'r').read())
66 def redirect(self, url, headers=None):
67 h = dict() if not headers else headers.copy()
68 h['Location'] = url
69 self.send(301, "Moved Permanently", "", headers=h)
71 def _url_decode(self, s): return urllib.unquote_plus(s)
73 def read_request(self):
74 s = ''
75 CHUNKSIZE = 1024
76 while 1:
77 rnrn = s.find('\r\n\r\n')
78 if rnrn != -1: break
79 r = self.conn.recv(CHUNKSIZE)
80 if len(r) == 0: return None
81 s += r
83 cl = 0
84 for line in s.split('\n'):
85 if line.lower().startswith('content-length:'):
86 try: cl = int(line.split(':', 1)[1].strip())
87 except: pass
88 break
89 while len(s) < rnrn + 4 + cl: # 4 == len('\r\n\r\n')
90 r = self.conn.recv(CHUNKSIZE)
91 if len(r) == 0: return None
92 s += r
94 err = False
95 if not s: err = True
96 if err:
97 self.active = False
98 self.conn.close()
99 return None
101 if self.debugreq: print "<<<\n", s
103 n = s.find('\r\n')
104 if n == -1: err = True
105 else:
106 line = s[:n]
107 a = s[n+2:]
108 meth, url, ver = _parse_req(line)
109 if not (ver == "HTTP/1.0" or ver == "HTTP/1.1"):
110 err = True
111 if not (meth == 'GET' or meth == 'POST'):
112 err = True
113 if err:
114 self.send(500, "error", "client sent invalid request")
115 self.active = False
116 self.conn.close()
117 return None
118 result = dict()
119 result['method'] = meth
120 result['url'] = url
121 for x in a.split('\r\n'):
122 if ':' in x:
123 y,z = x.split(':', 1)
124 result[y] = z.strip()
125 if meth == 'POST':
126 result['postdata'] = dict()
127 postdata = s[rnrn:].split('&')
128 for line in '\n'.join( postdata ).split('\n'):
129 if '=' in line:
130 k,v = line.split('=', 1)
131 result['postdata'][k] = self._url_decode(v.strip())
132 return result
134 def disconnect(self):
135 if self.active: self.conn.close()
136 self.conn = None
137 self.active = False
140 class HttpSrv():
141 def _isnumericipv4(self, ip):
142 try:
143 a,b,c,d = ip.split('.')
144 if int(a) < 256 and int(b) < 256 and int(c) < 256 and int(d) < 256:
145 return True
146 return False
147 except:
148 return False
150 def _resolve(self, host, port, want_v4=True):
151 if self._isnumericipv4(host):
152 return socket.AF_INET, (host, port)
153 for res in socket.getaddrinfo(host, port, \
154 socket.AF_UNSPEC, socket.SOCK_STREAM, 0, socket.AI_PASSIVE):
155 af, socktype, proto, canonname, sa = res
156 if want_v4 and af != socket.AF_INET: continue
157 if af != socket.AF_INET and af != socket.AF_INET6: continue
158 else: return af, sa
160 return None, None
162 def __init__(self, listenip, port):
163 self.port = port
164 self.listenip = listenip
165 self.s = None
167 def setup(self):
168 af, sa = self._resolve(self.listenip, self.port)
169 s = socket.socket(af, socket.SOCK_STREAM)
170 s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
171 s.bind((sa[0], sa[1]))
172 s.listen(128)
173 self.s = s
175 def wait_client(self):
176 conn, addr = self.s.accept()
177 c = HttpClient(addr, conn)
178 return c
181 if __name__ == "__main__":
182 hs = HttpSrv('0.0.0.0', 8080)
183 hs.setup()
184 while True:
185 c = hs.wait_client()
186 c.debugreq = True
187 req = c.read_request()
188 if req is not None:
189 url = req['url']
190 testdomain = 'foobar.corps'
191 if url == '/':
192 c.send(200, "OK", "<html><body>hello world</body></html>")
193 elif url.endswith("/redir") or url.endswith("/redir/"):
194 c.redirect("http://www.%s:%d/"%(testdomain, hs.port), headers={'Set-Cookie':'foo=bar; Path=/; HttpOnly; Domain=%s;'%testdomain})
195 elif url == '/post.html':
196 s = repr(req)
197 c.send(200, "OK", '<html><body><pre>%s</pre></body></html>'%s)
198 else:
199 c.send(404, "Not Found", "404: The requested resource was not found")
200 c.disconnect()