├── client ├── README.md ├── phost │ ├── __init__.py │ ├── upload.py │ ├── config.py │ ├── util.py │ └── __main__.py ├── install.sh ├── develop.sh ├── .gitignore ├── requirements.txt └── setup.py ├── proxy ├── .gitignore ├── migrations │ └── .gitkeep ├── .dockerignore ├── diesel.toml ├── src │ ├── models.rs │ ├── conf.rs │ ├── schema.rs │ └── main.rs ├── rustfmt.toml ├── Cargo.toml └── Cargo.lock ├── server ├── server │ ├── __init__.py │ ├── wsgi.py │ ├── urls.py │ └── settings.py ├── serversite │ ├── __init__.py │ ├── migrations │ │ ├── __init__.py │ │ ├── 0004_staticdeployment_not_found_document.py │ │ ├── 0003_auto_20180919_1143.py │ │ ├── 0002_auto_20180911_0341.py │ │ ├── 0005_proxydeployment.py │ │ └── 0001_initial.py │ ├── apps.py │ ├── admin.py │ ├── serialize.py │ ├── forms.py │ ├── urls.py │ ├── proxy.py │ ├── tests.py │ ├── validation.py │ ├── models.py │ ├── upload.py │ └── views.py ├── Justfile ├── .gitignore ├── requirements.txt ├── .dockerignore ├── renew-cert.sh ├── manage.py ├── apache-config-inner.conf ├── nginx-config-outer.conf └── apache-config-outer.conf ├── .dockerignore ├── .gitignore ├── Justfile ├── README.md ├── LICENSE └── Dockerfile /client/README.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /client/phost/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /proxy/.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | -------------------------------------------------------------------------------- /proxy/migrations/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /server/server/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /server/serversite/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | proxy/target 2 | .env 3 | -------------------------------------------------------------------------------- /client/install.sh: -------------------------------------------------------------------------------- 1 | pip3 install -e . 2 | -------------------------------------------------------------------------------- /server/serversite/migrations/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /client/develop.sh: -------------------------------------------------------------------------------- 1 | python3 setup.py develop 2 | -------------------------------------------------------------------------------- /server/Justfile: -------------------------------------------------------------------------------- 1 | run: 2 | python manage.py runserver 3 | -------------------------------------------------------------------------------- /client/.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | __pycache__ 3 | phost.egg-info 4 | -------------------------------------------------------------------------------- /proxy/.dockerignore: -------------------------------------------------------------------------------- 1 | target 2 | Dockerfile 3 | .dockerignore 4 | -------------------------------------------------------------------------------- /proxy/diesel.toml: -------------------------------------------------------------------------------- 1 | [print_schema] 2 | file = "src/schema.rs" 3 | -------------------------------------------------------------------------------- /server/.gitignore: -------------------------------------------------------------------------------- 1 | hosted 2 | .env 3 | digitalocean_creds.ini 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | env 2 | __pycache__ 3 | .vscode 4 | .env 5 | .pytest_cache 6 | -------------------------------------------------------------------------------- /server/requirements.txt: -------------------------------------------------------------------------------- 1 | django 2 | mysqlclient 3 | pylint_django 4 | python-dotenv~=0.10.3 5 | semver~=2.8.1 6 | -------------------------------------------------------------------------------- /client/requirements.txt: -------------------------------------------------------------------------------- 1 | click~=7.0 2 | requests~=2.7 3 | toml~=0.10 4 | terminaltables~=3.1 5 | python-dateutil~=2.8 6 | -------------------------------------------------------------------------------- /server/serversite/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class ServersiteConfig(AppConfig): 5 | name = 'serversite' 6 | -------------------------------------------------------------------------------- /server/.dockerignore: -------------------------------------------------------------------------------- 1 | env 2 | Dockerfile 3 | .dockerignore 4 | *.pyc 5 | __pycache__ 6 | hosted 7 | digitalocean_creds.ini 8 | .env 9 | hosted 10 | -------------------------------------------------------------------------------- /server/serversite/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | from serversite.models import StaticDeployment, DeploymentVersion, DeploymentCategory 4 | 5 | admin.site.register(StaticDeployment) 6 | admin.site.register(DeploymentVersion) 7 | admin.site.register(DeploymentCategory) 8 | -------------------------------------------------------------------------------- /proxy/src/models.rs: -------------------------------------------------------------------------------- 1 | use chrono::NaiveDateTime; 2 | 3 | #[derive(Serialize, Deserialize, Queryable, Clone, Debug)] 4 | pub struct ProxyDeployment { 5 | pub id: String, 6 | pub name: String, 7 | pub subdomain: String, 8 | pub use_cors_headers: bool, 9 | pub destination_address: String, 10 | pub created_on: NaiveDateTime, 11 | } 12 | -------------------------------------------------------------------------------- /Justfile: -------------------------------------------------------------------------------- 1 | build-docker: 2 | docker build -t phost . 3 | 4 | run-docker: 5 | docker kill phost || true 6 | docker rm phost || true 7 | docker run -d --name phost -p 7645:80 -p 5855:5855 -v /opt/phost:/var/www/hosted -e DB_USERNAME=$DB_USERNAME -e DB_PASSWORD=$DB_PASSWORD -e DB_HOST=$DB_HOST -e DB_DATABASE=$DB_DATABASE -e DATABASE_URL=$DATABASE_URL -e PROXY_SERVER_LOG_FILE=$PROXY_SERVER_LOG_FILE -e PROTOCOL=$PROTOCOL -e ROOT_URL=$ROOT_URL -e HOST_PATH=$HOST_PATH phost 8 | -------------------------------------------------------------------------------- /client/setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | 3 | setup( 4 | name="phost", 5 | version="0.1.0", 6 | packages=["phost"], 7 | entry_points={"console_scripts": ["phost=phost.__main__:main"]}, 8 | install_requires=["click", "requests", "toml", "terminaltables", "python-dateutil"], 9 | license="MIT", 10 | url="http://github.com/ameobea/project-hoster", 11 | author="Casey Primozic (Ameo)", 12 | author_email="me@ameo.link", 13 | ) 14 | -------------------------------------------------------------------------------- /server/renew-cert.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | docker run -it --rm --name certbot-phost \ 4 | -v "/etc/letsencrypt:/etc/letsencrypt" \ 5 | -v "/var/lib/letsencrypt:/var/lib/letsencrypt" \ 6 | -v "${PWD}:/root" \ 7 | certbot/dns-digitalocean certonly --dns-digitalocean \ 8 | --dns-digitalocean-credentials /root/digitalocean_creds.ini \ 9 | --cert-name ameo.design \ 10 | -d *.ameo.design \ 11 | -d *.p.ameo.design \ 12 | -d ameo.design 13 | 14 | service apache2 restart 15 | -------------------------------------------------------------------------------- /client/phost/upload.py: -------------------------------------------------------------------------------- 1 | """ Contains functions for compressing and uploaded directories to be served. """ 2 | 3 | import os 4 | import tarfile 5 | import tempfile 6 | 7 | 8 | def compress_dir(dir_path: str): 9 | target_dir_path = os.path.join(os.getcwd(), dir_path) 10 | temp_file = tempfile.NamedTemporaryFile(suffix=".tgz") 11 | temp_filename = temp_file.name 12 | 13 | with tarfile.open(temp_filename, mode="w:bz2") as out: 14 | out.add(target_dir_path, arcname=".") 15 | 16 | return temp_file 17 | -------------------------------------------------------------------------------- /server/serversite/migrations/0004_staticdeployment_not_found_document.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.2 on 2019-06-18 01:57 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('serversite', '0003_auto_20180919_1143'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name='staticdeployment', 15 | name='not_found_document', 16 | field=models.TextField(blank=True, null=True), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /proxy/rustfmt.toml: -------------------------------------------------------------------------------- 1 | max_width = 120 2 | comment_width = 100 3 | condense_wildcard_suffixes = true 4 | fn_single_line = true 5 | # where_single_line = true 6 | format_strings = true 7 | merge_imports = true 8 | match_block_trailing_comma = true 9 | reorder_impl_items = true 10 | report_todo = "Always" 11 | report_fixme = "Always" 12 | use_field_init_shorthand = true 13 | use_try_shorthand = true 14 | wrap_comments = true 15 | match_arm_blocks = false 16 | overflow_delimited_expr = true 17 | edition = "2018" 18 | normalize_doc_attributes = true 19 | trivial_copy_size_limit = 32 20 | -------------------------------------------------------------------------------- /server/manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import os 3 | import sys 4 | 5 | if __name__ == '__main__': 6 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'server.settings') 7 | try: 8 | from django.core.management import execute_from_command_line 9 | except ImportError as exc: 10 | raise ImportError( 11 | "Couldn't import Django. Are you sure it's installed and " 12 | "available on your PYTHONPATH environment variable? Did you " 13 | "forget to activate a virtual environment?" 14 | ) from exc 15 | execute_from_command_line(sys.argv) 16 | -------------------------------------------------------------------------------- /proxy/src/conf.rs: -------------------------------------------------------------------------------- 1 | use std::env; 2 | 3 | pub struct Conf { 4 | pub database_url: String, 5 | pub port: u16, 6 | } 7 | 8 | impl Conf { 9 | pub fn build_from_env() -> Self { 10 | Conf { 11 | database_url: env::var("DATABASE_URL").expect("The `DATABASE_URL` environment variable must be supplied"), 12 | port: env::var("PORT") 13 | .unwrap_or_else(|_| -> String { "5855".into() }) 14 | .parse() 15 | .expect("Unable to convert `PORT` to `u16`"), 16 | } 17 | } 18 | } 19 | 20 | lazy_static! { 21 | pub static ref CONF: Conf = Conf::build_from_env(); 22 | } 23 | -------------------------------------------------------------------------------- /server/serversite/serialize.py: -------------------------------------------------------------------------------- 1 | from functools import partial 2 | 3 | from django.core import serializers 4 | from django.db.models import QuerySet 5 | from django.http import JsonResponse 6 | 7 | 8 | def serialize(model, json=True): 9 | serialized = None 10 | if isinstance(model, list) or isinstance(model, QuerySet): 11 | serialized = list(map(partial(serialize, json=False), model)) 12 | else: 13 | data = serializers.serialize("python", [model])[0] 14 | serialized = {"id": data["pk"], **data["fields"]} 15 | 16 | if json: 17 | return JsonResponse(serialized, safe=False) 18 | else: 19 | return serialized 20 | -------------------------------------------------------------------------------- /server/serversite/forms.py: -------------------------------------------------------------------------------- 1 | from django import forms 2 | 3 | 4 | class StaticDeploymentForm(forms.Form): 5 | name = forms.CharField(max_length=255) 6 | subdomain = forms.CharField(max_length=255) 7 | version = forms.CharField(max_length=32) 8 | file = forms.FileField(allow_empty_file=False) 9 | categories = forms.CharField(required=False) # Comma-separated list of categories 10 | not_found_document = forms.CharField(required=False) 11 | 12 | class ProxyDeploymentForm(forms.Form): 13 | name = forms.CharField(max_length=255) 14 | subdomain = forms.CharField(max_length=255) 15 | use_cors_headers = forms.BooleanField(required=False) 16 | destination_address = forms.CharField(strip=True) 17 | -------------------------------------------------------------------------------- /server/server/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for server project. 3 | 4 | It exposes the WSGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/2.1/howto/deployment/wsgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.wsgi import get_wsgi_application 13 | 14 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "server.settings") 15 | 16 | inner_application = get_wsgi_application() 17 | 18 | 19 | def application(environ, start_response): 20 | # Pass all environment variables from Apache into the WSGI application/Django 21 | for (k, v) in os.environ.items(): 22 | environ[k] = v 23 | 24 | return inner_application(environ, start_response) 25 | -------------------------------------------------------------------------------- /proxy/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "phost-proxy" 3 | version = "0.1.0" 4 | authors = ["Casey Primozic "] 5 | edition = "2018" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | hyper-reverse-proxy = { git = "https://github.com/Ameobea/hyper-reverse-proxy.git", branch = "https-support", features = ["https"] } 11 | hyper = "0.12" 12 | futures = "0.1" 13 | 14 | diesel = { version = "1.4.2", features = ["chrono", "mysql", "r2d2"] } 15 | 16 | lazy_static = "1.3.0" 17 | 18 | serde_json = "1.0.39" 19 | serde = "1.0.90" 20 | serde_derive = "1.0.90" 21 | log = "0.4.6" 22 | chrono = { version="0.4.6", features=["serde"] } 23 | uuid = { version="0.7.4", features=["v4", "serde"] } 24 | regex = "1.1.7" 25 | signal-hook = "0.1.9" 26 | fern = "0.5.8" 27 | -------------------------------------------------------------------------------- /server/serversite/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | from . import views, proxy 3 | 4 | 5 | urlpatterns = [ 6 | path("", views.index, name="index"), 7 | path("deployments/", views.Deployments.as_view(), name="deployments"), 8 | path("deployments//", views.Deployment.as_view(), name="deployment"), 9 | path( 10 | "deployments///", 11 | views.DeploymentVersionView.as_view(), 12 | name="deployment_version", 13 | ), 14 | path("proxy//", views.ProxyDeploymentView.as_view(), name="proxy"), 15 | path("proxy/", views.ProxyDeployments.as_view(), name="proxies"), 16 | path("login/", views.login_user, name="login"), 17 | path("404/", views.not_found), 18 | ] 19 | 20 | # Initialize child proxy server process 21 | proxy.spawn_proxy_server() 22 | -------------------------------------------------------------------------------- /server/server/urls.py: -------------------------------------------------------------------------------- 1 | """server URL Configuration 2 | 3 | The `urlpatterns` list routes URLs to views. For more information please see: 4 | https://docs.djangoproject.com/en/2.1/topics/http/urls/ 5 | Examples: 6 | Function views 7 | 1. Add an import: from my_app import views 8 | 2. Add a URL to urlpatterns: path('', views.home, name='home') 9 | Class-based views 10 | 1. Add an import: from other_app.views import Home 11 | 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home') 12 | Including another URLconf 13 | 1. Import the include() function: from django.urls import include, path 14 | 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) 15 | """ 16 | from django.contrib import admin 17 | from django.urls import include, path 18 | 19 | urlpatterns = [path("admin/", admin.site.urls), path("", include("serversite.urls"))] 20 | -------------------------------------------------------------------------------- /server/serversite/migrations/0003_auto_20180919_1143.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.1.1 on 2018-09-19 16:43 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('serversite', '0002_auto_20180911_0341'), 10 | ] 11 | 12 | operations = [ 13 | migrations.CreateModel( 14 | name='DeploymentCategory', 15 | fields=[ 16 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 17 | ('category', models.CharField(max_length=64, unique=True)), 18 | ], 19 | ), 20 | migrations.AddField( 21 | model_name='staticdeployment', 22 | name='categories', 23 | field=models.ManyToManyField(to='serversite.DeploymentCategory'), 24 | ), 25 | ] 26 | -------------------------------------------------------------------------------- /server/serversite/proxy.py: -------------------------------------------------------------------------------- 1 | """Contains functions for interacting with the Rust proxy server child""" 2 | 3 | import signal 4 | from subprocess import Popen, DEVNULL 5 | import os 6 | 7 | from django.conf import settings 8 | 9 | 10 | CHILD_PID = None 11 | 12 | 13 | def spawn_proxy_server(): 14 | """ Spawns the proxy server and saves its PID so that we can communicate with it later """ 15 | 16 | output_file_handle = open(settings.PROXY_SERVER_LOG_FILE, "w") 17 | handle = Popen( 18 | ["phost-proxy"], stdin=DEVNULL, stdout=output_file_handle, stderr=output_file_handle 19 | ) 20 | 21 | global CHILD_PID 22 | CHILD_PID = handle.pid 23 | 24 | 25 | def trigger_proxy_server_update(): 26 | if CHILD_PID is None: 27 | print("Error: tried to trigger update of child proxy server before it's been spawned") 28 | return 29 | 30 | os.kill(CHILD_PID, signal.SIGUSR1) 31 | 32 | -------------------------------------------------------------------------------- /server/serversite/migrations/0002_auto_20180911_0341.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.1.1 on 2018-09-11 08:41 2 | 3 | from django.db import migrations, models 4 | import uuid 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('serversite', '0001_initial'), 11 | ] 12 | 13 | operations = [ 14 | migrations.AddField( 15 | model_name='deploymentversion', 16 | name='active', 17 | field=models.BooleanField(default=False), 18 | ), 19 | migrations.AlterField( 20 | model_name='staticdeployment', 21 | name='id', 22 | field=models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False), 23 | ), 24 | migrations.AlterField( 25 | model_name='staticdeployment', 26 | name='subdomain', 27 | field=models.SlugField(max_length=255, unique=True), 28 | ), 29 | ] 30 | -------------------------------------------------------------------------------- /server/serversite/migrations/0005_proxydeployment.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.1.1 on 2019-06-18 09:30 2 | 3 | from django.db import migrations, models 4 | import uuid 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('serversite', '0004_staticdeployment_not_found_document'), 11 | ] 12 | 13 | operations = [ 14 | migrations.CreateModel( 15 | name='ProxyDeployment', 16 | fields=[ 17 | ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), 18 | ('name', models.CharField(max_length=255, unique=True)), 19 | ('subdomain', models.SlugField(max_length=255, unique=True)), 20 | ('use_cors_headers', models.BooleanField(default=True)), 21 | ('destination_address', models.TextField()), 22 | ('created_on', models.DateTimeField(auto_now_add=True)), 23 | ], 24 | ), 25 | ] 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # phost 2 | 3 | `phost` as a utility that provides a range of functionality for deploying content and services on subdomains. It allows for static websites to be deployed from a directory on your local computer with a single command as well as HTTP reverse proxy that allows accessing arbitrary HTTP web services from subdomains as well. 4 | 5 | ## Features 6 | 7 | - Create deployments of static websites with a single command. 8 | - Automatically supports versioning where all previous versions can be accessed via a path extension 9 | - Supports SPA-style websites where a fallback file is served in the case of a 404 10 | - Supports randomly generated subdomains 11 | - Create reverse HTTP proxies to arbitrary endpoints locally or on the internet 12 | - Has the option to transparently add in CORS headers to allow for the proxied resource to be accessed from browser applications 13 | 14 | ## Installation 15 | 16 | The recommended way to run the server is via Docker. The whole service runs inside a single Docker container 17 | -------------------------------------------------------------------------------- /server/serversite/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | from django.db import transaction 3 | 4 | from .models import StaticDeployment 5 | from .views import get_or_none 6 | 7 | 8 | class DummyException(Exception): 9 | pass 10 | 11 | 12 | TEST_SUBDOMAIN = "__test_subdomain" 13 | 14 | 15 | class TestAtomicTransactionGenericErrorHandling(TestCase): 16 | """ Verify that random exceptions raised in `transaction.atomic()` blocks still cause the 17 | transaction to be rolled back. """ 18 | 19 | def test_fail(self): 20 | with self.assertRaises(DummyException): 21 | with transaction.atomic(): 22 | deployment = StaticDeployment(name="Test Deployment", subdomain=TEST_SUBDOMAIN) 23 | deployment.save() 24 | raise DummyException("generic exception") 25 | 26 | assert get_or_none(StaticDeployment, subdomain=TEST_SUBDOMAIN, do_raise=False) is None 27 | 28 | 29 | class EmptyQuery(TestCase): 30 | def test_empty_falsey(self): 31 | assert not StaticDeployment.objects.filter(name="__non-existant-name") 32 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Casey C Primozic (Ameo) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /server/apache-config-inner.conf: -------------------------------------------------------------------------------- 1 | WSGIPythonPath /var/www/phost 2 | 3 | 4 | ServerName v.ameo.design 5 | ServerAlias v.localhost 6 | ServerAlias v.* 7 | 8 | ServerAdmin me@ameo.link 9 | DocumentRoot /var/www/hosted 10 | 11 | AddType application/wasm .wasm 12 | AddType model/gltf .gltf 13 | AddType model/gltf-binary .glb 14 | 15 | RewriteCond %{REQUEST_URI} "^/([^/]+)/([^/]+)(.*)$" 16 | RewriteRule "^(.*)" "/var/www/hosted/%1/%2/%3" 17 | 18 | ErrorLog /dev/stdout 19 | TransferLog /dev/stdout 20 | 21 | ErrorDocument 404 /404/ 22 | 23 | 24 | 25 | ServerName ameo.design 26 | ServerAlias * 27 | 28 | ServerAdmin me@ameo.link 29 | DocumentRoot /var/www/hosted 30 | 31 | AddType application/wasm .wasm 32 | AddType model/gltf gltf 33 | AddType model/gltf-binary glb 34 | 35 | RewriteEngine on 36 | RewriteMap lowercase int:tolower 37 | 38 | RewriteCond %{REQUEST_URI} "/__HOSTED/(.+?)/(.*)$" 39 | RewriteRule "^(.*)" "/var/www/hosted/%1/latest/%2" 40 | 41 | WSGIScriptAlias / /var/www/phost/server/wsgi.py 42 | 43 | ErrorLog /dev/stdout 44 | TransferLog /dev/stdout 45 | 46 | ErrorDocument 404 /404/ 47 | 48 | -------------------------------------------------------------------------------- /server/serversite/validation.py: -------------------------------------------------------------------------------- 1 | """ Functions for validating input from the user """ 2 | 3 | 4 | import re 5 | 6 | 7 | class BadInputException(Exception): 8 | pass 9 | 10 | 11 | class NotFound(Exception): 12 | pass 13 | 14 | 15 | class NotAuthenticated(Exception): 16 | pass 17 | 18 | 19 | class InvalidCredentials(Exception): 20 | pass 21 | 22 | 23 | # We could technically allow special characters, but that makes slugification much harder and 24 | # just isn't worth it. 25 | DEPLOYMENT_NAME_RGX = re.compile("^[a-zA-Z0-9-_ ]+$") 26 | 27 | # Stolen from https://stackoverflow.com/a/7933253/3833068 28 | DEPLOYMENT_SUBDOMAIN_RGX = re.compile("^[A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?$") 29 | 30 | 31 | def validate_deployment_name(deployment_name: str): 32 | if not DEPLOYMENT_NAME_RGX.match(deployment_name): 33 | raise BadInputException( 34 | "Deployment name must contain only alphanumeric characters, spaces, dashes, and underscores." 35 | ) 36 | 37 | 38 | def validate_subdomain(subdomain: str): 39 | if not DEPLOYMENT_SUBDOMAIN_RGX.match(subdomain): 40 | raise BadInputException("Supplied subdomain is invalid") 41 | 42 | 43 | def get_validated_form(FormClass, request): 44 | form = FormClass(request.POST, request.FILES) 45 | if not form.is_valid(): 46 | raise BadInputException("Invalid fields provided to the static deployment creation form") 47 | 48 | return form 49 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # This has to match with the inner container due to shared library mismatches etc. 2 | FROM ubuntu:18.04 AS builder 3 | 4 | RUN apt-get update && apt-get install -y curl libmysqlclient-dev build-essential libssl-dev pkg-config 5 | RUN update-ca-certificates 6 | 7 | # Install rust 8 | RUN curl https://sh.rustup.rs/ -sSf | \ 9 | sh -s -- -y --default-toolchain nightly-2023-10-23 10 | 11 | ENV PATH="/root/.cargo/bin:${PATH}" 12 | 13 | ADD ./proxy /proxy 14 | WORKDIR /proxy 15 | 16 | RUN cargo build --release 17 | 18 | # Adapted from https://github.com/ramkulkarni1/django-apache2-docker/blob/master/Dockerfile 19 | # This has to match with the inner container due to shared library mismatches etc. 20 | FROM ubuntu:18.04 21 | 22 | COPY --from=builder \ 23 | /proxy/target/release/phost-proxy \ 24 | /usr/local/bin/phost-proxy 25 | 26 | RUN apt-get update && apt-get install -y vim curl apache2 apache2-utils 27 | RUN apt-get -y install python3 libapache2-mod-wsgi-py3 python3-dev libmysqlclient-dev 28 | RUN a2enmod rewrite 29 | RUN ln /usr/bin/python3 /usr/bin/python 30 | RUN apt-get -y install python3-pip 31 | RUN ln /usr/bin/pip3 /usr/bin/pip 32 | RUN mkdir /var/www/hosted 33 | RUN chown -R www-data /var/www/hosted 34 | 35 | RUN pip install --upgrade pip 36 | 37 | ADD ./server/requirements.txt /var/www/phost/requirements.txt 38 | RUN pip install -r /var/www/phost/requirements.txt 39 | 40 | ADD ./server/apache-config-inner.conf /etc/apache2/sites-available/000-default.conf 41 | 42 | ADD ./server /var/www/phost 43 | 44 | CMD ["apache2ctl", "-D", "FOREGROUND"] 45 | -------------------------------------------------------------------------------- /server/serversite/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.1.1 on 2018-09-10 13:32 2 | 3 | from django.db import migrations, models 4 | import django.db.models.deletion 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | initial = True 10 | 11 | dependencies = [ 12 | ] 13 | 14 | operations = [ 15 | migrations.CreateModel( 16 | name='DeploymentVersion', 17 | fields=[ 18 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 19 | ('version', models.CharField(max_length=32)), 20 | ('created_on', models.DateTimeField(auto_now_add=True)), 21 | ], 22 | options={ 23 | 'ordering': ['created_on'], 24 | }, 25 | ), 26 | migrations.CreateModel( 27 | name='StaticDeployment', 28 | fields=[ 29 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 30 | ('name', models.CharField(max_length=255, unique=True)), 31 | ('subdomain', models.SlugField(max_length=64, unique=True)), 32 | ('created_on', models.DateTimeField(auto_now_add=True)), 33 | ], 34 | options={ 35 | 'ordering': ['created_on'], 36 | }, 37 | ), 38 | migrations.AddField( 39 | model_name='deploymentversion', 40 | name='deployment', 41 | field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='serversite.StaticDeployment'), 42 | ), 43 | ] 44 | -------------------------------------------------------------------------------- /client/phost/config.py: -------------------------------------------------------------------------------- 1 | import os 2 | import pathlib 3 | import toml 4 | 5 | DEFAULT_CONF = { 6 | "api_server_url": "http://localhost:8000", 7 | "hosting_base_url": "ameo.design", 8 | "hosting_protocol": "https", 9 | "username": "user", 10 | "password": "pass", 11 | } 12 | 13 | CONFIG_DIR_PATH = os.path.join(os.path.expanduser("~"), ".phost") 14 | CONF_FILE_PATH = os.path.join(CONFIG_DIR_PATH, "conf.toml") 15 | COOKIE_FILE_PATH = os.path.join(CONFIG_DIR_PATH, "cookies.toml") 16 | 17 | 18 | def load_cookies() -> dict: 19 | if not os.path.isfile(COOKIE_FILE_PATH): 20 | return {} 21 | 22 | with open(COOKIE_FILE_PATH) as f: 23 | return toml.loads(f.read()) 24 | 25 | 26 | def save_cookies(cookies: dict): 27 | with open(COOKIE_FILE_PATH, "w") as f: 28 | f.write(toml.dumps(cookies)) 29 | 30 | 31 | def init_config(conf_file_path): 32 | if not os.path.isfile(conf_file_path): 33 | pathlib.Path(os.path.dirname(conf_file_path)).mkdir(parents=False, exist_ok=True) 34 | 35 | # Initialize config file with default config if it's empty 36 | default_conf_toml = toml.dumps(DEFAULT_CONF) 37 | with open(conf_file_path, "w") as f: 38 | f.write(default_conf_toml) 39 | print( 40 | f"Created default config file: {conf_file_path}; you must fill in the values yourself." 41 | ) 42 | exit(1) 43 | 44 | return open(conf_file_path, "r") 45 | 46 | 47 | def load_conf(conf_file) -> dict: 48 | 49 | conf_toml = conf_file.read() 50 | conf_file.close() 51 | 52 | try: 53 | return toml.loads(conf_toml) 54 | except toml.TomlDecodeError: 55 | print("Error reading supplied config file!") 56 | exit(1) 57 | -------------------------------------------------------------------------------- /server/serversite/models.py: -------------------------------------------------------------------------------- 1 | import uuid 2 | 3 | from django.db import models 4 | from django.conf import settings 5 | 6 | 7 | class DeploymentCategory(models.Model): 8 | category = models.CharField(max_length=64, unique=True) 9 | 10 | 11 | class StaticDeployment(models.Model): 12 | id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) 13 | name = models.CharField(max_length=255, unique=True) 14 | subdomain = models.SlugField(unique=True, max_length=255) 15 | created_on = models.DateTimeField(auto_now_add=True) 16 | categories = models.ManyToManyField(DeploymentCategory) 17 | not_found_document = models.TextField(null=True, blank=True) 18 | 19 | def get_url(self) -> str: 20 | return "{}://{}.{}/".format(settings.PROTOCOL, self.subdomain, settings.ROOT_URL) 21 | 22 | def save(self, *args, **kwargs): # pylint: disable=W0221 23 | self.subdomain = self.subdomain.lower() 24 | return super(StaticDeployment, self).save(*args, **kwargs) 25 | 26 | class Meta: 27 | ordering = ["created_on"] 28 | 29 | def __unicode__(self): 30 | return self.name # pylint: disable=E1101 31 | 32 | 33 | class DeploymentVersion(models.Model): 34 | version = models.CharField(max_length=32) 35 | created_on = models.DateTimeField(auto_now_add=True) 36 | deployment = models.ForeignKey(StaticDeployment, on_delete=models.CASCADE) 37 | active = models.BooleanField(default=False) 38 | 39 | def save(self, *args, **kwargs): # pylint: disable=W0221 40 | self.version = self.version.lower() 41 | return super(DeploymentVersion, self).save(*args, **kwargs) 42 | 43 | class Meta: 44 | ordering = ["created_on"] 45 | 46 | def __unicode__(self): 47 | return self.version # pylint: disable=E1101 48 | 49 | 50 | class ProxyDeployment(models.Model): 51 | id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) 52 | name = models.CharField(max_length=255, unique=True) 53 | subdomain = models.SlugField(unique=True, max_length=255) 54 | use_cors_headers = models.BooleanField(default=True) 55 | destination_address = models.TextField() 56 | created_on = models.DateTimeField(auto_now_add=True) 57 | 58 | def get_url(self) -> str: 59 | return "{}://{}.{}/".format(settings.PROTOCOL, self.subdomain, settings.ROOT_URL) 60 | -------------------------------------------------------------------------------- /server/serversite/upload.py: -------------------------------------------------------------------------------- 1 | """ 2 | Utilities for dealing with uploaded deployment archives 3 | """ 4 | 5 | import os 6 | import pathlib 7 | import shutil 8 | import tarfile 9 | 10 | from django.conf import settings 11 | 12 | 13 | HOST_DIR = settings.HOST_PATH 14 | 15 | 16 | def delete_dir_if_exists(dir_path: str): 17 | if os.path.exists(dir_path): 18 | shutil.rmtree(dir_path) 19 | 20 | 21 | def delete_hosted_version(deployment_subdomain: str, version: str): 22 | version_dir = os.path.join(HOST_DIR, deployment_subdomain, version) 23 | delete_dir_if_exists(version_dir) 24 | 25 | 26 | def delete_hosted_deployment(deployment_subdomain: str): 27 | deployment_dir = os.path.join(HOST_DIR, deployment_subdomain) 28 | delete_dir_if_exists(deployment_dir) 29 | 30 | 31 | def update_symlink(deployment_subdomain: str, new_version: str): 32 | """ Updates the directory that `latest` is symlinked to in the given host directory. This 33 | should be called after a new version is pushed. """ 34 | 35 | link_path = os.path.join(HOST_DIR, deployment_subdomain, "latest") 36 | try: 37 | os.unlink(link_path) 38 | except FileNotFoundError: 39 | pass 40 | 41 | os.symlink(os.path.join(HOST_DIR, deployment_subdomain, new_version), link_path) 42 | 43 | 44 | def handle_uploaded_static_archive(file, subdomain: str, version: str, init=True) -> str: 45 | """ 46 | Writes the archive to a temporary file and attempts to extract it to the project directory. 47 | Raises an exception if the extraction process was unsuccessful. 48 | """ 49 | 50 | try: 51 | dst_dir = os.path.join(HOST_DIR, subdomain, version) 52 | pathlib.Path(dst_dir).mkdir(parents=True, exist_ok=True) 53 | if init: 54 | if version != "latest": 55 | os.symlink(dst_dir, os.path.join(HOST_DIR, subdomain, "latest")) 56 | 57 | # Extract the archive into the hosting directory 58 | t = tarfile.open(mode="r:*", fileobj=file) 59 | t.extractall(dst_dir) 60 | t.close() 61 | 62 | return dst_dir 63 | except Exception as e: 64 | print("Error while creating deployment from tar archive:") 65 | print(e) 66 | 67 | directory_to_delete = os.path.join(HOST_DIR, subdomain) if init else dst_dir 68 | print(f"Deleting {directory_to_delete}...") 69 | shutil.rmtree(directory_to_delete) 70 | raise e 71 | -------------------------------------------------------------------------------- /client/phost/util.py: -------------------------------------------------------------------------------- 1 | """ Various utility and helper functions """ 2 | 3 | 4 | from functools import reduce 5 | import uuid 6 | 7 | 8 | class Composition(object): 9 | def __init__(self, inner_function, wrappers): 10 | self.composed = reduce( 11 | lambda acc, wrapper: wrapper(acc), reversed(wrappers), inner_function 12 | ) 13 | 14 | # Click holds metadata about what kinds of arguments, options, etc. have been added to 15 | # its commands in this attribute. It is accumulated during the composition reduction. 16 | # By setting it here, `click` gets access to all of the metadata accumulated by the 17 | # wrapper functions via the instance of this class that is returned. 18 | self.__click_params__ = getattr(self.composed, "__click_params__", None) 19 | 20 | def __call__(self, *args, **kwargs): 21 | return self.composed(*args, **kwargs) 22 | 23 | 24 | def compose(*wrappers): 25 | """ Composes all provided decorator functions from right to left (right-most is outermost) and 26 | returns a single function that applies them all as a decorator. 27 | 28 | Example: 29 | >>> def wrap(i: int): 30 | ... def wrapper(func): 31 | ... def inner(*args, **kwargs): 32 | ... print('inner {}'.format(i)) 33 | ... return func(*args, **kwargs) 34 | ... 35 | ... return inner 36 | ... 37 | ... return wrapper 38 | ... 39 | >>> @compose(wrap(0), wrap(1), wrap(2)) 40 | ... def foo(): 41 | ... print('foo') 42 | ... 43 | >>> foo() 44 | inner 0 45 | inner 1 46 | inner 2 47 | foo 48 | >>> 49 | """ 50 | 51 | return lambda func: Composition(func, wrappers) 52 | 53 | 54 | def slugify(s: str) -> str: 55 | return s.lower().replace(" ", "-").replace(".", "_") 56 | 57 | 58 | def create_random_subdomain() -> str: 59 | return uuid.uuid4().hex[:16] 60 | 61 | 62 | def test_compose(): 63 | def wrap(i: int): 64 | def wrapper(func): 65 | def inner(*args, **kwargs): 66 | return ["inner {}".format(i), *func(*args, **kwargs)] 67 | 68 | return inner 69 | 70 | return wrapper 71 | 72 | @compose(wrap(0), wrap(1), wrap(2)) 73 | def foo(): 74 | return ["foo"] 75 | 76 | assert foo() == ["inner 0", "inner 1", "inner 2", "foo"] 77 | -------------------------------------------------------------------------------- /server/nginx-config-outer.conf: -------------------------------------------------------------------------------- 1 | # Match requests to the root domain (no subdomain) and route them to the API server 2 | server { 3 | server_name ameo.design; 4 | 5 | location / { 6 | proxy_pass http://localhost:4300/; 7 | } 8 | 9 | listen 443 ssl; # managed by Certbot 10 | ssl_certificate /etc/letsencrypt/live/ameo.design/fullchain.pem; # managed by Certbot 11 | ssl_certificate_key /etc/letsencrypt/live/ameo.design/privkey.pem; # managed by Certbot 12 | include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot 13 | ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot 14 | } 15 | 16 | # Match *.ameo.design 17 | server { 18 | server_name "~^(?[A-Za-z0-9](?:[A-Za-z0-9\-]{0,61}[A-Za-z0-9])?)\.ameo\.design$"; 19 | 20 | # Force trailing slash 21 | rewrite ^/v/([^/]+)$ /v/$1/; 22 | 23 | # Handle explicit versions 24 | location ~/v/(?.+) { 25 | resolver 127.0.0.1; 26 | proxy_pass http://v.localhost:4300/$subdomain/$after_version_path; 27 | } 28 | 29 | location ~/(?.*) { 30 | proxy_pass http://localhost:4300/__HOSTED/$subdomain/$after_path; 31 | } 32 | 33 | listen 443 ssl; # managed by Certbot 34 | ssl_certificate /etc/letsencrypt/live/ameo.design/fullchain.pem; # managed by Certbot 35 | ssl_certificate_key /etc/letsencrypt/live/ameo.design/privkey.pem; # managed by Certbot 36 | include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot 37 | ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot 38 | } 39 | 40 | # Handle proxied requests 41 | server { 42 | server_name "~^(?[A-Za-z0-9](?:[A-Za-z0-9\-]{0,61}[A-Za-z0-9])?)\.p\.?ameo\.design$"; 43 | 44 | location ~/(?.*) { 45 | resolver 127.0.0.1; 46 | proxy_pass http://localhost:4301/$subdomain/$after_path; 47 | } 48 | 49 | listen 443 ssl; # managed by Certbot 50 | ssl_certificate /etc/letsencrypt/live/ameo.design/fullchain.pem; # managed by Certbot 51 | ssl_certificate_key /etc/letsencrypt/live/ameo.design/privkey.pem; # managed by Certbot 52 | include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot 53 | ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot 54 | } 55 | 56 | # HTTP->HTTPS Redirect 57 | server { 58 | listen 80; 59 | server_name "~^.*ameo\.design$"; 60 | 61 | return 301 https://$host$request_uri; 62 | } 63 | -------------------------------------------------------------------------------- /server/apache-config-outer.conf: -------------------------------------------------------------------------------- 1 | 2 | NameVirtualHost *:443 3 | 4 | # `io.ameo.design` is reserved for a separate application that uses encrypted websocket, which 5 | # is proxied into the non-encrypted internal version here. 6 | 7 | ServerName io.ameo.design 8 | SSLEngine on 9 | Include /etc/letsencrypt/options-ssl-apache.conf 10 | SSLCertificateFile /etc/letsencrypt/live/ameo.design/cert.pem 11 | SSLCertificateKeyFile /etc/letsencrypt/live/ameo.design/privkey.pem 12 | SSLCertificateChainFile /etc/letsencrypt/live/ameo.design/chain.pem 13 | 14 | AddType application/wasm .wasm 15 | 16 | ProxyPass /socket/websocket ws://localhost:3699/socket/websocket 17 | 18 | ProxyPass / http://localhost:3699/ 19 | ProxyPassReverse / http://localhost:3699/ 20 | 21 | 22 | 23 | ServerAdmin me@ameo.link 24 | ServerName ameo.design 25 | ServerAlias *.ameo.design 26 | 27 | SSLEngine on 28 | 29 | Include /etc/letsencrypt/options-ssl-apache.conf 30 | SSLCertificateFile /etc/letsencrypt/live/ameo.design/cert.pem 31 | SSLCertificateKeyFile /etc/letsencrypt/live/ameo.design/privkey.pem 32 | SSLCertificateChainFile /etc/letsencrypt/live/ameo.design/chain.pem 33 | 34 | AddType application/wasm .wasm 35 | 36 | ServerAdmin webmaster@localhost 37 | ServerName ameo.design 38 | ServerAlias www.ameo.design 39 | 40 | DocumentRoot /var/www/html 41 | 42 | RewriteEngine On 43 | SSLProxyEngine On 44 | 45 | # Match requests to the root domain (no subdomain) and route them to the API server 46 | RewriteCond %{HTTP_HOST} ^ameo\.design$ 47 | RewriteRule ^(.*) http://localhost:7645$1 [P] 48 | 49 | # Match a version without a trailing slash and no path to avoid the backend issuing a redirect 50 | # (we essentially do that redirect manually here) 51 | RewriteCond "%{HTTP_HOST},%{REQUEST_URI}" "^(.+)\.ameo\.design,/v/([^/]+)$" 52 | RewriteRule ^(.*) https://%1.ameo.design/v/%2/ [R,L] 53 | 54 | # Match a version with a trailing slash or a provided path 55 | RewriteCond "%{HTTP_HOST},%{REQUEST_URI}" "^(.+)\.ameo\.design,/v/(.+)" 56 | RewriteRule ^(.*) http://v.localhost:7645/%1/%2 [P] 57 | 58 | # Match requests to proxied subdomains (*.p.ameo.design) 59 | RewriteCond %{HTTP_HOST} ^([^.]+)\.p\.ameo\.design$ 60 | RewriteRule ^(.*) http://localhost:5855/%1$1 [P] 61 | 62 | # Match non-versioned requests to any deployment 63 | RewriteCond %{HTTP_HOST} ^([^.]+)\.ameo\.design$ 64 | RewriteRule ^(.*) http://localhost:7645/__HOSTED/%1$1 [P] 65 | 66 | 67 | Options Indexes FollowSymLinks MultiViews 68 | RewriteEngine On 69 | AllowOverride All 70 | Order allow,deny 71 | allow from all 72 | 73 | 74 | ErrorLog ${APACHE_LOG_DIR}/error.log 75 | CustomLog ${APACHE_LOG_DIR}/access.log combined 76 | 77 | SSLEngine on 78 | 79 | 80 | SSLOptions +StdEnvVars 81 | 82 | 83 | SSLOptions +StdEnvVars 84 | 85 | 86 | BrowserMatch "MSIE [2-6]" nokeepalive ssl-unclean-shutdown downgrade-1.0 force-response-1.0 87 | 88 | BrowserMatch "MSIE [17-9]" ssl-unclean-shutdown 89 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /server/server/settings.py: -------------------------------------------------------------------------------- 1 | """ 2 | Django settings for server project. 3 | 4 | Generated by 'django-admin startproject' using Django 2.1.1. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/2.1/topics/settings/ 8 | 9 | For the full list of settings and their values, see 10 | https://docs.djangoproject.com/en/2.1/ref/settings/ 11 | """ 12 | 13 | import os 14 | 15 | from dotenv import load_dotenv 16 | 17 | load_dotenv() 18 | 19 | # Build paths inside the project like this: os.path.join(BASE_DIR, ...) 20 | BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 21 | 22 | PROTOCOL = os.environ["PROTOCOL"] 23 | ROOT_URL = os.environ["ROOT_URL"] 24 | HOST_PATH = os.environ.get("HOST_PATH", os.path.join(BASE_DIR, "hosted/")) 25 | 26 | APPEND_SLASH = True 27 | 28 | # Quick-start development settings - unsuitable for production 29 | # See https://docs.djangoproject.com/en/2.1/howto/deployment/checklist/ 30 | 31 | # SECURITY WARNING: keep the secret key used in production secret! 32 | SECRET_KEY = ")22)a7uoo6b)_diu(01&c=jdkvv(fby#^1j#yl-laif#k&!=3n" 33 | 34 | # SECURITY WARNING: don't run with debug turned on in production! 35 | DEBUG = True 36 | 37 | ALLOWED_HOSTS = ["localhost"] 38 | 39 | 40 | # Application definition 41 | 42 | INSTALLED_APPS = [ 43 | "serversite", 44 | "django.contrib.admin", 45 | "django.contrib.auth", 46 | "django.contrib.contenttypes", 47 | "django.contrib.sessions", 48 | "django.contrib.messages", 49 | "django.contrib.staticfiles", 50 | ] 51 | 52 | MIDDLEWARE = [ 53 | "django.middleware.security.SecurityMiddleware", 54 | "django.contrib.sessions.middleware.SessionMiddleware", 55 | "django.middleware.common.CommonMiddleware", 56 | # "django.middleware.csrf.CsrfViewMiddleware", 57 | "django.contrib.auth.middleware.AuthenticationMiddleware", 58 | "django.contrib.messages.middleware.MessageMiddleware", 59 | "django.middleware.clickjacking.XFrameOptionsMiddleware", 60 | ] 61 | 62 | ROOT_URLCONF = "server.urls" 63 | 64 | TEMPLATES = [ 65 | { 66 | "BACKEND": "django.template.backends.django.DjangoTemplates", 67 | "DIRS": [], 68 | "APP_DIRS": True, 69 | "OPTIONS": { 70 | "context_processors": [ 71 | "django.template.context_processors.debug", 72 | "django.template.context_processors.request", 73 | "django.contrib.auth.context_processors.auth", 74 | "django.contrib.messages.context_processors.messages", 75 | ] 76 | }, 77 | } 78 | ] 79 | 80 | WSGI_APPLICATION = "server.wsgi.application" 81 | 82 | 83 | # Database 84 | # https://docs.djangoproject.com/en/2.1/ref/settings/#databases 85 | 86 | DATABASES = { 87 | "default": { 88 | "ENGINE": "django.db.backends.mysql", 89 | "NAME": os.environ["DB_DATABASE"], 90 | "HOST": os.environ["DB_HOST"], 91 | "USER": os.environ["DB_USERNAME"], 92 | "PASSWORD": os.environ["DB_PASSWORD"], 93 | "PORT": 3306, 94 | } 95 | } 96 | 97 | PROXY_SERVER_LOG_FILE = os.environ.get("PROXY_SERVER_LOG_FILE") or "/tmp/phost-proxy.log" 98 | 99 | 100 | # Password validation 101 | # https://docs.djangoproject.com/en/2.1/ref/settings/#auth-password-validators 102 | 103 | AUTH_PASSWORD_VALIDATORS = [ 104 | {"NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator"}, 105 | {"NAME": "django.contrib.auth.password_validation.MinimumLengthValidator"}, 106 | {"NAME": "django.contrib.auth.password_validation.CommonPasswordValidator"}, 107 | {"NAME": "django.contrib.auth.password_validation.NumericPasswordValidator"}, 108 | ] 109 | 110 | 111 | # Internationalization 112 | # https://docs.djangoproject.com/en/2.1/topics/i18n/ 113 | 114 | LANGUAGE_CODE = "en-us" 115 | 116 | TIME_ZONE = "America/Chicago" 117 | 118 | USE_I18N = True 119 | 120 | USE_L10N = True 121 | 122 | USE_TZ = True 123 | 124 | 125 | # Static files (CSS, JavaScript, Images) 126 | # https://docs.djangoproject.com/en/2.1/howto/static-files/ 127 | 128 | STATIC_URL = os.path.join(BASE_DIR, "static/") 129 | -------------------------------------------------------------------------------- /proxy/src/schema.rs: -------------------------------------------------------------------------------- 1 | table! { 2 | auth_group (id) { 3 | id -> Integer, 4 | name -> Varchar, 5 | } 6 | } 7 | 8 | table! { 9 | auth_group_permissions (id) { 10 | id -> Integer, 11 | group_id -> Integer, 12 | permission_id -> Integer, 13 | } 14 | } 15 | 16 | table! { 17 | auth_permission (id) { 18 | id -> Integer, 19 | name -> Varchar, 20 | content_type_id -> Integer, 21 | codename -> Varchar, 22 | } 23 | } 24 | 25 | table! { 26 | auth_user (id) { 27 | id -> Integer, 28 | password -> Varchar, 29 | last_login -> Nullable, 30 | is_superuser -> Bool, 31 | username -> Varchar, 32 | first_name -> Varchar, 33 | last_name -> Varchar, 34 | email -> Varchar, 35 | is_staff -> Bool, 36 | is_active -> Bool, 37 | date_joined -> Datetime, 38 | } 39 | } 40 | 41 | table! { 42 | auth_user_groups (id) { 43 | id -> Integer, 44 | user_id -> Integer, 45 | group_id -> Integer, 46 | } 47 | } 48 | 49 | table! { 50 | auth_user_user_permissions (id) { 51 | id -> Integer, 52 | user_id -> Integer, 53 | permission_id -> Integer, 54 | } 55 | } 56 | 57 | table! { 58 | django_admin_log (id) { 59 | id -> Integer, 60 | action_time -> Datetime, 61 | object_id -> Nullable, 62 | object_repr -> Varchar, 63 | action_flag -> Unsigned, 64 | change_message -> Longtext, 65 | content_type_id -> Nullable, 66 | user_id -> Integer, 67 | } 68 | } 69 | 70 | table! { 71 | django_content_type (id) { 72 | id -> Integer, 73 | app_label -> Varchar, 74 | model -> Varchar, 75 | } 76 | } 77 | 78 | table! { 79 | django_migrations (id) { 80 | id -> Integer, 81 | app -> Varchar, 82 | name -> Varchar, 83 | applied -> Datetime, 84 | } 85 | } 86 | 87 | table! { 88 | django_session (session_key) { 89 | session_key -> Varchar, 90 | session_data -> Longtext, 91 | expire_date -> Datetime, 92 | } 93 | } 94 | 95 | table! { 96 | serversite_deploymentcategory (id) { 97 | id -> Integer, 98 | category -> Varchar, 99 | } 100 | } 101 | 102 | table! { 103 | serversite_deploymentversion (id) { 104 | id -> Integer, 105 | version -> Varchar, 106 | created_on -> Datetime, 107 | deployment_id -> Char, 108 | active -> Bool, 109 | } 110 | } 111 | 112 | table! { 113 | serversite_proxydeployment (id) { 114 | id -> Char, 115 | name -> Varchar, 116 | subdomain -> Varchar, 117 | use_cors_headers -> Bool, 118 | destination_address -> Longtext, 119 | created_on -> Datetime, 120 | } 121 | } 122 | 123 | table! { 124 | serversite_staticdeployment (id) { 125 | id -> Char, 126 | name -> Varchar, 127 | subdomain -> Varchar, 128 | created_on -> Datetime, 129 | not_found_document -> Nullable, 130 | } 131 | } 132 | 133 | table! { 134 | serversite_staticdeployment_categories (id) { 135 | id -> Integer, 136 | staticdeployment_id -> Char, 137 | deploymentcategory_id -> Integer, 138 | } 139 | } 140 | 141 | table! { 142 | static_deployments (id) { 143 | id -> Bigint, 144 | deployment_name -> Varchar, 145 | subdomain -> Varchar, 146 | } 147 | } 148 | 149 | joinable!(auth_group_permissions -> auth_group (group_id)); 150 | joinable!(auth_group_permissions -> auth_permission (permission_id)); 151 | joinable!(auth_permission -> django_content_type (content_type_id)); 152 | joinable!(auth_user_groups -> auth_group (group_id)); 153 | joinable!(auth_user_groups -> auth_user (user_id)); 154 | joinable!(auth_user_user_permissions -> auth_permission (permission_id)); 155 | joinable!(auth_user_user_permissions -> auth_user (user_id)); 156 | joinable!(django_admin_log -> auth_user (user_id)); 157 | joinable!(django_admin_log -> django_content_type (content_type_id)); 158 | joinable!(serversite_deploymentversion -> serversite_staticdeployment (deployment_id)); 159 | joinable!(serversite_staticdeployment_categories -> serversite_deploymentcategory (deploymentcategory_id)); 160 | joinable!(serversite_staticdeployment_categories -> serversite_staticdeployment (staticdeployment_id)); 161 | 162 | allow_tables_to_appear_in_same_query!( 163 | auth_group, 164 | auth_group_permissions, 165 | auth_permission, 166 | auth_user, 167 | auth_user_groups, 168 | auth_user_user_permissions, 169 | django_admin_log, 170 | django_content_type, 171 | django_migrations, 172 | django_session, 173 | serversite_deploymentcategory, 174 | serversite_deploymentversion, 175 | serversite_proxydeployment, 176 | serversite_staticdeployment, 177 | serversite_staticdeployment_categories, 178 | static_deployments, 179 | ); 180 | -------------------------------------------------------------------------------- /proxy/src/main.rs: -------------------------------------------------------------------------------- 1 | #![feature(proc_macro_hygiene, decl_macro)] 2 | 3 | extern crate chrono; 4 | extern crate futures; 5 | extern crate hyper; 6 | extern crate hyper_reverse_proxy; 7 | #[macro_use] 8 | extern crate lazy_static; 9 | #[macro_use] 10 | extern crate log; 11 | extern crate fern; 12 | #[macro_use] 13 | extern crate diesel; 14 | extern crate regex; 15 | extern crate serde; 16 | extern crate serde_json; 17 | #[macro_use] 18 | extern crate serde_derive; 19 | extern crate signal_hook; 20 | 21 | use std::{ 22 | collections::HashMap, 23 | str::FromStr, 24 | sync::{Arc, Mutex}, 25 | }; 26 | 27 | use diesel::{ 28 | prelude::*, 29 | r2d2::{Builder, ConnectionManager, Pool}, 30 | MysqlConnection, 31 | }; 32 | use futures::future::{self, Future}; 33 | use hyper::{ 34 | http::{ 35 | uri::{PathAndQuery, Uri}, 36 | HeaderMap, 37 | }, 38 | server::conn::AddrStream, 39 | service::{make_service_fn, service_fn}, 40 | Body, Request, Response, Server, 41 | }; 42 | use regex::Regex; 43 | use signal_hook::{iterator::Signals, SIGUSR1}; 44 | 45 | pub mod conf; 46 | pub mod models; 47 | pub mod schema; 48 | use crate::{conf::CONF, models::ProxyDeployment}; 49 | 50 | lazy_static! { 51 | static ref REQUEST_URL_RGX: Regex = Regex::new("/([^/]+)/(.*)").unwrap(); 52 | } 53 | 54 | fn build_conn_pool() -> Pool> { 55 | let manager = ConnectionManager::new(&CONF.database_url); 56 | Builder::new() 57 | .build(manager) 58 | .expect("Failed to build R2D2/Diesel MySQL Connection Pool") 59 | } 60 | 61 | type BoxFut = Box, Error = hyper::Error> + Send>; 62 | 63 | /// Loads the full set of deployments from the database and updates the mapping with them. 64 | fn populate_proxy_deployments( 65 | proxy_deployments: &Mutex>, 66 | pool: &Mutex>>, 67 | ) { 68 | use crate::schema::serversite_proxydeployment::dsl::*; 69 | 70 | let active_deployments = { 71 | let pool_inner = &mut *pool.lock().unwrap(); 72 | let conn = pool_inner.get().expect("Failed to get connection from connection pool"); 73 | 74 | match serversite_proxydeployment 75 | .limit(1_000_000_000) 76 | .load::(&conn) 77 | { 78 | Ok(res) => res, 79 | Err(err) => { 80 | error!("Error loading proxy deployments from the database: {:?}", err); 81 | return; 82 | }, 83 | } 84 | }; 85 | info!( 86 | "Retrieved {} deployments; updating active deployments map...", 87 | active_deployments.len() 88 | ); 89 | 90 | let deployments_inner = &mut *proxy_deployments.lock().unwrap(); 91 | deployments_inner.clear(); 92 | 93 | for deployment in active_deployments { 94 | debug!( 95 | "Registering proxy {} : {}", 96 | deployment.subdomain, deployment.destination_address 97 | ); 98 | deployments_inner.insert(deployment.subdomain.clone(), deployment); 99 | } 100 | info!("Active deployments map updated"); 101 | } 102 | 103 | fn remove_all_headers(headers: &mut HeaderMap, header_name: &str) { 104 | loop { 105 | let removed_header = headers.remove(header_name); 106 | if removed_header.is_none() { 107 | break; 108 | } 109 | } 110 | } 111 | 112 | /// Registers signal handlers that trigger the set of active deployments to be reloaded from the 113 | /// database 114 | fn init_signal_handlers(proxy_deployments: Arc>>) { 115 | let pool = Arc::new(Mutex::new(build_conn_pool())); 116 | 117 | // Populate the proxy deployments with the initial set of 118 | populate_proxy_deployments(&*proxy_deployments, &*pool); 119 | 120 | let signals = Signals::new(&[SIGUSR1]).expect("Failed to create `signals` handle"); 121 | std::thread::spawn(move || { 122 | for _signal in signals.forever() { 123 | info!("Received signal; updating deployments from database..."); 124 | populate_proxy_deployments(&*proxy_deployments, &*pool); 125 | info!("Finished updating deployments.") 126 | } 127 | }); 128 | } 129 | 130 | fn early_return(status_code: u16, msg: String) -> BoxFut { 131 | let mut res = Response::new(Body::from(msg)); 132 | *res.status_mut() = hyper::StatusCode::from_u16(status_code).unwrap(); 133 | return Box::new(future::ok(res)); 134 | } 135 | 136 | fn setup_logger() { 137 | fern::Dispatch::new() 138 | .level(log::LevelFilter::Debug) 139 | .level(log::LevelFilter::Debug) 140 | .level_for("hyper", log::LevelFilter::Info) 141 | .level_for("mio", log::LevelFilter::Info) 142 | .level_for("tokio_core", log::LevelFilter::Info) 143 | .level_for("tokio_reactor", log::LevelFilter::Info) 144 | .chain(fern::log_file("/dev/stdout").expect("Error chaining Fern output to /dev/stdout")) 145 | .apply() 146 | .expect("Failed to apply Fern dispatch"); 147 | } 148 | 149 | fn main() { 150 | setup_logger(); 151 | 152 | let proxy_deployments: Arc>> = Arc::new(Mutex::new(HashMap::new())); 153 | 154 | init_signal_handlers(Arc::clone(&proxy_deployments)); 155 | 156 | let addr = ([0, 0, 0, 0], CONF.port).into(); 157 | 158 | // A `Service` is needed for every connection. 159 | let make_svc = make_service_fn(move |socket: &AddrStream| { 160 | let remote_addr = socket.remote_addr(); 161 | let proxy_deployments = Arc::clone(&proxy_deployments); 162 | 163 | service_fn(move |mut req: Request| { 164 | let req_path_and_query = req.uri().path_and_query().map(|pnq| pnq.as_str()).unwrap_or_else(|| ""); 165 | let (subdomain, path): (String, String) = match REQUEST_URL_RGX.captures(req_path_and_query) { 166 | Some(caps) => (String::from(&caps[1]), String::from(&caps[2])), 167 | None => return early_return(400, "Invalid URL; format is /subdomain/[...path]".into()), 168 | }; 169 | 170 | let deployment_descriptor = match { 171 | proxy_deployments 172 | .lock() 173 | .unwrap() 174 | .get(&subdomain) 175 | .map(|deployment_descriptor| deployment_descriptor.clone()) 176 | } { 177 | Some(deployment_descriptor) => deployment_descriptor, 178 | None => return early_return(404, format!("Deployment \"{}\" not found.", subdomain)), 179 | }; 180 | 181 | let dst_url = deployment_descriptor.destination_address.clone(); 182 | let mut uri_parts = req.uri().clone().into_parts(); 183 | let dst_pnq = match PathAndQuery::from_str(&format!("/{}", path)) { 184 | Ok(pnq) => pnq, 185 | Err(_) => return early_return(500, "Failed to build `PathAndQuery` from path+query string".into()), 186 | }; 187 | uri_parts.path_and_query = Some(dst_pnq); 188 | 189 | let uri = match Uri::from_parts(uri_parts) { 190 | Ok(uri) => uri, 191 | Err(_) => return early_return(500, "Unable to convert URI parts to URI".into()), 192 | }; 193 | *req.uri_mut() = uri; 194 | 195 | // Set the `Host` header to be accurate for the destination 196 | let dst_uri = match Uri::from_str(&dst_url) { 197 | Ok(uri) => uri, 198 | Err(_) => 199 | return early_return( 200 | 400, 201 | format!("Invalid target URL provided for this proxy: \"{}\"", dst_url), 202 | ), 203 | }; 204 | let dst_uri_parts = dst_uri.into_parts(); 205 | let authority = dst_uri_parts.authority.unwrap(); 206 | let host = authority.host(); 207 | 208 | remove_all_headers(req.headers_mut(), "HOST"); 209 | req.headers_mut().insert("HOST", host.parse().unwrap()); 210 | 211 | let use_cors_headers = deployment_descriptor.use_cors_headers; 212 | info!("[REQ] {}/{}", dst_url, path); 213 | Box::new( 214 | hyper_reverse_proxy::call(remote_addr.ip(), &dst_url, req).and_then(move |mut res| { 215 | // Set CORS headers if that option is enabled 216 | if use_cors_headers { 217 | debug!("Adding CORS header"); 218 | remove_all_headers(res.headers_mut(), "Access-Control-Allow-Origin"); 219 | res.headers_mut() 220 | .insert("Access-Control-Allow-Origin", "*".parse().unwrap()); 221 | } 222 | 223 | info!("[RES] {}/{}", dst_url, path); 224 | Ok(res) 225 | }), 226 | ) 227 | }) 228 | }); 229 | 230 | let server = Server::bind(&addr) 231 | .serve(make_svc) 232 | .map_err(|e| eprintln!("server error: {}", e)); 233 | 234 | println!("Running server on {:?}", addr); 235 | 236 | // Run this server for... forever! 237 | hyper::rt::run(server); 238 | } 239 | -------------------------------------------------------------------------------- /client/phost/__main__.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import traceback 3 | import json 4 | from typing import List 5 | import os 6 | from time import sleep 7 | 8 | import click 9 | import requests 10 | from requests.exceptions import ConnectionError as RequestsConnectionError 11 | from terminaltables import SingleTable 12 | import dateutil.parser 13 | 14 | from .config import load_conf, load_cookies, save_cookies, init_config 15 | from .upload import compress_dir 16 | from .util import compose, slugify, create_random_subdomain 17 | 18 | 19 | class PhostServerError(Exception): 20 | pass 21 | 22 | 23 | class GlobalAppState(object): 24 | """ What it says on the tin. """ 25 | 26 | def __init__(self, config_file): 27 | self.conf = load_conf(config_file) 28 | 29 | # Load cookies from previous session and construct a new session with them 30 | self.session = requests.Session() 31 | self.session.cookies.update(load_cookies()) 32 | 33 | def make_request(self, method: str, *args, json_body=None, form_data=None, multipart_data=None): 34 | # If we are re-trying a request that has multipart file data, we need to reset the seek position 35 | # for all of those files to the beginning since it is advanced during request building. 36 | if multipart_data: 37 | for val in multipart_data.values(): 38 | if hasattr(val, "tell") and hasattr(val, "seek"): 39 | needs_seek_reset = val.tell() != 0 40 | if needs_seek_reset: 41 | val.seek(0) 42 | 43 | (func, kwargs) = { 44 | "POST": ( 45 | self.session.post, 46 | {"json": json_body, "files": multipart_data, "data": form_data}, 47 | ), 48 | "GET": (self.session.get, {}), 49 | "PUT": (self.session.put, {"json": json_body}), 50 | "PATCH": (self.session.patch, {"json": json_body}), 51 | "DELETE": (self.session.delete, {}), 52 | }[method.upper()] 53 | 54 | return func(*args, **kwargs) 55 | 56 | def api_call(self, resource_path: str, method="GET", **kwargs): 57 | try: 58 | res = self.make_request( 59 | method, "{}/{}".format(self.conf["api_server_url"], resource_path), **kwargs 60 | ) 61 | 62 | if res.status_code == 404: 63 | raise PhostServerError("Resource not found") 64 | elif res.status_code == 500: 65 | raise PhostServerError("Internal server error") 66 | elif res.status_code == 403: 67 | # Try to login and repeat the request if this isn't the login route 68 | if resource_path != "login/": 69 | self.login() 70 | sleep(0.2) 71 | return self.api_call(resource_path, method=method, **kwargs) 72 | 73 | raise PhostServerError("Error logging in; invalid username/password?") 74 | elif res.status_code != 200: 75 | raise PhostServerError( 76 | "Received {} response code when making request: {}".format( 77 | res.status_code, res.text 78 | ) 79 | ) 80 | 81 | return res.json() 82 | 83 | except Exception as e: 84 | show_stacktrace = True 85 | if isinstance(e, PhostServerError): 86 | show_stacktrace = False 87 | elif isinstance(e, RequestsConnectionError): 88 | e = Exception("Error while communicating with the server's API") 89 | show_stacktrace = False 90 | 91 | if show_stacktrace: 92 | traceback.print_exc() 93 | else: 94 | print("Error: {}".format(e)) 95 | 96 | save_cookies(self.session.cookies.get_dict()) 97 | exit(1) 98 | 99 | def login(self): 100 | res = self.api_call( 101 | "login/", 102 | method="POST", 103 | form_data={"username": self.conf["username"], "password": self.conf["password"]}, 104 | ) 105 | 106 | if not res["success"]: 107 | print("Error logging into the server; invalid username/password?") 108 | exit(1) 109 | 110 | # Save the cookies from this session so that they can be re-used next time that the 111 | # application is run. 112 | save_cookies(self.session.cookies.get_dict()) 113 | 114 | 115 | STATE = None 116 | 117 | 118 | def print_table(rows): 119 | table = SingleTable(rows) 120 | table.inner_column_border = False 121 | table.inner_footing_row_border = False 122 | table.inner_heading_row_border = False 123 | table.inner_row_border = False 124 | table.outer_border = False 125 | table.padding_left = 0 126 | table.padding_right = 3 127 | 128 | print(table.table) 129 | 130 | 131 | def list_deployments(): 132 | def process_versions(versions: List[dict]) -> (str, str): 133 | active_version = "None" 134 | versions_string = "" 135 | 136 | for v in sorted(versions, key=lambda version: version["created_on"]): 137 | if v["active"]: 138 | active_version = v["version"] 139 | 140 | if versions_string: 141 | versions_string += ", " 142 | versions_string += v["version"] 143 | 144 | return (active_version, versions_string) 145 | 146 | deployments = STATE.api_call("deployments/") 147 | table_headers = ["Name", "URL", "Creation Date", "Active Version", "All Versions", "Categories"] 148 | table_data = map( 149 | lambda datum: [ 150 | datum["name"], 151 | "{}://{}.{}/".format( 152 | STATE.conf["hosting_protocol"], datum["subdomain"], STATE.conf["hosting_base_url"] 153 | ), 154 | dateutil.parser.parse(datum["created_on"]).strftime("%Y-%m-%d"), 155 | *process_versions(datum["versions"]), 156 | ", ".join( 157 | filter(None, map(lambda category: category["category"], datum["categories"])) 158 | ), 159 | ], 160 | deployments, 161 | ) 162 | 163 | table = SingleTable([table_headers, *table_data]) 164 | table.inner_column_border = False 165 | table.inner_footing_row_border = False 166 | table.inner_heading_row_border = False 167 | table.inner_row_border = False 168 | table.outer_border = False 169 | table.padding_left = 0 170 | table.padding_right = 3 171 | 172 | print(table.table) 173 | 174 | 175 | def list_proxies(): 176 | proxies = STATE.api_call("proxy/") 177 | table_headers = ["Name", "URL", "Creation Date"] 178 | 179 | table_data = map( 180 | lambda datum: [ 181 | datum["name"], 182 | "{}://{}.p.{}/".format( 183 | STATE.conf["hosting_protocol"], datum["subdomain"], STATE.conf["hosting_base_url"] 184 | ), 185 | dateutil.parser.parse(datum["created_on"]).strftime("%Y-%m-%d"), 186 | ], 187 | proxies, 188 | ) 189 | 190 | rows = [table_headers, *table_data] 191 | print_table(rows) 192 | 193 | 194 | def delete_deployment(query, lookup_field, version): 195 | req_path = ( 196 | "deployments/{}/?lookupField={}".format(query, lookup_field) 197 | if version is None 198 | else "deployments/{}/{}/?lookupField={}".format(query, version, lookup_field) 199 | ) 200 | STATE.api_call(req_path, method="DELETE") 201 | 202 | print("Deployment {}successfully deleted".format("" if version is None else "version ")) 203 | 204 | 205 | def delete_proxy(query, lookup_field): 206 | req_path = "proxy/{}/?lookupField={}".format(query, lookup_field) 207 | STATE.api_call(req_path, method="DELETE") 208 | 209 | print("Proxy successfully deleted") 210 | 211 | 212 | @click.group() 213 | @click.option("--config", "-c", type=click.Path(), default=os.path.expanduser("~/.phost/conf.toml")) 214 | def main(config): 215 | # Create empty config file + initialize with defaults if it doesn't exist 216 | config_file = init_config(config) 217 | 218 | global STATE # pylint: disable=W0603 219 | STATE = GlobalAppState(config_file) 220 | 221 | 222 | @main.group("deployment", help="Manage static subdomain deployments") 223 | def deployment(): 224 | pass 225 | 226 | 227 | @main.group("proxy", help="Manage HTTP proxies") 228 | def proxy(): 229 | pass 230 | 231 | 232 | with_query_lookup_decorators = compose( 233 | click.argument("query"), 234 | click.option( 235 | "--name", 236 | "lookup_field", 237 | flag_value="name", 238 | default=True, 239 | help="Look up deployment by name (default)", 240 | ), 241 | click.option( 242 | "--id", "lookup_field", flag_value="id", default=False, help="Look up deployment by ID" 243 | ), 244 | click.option( 245 | "--subdomain", 246 | "lookup_field", 247 | flag_value="subdomain", 248 | default=False, 249 | help="Look up deployment by subdomain", 250 | ), 251 | ) 252 | 253 | 254 | delete_deployment_decorators = compose( 255 | with_query_lookup_decorators, 256 | click.option( 257 | "--version", 258 | "-v", 259 | default=None, 260 | help=( 261 | "If supplied, only this version will be deleted. " 262 | "If not supplied, all versions will be deleted." 263 | ), 264 | ), 265 | ) 266 | 267 | 268 | @deployment.command("ls") 269 | def list_deployments_deployment(): 270 | list_deployments() 271 | 272 | 273 | @main.command("ls", help="Shorthand for `phost deployment ls`") 274 | def list_deployments_main(): 275 | list_deployments() 276 | 277 | 278 | @with_query_lookup_decorators 279 | @main.command("show", help="Shorthand for `phost deployment show`") 280 | def show_deployment_main(query, lookup_field): 281 | deployment_data = STATE.api_call("deployments/{}/?lookupField={}".format(query, lookup_field)) 282 | print(json.dumps(deployment_data, indent=4)) 283 | 284 | 285 | @deployment.command("rm") 286 | @delete_deployment_decorators 287 | def delete_deployment_deployment(query, lookup_field, version): 288 | delete_deployment(query, lookup_field, version) 289 | 290 | 291 | @main.command("rm", help="Shorthand for `phost deployment rm`") 292 | @delete_deployment_decorators 293 | def delete_deployment_main(query, lookup_field, version): 294 | delete_deployment(query, lookup_field, version) 295 | 296 | 297 | def create_deployment( 298 | name, subdomain, directory, version, random_subdomain, categories, spa, not_found_document 299 | ): 300 | if spa and not_found_document is None: 301 | not_found_document = "./index.html" 302 | 303 | if random_subdomain: 304 | if subdomain is None: 305 | subdomain = create_random_subdomain() 306 | else: 307 | print("Can't supply both `--random-subdomain` and an explicit subdomain") 308 | exit(1) 309 | elif not subdomain: 310 | subdomain = slugify(name) 311 | 312 | # Compress the target directory into a tempfile .tgz archive 313 | tgz_file = compress_dir(directory) 314 | 315 | multipart_data = { 316 | "name": ("", name), 317 | "subdomain": ("", subdomain), 318 | "file": ("directory.tgz", tgz_file), 319 | "version": ("", version), 320 | "categories": ("", ",".join(categories)), 321 | "not_found_document": ("", not_found_document), 322 | } 323 | 324 | res = STATE.api_call("deployments/", method="POST", multipart_data=multipart_data) 325 | print("Deployment successfully created: {}".format(res["url"])) 326 | 327 | 328 | def create_proxy(name, target_url, subdomain, random_subdomain, enable_cors): 329 | if random_subdomain: 330 | if subdomain is None: 331 | subdomain = create_random_subdomain() 332 | else: 333 | print("Can't supply both `--random-subdomain` and an explicit subdomain") 334 | exit(1) 335 | elif not subdomain: 336 | subdomain = slugify(name) 337 | 338 | multipart_data = { 339 | "name": ("", name), 340 | "subdomain": ("", subdomain), 341 | "use_cors_headers": ("", enable_cors), 342 | "destination_address": ("", target_url), 343 | } 344 | 345 | res = STATE.api_call("proxy/", method="POST", multipart_data=multipart_data) 346 | print("Proxy successfully created: {}".format(res["url"])) 347 | 348 | 349 | create_deployment_decorators = compose( 350 | click.argument("name"), 351 | click.argument("directory"), 352 | click.option( 353 | "--subdomain", 354 | "-s", 355 | default=None, 356 | help=( 357 | "The subdomain on which the deployment will be hosted. If left off, the subdomain" 358 | " will be constructed from the deployment name." 359 | ), 360 | ), 361 | click.option("--version", "-v", default="0.1.0"), 362 | click.option( 363 | "--private", 364 | "-p", 365 | default=False, 366 | help="Private deployments have a randomized subdomain", 367 | is_flag=True, 368 | ), 369 | click.option( 370 | "--spa", 371 | default=False, 372 | help=( 373 | "Create this deployment as a single-page web application where `index.html` is served" 374 | " for all missing routes" 375 | ), 376 | is_flag=True, 377 | ), 378 | click.option( 379 | "--not-found-document", 380 | default=None, 381 | help=( 382 | "A path to a file that will be served in case of a 404. If not provided, the default " 383 | 'Apache2 "Not Found" page will be displayed.' 384 | ), 385 | ), 386 | click.option( 387 | "--category", 388 | "-c", 389 | multiple=True, 390 | help=( 391 | "A string representing a category that this deployment should be added to." 392 | " (Multiple may be provided)" 393 | ), 394 | ), 395 | ) 396 | 397 | 398 | @main.command("create", help="Shorthand for `phost deployment create`") 399 | @create_deployment_decorators 400 | def create_deployment_main( 401 | name, subdomain, directory, version, private, category, spa, not_found_document 402 | ): 403 | create_deployment( 404 | name, subdomain, directory, version, private, category, spa, not_found_document 405 | ) 406 | 407 | 408 | @deployment.command("create") 409 | @create_deployment_decorators 410 | def create_deployment_deployment( 411 | name, subdomain, directory, version, private, category, spa, not_found_document 412 | ): 413 | create_deployment( 414 | name, subdomain, directory, version, private, category, spa, not_found_document 415 | ) 416 | 417 | 418 | with_update_deployment_decorators = compose( 419 | with_query_lookup_decorators, click.argument("version"), click.argument("directory") 420 | ) 421 | 422 | 423 | def update_deployment(query, lookup_field, version, directory): 424 | """ Pushes a new version for an existing deployment """ 425 | 426 | multipart_data = {"file": compress_dir(directory)} 427 | STATE.api_call( 428 | "deployments/{}/{}/?lookupField={}".format(query, version, lookup_field), 429 | multipart_data=multipart_data, 430 | method="POST", 431 | ) 432 | 433 | print("Deployment successfully updated") 434 | 435 | 436 | update_help = ( 437 | "\nVERSION: The new version to deploy. This takes some special values:" 438 | " \t\n\t - patch/p: Increments the patch version of the current version" 439 | " \t\n\t - minor/m: Increments the minor version of the current version" 440 | " \t\n\t - major/M: Increments the major version of the current version" 441 | ) 442 | 443 | 444 | @deployment.command("update", help=update_help) 445 | @with_update_deployment_decorators 446 | def update_deployment_deployment(query, lookup_field, version, directory): 447 | update_deployment(query, lookup_field, version, directory) 448 | 449 | 450 | @main.command("update", help="Shorthand for `phost deployment update`\n\n" + update_help) 451 | @with_update_deployment_decorators 452 | def update_deployment_main(query, lookup_field, version, directory): 453 | update_deployment(query, lookup_field, version, directory) 454 | 455 | 456 | @with_query_lookup_decorators 457 | @deployment.command("show") 458 | def show_deployment(query, lookup_field): 459 | deployment_data = STATE.api_call("deployments/{}/?lookupField={}".format(query, lookup_field)) 460 | print(json.dumps(deployment_data, indent=4)) 461 | 462 | 463 | @proxy.command("ls") 464 | def list_proxies_cmd(): 465 | list_proxies() 466 | 467 | 468 | @with_query_lookup_decorators 469 | @proxy.command("rm") 470 | def delete_proxy_cmd(query, lookup_field): 471 | delete_proxy(query, lookup_field) 472 | 473 | 474 | @click.argument("target_url") 475 | @click.argument("name") 476 | @click.option( 477 | "--subdomain", 478 | "-s", 479 | default=None, 480 | help=( 481 | "The subdomain on which the proxy will be mounted. If left off, the subdomain" 482 | " will be constructed from the proxy name." 483 | ), 484 | ) 485 | @click.option("--private", "-p", default=False, help="Use a randomized subdomain", is_flag=True) 486 | @click.option("--cors", "-c", default=False, is_flag=True, help="Enable CORS headers on the proxy") 487 | @proxy.command("create") 488 | def create_proxy_cmd(name, target_url, subdomain, private, cors): 489 | create_proxy(name, target_url, subdomain, private, cors) 490 | 491 | 492 | logging.getLogger("requests").setLevel(logging.CRITICAL) 493 | logging.getLogger("urllib3").setLevel(logging.CRITICAL) 494 | 495 | main() # pylint: disable=E1120 496 | -------------------------------------------------------------------------------- /server/serversite/views.py: -------------------------------------------------------------------------------- 1 | import mimetypes 2 | import os 3 | import re 4 | import traceback 5 | 6 | from django.http import ( 7 | HttpResponse, 8 | JsonResponse, 9 | HttpResponseBadRequest, 10 | HttpResponseServerError, 11 | HttpResponseNotFound, 12 | HttpResponseForbidden, 13 | ) 14 | from django.conf import settings 15 | from django.contrib.auth import authenticate, login 16 | from django.db import transaction 17 | from django.db.utils import IntegrityError 18 | from django.utils.datastructures import MultiValueDictKeyError 19 | from django.http.request import HttpRequest 20 | from django.views.decorators.http import require_GET, require_POST 21 | from django.views.generic import TemplateView 22 | import semver 23 | 24 | from .models import StaticDeployment, DeploymentVersion, DeploymentCategory, ProxyDeployment 25 | from .forms import StaticDeploymentForm, ProxyDeploymentForm 26 | from .upload import ( 27 | handle_uploaded_static_archive, 28 | update_symlink, 29 | delete_hosted_deployment, 30 | delete_hosted_version, 31 | ) 32 | from .serialize import serialize 33 | from .validation import ( 34 | BadInputException, 35 | validate_deployment_name, 36 | get_validated_form, 37 | NotFound, 38 | NotAuthenticated, 39 | InvalidCredentials, 40 | validate_subdomain, 41 | ) 42 | from .proxy import trigger_proxy_server_update 43 | 44 | # Used to get the name of the deployment into which a given URL points 45 | REDIRECT_URL_RGX = re.compile("^/__HOSTED/([^/]+)/.*$") 46 | 47 | # Taken from https://djangosnippets.org/snippets/101/ 48 | def send_data(path, filename=None, mimetype=None): 49 | 50 | if filename is None: 51 | filename = os.path.basename(path) 52 | 53 | if mimetype is None: 54 | mimetype, encoding = mimetypes.guess_type(filename) 55 | 56 | response = HttpResponse(content_type=mimetype) 57 | response.write(open(path, "rb").read()) 58 | return response 59 | 60 | 61 | def with_caught_exceptions(func): 62 | def wrapper(*args, **kwargs): 63 | try: 64 | return func(*args, **kwargs) 65 | except BadInputException as e: 66 | return HttpResponseBadRequest(str(e)) 67 | except NotFound: 68 | return HttpResponseNotFound() 69 | except NotAuthenticated: 70 | return HttpResponseForbidden("You must be logged into access this view") 71 | except InvalidCredentials: 72 | return HttpResponseForbidden("Invalid username or password provided") 73 | except Exception as e: 74 | print("Uncaught error: {}".format(str(e))) 75 | traceback.print_exc() 76 | 77 | return HttpResponseServerError( 78 | "An unhandled error occured while processing the request" 79 | ) 80 | 81 | return wrapper 82 | 83 | 84 | def with_default_success(func): 85 | """ Decorator that returns a JSON success message if no errors occur during the request. """ 86 | 87 | def wrapper(*args, **kwargs): 88 | func(*args, **kwargs) 89 | return JsonResponse({"success": True, "error": False}) 90 | 91 | return wrapper 92 | 93 | 94 | def with_login_required(func): 95 | """ Decorator that verifies that a user is logged in and returns a Forbidden status code if 96 | the requester is not. """ 97 | 98 | def wrapper(router: TemplateView, req: HttpRequest, *args, **kwargs): 99 | if not req.user.is_authenticated: 100 | raise NotAuthenticated() 101 | 102 | return func(router, req, *args, **kwargs) 103 | 104 | return wrapper 105 | 106 | 107 | @require_GET 108 | def index(_req: HttpRequest): 109 | return HttpResponse("Site is up and running! Try `GET /deployments`.") 110 | 111 | 112 | @require_POST 113 | @with_caught_exceptions 114 | @with_default_success 115 | def login_user(req: HttpRequest): 116 | username = None 117 | password = None 118 | try: 119 | username = req.POST["username"] 120 | password = req.POST["password"] 121 | except MultiValueDictKeyError: 122 | raise BadInputException("You must supply both a username and password") 123 | 124 | user = authenticate(req, username=username, password=password) 125 | 126 | if user is not None: 127 | login(req, user) 128 | else: 129 | raise InvalidCredentials("Invalid username or password") 130 | 131 | 132 | def get_or_none(Model, do_raise=True, **kwargs): 133 | """ Lookups a model given some query parameters. If a match is found, it is returned. 134 | Otherwise, either `None` is returned or a `NotFound` exception is raised depending on 135 | the value of `do_raise`. """ 136 | 137 | try: 138 | return Model.objects.get(**kwargs) 139 | except Model.DoesNotExist: 140 | if do_raise: 141 | raise NotFound() 142 | else: 143 | return None 144 | 145 | 146 | class Deployments(TemplateView): 147 | @with_caught_exceptions 148 | @with_login_required 149 | def get(self, request: HttpRequest): 150 | all_deployments = StaticDeployment.objects.prefetch_related( 151 | "deploymentversion_set", "categories" 152 | ).all() 153 | deployments_data = serialize(all_deployments, json=False) 154 | deployments_data_with_versions = [ 155 | { 156 | **datum, 157 | "versions": serialize(deployment_model.deploymentversion_set.all(), json=False), 158 | "categories": serialize(deployment_model.categories.all(), json=False), 159 | } 160 | for (datum, deployment_model) in zip(deployments_data, all_deployments) 161 | ] 162 | 163 | return JsonResponse(deployments_data_with_versions, safe=False) 164 | 165 | @with_caught_exceptions 166 | @with_login_required 167 | def post(self, request: HttpRequest): 168 | form = get_validated_form(StaticDeploymentForm, request) 169 | 170 | deployment_name = form.cleaned_data["name"] 171 | subdomain = form.cleaned_data["subdomain"] 172 | version = form.cleaned_data["version"] 173 | categories = form.cleaned_data["categories"].split(",") 174 | not_found_document = form.cleaned_data["not_found_document"] 175 | validate_deployment_name(deployment_name) 176 | validate_subdomain(subdomain) 177 | 178 | deployment_descriptor = None 179 | try: 180 | with transaction.atomic(): 181 | # Create the new deployment descriptor 182 | deployment_descriptor = StaticDeployment( 183 | name=deployment_name, subdomain=subdomain, not_found_document=not_found_document 184 | ) 185 | deployment_descriptor.save() 186 | 187 | # Create categories 188 | for category in categories: 189 | (category_model, _) = DeploymentCategory.objects.get_or_create( 190 | category=category 191 | ) 192 | deployment_descriptor.categories.add(category_model) 193 | 194 | # Create the new version and set it as active 195 | version_model = DeploymentVersion( 196 | version=version, deployment=deployment_descriptor, active=True 197 | ) 198 | version_model.save() 199 | 200 | handle_uploaded_static_archive(request.FILES["file"], subdomain, version) 201 | except IntegrityError as e: 202 | if "Duplicate entry" in str(e): 203 | raise BadInputException("`name` and `subdomain` must be unique!") 204 | else: 205 | raise e 206 | 207 | return JsonResponse( 208 | { 209 | "name": deployment_name, 210 | "subdomain": subdomain, 211 | "version": version, 212 | "url": deployment_descriptor.get_url(), 213 | } 214 | ) 215 | 216 | 217 | def get_query_dict(query_string: str, req: HttpRequest) -> dict: 218 | lookup_field = req.GET.get("lookupField", "id") 219 | if lookup_field not in ["id", "subdomain", "name"]: 220 | raise BadInputException("The supplied `lookupField` was invalid") 221 | 222 | return {lookup_field: query_string} 223 | 224 | 225 | class Deployment(TemplateView): 226 | @with_caught_exceptions 227 | def get(self, req: HttpRequest, deployment_id=None): 228 | query_dict = get_query_dict(deployment_id, req) 229 | deployment = get_or_none(StaticDeployment, **query_dict) 230 | versions = DeploymentVersion.objects.filter(deployment=deployment) 231 | active_version = next(v for v in versions if v.active) 232 | 233 | deployment_data = serialize(deployment, json=False) 234 | versions_data = serialize(versions, json=False) 235 | versions_list = list(map(lambda version_datum: version_datum["version"], versions_data)) 236 | 237 | deployment_data = { 238 | **deployment_data, 239 | "versions": versions_list, 240 | "active_version": serialize(active_version, json=False)["version"], 241 | } 242 | 243 | return JsonResponse(deployment_data, safe=False) 244 | 245 | @with_caught_exceptions 246 | @with_login_required 247 | @with_default_success 248 | def delete(self, req: HttpRequest, deployment_id=None): 249 | with transaction.atomic(): 250 | query_dict = get_query_dict(deployment_id, req) 251 | deployment = get_or_none(StaticDeployment, **query_dict) 252 | deployment_data = serialize(deployment, json=False) 253 | # This will also recursively delete all attached versions 254 | deployment.delete() 255 | 256 | delete_hosted_deployment(deployment_data["subdomain"]) 257 | 258 | 259 | class DeploymentVersionView(TemplateView): 260 | @staticmethod 261 | def is_version_special(version: str) -> bool: 262 | return version in ["minor", "m", "patch", "p", "major", "M"] 263 | 264 | @staticmethod 265 | def transform_special_version(special_version: str, previous_version: str) -> str: 266 | if special_version in ["patch", "p"]: 267 | return semver.bump_patch(previous_version) 268 | elif special_version in ["minor", "m"]: 269 | return semver.bump_minor(previous_version) 270 | elif special_version in ["major", "M"]: 271 | return semver.bump_major(previous_version) 272 | else: 273 | raise "Unreachable: `transform_special_version` should never be called with invalid special version" 274 | 275 | @with_caught_exceptions 276 | def get( 277 | self, req: HttpRequest, *args, deployment_id=None, version=None 278 | ): # pylint: disable=W0221 279 | query_dict = get_query_dict(deployment_id, req) 280 | deployment = get_or_none(StaticDeployment, **query_dict) 281 | version_model = get_or_none(DeploymentVersion, deployment=deployment, version=version) 282 | return serialize(version_model) 283 | 284 | @with_caught_exceptions 285 | @with_login_required 286 | def post(self, req: HttpRequest, deployment_id=None, version=None): 287 | query_dict = get_query_dict(deployment_id, req) 288 | deployment = get_or_none(StaticDeployment, **query_dict) 289 | 290 | if not req.FILES["file"]: 291 | raise BadInputException( 292 | "No multipart file named `file` found in request; this must be provided." 293 | ) 294 | 295 | # Assert that the new version is unique among other versions for the same deployment 296 | if (not self.is_version_special(version)) and DeploymentVersion.objects.filter( 297 | deployment=deployment, version=version 298 | ): 299 | raise BadInputException("The new version name must be unique.") 300 | 301 | version_model = None 302 | with transaction.atomic(): 303 | # Set any old active deployment as inactive 304 | old_version_model = DeploymentVersion.objects.get(deployment=deployment, active=True) 305 | if old_version_model: 306 | old_version_model.active = False 307 | old_version_model.save() 308 | 309 | # Transform special versions by bumping the previous semver version 310 | if self.is_version_special(version): 311 | try: 312 | version = self.transform_special_version(version, old_version_model.version) 313 | except Exception as e: 314 | raise BadInputException(e) 315 | 316 | # Create the new version and set it active 317 | version_model = DeploymentVersion(version=version, deployment=deployment, active=True) 318 | version_model.save() 319 | 320 | deployment_data = serialize(deployment, json=False) 321 | 322 | # Extract the supplied archive into the hosting directory 323 | handle_uploaded_static_archive( 324 | req.FILES["file"], deployment_data["subdomain"], version, init=False 325 | ) 326 | # Update the `latest` version to point to this new version 327 | update_symlink(deployment_data["subdomain"], version) 328 | 329 | return serialize(version_model) 330 | 331 | @with_caught_exceptions 332 | @with_login_required 333 | @with_default_success 334 | def delete(self, req: HttpRequest, deployment_id=None, version=None): 335 | with transaction.atomic(): 336 | query_dict = get_query_dict(deployment_id, req) 337 | deployment = get_or_none(StaticDeployment, **query_dict) 338 | deployment_data = serialize(deployment, json=False) 339 | # Delete the entry for the deployment version from the database 340 | DeploymentVersion.objects.filter(deployment=deployment, version=version).delete() 341 | # If no deployment versions remain for the owning deployment, delete the deployment 342 | delete_deployment = False 343 | if not DeploymentVersion.objects.filter(deployment=deployment): 344 | delete_deployment = True 345 | deployment.delete() 346 | 347 | if delete_deployment: 348 | delete_hosted_deployment(deployment_data["subdomain"]) 349 | else: 350 | delete_hosted_version(deployment_data["subdomain"], version) 351 | 352 | 353 | @with_caught_exceptions 354 | def not_found(req): 355 | # This environment variable is passed in from Apache 356 | redirect_url = req.META.get("REDIRECT_URL") 357 | 358 | if redirect_url is None: 359 | return HttpResponseNotFound() 360 | 361 | # Get the name of the deployment that this 404 applies to, if any 362 | match = REDIRECT_URL_RGX.match(redirect_url) 363 | if match is None: 364 | return HttpResponseNotFound() 365 | 366 | deployment_subdomain = match[1] 367 | 368 | # Check to see if there's a custom 404 handle for the given deployment 369 | deployment = get_or_none(StaticDeployment, subdomain=deployment_subdomain) 370 | not_found_document = deployment.not_found_document 371 | 372 | if not_found_document is None: 373 | return HttpResponseNotFound() 374 | 375 | if deployment is None: 376 | return HttpResponseNotFound() 377 | 378 | # Sandbox the retrieved pathname to be within the deployment's directory, preventing all kinds 379 | # of potentially nasty directory traversal stuff. 380 | deployment_dir_path = os.path.abspath(os.path.join(settings.HOST_PATH, deployment.subdomain)) 381 | document_path = os.path.abspath( 382 | os.path.relpath( 383 | os.path.join(deployment_dir_path, "latest", not_found_document), 384 | start=not_found_document, 385 | ) 386 | ) 387 | common_prefix = os.path.commonprefix([deployment_dir_path, document_path]) 388 | 389 | if common_prefix != deployment_dir_path: 390 | return HttpResponseBadRequest( 391 | ( 392 | f"Invalid error document provided: {not_found_document}; " 393 | "must be relative to deployment." 394 | ) 395 | ) 396 | 397 | if not os.path.exists(document_path): 398 | return HttpResponseBadRequest( 399 | f"The specified 404 document {not_found_document} doesn't exist in this deployment." 400 | ) 401 | 402 | # Since our way of serving this file loads it into memory, we block any files that are >128MB 403 | file_size = os.path.getsize(document_path) 404 | if file_size > 1024 * 1024 * 1024 * 128: 405 | return HttpResponseBadRequest( 406 | f"Custom not found document is {file_size} bytes, which is more than the 128MB limit." 407 | ) 408 | 409 | return send_data(document_path) 410 | 411 | 412 | class ProxyDeployments(TemplateView): 413 | @with_caught_exceptions 414 | @with_login_required 415 | def get(self, request: HttpRequest): 416 | all_proxy_deployments = ProxyDeployment.objects.all() 417 | return serialize(all_proxy_deployments) 418 | 419 | @with_caught_exceptions 420 | @with_login_required 421 | def post(self, request: HttpRequest): 422 | form = get_validated_form(ProxyDeploymentForm, request) 423 | 424 | name = form.cleaned_data["name"] 425 | subdomain = form.cleaned_data["subdomain"] 426 | use_cors_headers = form.cleaned_data["use_cors_headers"] or False 427 | validate_deployment_name(name) 428 | validate_subdomain(subdomain) 429 | 430 | proxy_deployment_descriptor = ProxyDeployment( 431 | name=name, 432 | subdomain=subdomain, 433 | destination_address=form.cleaned_data["destination_address"], 434 | use_cors_headers=use_cors_headers, 435 | ) 436 | try: 437 | proxy_deployment_descriptor.save() 438 | except IntegrityError as e: 439 | if "Duplicate entry" in str(e): 440 | raise BadInputException("`name` and `subdomain` must be unique!") 441 | else: 442 | raise e 443 | 444 | trigger_proxy_server_update() 445 | 446 | return JsonResponse( 447 | {"name": name, "subdomain": subdomain, "url": proxy_deployment_descriptor.get_url()} 448 | ) 449 | 450 | 451 | class ProxyDeploymentView(TemplateView): 452 | @with_caught_exceptions 453 | def get(self, req: HttpRequest, deployment_id=None): 454 | query_dict = get_query_dict(deployment_id, req) 455 | deployment = get_or_none(StaticDeployment, **query_dict) 456 | 457 | return serialize(deployment) 458 | 459 | @with_caught_exceptions 460 | @with_login_required 461 | @with_default_success 462 | def delete(self, req: HttpRequest, deployment_id=None): 463 | query_dict = get_query_dict(deployment_id, req) 464 | proxy_deployment = get_or_none(ProxyDeployment, **query_dict) 465 | 466 | proxy_deployment.delete() 467 | 468 | trigger_proxy_server_update() 469 | -------------------------------------------------------------------------------- /proxy/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | [[package]] 4 | name = "aho-corasick" 5 | version = "0.7.3" 6 | source = "registry+https://github.com/rust-lang/crates.io-index" 7 | dependencies = [ 8 | "memchr 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)", 9 | ] 10 | 11 | [[package]] 12 | name = "arc-swap" 13 | version = "0.3.11" 14 | source = "registry+https://github.com/rust-lang/crates.io-index" 15 | 16 | [[package]] 17 | name = "arrayvec" 18 | version = "0.4.10" 19 | source = "registry+https://github.com/rust-lang/crates.io-index" 20 | dependencies = [ 21 | "nodrop 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)", 22 | ] 23 | 24 | [[package]] 25 | name = "autocfg" 26 | version = "0.1.4" 27 | source = "registry+https://github.com/rust-lang/crates.io-index" 28 | 29 | [[package]] 30 | name = "bitflags" 31 | version = "1.1.0" 32 | source = "registry+https://github.com/rust-lang/crates.io-index" 33 | 34 | [[package]] 35 | name = "byteorder" 36 | version = "1.3.2" 37 | source = "registry+https://github.com/rust-lang/crates.io-index" 38 | 39 | [[package]] 40 | name = "bytes" 41 | version = "0.4.12" 42 | source = "registry+https://github.com/rust-lang/crates.io-index" 43 | dependencies = [ 44 | "byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", 45 | "either 1.5.2 (registry+https://github.com/rust-lang/crates.io-index)", 46 | "iovec 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", 47 | ] 48 | 49 | [[package]] 50 | name = "cc" 51 | version = "1.0.37" 52 | source = "registry+https://github.com/rust-lang/crates.io-index" 53 | 54 | [[package]] 55 | name = "cfg-if" 56 | version = "0.1.9" 57 | source = "registry+https://github.com/rust-lang/crates.io-index" 58 | 59 | [[package]] 60 | name = "chrono" 61 | version = "0.4.6" 62 | source = "registry+https://github.com/rust-lang/crates.io-index" 63 | dependencies = [ 64 | "num-integer 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)", 65 | "num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", 66 | "serde 1.0.92 (registry+https://github.com/rust-lang/crates.io-index)", 67 | "time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", 68 | ] 69 | 70 | [[package]] 71 | name = "cloudabi" 72 | version = "0.0.3" 73 | source = "registry+https://github.com/rust-lang/crates.io-index" 74 | dependencies = [ 75 | "bitflags 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", 76 | ] 77 | 78 | [[package]] 79 | name = "core-foundation" 80 | version = "0.6.4" 81 | source = "registry+https://github.com/rust-lang/crates.io-index" 82 | dependencies = [ 83 | "core-foundation-sys 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", 84 | "libc 0.2.58 (registry+https://github.com/rust-lang/crates.io-index)", 85 | ] 86 | 87 | [[package]] 88 | name = "core-foundation-sys" 89 | version = "0.6.2" 90 | source = "registry+https://github.com/rust-lang/crates.io-index" 91 | 92 | [[package]] 93 | name = "crossbeam-deque" 94 | version = "0.7.1" 95 | source = "registry+https://github.com/rust-lang/crates.io-index" 96 | dependencies = [ 97 | "crossbeam-epoch 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)", 98 | "crossbeam-utils 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)", 99 | ] 100 | 101 | [[package]] 102 | name = "crossbeam-epoch" 103 | version = "0.7.1" 104 | source = "registry+https://github.com/rust-lang/crates.io-index" 105 | dependencies = [ 106 | "arrayvec 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)", 107 | "cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", 108 | "crossbeam-utils 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)", 109 | "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", 110 | "memoffset 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", 111 | "scopeguard 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", 112 | ] 113 | 114 | [[package]] 115 | name = "crossbeam-queue" 116 | version = "0.1.2" 117 | source = "registry+https://github.com/rust-lang/crates.io-index" 118 | dependencies = [ 119 | "crossbeam-utils 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)", 120 | ] 121 | 122 | [[package]] 123 | name = "crossbeam-utils" 124 | version = "0.6.5" 125 | source = "registry+https://github.com/rust-lang/crates.io-index" 126 | dependencies = [ 127 | "cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", 128 | "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", 129 | ] 130 | 131 | [[package]] 132 | name = "diesel" 133 | version = "1.4.2" 134 | source = "registry+https://github.com/rust-lang/crates.io-index" 135 | dependencies = [ 136 | "byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", 137 | "chrono 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", 138 | "diesel_derives 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 139 | "mysqlclient-sys 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)", 140 | "r2d2 0.8.5 (registry+https://github.com/rust-lang/crates.io-index)", 141 | "url 1.7.2 (registry+https://github.com/rust-lang/crates.io-index)", 142 | ] 143 | 144 | [[package]] 145 | name = "diesel_derives" 146 | version = "1.4.0" 147 | source = "registry+https://github.com/rust-lang/crates.io-index" 148 | dependencies = [ 149 | "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)", 150 | "quote 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)", 151 | "syn 0.15.36 (registry+https://github.com/rust-lang/crates.io-index)", 152 | ] 153 | 154 | [[package]] 155 | name = "either" 156 | version = "1.5.2" 157 | source = "registry+https://github.com/rust-lang/crates.io-index" 158 | 159 | [[package]] 160 | name = "fern" 161 | version = "0.5.8" 162 | source = "registry+https://github.com/rust-lang/crates.io-index" 163 | dependencies = [ 164 | "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", 165 | ] 166 | 167 | [[package]] 168 | name = "fnv" 169 | version = "1.0.6" 170 | source = "registry+https://github.com/rust-lang/crates.io-index" 171 | 172 | [[package]] 173 | name = "foreign-types" 174 | version = "0.3.2" 175 | source = "registry+https://github.com/rust-lang/crates.io-index" 176 | dependencies = [ 177 | "foreign-types-shared 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", 178 | ] 179 | 180 | [[package]] 181 | name = "foreign-types-shared" 182 | version = "0.1.1" 183 | source = "registry+https://github.com/rust-lang/crates.io-index" 184 | 185 | [[package]] 186 | name = "fuchsia-cprng" 187 | version = "0.1.1" 188 | source = "registry+https://github.com/rust-lang/crates.io-index" 189 | 190 | [[package]] 191 | name = "fuchsia-zircon" 192 | version = "0.3.3" 193 | source = "registry+https://github.com/rust-lang/crates.io-index" 194 | dependencies = [ 195 | "bitflags 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", 196 | "fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", 197 | ] 198 | 199 | [[package]] 200 | name = "fuchsia-zircon-sys" 201 | version = "0.3.3" 202 | source = "registry+https://github.com/rust-lang/crates.io-index" 203 | 204 | [[package]] 205 | name = "futures" 206 | version = "0.1.27" 207 | source = "registry+https://github.com/rust-lang/crates.io-index" 208 | 209 | [[package]] 210 | name = "futures-cpupool" 211 | version = "0.1.8" 212 | source = "registry+https://github.com/rust-lang/crates.io-index" 213 | dependencies = [ 214 | "futures 0.1.27 (registry+https://github.com/rust-lang/crates.io-index)", 215 | "num_cpus 1.10.1 (registry+https://github.com/rust-lang/crates.io-index)", 216 | ] 217 | 218 | [[package]] 219 | name = "h2" 220 | version = "0.1.24" 221 | source = "registry+https://github.com/rust-lang/crates.io-index" 222 | dependencies = [ 223 | "byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", 224 | "bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)", 225 | "fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", 226 | "futures 0.1.27 (registry+https://github.com/rust-lang/crates.io-index)", 227 | "http 0.1.17 (registry+https://github.com/rust-lang/crates.io-index)", 228 | "indexmap 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", 229 | "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", 230 | "slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", 231 | "string 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", 232 | "tokio-io 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)", 233 | ] 234 | 235 | [[package]] 236 | name = "http" 237 | version = "0.1.17" 238 | source = "registry+https://github.com/rust-lang/crates.io-index" 239 | dependencies = [ 240 | "bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)", 241 | "fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", 242 | "itoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)", 243 | ] 244 | 245 | [[package]] 246 | name = "http-body" 247 | version = "0.1.0" 248 | source = "registry+https://github.com/rust-lang/crates.io-index" 249 | dependencies = [ 250 | "bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)", 251 | "futures 0.1.27 (registry+https://github.com/rust-lang/crates.io-index)", 252 | "http 0.1.17 (registry+https://github.com/rust-lang/crates.io-index)", 253 | "tokio-buf 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", 254 | ] 255 | 256 | [[package]] 257 | name = "httparse" 258 | version = "1.3.3" 259 | source = "registry+https://github.com/rust-lang/crates.io-index" 260 | 261 | [[package]] 262 | name = "hyper" 263 | version = "0.12.30" 264 | source = "registry+https://github.com/rust-lang/crates.io-index" 265 | dependencies = [ 266 | "bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)", 267 | "futures 0.1.27 (registry+https://github.com/rust-lang/crates.io-index)", 268 | "futures-cpupool 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", 269 | "h2 0.1.24 (registry+https://github.com/rust-lang/crates.io-index)", 270 | "http 0.1.17 (registry+https://github.com/rust-lang/crates.io-index)", 271 | "http-body 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", 272 | "httparse 1.3.3 (registry+https://github.com/rust-lang/crates.io-index)", 273 | "iovec 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", 274 | "itoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)", 275 | "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", 276 | "net2 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)", 277 | "rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", 278 | "time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", 279 | "tokio 0.1.21 (registry+https://github.com/rust-lang/crates.io-index)", 280 | "tokio-buf 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", 281 | "tokio-executor 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", 282 | "tokio-io 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)", 283 | "tokio-reactor 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", 284 | "tokio-tcp 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", 285 | "tokio-threadpool 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)", 286 | "tokio-timer 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", 287 | "want 0.0.6 (registry+https://github.com/rust-lang/crates.io-index)", 288 | ] 289 | 290 | [[package]] 291 | name = "hyper-reverse-proxy" 292 | version = "0.4.0" 293 | source = "git+https://github.com/Ameobea/hyper-reverse-proxy.git?branch=https-support#966f4b8c59b0f810c54a38d38aa83220a6828bc7" 294 | dependencies = [ 295 | "futures 0.1.27 (registry+https://github.com/rust-lang/crates.io-index)", 296 | "hyper 0.12.30 (registry+https://github.com/rust-lang/crates.io-index)", 297 | "hyper-tls 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", 298 | "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", 299 | "unicase 2.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 300 | ] 301 | 302 | [[package]] 303 | name = "hyper-tls" 304 | version = "0.3.2" 305 | source = "registry+https://github.com/rust-lang/crates.io-index" 306 | dependencies = [ 307 | "bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)", 308 | "futures 0.1.27 (registry+https://github.com/rust-lang/crates.io-index)", 309 | "hyper 0.12.30 (registry+https://github.com/rust-lang/crates.io-index)", 310 | "native-tls 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", 311 | "tokio-io 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)", 312 | ] 313 | 314 | [[package]] 315 | name = "idna" 316 | version = "0.1.5" 317 | source = "registry+https://github.com/rust-lang/crates.io-index" 318 | dependencies = [ 319 | "matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", 320 | "unicode-bidi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", 321 | "unicode-normalization 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", 322 | ] 323 | 324 | [[package]] 325 | name = "indexmap" 326 | version = "1.0.2" 327 | source = "registry+https://github.com/rust-lang/crates.io-index" 328 | 329 | [[package]] 330 | name = "iovec" 331 | version = "0.1.2" 332 | source = "registry+https://github.com/rust-lang/crates.io-index" 333 | dependencies = [ 334 | "libc 0.2.58 (registry+https://github.com/rust-lang/crates.io-index)", 335 | "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", 336 | ] 337 | 338 | [[package]] 339 | name = "itoa" 340 | version = "0.4.4" 341 | source = "registry+https://github.com/rust-lang/crates.io-index" 342 | 343 | [[package]] 344 | name = "kernel32-sys" 345 | version = "0.2.2" 346 | source = "registry+https://github.com/rust-lang/crates.io-index" 347 | dependencies = [ 348 | "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", 349 | "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", 350 | ] 351 | 352 | [[package]] 353 | name = "lazy_static" 354 | version = "1.3.0" 355 | source = "registry+https://github.com/rust-lang/crates.io-index" 356 | 357 | [[package]] 358 | name = "libc" 359 | version = "0.2.58" 360 | source = "registry+https://github.com/rust-lang/crates.io-index" 361 | 362 | [[package]] 363 | name = "lock_api" 364 | version = "0.1.5" 365 | source = "registry+https://github.com/rust-lang/crates.io-index" 366 | dependencies = [ 367 | "owning_ref 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 368 | "scopeguard 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", 369 | ] 370 | 371 | [[package]] 372 | name = "lock_api" 373 | version = "0.2.0" 374 | source = "registry+https://github.com/rust-lang/crates.io-index" 375 | dependencies = [ 376 | "scopeguard 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", 377 | ] 378 | 379 | [[package]] 380 | name = "log" 381 | version = "0.4.6" 382 | source = "registry+https://github.com/rust-lang/crates.io-index" 383 | dependencies = [ 384 | "cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", 385 | ] 386 | 387 | [[package]] 388 | name = "matches" 389 | version = "0.1.8" 390 | source = "registry+https://github.com/rust-lang/crates.io-index" 391 | 392 | [[package]] 393 | name = "memchr" 394 | version = "2.2.0" 395 | source = "registry+https://github.com/rust-lang/crates.io-index" 396 | 397 | [[package]] 398 | name = "memoffset" 399 | version = "0.2.1" 400 | source = "registry+https://github.com/rust-lang/crates.io-index" 401 | 402 | [[package]] 403 | name = "mio" 404 | version = "0.6.19" 405 | source = "registry+https://github.com/rust-lang/crates.io-index" 406 | dependencies = [ 407 | "fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", 408 | "fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", 409 | "iovec 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", 410 | "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", 411 | "libc 0.2.58 (registry+https://github.com/rust-lang/crates.io-index)", 412 | "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", 413 | "miow 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", 414 | "net2 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)", 415 | "slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", 416 | "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", 417 | ] 418 | 419 | [[package]] 420 | name = "miow" 421 | version = "0.2.1" 422 | source = "registry+https://github.com/rust-lang/crates.io-index" 423 | dependencies = [ 424 | "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", 425 | "net2 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)", 426 | "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", 427 | "ws2_32-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", 428 | ] 429 | 430 | [[package]] 431 | name = "mysqlclient-sys" 432 | version = "0.2.4" 433 | source = "registry+https://github.com/rust-lang/crates.io-index" 434 | dependencies = [ 435 | "pkg-config 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)", 436 | "vcpkg 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", 437 | ] 438 | 439 | [[package]] 440 | name = "native-tls" 441 | version = "0.2.3" 442 | source = "registry+https://github.com/rust-lang/crates.io-index" 443 | dependencies = [ 444 | "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", 445 | "libc 0.2.58 (registry+https://github.com/rust-lang/crates.io-index)", 446 | "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", 447 | "openssl 0.10.23 (registry+https://github.com/rust-lang/crates.io-index)", 448 | "openssl-probe 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", 449 | "openssl-sys 0.9.47 (registry+https://github.com/rust-lang/crates.io-index)", 450 | "schannel 0.1.15 (registry+https://github.com/rust-lang/crates.io-index)", 451 | "security-framework 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", 452 | "security-framework-sys 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", 453 | "tempfile 3.0.8 (registry+https://github.com/rust-lang/crates.io-index)", 454 | ] 455 | 456 | [[package]] 457 | name = "net2" 458 | version = "0.2.33" 459 | source = "registry+https://github.com/rust-lang/crates.io-index" 460 | dependencies = [ 461 | "cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", 462 | "libc 0.2.58 (registry+https://github.com/rust-lang/crates.io-index)", 463 | "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", 464 | ] 465 | 466 | [[package]] 467 | name = "nodrop" 468 | version = "0.1.13" 469 | source = "registry+https://github.com/rust-lang/crates.io-index" 470 | 471 | [[package]] 472 | name = "num-integer" 473 | version = "0.1.41" 474 | source = "registry+https://github.com/rust-lang/crates.io-index" 475 | dependencies = [ 476 | "autocfg 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", 477 | "num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", 478 | ] 479 | 480 | [[package]] 481 | name = "num-traits" 482 | version = "0.2.8" 483 | source = "registry+https://github.com/rust-lang/crates.io-index" 484 | dependencies = [ 485 | "autocfg 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", 486 | ] 487 | 488 | [[package]] 489 | name = "num_cpus" 490 | version = "1.10.1" 491 | source = "registry+https://github.com/rust-lang/crates.io-index" 492 | dependencies = [ 493 | "libc 0.2.58 (registry+https://github.com/rust-lang/crates.io-index)", 494 | ] 495 | 496 | [[package]] 497 | name = "openssl" 498 | version = "0.10.23" 499 | source = "registry+https://github.com/rust-lang/crates.io-index" 500 | dependencies = [ 501 | "bitflags 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", 502 | "cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", 503 | "foreign-types 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", 504 | "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", 505 | "libc 0.2.58 (registry+https://github.com/rust-lang/crates.io-index)", 506 | "openssl-sys 0.9.47 (registry+https://github.com/rust-lang/crates.io-index)", 507 | ] 508 | 509 | [[package]] 510 | name = "openssl-probe" 511 | version = "0.1.2" 512 | source = "registry+https://github.com/rust-lang/crates.io-index" 513 | 514 | [[package]] 515 | name = "openssl-sys" 516 | version = "0.9.47" 517 | source = "registry+https://github.com/rust-lang/crates.io-index" 518 | dependencies = [ 519 | "autocfg 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", 520 | "cc 1.0.37 (registry+https://github.com/rust-lang/crates.io-index)", 521 | "libc 0.2.58 (registry+https://github.com/rust-lang/crates.io-index)", 522 | "pkg-config 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)", 523 | "vcpkg 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", 524 | ] 525 | 526 | [[package]] 527 | name = "owning_ref" 528 | version = "0.4.0" 529 | source = "registry+https://github.com/rust-lang/crates.io-index" 530 | dependencies = [ 531 | "stable_deref_trait 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)", 532 | ] 533 | 534 | [[package]] 535 | name = "parking_lot" 536 | version = "0.7.1" 537 | source = "registry+https://github.com/rust-lang/crates.io-index" 538 | dependencies = [ 539 | "lock_api 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", 540 | "parking_lot_core 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 541 | ] 542 | 543 | [[package]] 544 | name = "parking_lot" 545 | version = "0.8.0" 546 | source = "registry+https://github.com/rust-lang/crates.io-index" 547 | dependencies = [ 548 | "lock_api 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", 549 | "parking_lot_core 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", 550 | "rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", 551 | ] 552 | 553 | [[package]] 554 | name = "parking_lot_core" 555 | version = "0.4.0" 556 | source = "registry+https://github.com/rust-lang/crates.io-index" 557 | dependencies = [ 558 | "libc 0.2.58 (registry+https://github.com/rust-lang/crates.io-index)", 559 | "rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)", 560 | "rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", 561 | "smallvec 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)", 562 | "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", 563 | ] 564 | 565 | [[package]] 566 | name = "parking_lot_core" 567 | version = "0.5.0" 568 | source = "registry+https://github.com/rust-lang/crates.io-index" 569 | dependencies = [ 570 | "cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", 571 | "cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)", 572 | "libc 0.2.58 (registry+https://github.com/rust-lang/crates.io-index)", 573 | "rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)", 574 | "redox_syscall 0.1.54 (registry+https://github.com/rust-lang/crates.io-index)", 575 | "rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", 576 | "smallvec 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)", 577 | "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", 578 | ] 579 | 580 | [[package]] 581 | name = "percent-encoding" 582 | version = "1.0.1" 583 | source = "registry+https://github.com/rust-lang/crates.io-index" 584 | 585 | [[package]] 586 | name = "phost-proxy" 587 | version = "0.1.0" 588 | dependencies = [ 589 | "chrono 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", 590 | "diesel 1.4.2 (registry+https://github.com/rust-lang/crates.io-index)", 591 | "fern 0.5.8 (registry+https://github.com/rust-lang/crates.io-index)", 592 | "futures 0.1.27 (registry+https://github.com/rust-lang/crates.io-index)", 593 | "hyper 0.12.30 (registry+https://github.com/rust-lang/crates.io-index)", 594 | "hyper-reverse-proxy 0.4.0 (git+https://github.com/Ameobea/hyper-reverse-proxy.git?branch=https-support)", 595 | "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", 596 | "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", 597 | "regex 1.1.7 (registry+https://github.com/rust-lang/crates.io-index)", 598 | "serde 1.0.92 (registry+https://github.com/rust-lang/crates.io-index)", 599 | "serde_derive 1.0.92 (registry+https://github.com/rust-lang/crates.io-index)", 600 | "serde_json 1.0.39 (registry+https://github.com/rust-lang/crates.io-index)", 601 | "signal-hook 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", 602 | "uuid 0.7.4 (registry+https://github.com/rust-lang/crates.io-index)", 603 | ] 604 | 605 | [[package]] 606 | name = "pkg-config" 607 | version = "0.3.14" 608 | source = "registry+https://github.com/rust-lang/crates.io-index" 609 | 610 | [[package]] 611 | name = "proc-macro2" 612 | version = "0.4.30" 613 | source = "registry+https://github.com/rust-lang/crates.io-index" 614 | dependencies = [ 615 | "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", 616 | ] 617 | 618 | [[package]] 619 | name = "quote" 620 | version = "0.6.12" 621 | source = "registry+https://github.com/rust-lang/crates.io-index" 622 | dependencies = [ 623 | "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)", 624 | ] 625 | 626 | [[package]] 627 | name = "r2d2" 628 | version = "0.8.5" 629 | source = "registry+https://github.com/rust-lang/crates.io-index" 630 | dependencies = [ 631 | "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", 632 | "parking_lot 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", 633 | "scheduled-thread-pool 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", 634 | ] 635 | 636 | [[package]] 637 | name = "rand" 638 | version = "0.6.5" 639 | source = "registry+https://github.com/rust-lang/crates.io-index" 640 | dependencies = [ 641 | "autocfg 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", 642 | "libc 0.2.58 (registry+https://github.com/rust-lang/crates.io-index)", 643 | "rand_chacha 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", 644 | "rand_core 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 645 | "rand_hc 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", 646 | "rand_isaac 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", 647 | "rand_jitter 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", 648 | "rand_os 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", 649 | "rand_pcg 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", 650 | "rand_xorshift 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", 651 | "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", 652 | ] 653 | 654 | [[package]] 655 | name = "rand_chacha" 656 | version = "0.1.1" 657 | source = "registry+https://github.com/rust-lang/crates.io-index" 658 | dependencies = [ 659 | "autocfg 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", 660 | "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", 661 | ] 662 | 663 | [[package]] 664 | name = "rand_core" 665 | version = "0.3.1" 666 | source = "registry+https://github.com/rust-lang/crates.io-index" 667 | dependencies = [ 668 | "rand_core 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 669 | ] 670 | 671 | [[package]] 672 | name = "rand_core" 673 | version = "0.4.0" 674 | source = "registry+https://github.com/rust-lang/crates.io-index" 675 | 676 | [[package]] 677 | name = "rand_hc" 678 | version = "0.1.0" 679 | source = "registry+https://github.com/rust-lang/crates.io-index" 680 | dependencies = [ 681 | "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", 682 | ] 683 | 684 | [[package]] 685 | name = "rand_isaac" 686 | version = "0.1.1" 687 | source = "registry+https://github.com/rust-lang/crates.io-index" 688 | dependencies = [ 689 | "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", 690 | ] 691 | 692 | [[package]] 693 | name = "rand_jitter" 694 | version = "0.1.4" 695 | source = "registry+https://github.com/rust-lang/crates.io-index" 696 | dependencies = [ 697 | "libc 0.2.58 (registry+https://github.com/rust-lang/crates.io-index)", 698 | "rand_core 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 699 | "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", 700 | ] 701 | 702 | [[package]] 703 | name = "rand_os" 704 | version = "0.1.3" 705 | source = "registry+https://github.com/rust-lang/crates.io-index" 706 | dependencies = [ 707 | "cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)", 708 | "fuchsia-cprng 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", 709 | "libc 0.2.58 (registry+https://github.com/rust-lang/crates.io-index)", 710 | "rand_core 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 711 | "rdrand 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 712 | "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", 713 | ] 714 | 715 | [[package]] 716 | name = "rand_pcg" 717 | version = "0.1.2" 718 | source = "registry+https://github.com/rust-lang/crates.io-index" 719 | dependencies = [ 720 | "autocfg 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", 721 | "rand_core 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 722 | ] 723 | 724 | [[package]] 725 | name = "rand_xorshift" 726 | version = "0.1.1" 727 | source = "registry+https://github.com/rust-lang/crates.io-index" 728 | dependencies = [ 729 | "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", 730 | ] 731 | 732 | [[package]] 733 | name = "rdrand" 734 | version = "0.4.0" 735 | source = "registry+https://github.com/rust-lang/crates.io-index" 736 | dependencies = [ 737 | "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", 738 | ] 739 | 740 | [[package]] 741 | name = "redox_syscall" 742 | version = "0.1.54" 743 | source = "registry+https://github.com/rust-lang/crates.io-index" 744 | 745 | [[package]] 746 | name = "regex" 747 | version = "1.1.7" 748 | source = "registry+https://github.com/rust-lang/crates.io-index" 749 | dependencies = [ 750 | "aho-corasick 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)", 751 | "memchr 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)", 752 | "regex-syntax 0.6.7 (registry+https://github.com/rust-lang/crates.io-index)", 753 | "thread_local 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", 754 | "utf8-ranges 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", 755 | ] 756 | 757 | [[package]] 758 | name = "regex-syntax" 759 | version = "0.6.7" 760 | source = "registry+https://github.com/rust-lang/crates.io-index" 761 | dependencies = [ 762 | "ucd-util 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", 763 | ] 764 | 765 | [[package]] 766 | name = "remove_dir_all" 767 | version = "0.5.2" 768 | source = "registry+https://github.com/rust-lang/crates.io-index" 769 | dependencies = [ 770 | "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", 771 | ] 772 | 773 | [[package]] 774 | name = "rustc_version" 775 | version = "0.2.3" 776 | source = "registry+https://github.com/rust-lang/crates.io-index" 777 | dependencies = [ 778 | "semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", 779 | ] 780 | 781 | [[package]] 782 | name = "ryu" 783 | version = "0.2.8" 784 | source = "registry+https://github.com/rust-lang/crates.io-index" 785 | 786 | [[package]] 787 | name = "schannel" 788 | version = "0.1.15" 789 | source = "registry+https://github.com/rust-lang/crates.io-index" 790 | dependencies = [ 791 | "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", 792 | "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", 793 | ] 794 | 795 | [[package]] 796 | name = "scheduled-thread-pool" 797 | version = "0.2.1" 798 | source = "registry+https://github.com/rust-lang/crates.io-index" 799 | dependencies = [ 800 | "parking_lot 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", 801 | ] 802 | 803 | [[package]] 804 | name = "scopeguard" 805 | version = "0.3.3" 806 | source = "registry+https://github.com/rust-lang/crates.io-index" 807 | 808 | [[package]] 809 | name = "scopeguard" 810 | version = "1.0.0" 811 | source = "registry+https://github.com/rust-lang/crates.io-index" 812 | 813 | [[package]] 814 | name = "security-framework" 815 | version = "0.3.1" 816 | source = "registry+https://github.com/rust-lang/crates.io-index" 817 | dependencies = [ 818 | "core-foundation 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)", 819 | "core-foundation-sys 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", 820 | "libc 0.2.58 (registry+https://github.com/rust-lang/crates.io-index)", 821 | "security-framework-sys 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", 822 | ] 823 | 824 | [[package]] 825 | name = "security-framework-sys" 826 | version = "0.3.1" 827 | source = "registry+https://github.com/rust-lang/crates.io-index" 828 | dependencies = [ 829 | "core-foundation-sys 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", 830 | ] 831 | 832 | [[package]] 833 | name = "semver" 834 | version = "0.9.0" 835 | source = "registry+https://github.com/rust-lang/crates.io-index" 836 | dependencies = [ 837 | "semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", 838 | ] 839 | 840 | [[package]] 841 | name = "semver-parser" 842 | version = "0.7.0" 843 | source = "registry+https://github.com/rust-lang/crates.io-index" 844 | 845 | [[package]] 846 | name = "serde" 847 | version = "1.0.92" 848 | source = "registry+https://github.com/rust-lang/crates.io-index" 849 | 850 | [[package]] 851 | name = "serde_derive" 852 | version = "1.0.92" 853 | source = "registry+https://github.com/rust-lang/crates.io-index" 854 | dependencies = [ 855 | "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)", 856 | "quote 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)", 857 | "syn 0.15.36 (registry+https://github.com/rust-lang/crates.io-index)", 858 | ] 859 | 860 | [[package]] 861 | name = "serde_json" 862 | version = "1.0.39" 863 | source = "registry+https://github.com/rust-lang/crates.io-index" 864 | dependencies = [ 865 | "itoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)", 866 | "ryu 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", 867 | "serde 1.0.92 (registry+https://github.com/rust-lang/crates.io-index)", 868 | ] 869 | 870 | [[package]] 871 | name = "signal-hook" 872 | version = "0.1.9" 873 | source = "registry+https://github.com/rust-lang/crates.io-index" 874 | dependencies = [ 875 | "libc 0.2.58 (registry+https://github.com/rust-lang/crates.io-index)", 876 | "signal-hook-registry 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", 877 | ] 878 | 879 | [[package]] 880 | name = "signal-hook-registry" 881 | version = "1.0.1" 882 | source = "registry+https://github.com/rust-lang/crates.io-index" 883 | dependencies = [ 884 | "arc-swap 0.3.11 (registry+https://github.com/rust-lang/crates.io-index)", 885 | "libc 0.2.58 (registry+https://github.com/rust-lang/crates.io-index)", 886 | ] 887 | 888 | [[package]] 889 | name = "slab" 890 | version = "0.4.2" 891 | source = "registry+https://github.com/rust-lang/crates.io-index" 892 | 893 | [[package]] 894 | name = "smallvec" 895 | version = "0.6.10" 896 | source = "registry+https://github.com/rust-lang/crates.io-index" 897 | 898 | [[package]] 899 | name = "stable_deref_trait" 900 | version = "1.1.1" 901 | source = "registry+https://github.com/rust-lang/crates.io-index" 902 | 903 | [[package]] 904 | name = "string" 905 | version = "0.2.0" 906 | source = "registry+https://github.com/rust-lang/crates.io-index" 907 | dependencies = [ 908 | "bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)", 909 | ] 910 | 911 | [[package]] 912 | name = "syn" 913 | version = "0.15.36" 914 | source = "registry+https://github.com/rust-lang/crates.io-index" 915 | dependencies = [ 916 | "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)", 917 | "quote 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)", 918 | "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", 919 | ] 920 | 921 | [[package]] 922 | name = "tempfile" 923 | version = "3.0.8" 924 | source = "registry+https://github.com/rust-lang/crates.io-index" 925 | dependencies = [ 926 | "cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", 927 | "libc 0.2.58 (registry+https://github.com/rust-lang/crates.io-index)", 928 | "rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)", 929 | "redox_syscall 0.1.54 (registry+https://github.com/rust-lang/crates.io-index)", 930 | "remove_dir_all 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)", 931 | "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", 932 | ] 933 | 934 | [[package]] 935 | name = "thread_local" 936 | version = "0.3.6" 937 | source = "registry+https://github.com/rust-lang/crates.io-index" 938 | dependencies = [ 939 | "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", 940 | ] 941 | 942 | [[package]] 943 | name = "time" 944 | version = "0.1.42" 945 | source = "registry+https://github.com/rust-lang/crates.io-index" 946 | dependencies = [ 947 | "libc 0.2.58 (registry+https://github.com/rust-lang/crates.io-index)", 948 | "redox_syscall 0.1.54 (registry+https://github.com/rust-lang/crates.io-index)", 949 | "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", 950 | ] 951 | 952 | [[package]] 953 | name = "tokio" 954 | version = "0.1.21" 955 | source = "registry+https://github.com/rust-lang/crates.io-index" 956 | dependencies = [ 957 | "bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)", 958 | "futures 0.1.27 (registry+https://github.com/rust-lang/crates.io-index)", 959 | "mio 0.6.19 (registry+https://github.com/rust-lang/crates.io-index)", 960 | "num_cpus 1.10.1 (registry+https://github.com/rust-lang/crates.io-index)", 961 | "tokio-current-thread 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", 962 | "tokio-executor 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", 963 | "tokio-io 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)", 964 | "tokio-reactor 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", 965 | "tokio-threadpool 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)", 966 | "tokio-timer 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", 967 | "tokio-trace-core 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", 968 | ] 969 | 970 | [[package]] 971 | name = "tokio-buf" 972 | version = "0.1.1" 973 | source = "registry+https://github.com/rust-lang/crates.io-index" 974 | dependencies = [ 975 | "bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)", 976 | "either 1.5.2 (registry+https://github.com/rust-lang/crates.io-index)", 977 | "futures 0.1.27 (registry+https://github.com/rust-lang/crates.io-index)", 978 | ] 979 | 980 | [[package]] 981 | name = "tokio-current-thread" 982 | version = "0.1.6" 983 | source = "registry+https://github.com/rust-lang/crates.io-index" 984 | dependencies = [ 985 | "futures 0.1.27 (registry+https://github.com/rust-lang/crates.io-index)", 986 | "tokio-executor 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", 987 | ] 988 | 989 | [[package]] 990 | name = "tokio-executor" 991 | version = "0.1.7" 992 | source = "registry+https://github.com/rust-lang/crates.io-index" 993 | dependencies = [ 994 | "crossbeam-utils 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)", 995 | "futures 0.1.27 (registry+https://github.com/rust-lang/crates.io-index)", 996 | ] 997 | 998 | [[package]] 999 | name = "tokio-io" 1000 | version = "0.1.12" 1001 | source = "registry+https://github.com/rust-lang/crates.io-index" 1002 | dependencies = [ 1003 | "bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)", 1004 | "futures 0.1.27 (registry+https://github.com/rust-lang/crates.io-index)", 1005 | "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", 1006 | ] 1007 | 1008 | [[package]] 1009 | name = "tokio-reactor" 1010 | version = "0.1.9" 1011 | source = "registry+https://github.com/rust-lang/crates.io-index" 1012 | dependencies = [ 1013 | "crossbeam-utils 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)", 1014 | "futures 0.1.27 (registry+https://github.com/rust-lang/crates.io-index)", 1015 | "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", 1016 | "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", 1017 | "mio 0.6.19 (registry+https://github.com/rust-lang/crates.io-index)", 1018 | "num_cpus 1.10.1 (registry+https://github.com/rust-lang/crates.io-index)", 1019 | "parking_lot 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)", 1020 | "slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", 1021 | "tokio-executor 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", 1022 | "tokio-io 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)", 1023 | "tokio-sync 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", 1024 | ] 1025 | 1026 | [[package]] 1027 | name = "tokio-sync" 1028 | version = "0.1.6" 1029 | source = "registry+https://github.com/rust-lang/crates.io-index" 1030 | dependencies = [ 1031 | "fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", 1032 | "futures 0.1.27 (registry+https://github.com/rust-lang/crates.io-index)", 1033 | ] 1034 | 1035 | [[package]] 1036 | name = "tokio-tcp" 1037 | version = "0.1.3" 1038 | source = "registry+https://github.com/rust-lang/crates.io-index" 1039 | dependencies = [ 1040 | "bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)", 1041 | "futures 0.1.27 (registry+https://github.com/rust-lang/crates.io-index)", 1042 | "iovec 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", 1043 | "mio 0.6.19 (registry+https://github.com/rust-lang/crates.io-index)", 1044 | "tokio-io 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)", 1045 | "tokio-reactor 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", 1046 | ] 1047 | 1048 | [[package]] 1049 | name = "tokio-threadpool" 1050 | version = "0.1.14" 1051 | source = "registry+https://github.com/rust-lang/crates.io-index" 1052 | dependencies = [ 1053 | "crossbeam-deque 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)", 1054 | "crossbeam-queue 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", 1055 | "crossbeam-utils 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)", 1056 | "futures 0.1.27 (registry+https://github.com/rust-lang/crates.io-index)", 1057 | "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", 1058 | "num_cpus 1.10.1 (registry+https://github.com/rust-lang/crates.io-index)", 1059 | "rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)", 1060 | "slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", 1061 | "tokio-executor 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", 1062 | ] 1063 | 1064 | [[package]] 1065 | name = "tokio-timer" 1066 | version = "0.2.11" 1067 | source = "registry+https://github.com/rust-lang/crates.io-index" 1068 | dependencies = [ 1069 | "crossbeam-utils 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)", 1070 | "futures 0.1.27 (registry+https://github.com/rust-lang/crates.io-index)", 1071 | "slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", 1072 | "tokio-executor 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", 1073 | ] 1074 | 1075 | [[package]] 1076 | name = "tokio-trace-core" 1077 | version = "0.2.0" 1078 | source = "registry+https://github.com/rust-lang/crates.io-index" 1079 | dependencies = [ 1080 | "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", 1081 | ] 1082 | 1083 | [[package]] 1084 | name = "try-lock" 1085 | version = "0.2.2" 1086 | source = "registry+https://github.com/rust-lang/crates.io-index" 1087 | 1088 | [[package]] 1089 | name = "ucd-util" 1090 | version = "0.1.3" 1091 | source = "registry+https://github.com/rust-lang/crates.io-index" 1092 | 1093 | [[package]] 1094 | name = "unicase" 1095 | version = "2.4.0" 1096 | source = "registry+https://github.com/rust-lang/crates.io-index" 1097 | dependencies = [ 1098 | "version_check 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", 1099 | ] 1100 | 1101 | [[package]] 1102 | name = "unicode-bidi" 1103 | version = "0.3.4" 1104 | source = "registry+https://github.com/rust-lang/crates.io-index" 1105 | dependencies = [ 1106 | "matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", 1107 | ] 1108 | 1109 | [[package]] 1110 | name = "unicode-normalization" 1111 | version = "0.1.8" 1112 | source = "registry+https://github.com/rust-lang/crates.io-index" 1113 | dependencies = [ 1114 | "smallvec 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)", 1115 | ] 1116 | 1117 | [[package]] 1118 | name = "unicode-xid" 1119 | version = "0.1.0" 1120 | source = "registry+https://github.com/rust-lang/crates.io-index" 1121 | 1122 | [[package]] 1123 | name = "url" 1124 | version = "1.7.2" 1125 | source = "registry+https://github.com/rust-lang/crates.io-index" 1126 | dependencies = [ 1127 | "idna 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", 1128 | "matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", 1129 | "percent-encoding 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", 1130 | ] 1131 | 1132 | [[package]] 1133 | name = "utf8-ranges" 1134 | version = "1.0.3" 1135 | source = "registry+https://github.com/rust-lang/crates.io-index" 1136 | 1137 | [[package]] 1138 | name = "uuid" 1139 | version = "0.7.4" 1140 | source = "registry+https://github.com/rust-lang/crates.io-index" 1141 | dependencies = [ 1142 | "rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)", 1143 | "serde 1.0.92 (registry+https://github.com/rust-lang/crates.io-index)", 1144 | ] 1145 | 1146 | [[package]] 1147 | name = "vcpkg" 1148 | version = "0.2.6" 1149 | source = "registry+https://github.com/rust-lang/crates.io-index" 1150 | 1151 | [[package]] 1152 | name = "version_check" 1153 | version = "0.1.5" 1154 | source = "registry+https://github.com/rust-lang/crates.io-index" 1155 | 1156 | [[package]] 1157 | name = "want" 1158 | version = "0.0.6" 1159 | source = "registry+https://github.com/rust-lang/crates.io-index" 1160 | dependencies = [ 1161 | "futures 0.1.27 (registry+https://github.com/rust-lang/crates.io-index)", 1162 | "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", 1163 | "try-lock 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", 1164 | ] 1165 | 1166 | [[package]] 1167 | name = "winapi" 1168 | version = "0.2.8" 1169 | source = "registry+https://github.com/rust-lang/crates.io-index" 1170 | 1171 | [[package]] 1172 | name = "winapi" 1173 | version = "0.3.7" 1174 | source = "registry+https://github.com/rust-lang/crates.io-index" 1175 | dependencies = [ 1176 | "winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 1177 | "winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 1178 | ] 1179 | 1180 | [[package]] 1181 | name = "winapi-build" 1182 | version = "0.1.1" 1183 | source = "registry+https://github.com/rust-lang/crates.io-index" 1184 | 1185 | [[package]] 1186 | name = "winapi-i686-pc-windows-gnu" 1187 | version = "0.4.0" 1188 | source = "registry+https://github.com/rust-lang/crates.io-index" 1189 | 1190 | [[package]] 1191 | name = "winapi-x86_64-pc-windows-gnu" 1192 | version = "0.4.0" 1193 | source = "registry+https://github.com/rust-lang/crates.io-index" 1194 | 1195 | [[package]] 1196 | name = "ws2_32-sys" 1197 | version = "0.2.1" 1198 | source = "registry+https://github.com/rust-lang/crates.io-index" 1199 | dependencies = [ 1200 | "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", 1201 | "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", 1202 | ] 1203 | 1204 | [metadata] 1205 | "checksum aho-corasick 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)" = "e6f484ae0c99fec2e858eb6134949117399f222608d84cadb3f58c1f97c2364c" 1206 | "checksum arc-swap 0.3.11 (registry+https://github.com/rust-lang/crates.io-index)" = "bc4662175ead9cd84451d5c35070517777949a2ed84551764129cedb88384841" 1207 | "checksum arrayvec 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)" = "92c7fb76bc8826a8b33b4ee5bb07a247a81e76764ab4d55e8f73e3a4d8808c71" 1208 | "checksum autocfg 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "0e49efa51329a5fd37e7c79db4621af617cd4e3e5bc224939808d076077077bf" 1209 | "checksum bitflags 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3d155346769a6855b86399e9bc3814ab343cd3d62c7e985113d46a0ec3c281fd" 1210 | "checksum byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a7c3dd8985a7111efc5c80b44e23ecdd8c007de8ade3b96595387e812b957cf5" 1211 | "checksum bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)" = "206fdffcfa2df7cbe15601ef46c813fce0965eb3286db6b56c583b814b51c81c" 1212 | "checksum cc 1.0.37 (registry+https://github.com/rust-lang/crates.io-index)" = "39f75544d7bbaf57560d2168f28fd649ff9c76153874db88bdbdfd839b1a7e7d" 1213 | "checksum cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "b486ce3ccf7ffd79fdeb678eac06a9e6c09fc88d33836340becb8fffe87c5e33" 1214 | "checksum chrono 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "45912881121cb26fad7c38c17ba7daa18764771836b34fab7d3fbd93ed633878" 1215 | "checksum cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f" 1216 | "checksum core-foundation 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)" = "25b9e03f145fd4f2bf705e07b900cd41fc636598fe5dc452fd0db1441c3f496d" 1217 | "checksum core-foundation-sys 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e7ca8a5221364ef15ce201e8ed2f609fc312682a8f4e0e3d4aa5879764e0fa3b" 1218 | "checksum crossbeam-deque 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b18cd2e169ad86297e6bc0ad9aa679aee9daa4f19e8163860faf7c164e4f5a71" 1219 | "checksum crossbeam-epoch 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)" = "04c9e3102cc2d69cd681412141b390abd55a362afc1540965dad0ad4d34280b4" 1220 | "checksum crossbeam-queue 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7c979cd6cfe72335896575c6b5688da489e420d36a27a0b9eb0c73db574b4a4b" 1221 | "checksum crossbeam-utils 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)" = "f8306fcef4a7b563b76b7dd949ca48f52bc1141aa067d2ea09565f3e2652aa5c" 1222 | "checksum diesel 1.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "8d24935ba50c4a8dc375a0fd1f8a2ba6bdbdc4125713126a74b965d6a01a06d7" 1223 | "checksum diesel_derives 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "62a27666098617d52c487a41f70de23d44a1dc1f3aa5877ceba2790fb1f1cab4" 1224 | "checksum either 1.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "5527cfe0d098f36e3f8839852688e63c8fff1c90b2b405aef730615f9a7bcf7b" 1225 | "checksum fern 0.5.8 (registry+https://github.com/rust-lang/crates.io-index)" = "29d26fa0f4d433d1956746e66ec10d6bf4d6c8b93cd39965cceea7f7cc78c7dd" 1226 | "checksum fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "2fad85553e09a6f881f739c29f0b00b0f01357c743266d478b68951ce23285f3" 1227 | "checksum foreign-types 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" 1228 | "checksum foreign-types-shared 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" 1229 | "checksum fuchsia-cprng 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" 1230 | "checksum fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82" 1231 | "checksum fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" 1232 | "checksum futures 0.1.27 (registry+https://github.com/rust-lang/crates.io-index)" = "a2037ec1c6c1c4f79557762eab1f7eae1f64f6cb418ace90fae88f0942b60139" 1233 | "checksum futures-cpupool 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "ab90cde24b3319636588d0c35fe03b1333857621051837ed769faefb4c2162e4" 1234 | "checksum h2 0.1.24 (registry+https://github.com/rust-lang/crates.io-index)" = "69b2a5a3092cbebbc951fe55408402e696ee2ed09019137d1800fc2c411265d2" 1235 | "checksum http 0.1.17 (registry+https://github.com/rust-lang/crates.io-index)" = "eed324f0f0daf6ec10c474f150505af2c143f251722bf9dbd1261bd1f2ee2c1a" 1236 | "checksum http-body 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "6741c859c1b2463a423a1dbce98d418e6c3c3fc720fb0d45528657320920292d" 1237 | "checksum httparse 1.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "e8734b0cfd3bc3e101ec59100e101c2eecd19282202e87808b3037b442777a83" 1238 | "checksum hyper 0.12.30 (registry+https://github.com/rust-lang/crates.io-index)" = "40e7692b2009a70b1e9b362284add4d8b75880fefddb4acaa5e67194e843f219" 1239 | "checksum hyper-reverse-proxy 0.4.0 (git+https://github.com/Ameobea/hyper-reverse-proxy.git?branch=https-support)" = "" 1240 | "checksum hyper-tls 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "3a800d6aa50af4b5850b2b0f659625ce9504df908e9733b635720483be26174f" 1241 | "checksum idna 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "38f09e0f0b1fb55fdee1f17470ad800da77af5186a1a76c026b679358b7e844e" 1242 | "checksum indexmap 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7e81a7c05f79578dbc15793d8b619db9ba32b4577003ef3af1a91c416798c58d" 1243 | "checksum iovec 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "dbe6e417e7d0975db6512b90796e8ce223145ac4e33c377e4a42882a0e88bb08" 1244 | "checksum itoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "501266b7edd0174f8530248f87f99c88fbe60ca4ef3dd486835b8d8d53136f7f" 1245 | "checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" 1246 | "checksum lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bc5729f27f159ddd61f4df6228e827e86643d4d3e7c32183cb30a1c08f604a14" 1247 | "checksum libc 0.2.58 (registry+https://github.com/rust-lang/crates.io-index)" = "6281b86796ba5e4366000be6e9e18bf35580adf9e63fbe2294aadb587613a319" 1248 | "checksum lock_api 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "62ebf1391f6acad60e5c8b43706dde4582df75c06698ab44511d15016bc2442c" 1249 | "checksum lock_api 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ed946d4529956a20f2d63ebe1b69996d5a2137c91913fe3ebbeff957f5bca7ff" 1250 | "checksum log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c84ec4b527950aa83a329754b01dbe3f58361d1c5efacd1f6d68c494d08a17c6" 1251 | "checksum matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08" 1252 | "checksum memchr 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2efc7bc57c883d4a4d6e3246905283d8dae951bb3bd32f49d6ef297f546e1c39" 1253 | "checksum memoffset 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0f9dc261e2b62d7a622bf416ea3c5245cdd5d9a7fcc428c0d06804dfce1775b3" 1254 | "checksum mio 0.6.19 (registry+https://github.com/rust-lang/crates.io-index)" = "83f51996a3ed004ef184e16818edc51fadffe8e7ca68be67f9dee67d84d0ff23" 1255 | "checksum miow 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "8c1f2f3b1cf331de6896aabf6e9d55dca90356cc9960cca7eaaf408a355ae919" 1256 | "checksum mysqlclient-sys 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)" = "7e9637d93448044078aaafea7419aed69d301b4a12bcc4aa0ae856eb169bef85" 1257 | "checksum native-tls 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "4b2df1a4c22fd44a62147fd8f13dd0f95c9d8ca7b2610299b2a2f9cf8964274e" 1258 | "checksum net2 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)" = "42550d9fb7b6684a6d404d9fa7250c2eb2646df731d1c06afc06dcee9e1bcf88" 1259 | "checksum nodrop 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)" = "2f9667ddcc6cc8a43afc9b7917599d7216aa09c463919ea32c59ed6cac8bc945" 1260 | "checksum num-integer 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)" = "b85e541ef8255f6cf42bbfe4ef361305c6c135d10919ecc26126c4e5ae94bc09" 1261 | "checksum num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "6ba9a427cfca2be13aa6f6403b0b7e7368fe982bfa16fccc450ce74c46cd9b32" 1262 | "checksum num_cpus 1.10.1 (registry+https://github.com/rust-lang/crates.io-index)" = "bcef43580c035376c0705c42792c294b66974abbfd2789b511784023f71f3273" 1263 | "checksum openssl 0.10.23 (registry+https://github.com/rust-lang/crates.io-index)" = "97c140cbb82f3b3468193dd14c1b88def39f341f68257f8a7fe8ed9ed3f628a5" 1264 | "checksum openssl-probe 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "77af24da69f9d9341038eba93a073b1fdaaa1b788221b00a69bce9e762cb32de" 1265 | "checksum openssl-sys 0.9.47 (registry+https://github.com/rust-lang/crates.io-index)" = "75bdd6dbbb4958d38e47a1d2348847ad1eb4dc205dc5d37473ae504391865acc" 1266 | "checksum owning_ref 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "49a4b8ea2179e6a2e27411d3bca09ca6dd630821cf6894c6c7c8467a8ee7ef13" 1267 | "checksum parking_lot 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ab41b4aed082705d1056416ae4468b6ea99d52599ecf3169b00088d43113e337" 1268 | "checksum parking_lot 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fa7767817701cce701d5585b9c4db3cdd02086398322c1d7e8bf5094a96a2ce7" 1269 | "checksum parking_lot_core 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "94c8c7923936b28d546dfd14d4472eaf34c99b14e1c973a32b3e6d4eb04298c9" 1270 | "checksum parking_lot_core 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "cb88cb1cb3790baa6776844f968fea3be44956cf184fa1be5a03341f5491278c" 1271 | "checksum percent-encoding 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "31010dd2e1ac33d5b46a5b413495239882813e0369f8ed8a5e266f173602f831" 1272 | "checksum pkg-config 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)" = "676e8eb2b1b4c9043511a9b7bea0915320d7e502b0a079fb03f9635a5252b18c" 1273 | "checksum proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)" = "cf3d2011ab5c909338f7887f4fc896d35932e29146c12c8d01da6b22a80ba759" 1274 | "checksum quote 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)" = "faf4799c5d274f3868a4aae320a0a182cbd2baee377b378f080e16a23e9d80db" 1275 | "checksum r2d2 0.8.5 (registry+https://github.com/rust-lang/crates.io-index)" = "bc42ce75d9f4447fb2a04bbe1ed5d18dd949104572850ec19b164e274919f81b" 1276 | "checksum rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)" = "6d71dacdc3c88c1fde3885a3be3fbab9f35724e6ce99467f7d9c5026132184ca" 1277 | "checksum rand_chacha 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "556d3a1ca6600bfcbab7c7c91ccb085ac7fbbcd70e008a98742e7847f4f7bcef" 1278 | "checksum rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b" 1279 | "checksum rand_core 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d0e7a549d590831370895ab7ba4ea0c1b6b011d106b5ff2da6eee112615e6dc0" 1280 | "checksum rand_hc 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7b40677c7be09ae76218dc623efbf7b18e34bced3f38883af07bb75630a21bc4" 1281 | "checksum rand_isaac 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ded997c9d5f13925be2a6fd7e66bf1872597f759fd9dd93513dd7e92e5a5ee08" 1282 | "checksum rand_jitter 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "1166d5c91dc97b88d1decc3285bb0a99ed84b05cfd0bc2341bdf2d43fc41e39b" 1283 | "checksum rand_os 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "7b75f676a1e053fc562eafbb47838d67c84801e38fc1ba459e8f180deabd5071" 1284 | "checksum rand_pcg 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "abf9b09b01790cfe0364f52bf32995ea3c39f4d2dd011eac241d2914146d0b44" 1285 | "checksum rand_xorshift 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cbf7e9e623549b0e21f6e97cf8ecf247c1a8fd2e8a992ae265314300b2455d5c" 1286 | "checksum rdrand 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2" 1287 | "checksum redox_syscall 0.1.54 (registry+https://github.com/rust-lang/crates.io-index)" = "12229c14a0f65c4f1cb046a3b52047cdd9da1f4b30f8a39c5063c8bae515e252" 1288 | "checksum regex 1.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "0b2f0808e7d7e4fb1cb07feb6ff2f4bc827938f24f8c2e6a3beb7370af544bdd" 1289 | "checksum regex-syntax 0.6.7 (registry+https://github.com/rust-lang/crates.io-index)" = "9d76410686f9e3a17f06128962e0ecc5755870bb890c34820c7af7f1db2e1d48" 1290 | "checksum remove_dir_all 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "4a83fa3702a688b9359eccba92d153ac33fd2e8462f9e0e3fdf155239ea7792e" 1291 | "checksum rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" 1292 | "checksum ryu 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "b96a9549dc8d48f2c283938303c4b5a77aa29bfbc5b54b084fb1630408899a8f" 1293 | "checksum schannel 0.1.15 (registry+https://github.com/rust-lang/crates.io-index)" = "f2f6abf258d99c3c1c5c2131d99d064e94b7b3dd5f416483057f308fea253339" 1294 | "checksum scheduled-thread-pool 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "bbecfcb36d47e0d6a4aefb198d475b13aa06e326770c1271171d44893766ae1c" 1295 | "checksum scopeguard 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "94258f53601af11e6a49f722422f6e3425c52b06245a5cf9bc09908b174f5e27" 1296 | "checksum scopeguard 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b42e15e59b18a828bbf5c58ea01debb36b9b096346de35d941dcb89009f24a0d" 1297 | "checksum security-framework 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "eee63d0f4a9ec776eeb30e220f0bc1e092c3ad744b2a379e3993070364d3adc2" 1298 | "checksum security-framework-sys 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "9636f8989cbf61385ae4824b98c1aaa54c994d7d8b41f11c601ed799f0549a56" 1299 | "checksum semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" 1300 | "checksum semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" 1301 | "checksum serde 1.0.92 (registry+https://github.com/rust-lang/crates.io-index)" = "32746bf0f26eab52f06af0d0aa1984f641341d06d8d673c693871da2d188c9be" 1302 | "checksum serde_derive 1.0.92 (registry+https://github.com/rust-lang/crates.io-index)" = "46a3223d0c9ba936b61c0d2e3e559e3217dbfb8d65d06d26e8b3c25de38bae3e" 1303 | "checksum serde_json 1.0.39 (registry+https://github.com/rust-lang/crates.io-index)" = "5a23aa71d4a4d43fdbfaac00eff68ba8a06a51759a89ac3304323e800c4dd40d" 1304 | "checksum signal-hook 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "72ab58f1fda436857e6337dcb6a5aaa34f16c5ddc87b3a8b6ef7a212f90b9c5a" 1305 | "checksum signal-hook-registry 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cded4ffa32146722ec54ab1f16320568465aa922aa9ab4708129599740da85d7" 1306 | "checksum slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8" 1307 | "checksum smallvec 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)" = "ab606a9c5e214920bb66c458cd7be8ef094f813f20fe77a54cc7dbfff220d4b7" 1308 | "checksum stable_deref_trait 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "dba1a27d3efae4351c8051072d619e3ade2820635c3958d826bfea39d59b54c8" 1309 | "checksum string 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d0bbfb8937e38e34c3444ff00afb28b0811d9554f15c5ad64d12b0308d1d1995" 1310 | "checksum syn 0.15.36 (registry+https://github.com/rust-lang/crates.io-index)" = "8b4f551a91e2e3848aeef8751d0d4eec9489b6474c720fd4c55958d8d31a430c" 1311 | "checksum tempfile 3.0.8 (registry+https://github.com/rust-lang/crates.io-index)" = "7dc4738f2e68ed2855de5ac9cdbe05c9216773ecde4739b2f095002ab03a13ef" 1312 | "checksum thread_local 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c6b53e329000edc2b34dbe8545fd20e55a333362d0a321909685a19bd28c3f1b" 1313 | "checksum time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)" = "db8dcfca086c1143c9270ac42a2bbd8a7ee477b78ac8e45b19abfb0cbede4b6f" 1314 | "checksum tokio 0.1.21 (registry+https://github.com/rust-lang/crates.io-index)" = "ec2ffcf4bcfc641413fa0f1427bf8f91dfc78f56a6559cbf50e04837ae442a87" 1315 | "checksum tokio-buf 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "8fb220f46c53859a4b7ec083e41dec9778ff0b1851c0942b211edb89e0ccdc46" 1316 | "checksum tokio-current-thread 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "d16217cad7f1b840c5a97dfb3c43b0c871fef423a6e8d2118c604e843662a443" 1317 | "checksum tokio-executor 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "83ea44c6c0773cc034771693711c35c677b4b5a4b21b9e7071704c54de7d555e" 1318 | "checksum tokio-io 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)" = "5090db468dad16e1a7a54c8c67280c5e4b544f3d3e018f0b913b400261f85926" 1319 | "checksum tokio-reactor 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "6af16bfac7e112bea8b0442542161bfc41cbfa4466b580bdda7d18cb88b911ce" 1320 | "checksum tokio-sync 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "2162248ff317e2bc713b261f242b69dbb838b85248ed20bb21df56d60ea4cae7" 1321 | "checksum tokio-tcp 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "1d14b10654be682ac43efee27401d792507e30fd8d26389e1da3b185de2e4119" 1322 | "checksum tokio-threadpool 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)" = "72558af20be886ea124595ea0f806dd5703b8958e4705429dd58b3d8231f72f2" 1323 | "checksum tokio-timer 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "f2106812d500ed25a4f38235b9cae8f78a09edf43203e16e59c3b769a342a60e" 1324 | "checksum tokio-trace-core 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a9c8a256d6956f7cb5e2bdfe8b1e8022f1a09206c6c2b1ba00f3b746b260c613" 1325 | "checksum try-lock 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e604eb7b43c06650e854be16a2a03155743d3752dd1c943f6829e26b7a36e382" 1326 | "checksum ucd-util 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "535c204ee4d8434478593480b8f86ab45ec9aae0e83c568ca81abf0fd0e88f86" 1327 | "checksum unicase 2.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a84e5511b2a947f3ae965dcb29b13b7b1691b6e7332cf5dbc1744138d5acb7f6" 1328 | "checksum unicode-bidi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "49f2bd0c6468a8230e1db229cff8029217cf623c767ea5d60bfbd42729ea54d5" 1329 | "checksum unicode-normalization 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "141339a08b982d942be2ca06ff8b076563cbe223d1befd5450716790d44e2426" 1330 | "checksum unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc" 1331 | "checksum url 1.7.2 (registry+https://github.com/rust-lang/crates.io-index)" = "dd4e7c0d531266369519a4aa4f399d748bd37043b00bde1e4ff1f60a120b355a" 1332 | "checksum utf8-ranges 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "9d50aa7650df78abf942826607c62468ce18d9019673d4a2ebe1865dbb96ffde" 1333 | "checksum uuid 0.7.4 (registry+https://github.com/rust-lang/crates.io-index)" = "90dbc611eb48397705a6b0f6e917da23ae517e4d127123d2cf7674206627d32a" 1334 | "checksum vcpkg 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "def296d3eb3b12371b2c7d0e83bfe1403e4db2d7a0bba324a12b21c4ee13143d" 1335 | "checksum version_check 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "914b1a6776c4c929a602fafd8bc742e06365d4bcbe48c30f9cca5824f70dc9dd" 1336 | "checksum want 0.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "797464475f30ddb8830cc529aaaae648d581f99e2036a928877dfde027ddf6b3" 1337 | "checksum winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" 1338 | "checksum winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)" = "f10e386af2b13e47c89e7236a7a14a086791a2b88ebad6df9bf42040195cf770" 1339 | "checksum winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" 1340 | "checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 1341 | "checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 1342 | "checksum ws2_32-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d59cefebd0c892fa2dd6de581e937301d8552cb44489cdff035c6187cb63fa5e" 1343 | --------------------------------------------------------------------------------