Yet Another WebIOPi+
 All Classes Namespaces Files Functions Variables Macros Pages
http.py
Go to the documentation of this file.
1 # Copyright 2012-2013 Eric Ptak - trouch.com
2 #
3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at
6 #
7 # http://www.apache.org/licenses/LICENSE-2.0
8 #
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 # See the License for the specific language governing permissions and
13 # limitations under the License.
14 
15 import os
16 import socket
17 import threading
18 import codecs
19 import mimetypes as mime
20 import logging
21 
22 from webiopi.utils.version import VERSION_STRING, PYTHON_MAJOR
23 from webiopi.utils.logger import info, exception
24 from webiopi.utils.crypto import encrypt
25 from webiopi.utils.types import str2bool
26 
27 if PYTHON_MAJOR >= 3:
28  import http.server as BaseHTTPServer
29 else:
30  import BaseHTTPServer
31 
32 try :
33  import _webiopi.GPIO as GPIO
34 except:
35  pass
36 
37 WEBIOPI_DOCROOT = "/usr/share/webiopi/htdocs"
38 
39 class HTTPServer(BaseHTTPServer.HTTPServer, threading.Thread):
40  if socket.has_ipv6:
41  address_family = socket.AF_INET6
42 
43  def __init__(self, host, port, handler, context, docroot, index, auth=None, realm=None):
44  try:
45  BaseHTTPServer.HTTPServer.__init__(self, ("", port), HTTPHandler)
46  except:
47  self.address_family = socket.AF_INET
48  BaseHTTPServer.HTTPServer.__init__(self, ("", port), HTTPHandler)
49 
50  threading.Thread.__init__(self, name="HTTPThread")
51  self.host = host
52  self.port = port
53 
54  if context:
55  self.context = context
56  if not self.context.startswith("/"):
57  self.context = "/" + self.context
58  if not self.context.endswith("/"):
59  self.context += "/"
60  else:
61  self.context = "/"
62 
63  self.docroot = docroot
64 
65  if index:
66  self.index = index
67  else:
68  self.index = "index.html"
69 
70  self.handler = handler
71  self.auth = auth
72  if (realm == None):
73  self.authenticateHeader = "Basic realm=webiopi"
74  else:
75  self.authenticateHeader = "Basic realm=%s" % realm
76 
77  self.running = True
78  self.start()
79 
80  def get_request(self):
81  sock, addr = self.socket.accept()
82  sock.settimeout(10.0)
83  return (sock, addr)
84 
85  def run(self):
86  info("HTTP Server binded on http://%s:%s%s" % (self.host, self.port, self.context))
87  try:
88  self.serve_forever()
89  except Exception as e:
90  if self.running == True:
91  exception(e)
92  info("HTTP Server stopped")
93 
94  def stop(self):
95  self.running = False
96  self.server_close()
97 
98 class HTTPHandler(BaseHTTPServer.BaseHTTPRequestHandler):
99  logger = logging.getLogger("HTTP")
100 
101  def log_message(self, fmt, *args):
102  pass
103 
104  def log_error(self, fmt, *args):
105  pass
106 
107  def version_string(self):
108  return VERSION_STRING
109 
111  if self.server.auth == None or len(self.server.auth) == 0:
112  return True
113 
114  authHeader = self.headers.get('Authorization')
115  if authHeader == None:
116  return False
117 
118  if not authHeader.startswith("Basic "):
119  return False
120 
121  auth = authHeader.replace("Basic ", "")
122  if PYTHON_MAJOR >= 3:
123  auth_hash = encrypt(auth.encode())
124  else:
125  auth_hash = encrypt(auth)
126 
127  if auth_hash == self.server.auth:
128  return True
129  return False
130 
132  self.send_response(401)
133  self.send_header("WWW-Authenticate", self.server.authenticateHeader)
134  self.end_headers();
135 
136 
137  def logRequest(self, code):
138  self.logger.debug('"%s %s %s" - %s %s (Client: %s <%s>)' % (self.command, self.path, self.request_version, code, self.responses[code][0], self.client_address[0], self.headers["User-Agent"]))
139 
140  def sendResponse(self, code, body=None, contentType="text/plain"):
141  if code >= 400:
142  if body != None:
143  self.send_error(code, body)
144  else:
145  self.send_error(code)
146  else:
147  self.send_response(code)
148  self.send_header("Cache-Control", "no-cache")
149  if body != None:
150  encodedBody = body.encode();
151  self.send_header("Content-Type", contentType);
152  self.send_header("Content-Length", len(encodedBody));
153  self.end_headers();
154  self.wfile.write(encodedBody)
155  self.logRequest(code)
156 
157  def findFile(self, filepath):
158  if os.path.exists(filepath):
159  if os.path.isdir(filepath):
160  filepath += "/" + self.server.index
161  if os.path.exists(filepath):
162  return filepath
163  else:
164  return filepath
165  return None
166 
167 
168  def serveFile(self, relativePath):
169  if self.server.docroot != None:
170  path = self.findFile(self.server.docroot + "/" + relativePath)
171  if path == None:
172  path = self.findFile("./" + relativePath)
173 
174  else:
175  path = self.findFile("./" + relativePath)
176  if path == None:
177  path = self.findFile(WEBIOPI_DOCROOT + "/" + relativePath)
178 
179  if path == None and (relativePath.startswith("webiopi.") or relativePath.startswith("jquery")):
180  path = self.findFile(WEBIOPI_DOCROOT + "/" + relativePath)
181 
182  if path == None:
183  return self.sendResponse(404, "Not Found")
184 
185  realPath = os.path.realpath(path)
186 
187  if realPath.endswith(".py"):
188  return self.sendResponse(403, "Not Authorized")
189 
190  if not (realPath.startswith(os.getcwd())
191  or (self.server.docroot and realPath.startswith(self.server.docroot))
192  or realPath.startswith(WEBIOPI_DOCROOT)):
193  return self.sendResponse(403, "Not Authorized")
194 
195  (contentType, encoding) = mime.guess_type(path)
196  f = codecs.open(path, encoding=encoding)
197  data = f.read()
198  f.close()
199  self.send_response(200)
200  self.send_header("Content-Type", contentType);
201  self.send_header("Content-Length", os.path.getsize(realPath))
202  self.end_headers()
203  self.wfile.write(data)
204  self.logRequest(200)
205 
206  def processRequest(self):
207  self.request.settimeout(None)
208  if not self.checkAuthentication():
209  return self.requestAuthentication()
210 
211  request = self.path.replace(self.server.context, "/").split('?')
212  relativePath = request[0]
213  if relativePath[0] == "/":
214  relativePath = relativePath[1:]
215 
216  if relativePath == "webiopi" or relativePath == "webiopi/":
217  self.send_response(301)
218  self.send_header("Location", "/")
219  self.end_headers()
220  return
221 
222  params = {}
223  if len(request) > 1:
224  for s in request[1].split('&'):
225  if s.find('=') > 0:
226  (name, value) = s.split('=')
227  params[name] = value
228  else:
229  params[s] = None
230 
231  compact = False
232  if 'compact' in params:
233  compact = str2bool(params['compact'])
234 
235  try:
236  result = (None, None, None)
237  if self.command == "GET":
238  result = self.server.handler.do_GET(relativePath, compact)
239  elif self.command == "POST":
240  length = 0
241  length_header = 'content-length'
242  if length_header in self.headers:
243  length = int(self.headers[length_header])
244  result = self.server.handler.do_POST(relativePath, self.rfile.read(length), compact)
245  else:
246  result = (405, None, None)
247 
248  (code, body, contentType) = result
249 
250  if code > 0:
251  self.sendResponse(code, body, contentType)
252  else:
253  if self.command == "GET":
254  self.serveFile(relativePath)
255  else:
256  self.sendResponse(404)
257 
258  except (GPIO.InvalidDirectionException, GPIO.InvalidChannelException, GPIO.SetupException) as e:
259  self.sendResponse(403, "%s" % e)
260  except ValueError as e:
261  self.sendResponse(403, "%s" % e)
262  except Exception as e:
263  self.sendResponse(500)
264  raise e
265 
266  def do_GET(self):
267  self.processRequest()
268 
269  def do_POST(self):
270  self.processRequest()