Add CORS (Cross Origin Resource Sharing) support to HTTP

pull/2000/head
Robbie Trencheny 2016-05-07 11:59:47 -07:00
parent 16adc30210
commit ab294d12f7
2 changed files with 44 additions and 3 deletions

View File

@ -16,18 +16,23 @@ from http import cookies
from http.server import HTTPServer, SimpleHTTPRequestHandler from http.server import HTTPServer, SimpleHTTPRequestHandler
from socketserver import ThreadingMixIn from socketserver import ThreadingMixIn
from urllib.parse import parse_qs, urlparse from urllib.parse import parse_qs, urlparse
import voluptuous as vol
import homeassistant.bootstrap as bootstrap import homeassistant.bootstrap as bootstrap
import homeassistant.core as ha import homeassistant.core as ha
import homeassistant.remote as rem import homeassistant.remote as rem
import homeassistant.util as util import homeassistant.util as util
import homeassistant.util.dt as date_util import homeassistant.util.dt as date_util
import homeassistant.helpers.config_validation as cv
from homeassistant.const import ( from homeassistant.const import (
CONTENT_TYPE_JSON, CONTENT_TYPE_TEXT_PLAIN, HTTP_HEADER_ACCEPT_ENCODING, CONTENT_TYPE_JSON, CONTENT_TYPE_TEXT_PLAIN, HTTP_HEADER_ACCEPT_ENCODING,
HTTP_HEADER_CACHE_CONTROL, HTTP_HEADER_CONTENT_ENCODING, HTTP_HEADER_CACHE_CONTROL, HTTP_HEADER_CONTENT_ENCODING,
HTTP_HEADER_CONTENT_LENGTH, HTTP_HEADER_CONTENT_TYPE, HTTP_HEADER_EXPIRES, HTTP_HEADER_CONTENT_LENGTH, HTTP_HEADER_CONTENT_TYPE, HTTP_HEADER_EXPIRES,
HTTP_HEADER_HA_AUTH, HTTP_HEADER_VARY, HTTP_METHOD_NOT_ALLOWED, HTTP_HEADER_HA_AUTH, HTTP_HEADER_VARY,
HTTP_HEADER_ACCESS_CONTROL_ALLOW_ORIGIN,
HTTP_HEADER_ACCESS_CONTROL_ALLOW_HEADERS, HTTP_METHOD_NOT_ALLOWED,
HTTP_NOT_FOUND, HTTP_OK, HTTP_UNAUTHORIZED, HTTP_UNPROCESSABLE_ENTITY, HTTP_NOT_FOUND, HTTP_OK, HTTP_UNAUTHORIZED, HTTP_UNPROCESSABLE_ENTITY,
ALLOWED_CORS_HEADERS,
SERVER_PORT, URL_ROOT, URL_API_EVENT_FORWARD) SERVER_PORT, URL_ROOT, URL_API_EVENT_FORWARD)
DOMAIN = "http" DOMAIN = "http"
@ -38,6 +43,7 @@ CONF_SERVER_PORT = "server_port"
CONF_DEVELOPMENT = "development" CONF_DEVELOPMENT = "development"
CONF_SSL_CERTIFICATE = 'ssl_certificate' CONF_SSL_CERTIFICATE = 'ssl_certificate'
CONF_SSL_KEY = 'ssl_key' CONF_SSL_KEY = 'ssl_key'
CONF_CORS_ORIGINS = 'cors_allowed_origins'
DATA_API_PASSWORD = 'api_password' DATA_API_PASSWORD = 'api_password'
@ -48,6 +54,19 @@ SESSION_KEY = 'sessionId'
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
CONFIG_SCHEMA = vol.Schema({
DOMAIN: vol.Schema({
vol.Optional(CONF_API_PASSWORD): cv.string,
vol.Optional(CONF_SERVER_HOST): cv.string,
vol.Optional(CONF_SERVER_PORT, default=SERVER_PORT):
vol.All(vol.Coerce(int), vol.Range(min=1, max=65535)),
vol.Optional(CONF_DEVELOPMENT): cv.string,
vol.Optional(CONF_SSL_CERTIFICATE): cv.isfile,
vol.Optional(CONF_SSL_KEY): cv.isfile,
vol.Optional(CONF_CORS_ORIGINS): cv.ensure_list
}),
}, extra=vol.ALLOW_EXTRA)
def setup(hass, config): def setup(hass, config):
"""Set up the HTTP API and debug interface.""" """Set up the HTTP API and debug interface."""
@ -61,11 +80,12 @@ def setup(hass, config):
development = str(conf.get(CONF_DEVELOPMENT, "")) == "1" development = str(conf.get(CONF_DEVELOPMENT, "")) == "1"
ssl_certificate = conf.get(CONF_SSL_CERTIFICATE) ssl_certificate = conf.get(CONF_SSL_CERTIFICATE)
ssl_key = conf.get(CONF_SSL_KEY) ssl_key = conf.get(CONF_SSL_KEY)
cors_origins = conf.get(CONF_CORS_ORIGINS, [])
try: try:
server = HomeAssistantHTTPServer( server = HomeAssistantHTTPServer(
(server_host, server_port), RequestHandler, hass, api_password, (server_host, server_port), RequestHandler, hass, api_password,
development, ssl_certificate, ssl_key) development, ssl_certificate, ssl_key, cors_origins)
except OSError: except OSError:
# If address already in use # If address already in use
_LOGGER.exception("Error setting up HTTP server") _LOGGER.exception("Error setting up HTTP server")
@ -96,7 +116,8 @@ class HomeAssistantHTTPServer(ThreadingMixIn, HTTPServer):
# pylint: disable=too-many-arguments # pylint: disable=too-many-arguments
def __init__(self, server_address, request_handler_class, def __init__(self, server_address, request_handler_class,
hass, api_password, development, ssl_certificate, ssl_key): hass, api_password, development, ssl_certificate, ssl_key,
cors_origins):
"""Initialize the server.""" """Initialize the server."""
super().__init__(server_address, request_handler_class) super().__init__(server_address, request_handler_class)
@ -107,6 +128,7 @@ class HomeAssistantHTTPServer(ThreadingMixIn, HTTPServer):
self.paths = [] self.paths = []
self.sessions = SessionStore() self.sessions = SessionStore()
self.use_ssl = ssl_certificate is not None self.use_ssl = ssl_certificate is not None
self.cors_origins = cors_origins
# We will lazy init this one if needed # We will lazy init this one if needed
self.event_forwarder = None self.event_forwarder = None
@ -351,6 +373,16 @@ class RequestHandler(SimpleHTTPRequestHandler):
self.send_header(HTTP_HEADER_VARY, HTTP_HEADER_ACCEPT_ENCODING) self.send_header(HTTP_HEADER_VARY, HTTP_HEADER_ACCEPT_ENCODING)
self.send_header(HTTP_HEADER_CONTENT_LENGTH, str(len(content))) self.send_header(HTTP_HEADER_CONTENT_LENGTH, str(len(content)))
cors_check = (self.headers.get("Origin") in self.server.cors_origins)
cors_headers = ", ".join(ALLOWED_CORS_HEADERS)
if self.server.cors_origins and cors_check:
self.send_header(HTTP_HEADER_ACCESS_CONTROL_ALLOW_ORIGIN,
self.headers.get("Origin"))
self.send_header(HTTP_HEADER_ACCESS_CONTROL_ALLOW_HEADERS,
cors_headers)
self.end_headers() self.end_headers()
if self.command == 'HEAD': if self.command == 'HEAD':

View File

@ -229,6 +229,15 @@ HTTP_HEADER_VARY = "Vary"
HTTP_HEADER_CONTENT_LENGTH = "Content-Length" HTTP_HEADER_CONTENT_LENGTH = "Content-Length"
HTTP_HEADER_CACHE_CONTROL = "Cache-Control" HTTP_HEADER_CACHE_CONTROL = "Cache-Control"
HTTP_HEADER_EXPIRES = "Expires" HTTP_HEADER_EXPIRES = "Expires"
HTTP_HEADER_ORIGIN = "Origin"
HTTP_HEADER_X_REQUESTED_WITH = "X-Requested-With"
HTTP_HEADER_ACCEPT = "Accept"
HTTP_HEADER_ACCESS_CONTROL_ALLOW_ORIGIN = "Access-Control-Allow-Origin"
HTTP_HEADER_ACCESS_CONTROL_ALLOW_HEADERS = "Access-Control-Allow-Headers"
ALLOWED_CORS_HEADERS = [HTTP_HEADER_ORIGIN, HTTP_HEADER_ACCEPT,
HTTP_HEADER_X_REQUESTED_WITH, HTTP_HEADER_CONTENT_TYPE,
HTTP_HEADER_HA_AUTH]
CONTENT_TYPE_JSON = "application/json" CONTENT_TYPE_JSON = "application/json"
CONTENT_TYPE_MULTIPART = 'multipart/x-mixed-replace; boundary={}' CONTENT_TYPE_MULTIPART = 'multipart/x-mixed-replace; boundary={}'