├── .gitignore ├── LICENSE ├── README.md ├── doc ├── api.md └── config.md ├── misc ├── config.json ├── requirements.txt └── structure.sql ├── src ├── barcode.py ├── database.py ├── error_codes.py └── main.py ├── static ├── favicon.ico ├── font-awesome │ ├── css │ │ ├── font-awesome.css │ │ └── font-awesome.min.css │ └── fonts │ │ ├── FontAwesome.otf │ │ ├── fontawesome-webfont.eot │ │ ├── fontawesome-webfont.svg │ │ ├── fontawesome-webfont.ttf │ │ └── fontawesome-webfont.woff ├── fonts │ ├── Lato-Black.css │ ├── Lato-Black.eot │ ├── Lato-Black.html │ ├── Lato-Black.ttf │ ├── Lato-Black.woff │ ├── Lato-BlackItalic.css │ ├── Lato-BlackItalic.eot │ ├── Lato-BlackItalic.html │ ├── Lato-BlackItalic.ttf │ ├── Lato-BlackItalic.woff │ ├── Lato-Bold.css │ ├── Lato-Bold.eot │ ├── Lato-Bold.html │ ├── Lato-Bold.min.css │ ├── Lato-Bold.ttf │ ├── Lato-Bold.woff │ ├── Lato-BoldItalic.css │ ├── Lato-BoldItalic.eot │ ├── Lato-BoldItalic.html │ ├── Lato-BoldItalic.ttf │ ├── Lato-BoldItalic.woff │ ├── Lato-Hairline.css │ ├── Lato-Hairline.eot │ ├── Lato-Hairline.html │ ├── Lato-Hairline.ttf │ ├── Lato-Hairline.woff │ ├── Lato-HairlineItalic.css │ ├── Lato-HairlineItalic.eot │ ├── Lato-HairlineItalic.html │ ├── Lato-HairlineItalic.ttf │ ├── Lato-HairlineItalic.woff │ ├── Lato-Heavy.css │ ├── Lato-Heavy.eot │ ├── Lato-Heavy.html │ ├── Lato-Heavy.ttf │ ├── Lato-Heavy.woff │ ├── Lato-HeavyItalic.css │ ├── Lato-HeavyItalic.eot │ ├── Lato-HeavyItalic.html │ ├── Lato-HeavyItalic.ttf │ ├── Lato-HeavyItalic.woff │ ├── Lato-Italic.css │ ├── Lato-Italic.eot │ ├── Lato-Italic.html │ ├── Lato-Italic.ttf │ ├── Lato-Italic.woff │ ├── Lato-Light.css │ ├── Lato-Light.eot │ ├── Lato-Light.html │ ├── Lato-Light.ttf │ ├── Lato-Light.woff │ ├── Lato-LightItalic.css │ ├── Lato-LightItalic.eot │ ├── Lato-LightItalic.html │ ├── Lato-LightItalic.ttf │ ├── Lato-LightItalic.woff │ ├── Lato-Medium.css │ ├── Lato-Medium.eot │ ├── Lato-Medium.html │ ├── Lato-Medium.ttf │ ├── Lato-Medium.woff │ ├── Lato-MediumItalic.css │ ├── Lato-MediumItalic.eot │ ├── Lato-MediumItalic.html │ ├── Lato-MediumItalic.ttf │ ├── Lato-MediumItalic.woff │ ├── Lato-Regular.css │ ├── Lato-Regular.eot │ ├── Lato-Regular.html │ ├── Lato-Regular.min.css │ ├── Lato-Regular.ttf │ ├── Lato-Regular.woff │ ├── Lato-Semibold.css │ ├── Lato-Semibold.eot │ ├── Lato-Semibold.html │ ├── Lato-Semibold.ttf │ ├── Lato-Semibold.woff │ ├── Lato-SemiboldItalic.css │ ├── Lato-SemiboldItalic.eot │ ├── Lato-SemiboldItalic.html │ ├── Lato-SemiboldItalic.ttf │ ├── Lato-SemiboldItalic.woff │ ├── Lato-Thin.css │ ├── Lato-Thin.eot │ ├── Lato-Thin.html │ ├── Lato-Thin.ttf │ ├── Lato-Thin.woff │ ├── Lato-ThinItalic.css │ ├── Lato-ThinItalic.eot │ ├── Lato-ThinItalic.html │ ├── Lato-ThinItalic.ttf │ ├── Lato-ThinItalic.woff │ ├── Lato2OFLWeb.zip │ └── OFL.txt ├── lato │ ├── Lato-Black.ttf │ ├── Lato-BlackItalic.ttf │ ├── Lato-Bold.css │ ├── Lato-Bold.eot │ ├── Lato-Bold.ttf │ ├── Lato-Bold.woff │ ├── Lato-BoldItalic.ttf │ ├── Lato-Hairline.ttf │ ├── Lato-HairlineItalic.ttf │ ├── Lato-Italic.css │ ├── Lato-Italic.eot │ ├── Lato-Italic.ttf │ ├── Lato-Italic.woff │ ├── Lato-Light.css │ ├── Lato-Light.eot │ ├── Lato-Light.ttf │ ├── Lato-Light.woff │ ├── Lato-LightItalic.ttf │ ├── Lato-Regular.css │ ├── Lato-Regular.eot │ ├── Lato-Regular.ttf │ └── Lato-Regular.woff ├── logo-head.png ├── lookup.js ├── robots.txt ├── tail.png ├── tail.svg ├── tox.css └── tox │ ├── 404.html │ └── 500.html └── templates └── tox ├── add_ui.html ├── addkeyweb_success.html ├── api_error_pretty.html ├── edit_ui.html ├── fourohfour.html ├── lookup_home.html ├── onemomentplease.html └── public_userlist.html /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | bin/ 3 | include/ 4 | lib/ 5 | .Python 6 | share/ 7 | *.db 8 | te3.py 9 | __pycache__ 10 | key 11 | djbroot 12 | .idea/ 13 | keybag.json 14 | config.json 15 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## I no longer run this service. 2 | 3 | https://blog.fefe.de/?ts=a3695410 and this is why we can't have nice things. 4 | 5 | Due to recent uninformed and stupid decisions by people who should uphold and protect the law and order, and because I have no need of such drama in my life, I'm no longer maintaining this service. 6 | 7 | You can find me on toktok IRC if you are interested in having the torch passed to you, but I'll only accept trustworthy individuals I've known to be active in the community/dev team. 8 | 9 | So long. 10 | 11 | 12 | ~~## You can contact the maintainer of the toxme.io directly.~~ 13 | 14 | ~~I can be found on freenode IRC, channel #tox , or you can add me from tox by adding toxmeio@toxme.io .~~ 15 | 16 | ~~I can assist you with removing or changing your user account on toxme.io in the event that you forget your toxme.io-generated password.~~ 17 | 18 | ~~However, please note that you need to be able to prove the account in question is yours. This means that you need to have access to your old toxid.~~ 19 | 20 | ~~I am not a tech support for tox issues. If you have an issue with tox, raise a ticket on github or ask someone on the IRC.~~ 21 | 22 | # ToxMe source 23 | 24 | ToxMe is a speedy and feature-packed Tox name resolution server. 25 | 26 | ## Installing: 27 | 28 | Quick notes before we get started, ToxMe's source is not required to access and use it in a client. Additionally, it's being written in OS X and ran on Ubuntu, so please correct any odd quirks I might accidentally include. 29 | 30 | ### OS X 31 | Install homebrew from http://brew.sh 32 | 33 | ```bash 34 | brew install libsodium python3 git libffi 35 | git clone https://github.com/LittleVulpix/toxme 36 | pip install -r misc/requirements.txt 37 | ``` 38 | 39 | And you should be ready! 40 | 41 | ### Ubuntu 42 | Note: we use Ubuntu 14.04, but newer releases should work too. 43 | 44 | ```bash 45 | apt-get install python3 python3-pip libffi-dev build-essential wget git sqlite libtool autotools-dev automake checkinstall check git yasm 46 | git clone https://github.com/jedisct1/libsodium.git 47 | cd libsodium 48 | git checkout tags/1.0.3 49 | ./autogen.sh 50 | ./configure --prefix=/usr 51 | make check 52 | sudo make install 53 | cd ~ 54 | git clone https://github.com/LittleVulpix/toxme 55 | cd toxme 56 | pip3 install -r misc/requirements.txt 57 | ``` 58 | 59 | ### Optional: 60 | #### postgres support: 61 | ##### OS X 62 | ```brew install postgresql``` 63 | 64 | ##### Ubuntu 65 | ```apt-get install libpq-dev``` 66 | 67 | ##### All ( For Ubuntu, use pip3 instead of pip ) 68 | ```pip install psycopg2``` 69 | 70 | 71 | ## Getting started: 72 | 73 | For most testing and development work you'll need both a config.json and a sqlite3 database. 74 | 75 | A sample config.json is provided at misc/config.json 76 | 77 | A database can be generated locally by running ```sqlite3 -init misc/structure.sql database.db ""``` 78 | 79 | Now just run python3 src/main.py and it should start automatically! 80 | 81 | ## Tips: 82 | 83 | If you're testing it locally make sure secure_mode in config.json is marked off (0) otherwise you'll be required to reverse proxy it and use an SSL cert 84 | 85 | ## Documentation: 86 | - [API reference](/doc/api.md) 87 | - [config options](/doc/config.md) 88 | - [PyToxMe](https://github.com/ToxMe/PyToxMe) 89 | 90 | ~~ 91 | -------------------------------------------------------------------------------- /doc/api.md: -------------------------------------------------------------------------------- 1 | 2 | ### How to read: 3 | - JSON is inlined between { }. 4 | - <> denotes substitution. 5 | - [] denotes optionality. 6 | - Everything else is taken literally. 7 | 8 | Assume all requests are POSTed to /api. 9 | 10 | ### Signing API for verification 11 | Every request accepts an optional additional field "memorabilia", which should be a 64-byte base64-encoded random sequence. 12 | If this is specified, the return value will contain the field "signed_memorabilia", which is the sequence sent in the original 13 | request signed with the toxme instance's private key, the corresponding public key of which can be found at example.com/pk 14 | If the "memorabilia" field contains invalid data or data of an invalid length, the appropriate response will be returned as 15 | though "memorabilia" had not been specified. 16 | 17 | ### Anonymous APIs: 18 | ``` 19 | lookup (3): { 20 | "action": 3, 21 | "name": 22 | } 23 | ``` 24 | Where `` is a name[@domain.com] ID. If the domain part is omittted, the 25 | server decides where to look up. 26 | 27 | ``` 28 | reverse lookup (5): { 29 | "action": 5, 30 | "id": 31 | } 32 | ``` 33 | Where ID is a Tox ID's public key. If the key exists and the account associated with it is not marked private a name will be returned. 34 | 35 | ``` 36 | search (6): { 37 | "action": 6 38 | "name": 39 | "page": 40 | } 41 | ``` 42 | Where "name" is the partial name that will be searched for and "page" is the offset (page * ENTRIES_PER_SEARCH) for the returned list. 43 | Note: This query returns a list of users, not IDs. If you have a full toxme name and want to lookup the ID, use lookup(3). 44 | 45 | ### "Authenticated" APIs: 46 | 47 | "Authenticated" API payloads have the following format. 48 | ``` 49 | { 50 | "action": , 51 | "public_key": , 52 | "encrypted": , 53 | "nonce": 54 | } 55 | ``` 56 | The following payloads are JSON strings encrypted with crypto_box using toxme's public key (available at https://example.com/pk) and the private key of the profile that is being registered, then encoded 57 | to base64. 58 | 59 | push (1): 60 | ``` 61 | { 62 | "tox_id": 63 | "name": 64 | "privacy": 1 it appears in /friends> 65 | "bio": 66 | "timestamp": 67 | } 68 | ``` 69 | 70 | delete (2): 71 | ``` 72 | { 73 | "public_key": 74 | "timestamp": 75 | } 76 | ``` 77 | 78 | ### Return values: 79 | 80 | Returns take the form 81 | ``` 82 | { 83 | "c": 84 | } 85 | ``` 86 | 87 | Possible codes: 88 | ``` 89 | ERROR_OK = {"c": 0} 90 | 91 | # Client didn't POST to /api 92 | ERROR_METHOD_UNSUPPORTED = {"c": -1} 93 | 94 | # Client is not using a secure connection 95 | ERROR_NOTSECURE = {"c": -2} 96 | 97 | # Bad payload (possibly not encrypted with the correct key) 98 | ERROR_BAD_PAYLOAD = {"c": -3} 99 | 100 | # Name is taken. 101 | ERROR_NAME_TAKEN = {"c": -25} 102 | 103 | # The public key given is bound to a name already. 104 | ERROR_DUPE_ID = {"c": -26} 105 | 106 | # Invalid char or type was used in a request. 107 | ERROR_INVALID_CHAR = {"c": -27} 108 | 109 | # Invalid name was sent 110 | ERROR_INVALID_NAME = {"c": -29} 111 | 112 | # Name not found 113 | ERROR_UNKNOWN_NAME = {"c": -30} 114 | 115 | # Sent invalid data in place of an ID 116 | ERROR_INVALID_ID = {"c": -31} 117 | 118 | # Lookup failed because of an error on the other domain's side. 119 | ERROR_LOOKUP_FAILED = {"c": -41} 120 | 121 | # Lookup failed because that user doesn't exist on the domain 122 | ERROR_NO_USER = {"c": -42} 123 | 124 | # Lookup failed because of an error on our side. 125 | ERROR_LOOKUP_INTERNAL = {"c": -43} 126 | 127 | # Client is publishing IDs too fast 128 | ERROR_RATE_LIMIT = {"c": -4} 129 | ``` 130 | 131 | For push(1), a password is issued for editing the record without having 132 | the private key. 133 | 134 | ``` 135 | { 136 | "c": 0, 137 | "password": 138 | } 139 | ``` 140 | 141 | For lookup(3), information is included about the ID: 142 | ``` 143 | { 144 | "version": "Tox V3 (local)", 145 | "source": 1, 146 | "tox_id": "56A1ADE4B65B86BCD51CC73E2CD4E542179F47959FE3E0E21B4B0ACDADE51855D34D34D37CB5", 147 | "c": 0, 148 | "url": "tox:groupbot@toxme.io", 149 | "name": "groupbot", 150 | "regdomain": "toxme.io", 151 | "verify": { 152 | "status": 1, 153 | "detail": "Good (signed by local authority)" 154 | } 155 | } 156 | ``` 157 | 158 | For search(4), an array of users that matched the query name is returned. Each user dict contains their full 159 | toxme name and bio. The array will always be of length ENTRIES_PER_SEARCH (30) or shorter. If no matching names are 160 | found the users array is empty. 161 | ``` 162 | { 163 | "c": 0, 164 | "users": [{ 165 | "name": 166 | "bio": 167 | }] 168 | } 169 | -------------------------------------------------------------------------------- /doc/config.md: -------------------------------------------------------------------------------- 1 | # Config options refrence 2 | 3 | config.json is a json based set of options evaluated by main.py that set various properties and options in the sever. 4 | 5 | ### Database URL 6 | ```"database_url": "sqlite:///tox.db"``` 7 | 8 | A SQL alchemy style connector to the database to use. This can be sqlite, postgres, mssql, etc. 9 | 10 | ### Registration domain 11 | ```"registration_domain": "localhost"``` 12 | 13 | The domain appended by ToxMe to the end of records (user@localhost for http://localhost/u/user). 14 | 15 | ### Server port 16 | ```"server_port": 8080``` 17 | 18 | The port ToxMe listens on for http. 19 | 20 | ### Server address 21 | ```"server_addr": "127.0.0.1"``` 22 | 23 | The IP ToxMe listens on. 24 | 25 | `127.0.0.1` prevents outside connections while `0.0.0.0` allows all. 26 | 27 | ### PID File 28 | ```"pid_file": "pidfile.dl"``` 29 | 30 | Where ToxMe places it's own PID. Useful for daemons. 31 | 32 | ### Is proxied 33 | ```"is_proxied": 1``` 34 | 35 | Tells ToxMe to resolve a connecting clients IP from a reverse proxies headers. 36 | 37 | ### suid 38 | ```"suid": "toxme"``` 39 | 40 | The user ToxMe runs as, please ensure it exists. 41 | 42 | ### Sandboxing 43 | ```"sandbox": 1``` 44 | 45 | Removes API limits for testing. 46 | 47 | 48 | ### Template 49 | ```"templates" : "tox"``` 50 | 51 | The template to use for the web interface. 52 | 53 | ### Find friends 54 | ```"findfriends_enabled" : 1``` 55 | 56 | Enables friend discovery features. 57 | 58 | ### Number of workers 59 | ```"number_of_workers": 2``` 60 | 61 | Number of processes to use. 62 | -------------------------------------------------------------------------------- /misc/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "database_url": "sqlite:///toxme.db", 3 | "debug": 0, 4 | "registration_domain": "localhost", 5 | "server_port": 8080, 6 | "server_addr": "127.0.0.1", 7 | "pid_file": "pidfile.dl", 8 | "secure_mode": 0, 9 | "is_proxied": 0, 10 | "templates" : "tox", 11 | "findfriends_enabled" : 1, 12 | "number_of_workers": 4, 13 | "sandbox": 1, 14 | "suid": "root" 15 | } 16 | -------------------------------------------------------------------------------- /misc/requirements.txt: -------------------------------------------------------------------------------- 1 | PyNaCl==0.2.3 2 | SQLAlchemy==0.9.4 3 | cffi==0.8.2 4 | pycparser==2.10 5 | qrcode==4.0.4 6 | requests==2.2.1 7 | six==1.6.1 8 | tornado==3.2 9 | -------------------------------------------------------------------------------- /misc/structure.sql: -------------------------------------------------------------------------------- 1 | PRAGMA foreign_keys=OFF; 2 | BEGIN TRANSACTION; 3 | CREATE TABLE records ( 4 | user_id INTEGER NOT NULL, 5 | name VARCHAR, 6 | bio VARCHAR, 7 | public_key VARCHAR, 8 | checksum VARCHAR, 9 | privacy INTEGER, 10 | timestamp DATETIME, 11 | sig VARCHAR, 12 | pin VARCHAR, 13 | password BLOB NOT NULL, 14 | PRIMARY KEY (user_id), 15 | UNIQUE (name), 16 | UNIQUE (public_key) 17 | ); 18 | COMMIT; 19 | -------------------------------------------------------------------------------- /src/barcode.py: -------------------------------------------------------------------------------- 1 | import qrcode 2 | import qrcode.image.svg 3 | import xml.etree.ElementTree as ET 4 | import io 5 | 6 | MAX_QR_CACHE_ENTRIES = 256 7 | 8 | class QRImage(qrcode.image.svg.SvgPathFillImage): 9 | YUU_CACHE = {} 10 | QR_PATH_STYLE = "fill:#000;fill-opacity:1;fill-rule:nonzero;stroke:none" 11 | BACKGROUND_COLOUR = "rgba(255,255,255,0.9)" 12 | 13 | def _svg(self, tag="svg", **kwargs): 14 | svg = super(qrcode.image.svg.SvgImage, self)._svg(tag=tag, **kwargs) 15 | svg.set("xmlns", self._SVG_namespace) 16 | svg.append( 17 | ET.Element("rect", fill=self.BACKGROUND_COLOUR, x="0", y="0", 18 | rx="8", ry="8", width="100%", height="100%") 19 | ) 20 | return svg 21 | 22 | def units(self, units, text=True): 23 | # Override: specify units in pixels for sharpness. 24 | if not text: 25 | return units 26 | return "{0}px".format(units) 27 | 28 | @classmethod 29 | def _generate(cls, uri): 30 | code = qrcode.QRCode( 31 | version=3, 32 | error_correction=qrcode.constants.ERROR_CORRECT_L, 33 | box_size=5, 34 | border=3, 35 | image_factory=cls 36 | ) 37 | code.add_data(uri) 38 | svg = code.make_image() 39 | stream = io.BytesIO() 40 | svg.save(stream) 41 | stream.seek(0) 42 | data = stream.read() 43 | if len(cls.YUU_CACHE) > MAX_QR_CACHE_ENTRIES: 44 | cls.YUU_CACHE.popitem() 45 | cls.YUU_CACHE[uri] = data 46 | return data 47 | 48 | @classmethod 49 | def get(cls, address): 50 | text = "".join(("tox:", address)).lower() 51 | return cls.YUU_CACHE.get(text, cls._generate(text)) 52 | 53 | -------------------------------------------------------------------------------- /src/database.py: -------------------------------------------------------------------------------- 1 | """ 2 | * database.py 3 | * Author: stal, stqism; April 2014 4 | * Copyright (c) 2014 Zodiac Labs. 5 | * Further licensing information: see LICENSE. 6 | """ 7 | import sqlalchemy 8 | import sqlalchemy.exc 9 | from sqlalchemy import Integer, DateTime, Unicode, Column, String, Binary 10 | from sqlalchemy.ext.declarative import declarative_base 11 | from string import printable 12 | import re 13 | import threading 14 | import math 15 | import hashlib 16 | 17 | """ 18 | Module summary: manages the database of users. 19 | """ 20 | 21 | NON_SPECIAL = set(printable) - {":", ";", "(", ")"} 22 | BASE = declarative_base() 23 | DJB_SPECIAL = re.compile(r"([;=:])") 24 | PRESENCE_CACHE_CEILING = 1000 25 | OCT_ENCODE = lambda c: "\\" + "{0:o}".format(ord(c.group(0))).zfill(3) 26 | 27 | class User(BASE): 28 | __tablename__ = "records" 29 | user_id = Column(Integer, primary_key=True) 30 | name = Column(Unicode, unique=True) 31 | bio = Column(Unicode) 32 | public_key = Column(String, unique=True) # 64 33 | checksum = Column(String) # 4 34 | privacy = Column(Integer) 35 | timestamp = Column(DateTime) 36 | sig = Column(String) 37 | pin = Column(String) 38 | password = Column(Binary, nullable=False) 39 | 40 | def is_searchable(self): 41 | """Whether searching will find this user.""" 42 | return self.privacy > 0 43 | 44 | def tox_id(self): 45 | return "".join((self.public_key, self.pin, self.checksum)) 46 | 47 | def record(self, escaped=1): 48 | """Return a record for this user, escaping weird bytes in 49 | octal format. 50 | If our PIN is available, we return a tox1 record.""" 51 | rec = "v=tox1;id={0}{1}{2};sign={3}".format(self.public_key, 52 | self.pin, self.checksum, 53 | self.sig) 54 | if escaped: 55 | return DJB_SPECIAL.sub(OCT_ENCODE, rec) 56 | else: 57 | return rec 58 | 59 | def fqdn(self, suffix): 60 | """Return the FQDN for this User. 61 | User("stal").fqdn("id.kirara.ca") -> "stal._tox.id.kirara.ca." 62 | User("stal").fqdn("id.kirara.ca.") -> "stal._tox.id.kirara.ca." 63 | """ 64 | o = [] 65 | rep = lambda char: ("\\" + "{0:o}".format(char).zfill(3) 66 | if chr(char) not in NON_SPECIAL else chr(char)) 67 | for ch in self.name.encode("utf8"): 68 | o.append(rep(ch)) 69 | return "._tox.".join(("".join(o), "".join((suffix, ".")) 70 | if not suffix.endswith(".") else suffix)) 71 | 72 | def is_password_matching(self, checkpass): 73 | salt, correct_digest = self.password[:16], self.password[16:] 74 | hash_ = hashlib.sha512(salt) 75 | hash_.update(checkpass.encode("utf8")) 76 | if hash_.digest() == correct_digest: 77 | return 1 78 | else: 79 | return 0 80 | 81 | class StaleUser(object): 82 | def __init__(self, u): 83 | self.user_id = u.user_id 84 | self.name = u.name 85 | self.bio = u.bio 86 | self.public_key = u.public_key 87 | self.checksum = u.checksum 88 | self.privacy = u.privacy 89 | self.timestamp = u.timestamp 90 | self.sig = u.sig 91 | self.pin = u.pin 92 | self.password = u.password 93 | 94 | def is_searchable(self): 95 | return User.is_searchable(self) 96 | 97 | def tox_id(self): 98 | return User.tox_id(self) 99 | 100 | def record(self, escaped=1): 101 | return User.record(self, escaped) 102 | 103 | def fqdn(self, suffix): 104 | return User.fqdn(self, suffix) 105 | 106 | def is_password_matching(self, checkpass): 107 | return User.is_password_matching(self, checkpass) 108 | 109 | class Database(object): 110 | def __init__(self, backing="sqlite:///:memory:", should_echo=1): 111 | self.presence_cache = {} 112 | self.backing = backing 113 | self.should_echo = should_echo 114 | self.lock = threading.RLock() 115 | self.cached_first_page = None 116 | self.cached_page_count = None 117 | self.cached_user_count = None 118 | 119 | def late_init(self): 120 | self.requests_serviced = 0 121 | self.dbc = sqlalchemy.create_engine(self.backing, echo=self.should_echo) 122 | BASE.metadata.create_all(self.dbc) 123 | self.gs = sqlalchemy.orm.sessionmaker(bind=self.dbc) 124 | 125 | def _cache_entity_ins(self, name, prefetch): 126 | if len(self.presence_cache) > PRESENCE_CACHE_CEILING: 127 | self.presence_cache.popitem() 128 | u = StaleUser(prefetch) 129 | self.presence_cache[name] = u 130 | return u 131 | 132 | def _cache_entity_sel(self, name): 133 | sess = self.gs() 134 | ex = sess.query(User).filter_by(name=name).first() 135 | if len(self.presence_cache) > PRESENCE_CACHE_CEILING: 136 | self.presence_cache.popitem() 137 | u = StaleUser(ex) if ex else None 138 | self.presence_cache[name] = u 139 | sess.close() 140 | return u 141 | 142 | def _cache_entity_rem(self, name, prefetch): 143 | self.presence_cache[name] = -1 144 | return prefetch 145 | 146 | def get(self, name): 147 | self.requests_serviced += 1 148 | e = self.presence_cache.get(name, -1) 149 | return e if e != -1 else self._cache_entity_sel(name) 150 | 151 | def get_page(self, num, length): 152 | if num != 0 or self.cached_first_page is None: 153 | sess, records = self.get_page_ig(num, length) 154 | sess.close() 155 | return records 156 | else: 157 | return self.cached_first_page 158 | 159 | def count_users(self): 160 | return (self.cached_user_count if self.cached_user_count is not None 161 | else self.count_users_ig()) 162 | 163 | def count_pages(self, length): 164 | return (self.cached_page_count if self.cached_page_count is not None 165 | else self.count_pages_ig(length)) 166 | 167 | def contains(self, name): 168 | e = self.presence_cache.get(name, -1) 169 | return 1 if e != -1 else bool(self._cache_entity_sel(name)) 170 | 171 | def update_atomic(self, object_, s=None): 172 | s = s or self.gs() 173 | s.add(object_) 174 | try: 175 | s.commit() 176 | self._cache_entity_ins(object_.name, object_) 177 | except sqlalchemy.exc.IntegrityError as e: 178 | print(e) 179 | return 0 180 | finally: 181 | s.close() 182 | self.cached_first_page = None 183 | self.cached_page_count = None 184 | self.cached_user_count = None 185 | return 1 186 | 187 | def get_ig(self, name, sess=None): 188 | sess = sess or self.gs() 189 | ex = sess.query(User).filter_by(name=name).first() 190 | return sess, ex 191 | 192 | def get_by_id_ig(self, id, sess=None): 193 | sess = sess or self.gs() 194 | ex = sess.query(User).filter_by(public_key=id).first() 195 | return sess, ex 196 | 197 | def get_by_id(self, id, sess=None): 198 | sess = sess or self.gs() 199 | id = id.upper() 200 | pkey = id[0:64] 201 | 202 | ex = sess.query(User).filter_by(public_key=pkey).first() 203 | u = StaleUser(ex) if ex else None 204 | sess.close() 205 | print (u.privacy) 206 | return u 207 | 208 | def get_page_ig(self, num, length, sess=None): 209 | sess = sess or self.gs() 210 | ex = (sess.query(User).filter(User.privacy > 0).order_by(User.timestamp.desc()) 211 | .limit(length).offset(num * length)) 212 | make_stale = lambda n: (self.presence_cache.get(n.name, 0) 213 | or self._cache_entity_ins(n.name, n)) 214 | if num == 0: 215 | self.cached_first_page = [make_stale(x) for x in ex] 216 | return sess, self.cached_first_page 217 | else: 218 | return sess, [make_stale(x) for x in ex] 219 | 220 | def count_pages_ig(self, length): 221 | sess = self.gs() 222 | count = sess.query(User).count() 223 | self.cached_page_count = math.ceil(float(count) / length) 224 | return self.cached_page_count 225 | 226 | def count_users_ig(self): 227 | sess = self.gs() 228 | self.cached_user_count = sess.query(User).count() 229 | return self.cached_user_count 230 | 231 | def iterate_all_users(self, mutates=0): 232 | sess = self.gs() 233 | results = sess.query(User) 234 | for obj in results: 235 | yield obj 236 | if mutates: 237 | sess.commit() 238 | sess.close() 239 | 240 | def search_users(self, name, length, num): 241 | sess = self.gs() 242 | results = (sess.query(User) 243 | .filter(User.privacy > 0) 244 | .order_by(User.name)) 245 | sess.close() 246 | result_len = results.count() 247 | start = length * num if length * num < result_len else results_len 248 | end = start + length if start + length < result_len else results_len 249 | return list(user for user in results if name in user.name)[start:end] 250 | 251 | def delete_pk(self, pk): 252 | sess = self.gs() 253 | ex = sess.query(User).filter_by(public_key=pk).first() 254 | self.presence_cache[ex.name] = -1 255 | sess.query(User).filter_by(public_key=pk).delete() 256 | sess.commit() 257 | sess.close() 258 | self.cached_first_page = None 259 | self.cached_page_count = None 260 | self.cached_user_count = None 261 | -------------------------------------------------------------------------------- /src/error_codes.py: -------------------------------------------------------------------------------- 1 | """ 2 | * error_codes.py 3 | * Author: stal, stqism; April 2014 4 | * Copyright (c) 2014 Zodiac Labs. 5 | * Further licensing information: see LICENSE. 6 | """ 7 | 8 | ERROR_OK = {"c": 0} 9 | 10 | # Client didn't POST to /api 11 | ERROR_METHOD_UNSUPPORTED = {"c": -1} 12 | 13 | # Client is not using a secure connection 14 | ERROR_NOTSECURE = {"c": -2} 15 | 16 | # Bad encrypted payload (not encrypted with our key) 17 | ERROR_BAD_PAYLOAD = {"c": -3} 18 | 19 | # Name is taken. 20 | ERROR_NAME_TAKEN = {"c": -25} 21 | 22 | # The public key given is bound to a name already. 23 | ERROR_DUPE_ID = {"c": -26} 24 | 25 | # No spaces allowed. 26 | ERROR_INVALID_CHAR = {"c": -27} 27 | 28 | ERROR_BAD_PASSWORD = {"c": -28} 29 | 30 | ERROR_INVALID_NAME = {"c": -29} 31 | 32 | # Lookup failed because of an error on the other domain's side. 33 | ERROR_LOOKUP_FAILED = {"c": -41} 34 | 35 | # Lookup failed because that user doesn't exist on the domain 36 | ERROR_NO_USER = {"c": -42} 37 | 38 | # Lookup failed because of an error on our side. 39 | ERROR_LOOKUP_INTERNAL = {"c": -43} 40 | 41 | # Client is publishing IDs too fast 42 | ERROR_RATE_LIMIT = {"c": -4} 43 | 44 | # Reverse lookup not found 45 | ERROR_UNKNOWN_NAME = {"c": -30} 46 | 47 | # Invalid ID 48 | ERROR_INVALID_ID = {"c": -31} 49 | 50 | DESCRIPTIONS = { 51 | -1: "You must send POST requests to /api.", 52 | -2: "Please try again using a HTTPS connection.", 53 | -3: "I was unable to read your encrypted payload.", 54 | -4: "You're making too many requests. Wait an hour and try again.", 55 | -25: "This name is already in use.", 56 | -26: "This Tox ID is already registered under another name.", 57 | -27: "Please don't use a space in your name.", 58 | -28: "Password incorrect.", 59 | -29: "You can't use this name.", 60 | -30: "Name not found", 61 | -31: "Tox ID not sent", 62 | -41: "Lookup failed because the other server replied with invalid data.", 63 | -42: "That user does not exist.", 64 | -43: "Internal lookup error. Please file a bug.", 65 | } 66 | -------------------------------------------------------------------------------- /src/main.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ 3 | * yuu.py 4 | * Author: stal, stqism; April 2014 5 | * Copyright (c) 2015 Project ToxMe 6 | * Further licensing information: see LICENSE. 7 | """ 8 | import tornado.ioloop 9 | import tornado.httpserver 10 | import tornado.web 11 | import tornado.log 12 | import os 13 | import json 14 | import nacl.public as public 15 | import nacl.signing as signing 16 | import nacl.encoding 17 | import nacl.exceptions 18 | import database 19 | import datetime 20 | import time 21 | import logging 22 | import re 23 | import pwd 24 | import grp 25 | import sys 26 | import random 27 | import hashlib 28 | import urllib.parse as parse 29 | from collections import Counter, defaultdict 30 | import base64 31 | import binascii 32 | 33 | import error_codes 34 | import barcode 35 | 36 | tornado.log.enable_pretty_logging() 37 | LOGGER = logging.getLogger("toxme") 38 | 39 | ACTION_PUBLISH = 1 40 | ACTION_UNPUBLISH = 2 41 | ACTION_LOOKUP = 3 42 | ACTION_STATUS = 4 43 | ACTION_RLOOKUP = 5 44 | ACTION_SEARCH = 6 45 | INVOKABLE_ACTIONS = {ACTION_PUBLISH, ACTION_UNPUBLISH, ACTION_LOOKUP, 46 | ACTION_STATUS, ACTION_RLOOKUP, ACTION_SEARCH} 47 | THROTTLE_THRESHOLD = 13 48 | 49 | VALID_KEY = re.compile(r"^[A-Fa-f0-9]{64}$") 50 | VALID_ID = re.compile(r"^[A-Fa-f0-9]{76}$") 51 | REMOVE_NEWLINES = re.compile("[\r\n]+") 52 | DISALLOWED_CHARS = set(" @/:;()\"'") 53 | DISALLOWED_NAMES = {} 54 | NAME_LIMIT_HARD = 63 55 | BIO_LIMIT = 1372 # fixme this should be configurable || hue hue 56 | 57 | ENTRIES_PER_PAGE = 30 58 | ENTRIES_PER_SEARCH = 30 59 | 60 | SIGNSTATUS_GOOD = 1 61 | SIGNSTATUS_BAD = 2 62 | SIGNSTATUS_UNDECIDED = 3 63 | 64 | SOURCE_LOCAL = 1 65 | SOURCE_REMOTE = 2 66 | 67 | #pragma mark - crypto 68 | 69 | SIGNATURE_ENC = nacl.encoding.Base64Encoder 70 | KEY_ENC = nacl.encoding.HexEncoder 71 | STORE_ENC = nacl.encoding.HexEncoder 72 | 73 | SECURE_MODE = 1 74 | 75 | class CryptoCore(object): 76 | def __init__(self): 77 | """Load or initialize crypto keys.""" 78 | try: 79 | with open("key", "rb") as keys_file: 80 | keys = keys_file.read() 81 | except IOError: 82 | keys = None 83 | if keys: 84 | self.pkey = public.PrivateKey(keys, STORE_ENC) 85 | self.skey = signing.SigningKey(keys, STORE_ENC) 86 | else: 87 | kp = public.PrivateKey.generate() 88 | with open("key", "wb") as keys_file: 89 | keys_file.write(kp.encode(STORE_ENC)) 90 | self.pkey = kp 91 | self.skey = signing.SigningKey(bytes(self.pkey), 92 | nacl.encoding.RawEncoder) 93 | 94 | def sign(self, uobj): 95 | e = nacl.encoding.HexEncoder 96 | pubkey = e.decode(uobj.public_key) 97 | pin = e.decode(uobj.pin) if uobj.pin else b"" 98 | checksum = e.decode(uobj.checksum) 99 | name = uobj.name.encode("utf8") 100 | 101 | text = b"".join((name, pubkey, pin, checksum)) 102 | return self.skey.sign(text, encoder=SIGNATURE_ENC).decode("utf8") 103 | 104 | @staticmethod 105 | def compute_checksum(data, iv=(0, 0)): 106 | e = nacl.encoding.HexEncoder 107 | checksum = list(iv) 108 | for ind, byte in enumerate(e.decode(data)): 109 | checksum[ind % 2] ^= byte 110 | return "".join(hex(byte)[2:].zfill(2) for byte in checksum).upper() 111 | 112 | @property 113 | def public_key(self): 114 | return self.pkey.public_key.encode(KEY_ENC).decode("utf8").upper() 115 | 116 | @property 117 | def verify_key(self): 118 | return self.skey.verify_key.encode(KEY_ENC).decode("utf8").upper() 119 | 120 | def dsrep_decode_name(self, client, nonce, pl): 121 | box = public.Box(self.pkey, public.PublicKey(client)) 122 | by = box.decrypt(pl, nonce) 123 | return by 124 | 125 | def dsrec_encrypt_key(self, client, nonce, msg): 126 | box = public.Box(self.pkey, public.PublicKey(client)) 127 | by = box.encrypt(msg, nonce) 128 | return by[24:] 129 | 130 | #pragma mark - web 131 | 132 | CONSONANTS = "bcdfghjklmnpqrstvwxyz" 133 | VOWELS = "aeiou" 134 | 135 | def new_password(): 136 | def sylfunc(): 137 | rng = random.SystemRandom() 138 | return "".join([rng.choice(CONSONANTS), rng.choice(VOWELS), 139 | rng.choice(CONSONANTS)]) 140 | return "-".join( 141 | [sylfunc() for x in range(random.randint(4, 6))] 142 | ) 143 | 144 | class HTTPSPolicyEnforcer(tornado.web.RequestHandler): 145 | def _fail(self): 146 | self.set_status(400) 147 | self.write_secure(error_codes.ERROR_NOTSECURE) 148 | return "" 149 | 150 | post = get = _fail 151 | 152 | 153 | SIGNED_RANDOM_LENGTH = 64 154 | 155 | class BaseAPIHandler(tornado.web.RequestHandler): 156 | 157 | def handle_envelope_hash(self, envelope): 158 | self.signed_hash = None 159 | if "memorabilia" in envelope and isinstance(envelope["memorabilia"], str): 160 | try: 161 | decoded = base64.b64decode(envelope["memorabilia"].encode('ascii')) 162 | LOGGER.info(len(decoded)) 163 | if len(decoded) == SIGNED_RANDOM_LENGTH: 164 | self.signed_hash = self.settings["crypto_core"].skey.sign(decoded) 165 | LOGGER.info(self.signed_hash) 166 | except (ValueError, TypeError, KeyError, binascii.Error, nacl.exceptions.CryptoError): 167 | LOGGER.info("did fail request because random data was bad") 168 | 169 | def write_secure(self, chunk): 170 | new_chunk = chunk 171 | try: 172 | if isinstance(chunk, dict): 173 | new_chunk = chunk.copy() 174 | if self.signed_hash is not None: 175 | new_chunk["signed_memorabilia"] = str(base64.b64encode(self.signed_hash), 'ascii') 176 | self.signed_hash = None 177 | except AttributeError: 178 | LOGGER.info("did fail request because data was even worse") 179 | 180 | self.write(new_chunk) 181 | 182 | class APIHandler(BaseAPIHandler): 183 | RETURNS_JSON = 1 184 | 185 | @staticmethod 186 | def _typecheck_dict(envelope, expect): 187 | for key, value in expect.items(): 188 | if not isinstance(envelope.get(key), value): 189 | LOGGER.warn("typecheck failed on json") 190 | return 0 191 | return 1 192 | 193 | def _encrypted_payload_prologue(self, envelope): 194 | if not self._typecheck_dict(envelope, {"public_key": str, "nonce": str, 195 | "encrypted": str}): 196 | self.set_status(400) 197 | self.json_payload(error_codes.ERROR_BAD_PAYLOAD) 198 | LOGGER.warn("Unable to read payload") 199 | return 200 | try: 201 | other_key = public.PublicKey(envelope["public_key"], KEY_ENC) 202 | except nacl.exceptions.CryptoError: 203 | LOGGER.warn("did fail req because other pk was bad") 204 | self.set_status(400) 205 | self.json_payload(error_codes.ERROR_BAD_PAYLOAD) 206 | return 207 | 208 | box = public.Box(self.settings["crypto_core"].pkey, other_key) 209 | 210 | try: 211 | nonce = nacl.encoding.Base64Encoder.decode(envelope["nonce"]) 212 | ciphertext = nacl.encoding.Base64Encoder.decode(envelope["encrypted"]) 213 | clear = box.decrypt(ciphertext, nonce, nacl.encoding.RawEncoder) 214 | except (ValueError, TypeError, nacl.exceptions.CryptoError): 215 | LOGGER.warn("did fail req because a base64 value was bad") 216 | self.set_status(400) 217 | self.json_payload(error_codes.ERROR_BAD_PAYLOAD) 218 | return 219 | 220 | try: 221 | clear = json.loads(clear.decode("utf8")) 222 | except (UnicodeDecodeError, TypeError): 223 | LOGGER.warn("did fail req because inner json decode failed") 224 | self.set_status(400) 225 | self.json_payload(error_codes.ERROR_BAD_PAYLOAD) 226 | return 227 | return clear 228 | 229 | def json_payload(self, payload): 230 | if self.RETURNS_JSON: 231 | self.write_secure(payload) 232 | else: 233 | self.render("api_error_pretty.html", payload=payload, 234 | f=error_codes.DESCRIPTIONS[payload["c"]]) 235 | 236 | def update_db_entry(self, auth, name, pub, bio, check, privacy, pin=None, 237 | password=None): 238 | dbc = self.settings["local_store"] 239 | with dbc.lock: 240 | session, owner_of_cid = dbc.get_by_id_ig(pub) 241 | if owner_of_cid and owner_of_cid.name != name: 242 | session.close() 243 | self.set_status(400) 244 | self.json_payload(error_codes.ERROR_DUPE_ID) 245 | return 0 246 | 247 | session, mus = dbc.get_ig(name, session) 248 | if not mus: 249 | mus = database.User() 250 | elif mus.public_key != auth: 251 | session.close() 252 | self.set_status(400) 253 | self.json_payload(error_codes.ERROR_NAME_TAKEN) 254 | return 0 255 | 256 | mus.name = name 257 | mus.public_key = pub 258 | mus.checksum = check 259 | mus.privacy = privacy 260 | mus.timestamp = datetime.datetime.now() 261 | mus.sig = self.settings["crypto_core"].sign(mus) 262 | mus.bio = bio 263 | mus.pin = pin 264 | if password: 265 | mus.password = password 266 | ok = dbc.update_atomic(mus, session) 267 | if not ok: 268 | session.close() 269 | self.set_status(400) 270 | self.json_payload(error_codes.ERROR_DUPE_ID) 271 | return 0 272 | session.close() 273 | return 1 274 | 275 | class APIUpdateName(APIHandler): 276 | def initialize(self, envelope): 277 | self.envelope = envelope 278 | self.handle_envelope_hash(envelope) 279 | 280 | def post(self): 281 | if self.settings["address_ctr"]: 282 | ctr = self.settings["address_ctr"][ACTION_PUBLISH] 283 | if ctr["clear_date"][self.request.remote_ip] < time.time(): 284 | del ctr["counter"][self.request.remote_ip] 285 | del ctr["clear_date"][self.request.remote_ip] 286 | ctr["counter"][self.request.remote_ip] += 1 287 | # Clears in one hour 288 | ctr["clear_date"][self.request.remote_ip] = time.time() + 3600 289 | 290 | if ctr["counter"][self.request.remote_ip] > THROTTLE_THRESHOLD: 291 | self.set_status(400) 292 | self.write_secure(error_codes.ERROR_RATE_LIMIT) 293 | return 294 | 295 | clear = self._encrypted_payload_prologue(self.envelope) 296 | if not clear: 297 | return 298 | 299 | if not self._typecheck_dict(clear, {"tox_id": str, "name": str, 300 | "timestamp": int, "privacy": int, 301 | "bio": str}): 302 | self.set_status(400) 303 | self.write_secure(error_codes.ERROR_BAD_PAYLOAD) 304 | LOGGER.warn("encrypted payload incorrect") 305 | return 306 | 307 | auth = self.envelope["public_key"].upper() 308 | id_ = clear["tox_id"].upper() 309 | name = clear["name"].lower() 310 | bio = REMOVE_NEWLINES.sub(" ", clear["bio"].strip()) 311 | ctime = int(time.time()) 312 | 313 | input_error = 0 314 | 315 | if (not VALID_ID.match(id_) 316 | or abs(ctime - clear["timestamp"]) > 300 317 | or len(name) > NAME_LIMIT_HARD 318 | or len(bio) > BIO_LIMIT): 319 | input_error = error_codes.ERROR_BAD_PAYLOAD 320 | LOGGER.warn("Size limit reached") 321 | 322 | if not set(name).isdisjoint(DISALLOWED_CHARS): 323 | input_error = error_codes.ERROR_INVALID_CHAR 324 | 325 | if name in DISALLOWED_NAMES: 326 | input_error = error_codes.ERROR_INVALID_NAME 327 | 328 | if input_error: 329 | self.set_status(400) 330 | self.json_payload(input_error) 331 | return 332 | 333 | pub, pin, check = id_[:64], id_[64:72], id_[72:] 334 | 335 | old_rec = self.settings["local_store"].get(name) 336 | if not old_rec: 337 | salt = os.urandom(16) 338 | password = new_password() 339 | hash_ = salt + hashlib.sha512(salt + password.encode("ascii")).digest() 340 | else: 341 | password = None 342 | hash_ = None 343 | 344 | if self.update_db_entry(auth, name, pub, bio, check, 345 | max(clear["privacy"], 0), pin, hash_): 346 | ok = error_codes.ERROR_OK.copy() 347 | ok["password"] = password 348 | self.json_payload(ok) 349 | return 350 | 351 | class APIReleaseName(APIHandler): 352 | def initialize(self, envelope): 353 | self.envelope = envelope 354 | self.handle_envelope_hash(envelope) 355 | 356 | def post(self): 357 | clear = self._encrypted_payload_prologue(self.envelope) 358 | if not clear: 359 | return 360 | 361 | ctime = int(time.time()) 362 | pk = clear.get("public_key", "").upper() 363 | if (not VALID_KEY.match(pk) 364 | or abs(ctime - clear.get("timestamp", 0)) > 300): 365 | self.set_status(400) 366 | self.json_payload(error_codes.ERROR_BAD_PAYLOAD) 367 | LOGGER.warn("Invalid timestamp") 368 | return 369 | 370 | rec = self.settings["local_store"].get_by_id_ig(pk)[1] 371 | old = database.StaleUser(rec) 372 | self.settings["local_store"].delete_pk(pk) 373 | self.json_payload(error_codes.ERROR_OK) 374 | return 375 | 376 | class APILookupID(BaseAPIHandler): 377 | def initialize(self, envelope): 378 | self.envelope = envelope 379 | self.handle_envelope_hash(envelope) 380 | 381 | def _results(self, result): 382 | self.set_status(200 if result["c"] == 0 else 400) 383 | self.write_secure(result) 384 | self.finish() 385 | 386 | def _build_local_result(self, name): 387 | rec = self.settings["local_store"].get(name) 388 | if not rec: 389 | return error_codes.ERROR_NO_USER 390 | base_ret = { 391 | "c": 0, 392 | "name": rec.name, 393 | "regdomain": self.settings["home"], 394 | "tox_id": rec.tox_id(), 395 | "url": "tox:{0}@{1}".format(rec.name, self.settings["home"]), 396 | "verify": { 397 | "status": SIGNSTATUS_GOOD, 398 | "detail": "Good (signed by local authority)", 399 | }, 400 | "source": SOURCE_LOCAL, 401 | "version": "Tox V3 (local)" 402 | } 403 | return base_ret 404 | 405 | @tornado.web.asynchronous 406 | def post(self): 407 | name = self.envelope.get("name").lower() 408 | if not name or name.endswith("@") or name.startswith("@"): 409 | self.set_status(400) 410 | self.write_secure(error_codes.ERROR_BAD_PAYLOAD) 411 | LOGGER.warn("Name invalid") 412 | self.finish() 413 | return 414 | if "@" not in name: 415 | name = "@".join((name, self.settings["home"])) 416 | user, domain = name.rsplit("@", 1) 417 | if domain == self.settings["home"]: 418 | self._results(self._build_local_result(user)) 419 | return 420 | else: 421 | LOGGER.warn("What (a) Terrible (dns-related) Failure") 422 | 423 | class APILookupName(BaseAPIHandler): 424 | def initialize(self, envelope): 425 | self.envelope = envelope 426 | self.handle_envelope_hash(envelope) 427 | 428 | def _results(self, result): 429 | self.set_status(200 if result["c"] == 0 else 400) 430 | self.write_secure(result) 431 | self.finish() 432 | 433 | def _build_local_result(self, id): 434 | rec = self.settings["local_store"].get_by_id(id) 435 | if not rec: 436 | return error_codes.ERROR_NO_USER 437 | base_ret = { 438 | "c": 0, 439 | "name": rec.name, 440 | } 441 | return base_ret 442 | 443 | @tornado.web.asynchronous 444 | def post(self): 445 | id = self.envelope.get("id").lower() 446 | if not id: 447 | self.set_status(400) 448 | self.write_secure(error_codes.ERROR_BAD_PAYLOAD) 449 | LOGGER.warn("ID unknown") 450 | self.finish() 451 | return 452 | else: 453 | if len(id) != 64: 454 | self.set_status(400) 455 | self.write_secure(error_codes.ERROR_INVALID_ID) 456 | LOGGER.warn("ID unknown") 457 | self.finish() 458 | return 459 | self._results(self._build_local_result(id)) 460 | return 461 | 462 | class APISearch(BaseAPIHandler): 463 | def initialize(self, envelope): 464 | self.envelope = envelope 465 | self.handle_envelope_hash(envelope) 466 | 467 | def _results(self, result): 468 | self.set_status(200 if result["c"] == 0 else 400) 469 | self.write_secure(result) 470 | self.finish() 471 | 472 | def _build_local_result(self, name, page): 473 | users = self.settings["local_store"].search_users(name, ENTRIES_PER_SEARCH, page) 474 | base_ret = { 475 | "c": 0, 476 | "users": [{"name": user.name, "bio": user.bio} for user in users], 477 | } 478 | return base_ret 479 | 480 | @tornado.web.asynchronous 481 | def post(self): 482 | name = self.envelope.get("name").lower() 483 | page = self.envelope.get("page") 484 | 485 | if (type(page) is not int) or page < 0: 486 | self.set_status(400) 487 | self.write_secure(error_codes.ERROR_INVALID_CHAR) 488 | LOGGER.warn("Invalid page") 489 | self.finish() 490 | return 491 | 492 | if not name: 493 | self.set_status(400) 494 | self.write_secure(error_codes.ERROR_INVALID_NAME) 495 | LOGGER.warn("No name given") 496 | self.finish() 497 | return 498 | else: 499 | if len(name) > NAME_LIMIT_HARD: 500 | self.set_status(400) 501 | self.write_secure(error_codes.ERROR_INVALID_NAME) 502 | LOGGER.warn("Name too long") 503 | self.finish() 504 | self._results(self._build_local_result(name, page)) 505 | return 506 | 507 | class APIStatus(BaseAPIHandler): 508 | def initialize(self, envelope): 509 | self.envelope = envelope 510 | self.handle_envelope_hash(envelope) 511 | 512 | def _results(self, result): 513 | self.set_status(200 if result["c"] == 0 else 400) 514 | self.write_secure(result) 515 | self.finish() 516 | 517 | @staticmethod 518 | def fuzz(n): 519 | # rounds to hundreds after munging the count a bit 520 | n += random.randint(-100, 100) 521 | return max(0, n if not n % 100 else n + 100 - n % 100) 522 | 523 | @tornado.web.asynchronous 524 | def post(self): 525 | self._results({ 526 | "c": 0, 527 | "ut": int(time.time()) - self.settings["app_startup"], 528 | "rs": self.fuzz(self.settings["local_store"].requests_serviced), 529 | "uc": self.fuzz(self.settings["local_store"].count_users()), 530 | }) 531 | 532 | class APIFailure(APIHandler): 533 | def get(self): 534 | self.set_status(400) 535 | self.json_payload(error_codes.ERROR_METHOD_UNSUPPORTED) 536 | return 537 | 538 | def post(self): 539 | self.set_status(400) 540 | self.json_payload(error_codes.ERROR_BAD_PAYLOAD) 541 | return 542 | 543 | def _make_handler_for_api_method(application, request, **kwargs): 544 | if SECURE_MODE: 545 | if request.protocol != "https": 546 | return HTTPSPolicyEnforcer(application, request, **kwargs) 547 | 548 | if request.method != "POST": 549 | return APIFailure(application, request, **kwargs) 550 | 551 | try: 552 | envelope = request.body.decode("utf8") 553 | envelope = json.loads(envelope) 554 | if envelope.get("action", -1) not in INVOKABLE_ACTIONS: 555 | raise TypeError("blah blah blah exceptions are flow control") 556 | except (UnicodeDecodeError, TypeError, ValueError): 557 | LOGGER.warn("failing request because of an invalid first payload") 558 | return APIFailure(application, request, **kwargs) 559 | 560 | action = envelope.get("action") 561 | if action == ACTION_PUBLISH: 562 | return APIUpdateName(application, request, envelope=envelope) 563 | elif action == ACTION_UNPUBLISH: 564 | return APIReleaseName(application, request, envelope=envelope) 565 | elif action == ACTION_LOOKUP: 566 | return APILookupID(application, request, envelope=envelope) 567 | elif action == ACTION_STATUS: 568 | return APIStatus(application, request, envelope=envelope) 569 | elif action == ACTION_RLOOKUP: 570 | return APILookupName(application, request, envelope=envelope) 571 | elif action == ACTION_SEARCH: 572 | return APISearch(application, request, envelope=envelope) 573 | 574 | class PublicKey(BaseAPIHandler): 575 | def get(self): 576 | if SECURE_MODE: 577 | if self.request.protocol != "https": 578 | self.write_secure(error_codes.ERROR_NOTSECURE) 579 | else: 580 | self.write_secure({ 581 | "c": 0, 582 | "key": self.settings["crypto_core"].public_key 583 | }) 584 | else: 585 | self.write_secure({ 586 | "c": 0, 587 | "key": self.settings["crypto_core"].public_key 588 | }) 589 | 590 | 591 | class CreateQR(BaseAPIHandler): 592 | def _fail(self): 593 | self.set_status(404) 594 | return 595 | 596 | def get(self, path_id): 597 | if SECURE_MODE: 598 | if self.request.protocol != "https": 599 | self.write_secure(error_codes.ERROR_NOTSECURE) 600 | return 601 | name = (parse.unquote(path_id) if path_id else "").lower() 602 | if not name or not set(name).isdisjoint(DISALLOWED_CHARS): 603 | return self._fail() 604 | rec = self.settings["local_store"].get(name) 605 | if not rec: 606 | return self._fail() 607 | 608 | self.set_header("Cache-Control", "public; max-age=86400") 609 | self.set_header("Content-Type", "image/svg+xml; charset=utf-8") 610 | self.write_secure(barcode.QRImage.get(self.settings["local_store"].get(name).tox_id())) 611 | return 612 | 613 | class LookupAndOpenUser(BaseAPIHandler): 614 | def _user_id(self): 615 | spl = self.request.host.rsplit(".", 1)[0] 616 | if spl == self.request.host: 617 | return None 618 | else: 619 | return spl 620 | 621 | def _render_open_user(self, name): 622 | if not name or not set(name).isdisjoint(DISALLOWED_CHARS): 623 | self.set_status(404) 624 | self.render("fourohfour.html", record=name, 625 | realm=self.settings["home"]) 626 | return 627 | 628 | rec = self.settings["local_store"].get(name) 629 | if not rec: 630 | self.set_status(404) 631 | self.render("fourohfour.html", record=name, 632 | realm=self.settings["home"]) 633 | return 634 | 635 | self.render("onemomentplease.html", record=rec, 636 | realm=self.settings["home"]) 637 | 638 | def _lookup_home(self): 639 | self.render("lookup_home.html") 640 | 641 | def get(self, path_id=None): 642 | if SECURE_MODE: 643 | if self.request.protocol != "https": 644 | self.write_secure(error_codes.ERROR_NOTSECURE) 645 | return 646 | name = (parse.unquote(path_id) if path_id else "").lower() 647 | if name: 648 | return self._render_open_user(name) 649 | else: 650 | return self._lookup_home() 651 | 652 | class FindFriends(BaseAPIHandler): 653 | def _render_page(self, num): 654 | num = int(num) 655 | results = self.settings["local_store"].get_page(num, 656 | ENTRIES_PER_PAGE) 657 | if not results: 658 | self.set_status(404) 659 | self.render("fourohfour.html", record="", 660 | realm=self.settings["home"]) 661 | return 662 | self.render("public_userlist.html", results_set=results, 663 | realm=self.settings["home"], 664 | next_page=(None if len(results) < ENTRIES_PER_PAGE 665 | else num + 1), 666 | previous_page=(num - 1) if num > 0 else None) 667 | 668 | def get(self, page): 669 | if SECURE_MODE: 670 | if self.request.protocol != "https": 671 | self.write_secure(error_codes.ERROR_NOTSECURE) 672 | return 673 | 674 | return self._render_page(page) 675 | 676 | class EditKeyWeb(APIHandler): 677 | RETURNS_JSON = 0 678 | 679 | def get(self): 680 | if SECURE_MODE: 681 | if self.request.protocol != "https": 682 | self.json_payload(error_codes.ERROR_NOTSECURE) 683 | return 684 | self.render("edit_ui.html") 685 | 686 | def post(self): 687 | if SECURE_MODE: 688 | if self.request.protocol != "https": 689 | self.json_payload(error_codes.ERROR_NOTSECURE) 690 | return 691 | 692 | if self.settings["address_ctr"]: 693 | ctr = self.settings["address_ctr"][ACTION_PUBLISH] 694 | if ctr["clear_date"][self.request.remote_ip] < time.time(): 695 | del ctr["counter"][self.request.remote_ip] 696 | del ctr["clear_date"][self.request.remote_ip] 697 | ctr["counter"][self.request.remote_ip] += 1 698 | # Clears in one hour 699 | ctr["clear_date"][self.request.remote_ip] = time.time() + 3600 700 | 701 | if ctr["counter"][self.request.remote_ip] > THROTTLE_THRESHOLD: 702 | self.set_status(400) 703 | self.json_payload(error_codes.ERROR_RATE_LIMIT) 704 | return 705 | 706 | name = self.get_body_argument("name", "").lower() 707 | password = self.get_body_argument("password", "").lower() 708 | rec = self.settings["local_store"].get(name) 709 | if not (rec and rec.is_password_matching(password)): 710 | self.set_status(400) 711 | self.json_payload(error_codes.ERROR_BAD_PASSWORD) 712 | return 713 | 714 | action = self.get_body_argument("edit_action", "") 715 | if action not in {"Delete", "Update"}: 716 | self.set_status(400) 717 | self.json_payload(error_codes.ERROR_BAD_PAYLOAD) 718 | LOGGER.warn("Invalid action") 719 | return 720 | elif action == "Delete": 721 | self.settings["local_store"].delete_pk(rec.public_key) 722 | self.redirect("/") 723 | return 724 | 725 | bio = self.get_body_argument("bio", "") or rec.bio 726 | if len(bio) > BIO_LIMIT: 727 | self.set_status(400) 728 | self.json_payload(error_codes.ERROR_BAD_PAYLOAD) 729 | LOGGER.warn("bio size over limit") 730 | return 731 | toxid = (self.get_body_argument("tox_id", "") or rec.tox_id()).upper() 732 | if not VALID_ID.match(toxid): 733 | self.set_status(400) 734 | self.json_payload(error_codes.ERROR_BAD_PAYLOAD) 735 | LOGGER.warn("Invalid checksum") 736 | return 737 | privacy = 0 if self.get_body_argument("privacy", "off") == "on" else 1 738 | lock = 1 if self.get_body_argument("lock", "off") == "on" else 0 739 | 740 | pkey = toxid[:64] 741 | pin = toxid[64:72] 742 | check = toxid[72:] 743 | if CryptoCore.compute_checksum("".join((pkey, pin))) != check: 744 | self.set_status(400) 745 | self.json_payload(error_codes.ERROR_BAD_PAYLOAD) 746 | LOGGER.warn("Invalid checksum") 747 | return 748 | 749 | if self.update_db_entry(rec.public_key, name, pkey, bio, check, privacy, pin): 750 | self.redirect("/") 751 | return 752 | 753 | class AddKeyWeb(APIHandler): 754 | RETURNS_JSON = 0 755 | 756 | def get(self): 757 | if SECURE_MODE: 758 | if self.request.protocol != "https": 759 | self.json_payload(error_codes.ERROR_NOTSECURE) 760 | return 761 | self.render("add_ui.html") 762 | 763 | def post(self): 764 | if SECURE_MODE: 765 | if self.request.protocol != "https": 766 | self.json_payload(error_codes.ERROR_NOTSECURE) 767 | return 768 | 769 | if self.settings["address_ctr"]: 770 | ctr = self.settings["address_ctr"][ACTION_PUBLISH] 771 | if ctr["clear_date"][self.request.remote_ip] < time.time(): 772 | del ctr["counter"][self.request.remote_ip] 773 | del ctr["clear_date"][self.request.remote_ip] 774 | ctr["counter"][self.request.remote_ip] += 1 775 | # Clears in one hour 776 | ctr["clear_date"][self.request.remote_ip] = time.time() + 3600 777 | 778 | if ctr["counter"][self.request.remote_ip] > THROTTLE_THRESHOLD: 779 | self.set_status(400) 780 | return 781 | 782 | name = self.get_body_argument("name", "").lower() 783 | if (not DISALLOWED_CHARS.isdisjoint(set(name)) 784 | or name in DISALLOWED_NAMES): 785 | self.set_status(400) 786 | self.json_payload(error_codes.ERROR_INVALID_CHAR) 787 | return 788 | 789 | bio = self.get_body_argument("bio", "") 790 | if len(bio) > BIO_LIMIT: 791 | self.set_status(400) 792 | self.json_payload(error_codes.ERROR_BAD_PAYLOAD) 793 | LOGGER.warn("Missing bio") 794 | return 795 | 796 | toxid = self.get_body_argument("tox_id", "").upper() 797 | if not VALID_ID.match(toxid): 798 | self.set_status(400) 799 | self.json_payload(error_codes.ERROR_BAD_PAYLOAD) 800 | LOGGER.warn("invalid Tox ID") 801 | return 802 | 803 | privacy = 0 if self.get_body_argument("privacy", "off") == "on" else 1 804 | lock = 1 if self.get_body_argument("lock", "off") == "on" else 0 805 | 806 | pkey = toxid[:64] 807 | pin = toxid[64:72] 808 | check = toxid[72:] 809 | if CryptoCore.compute_checksum("".join((pkey, pin))) != check: 810 | self.set_status(400) 811 | self.json_payload(error_codes.ERROR_BAD_PAYLOAD) 812 | LOGGER.warn("Checksum error") 813 | return 814 | 815 | old_rec = self.settings["local_store"].get(name) 816 | if not old_rec: 817 | if lock == 0: 818 | salt = os.urandom(16) 819 | password = new_password() 820 | hash_ = salt + hashlib.sha512(salt + password.encode("utf8")).digest() 821 | else: 822 | password = "None set" 823 | hash_ = None 824 | else: 825 | self.set_status(400) 826 | self.json_payload(error_codes.ERROR_NAME_TAKEN) 827 | return 828 | 829 | if self.update_db_entry(None, name, pkey, bio, check, privacy, pin, 830 | hash_): 831 | self.render("addkeyweb_success.html", n=name, p=password, 832 | regdomain=self.settings["home"]) 833 | return 834 | 835 | def main(): 836 | global SECURE_MODE 837 | 838 | with open("config.json", "r") as config_file: 839 | cfg = json.load(config_file) 840 | 841 | try: 842 | SECURE_MODE = cfg["secure_mode"] 843 | except: 844 | SECURE_MODE = 1 845 | 846 | ioloop = tornado.ioloop.IOLoop.instance() 847 | crypto_core = CryptoCore() 848 | local_store = database.Database(cfg["database_url"]) 849 | 850 | # an interesting object structure 851 | if cfg["sandbox"] == 0: 852 | address_ctr = {ACTION_PUBLISH: {"counter": Counter(), 853 | "clear_date": defaultdict(lambda: 0)}} 854 | else: 855 | LOGGER.info("Running in sandbox mode, limits are disabled.") 856 | address_ctr = None 857 | 858 | LOGGER.info("API public key: {0}".format(crypto_core.public_key)) 859 | LOGGER.info("Record sign key: {0}".format(crypto_core.verify_key)) 860 | 861 | templates_dir = "../templates/" + cfg["templates"] 862 | robots_path=os.path.join(os.path.dirname(__file__), "../static") 863 | handlers = [ 864 | ("/api", _make_handler_for_api_method), 865 | ("/pk", PublicKey), 866 | (r"/barcode/(.+)\.svg$", CreateQR), 867 | (r"/u/(.+)?$", LookupAndOpenUser), 868 | (r"^/$", LookupAndOpenUser), 869 | (r"/add_ui", AddKeyWeb), 870 | (r"/edit_ui", EditKeyWeb), 871 | (r'/robots.txt', tornado.web.StaticFileHandler, {'path': robots_path}) 872 | ] 873 | if cfg["findfriends_enabled"]: 874 | handlers.append((r"/friends/([0-9]+)$", FindFriends)) 875 | app = tornado.web.Application( 876 | handlers, 877 | template_path=os.path.join(os.path.dirname(__file__), templates_dir), 878 | static_path=os.path.join(os.path.dirname(__file__), "../static"), 879 | crypto_core=crypto_core, 880 | local_store=local_store, 881 | address_ctr=address_ctr, 882 | hooks_state=None, 883 | app_startup=int(time.time()), 884 | home=cfg["registration_domain"], 885 | ) 886 | server = tornado.httpserver.HTTPServer(app, **{ 887 | "ssl_options": cfg.get("ssl_options"), 888 | "xheaders": cfg.get("is_proxied") 889 | }) 890 | server.listen(cfg["server_port"], cfg["server_addr"]) 891 | 892 | if "suid" in cfg: 893 | LOGGER.info("Descending...") 894 | if os.getuid() == 0: 895 | if ":" not in cfg["suid"]: 896 | user = cfg["suid"] 897 | group = None 898 | else: 899 | user, group = cfg["suid"].split(":", 1) 900 | uid = pwd.getpwnam(user).pw_uid 901 | if group: 902 | gid = grp.getgrnam(group).gr_gid 903 | else: 904 | gid = pwd.getpwnam(user).pw_gid 905 | os.setgid(gid) 906 | os.setuid(uid) 907 | LOGGER.info("Continuing.") 908 | else: 909 | LOGGER.info("suid key exists in config, but not running as root. " 910 | "Exiting.") 911 | sys.exit() 912 | 913 | if "secure" in cfg: 914 | opt = cfg['secure'] 915 | 916 | if type(opt) != int: 917 | LOGGER.info("Invalid secure mode option") 918 | sys.exit() 919 | else: 920 | SECURE_MODE = opt 921 | LOGGER.info("secure mode is " + str(SECURE_MODE)) 922 | 923 | local_store.late_init() 924 | 925 | if "pid_file" in cfg: 926 | with open(cfg["pid_file"], "w") as pid: 927 | pid.write(str(os.getpid())) 928 | LOGGER.info("Notice: listening on {0}:{1}".format( 929 | cfg["server_addr"], cfg["server_port"] 930 | )) 931 | 932 | try: 933 | ioloop.start() 934 | finally: 935 | os.remove(cfg["pid_file"]) 936 | 937 | if __name__ == "__main__": 938 | main() 939 | -------------------------------------------------------------------------------- /static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LittleVulpix/toxme/52c731993ad9bfb6eacde8ace91f896d718a2811/static/favicon.ico -------------------------------------------------------------------------------- /static/font-awesome/css/font-awesome.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Font Awesome 4.0.3 by @davegandy - http://fontawesome.io - @fontawesome 3 | * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License) 4 | */ 5 | /* FONT PATH 6 | * -------------------------- */ 7 | @font-face { 8 | font-family: 'FontAwesome'; 9 | src: url('../fonts/fontawesome-webfont.eot?v=4.0.3'); 10 | src: url('../fonts/fontawesome-webfont.eot?#iefix&v=4.0.3') format('embedded-opentype'), url('../fonts/fontawesome-webfont.woff?v=4.0.3') format('woff'), url('../fonts/fontawesome-webfont.ttf?v=4.0.3') format('truetype'), url('../fonts/fontawesome-webfont.svg?v=4.0.3#fontawesomeregular') format('svg'); 11 | font-weight: normal; 12 | font-style: normal; 13 | } 14 | .fa { 15 | display: inline-block; 16 | font-family: FontAwesome; 17 | font-style: normal; 18 | font-weight: normal; 19 | line-height: 1; 20 | -webkit-font-smoothing: antialiased; 21 | -moz-osx-font-smoothing: grayscale; 22 | } 23 | /* makes the font 33% larger relative to the icon container */ 24 | .fa-lg { 25 | font-size: 1.3333333333333333em; 26 | line-height: 0.75em; 27 | vertical-align: -15%; 28 | } 29 | .fa-2x { 30 | font-size: 2em; 31 | } 32 | .fa-3x { 33 | font-size: 3em; 34 | } 35 | .fa-4x { 36 | font-size: 4em; 37 | } 38 | .fa-5x { 39 | font-size: 5em; 40 | } 41 | .fa-fw { 42 | width: 1.2857142857142858em; 43 | text-align: center; 44 | } 45 | .fa-ul { 46 | padding-left: 0; 47 | margin-left: 2.142857142857143em; 48 | list-style-type: none; 49 | } 50 | .fa-ul > li { 51 | position: relative; 52 | } 53 | .fa-li { 54 | position: absolute; 55 | left: -2.142857142857143em; 56 | width: 2.142857142857143em; 57 | top: 0.14285714285714285em; 58 | text-align: center; 59 | } 60 | .fa-li.fa-lg { 61 | left: -1.8571428571428572em; 62 | } 63 | .fa-border { 64 | padding: .2em .25em .15em; 65 | border: solid 0.08em #eeeeee; 66 | border-radius: .1em; 67 | } 68 | .pull-right { 69 | float: right; 70 | } 71 | .pull-left { 72 | float: left; 73 | } 74 | .fa.pull-left { 75 | margin-right: .3em; 76 | } 77 | .fa.pull-right { 78 | margin-left: .3em; 79 | } 80 | .fa-spin { 81 | -webkit-animation: spin 2s infinite linear; 82 | -moz-animation: spin 2s infinite linear; 83 | -o-animation: spin 2s infinite linear; 84 | animation: spin 2s infinite linear; 85 | } 86 | @-moz-keyframes spin { 87 | 0% { 88 | -moz-transform: rotate(0deg); 89 | } 90 | 100% { 91 | -moz-transform: rotate(359deg); 92 | } 93 | } 94 | @-webkit-keyframes spin { 95 | 0% { 96 | -webkit-transform: rotate(0deg); 97 | } 98 | 100% { 99 | -webkit-transform: rotate(359deg); 100 | } 101 | } 102 | @-o-keyframes spin { 103 | 0% { 104 | -o-transform: rotate(0deg); 105 | } 106 | 100% { 107 | -o-transform: rotate(359deg); 108 | } 109 | } 110 | @-ms-keyframes spin { 111 | 0% { 112 | -ms-transform: rotate(0deg); 113 | } 114 | 100% { 115 | -ms-transform: rotate(359deg); 116 | } 117 | } 118 | @keyframes spin { 119 | 0% { 120 | transform: rotate(0deg); 121 | } 122 | 100% { 123 | transform: rotate(359deg); 124 | } 125 | } 126 | .fa-rotate-90 { 127 | filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=1); 128 | -webkit-transform: rotate(90deg); 129 | -moz-transform: rotate(90deg); 130 | -ms-transform: rotate(90deg); 131 | -o-transform: rotate(90deg); 132 | transform: rotate(90deg); 133 | } 134 | .fa-rotate-180 { 135 | filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=2); 136 | -webkit-transform: rotate(180deg); 137 | -moz-transform: rotate(180deg); 138 | -ms-transform: rotate(180deg); 139 | -o-transform: rotate(180deg); 140 | transform: rotate(180deg); 141 | } 142 | .fa-rotate-270 { 143 | filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=3); 144 | -webkit-transform: rotate(270deg); 145 | -moz-transform: rotate(270deg); 146 | -ms-transform: rotate(270deg); 147 | -o-transform: rotate(270deg); 148 | transform: rotate(270deg); 149 | } 150 | .fa-flip-horizontal { 151 | filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1); 152 | -webkit-transform: scale(-1, 1); 153 | -moz-transform: scale(-1, 1); 154 | -ms-transform: scale(-1, 1); 155 | -o-transform: scale(-1, 1); 156 | transform: scale(-1, 1); 157 | } 158 | .fa-flip-vertical { 159 | filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1); 160 | -webkit-transform: scale(1, -1); 161 | -moz-transform: scale(1, -1); 162 | -ms-transform: scale(1, -1); 163 | -o-transform: scale(1, -1); 164 | transform: scale(1, -1); 165 | } 166 | .fa-stack { 167 | position: relative; 168 | display: inline-block; 169 | width: 2em; 170 | height: 2em; 171 | line-height: 2em; 172 | vertical-align: middle; 173 | } 174 | .fa-stack-1x, 175 | .fa-stack-2x { 176 | position: absolute; 177 | left: 0; 178 | width: 100%; 179 | text-align: center; 180 | } 181 | .fa-stack-1x { 182 | line-height: inherit; 183 | } 184 | .fa-stack-2x { 185 | font-size: 2em; 186 | } 187 | .fa-inverse { 188 | color: #ffffff; 189 | } 190 | /* Font Awesome uses the Unicode Private Use Area (PUA) to ensure screen 191 | readers do not read off random characters that represent icons */ 192 | .fa-glass:before { 193 | content: "\f000"; 194 | } 195 | .fa-music:before { 196 | content: "\f001"; 197 | } 198 | .fa-search:before { 199 | content: "\f002"; 200 | } 201 | .fa-envelope-o:before { 202 | content: "\f003"; 203 | } 204 | .fa-heart:before { 205 | content: "\f004"; 206 | } 207 | .fa-star:before { 208 | content: "\f005"; 209 | } 210 | .fa-star-o:before { 211 | content: "\f006"; 212 | } 213 | .fa-user:before { 214 | content: "\f007"; 215 | } 216 | .fa-film:before { 217 | content: "\f008"; 218 | } 219 | .fa-th-large:before { 220 | content: "\f009"; 221 | } 222 | .fa-th:before { 223 | content: "\f00a"; 224 | } 225 | .fa-th-list:before { 226 | content: "\f00b"; 227 | } 228 | .fa-check:before { 229 | content: "\f00c"; 230 | } 231 | .fa-times:before { 232 | content: "\f00d"; 233 | } 234 | .fa-search-plus:before { 235 | content: "\f00e"; 236 | } 237 | .fa-search-minus:before { 238 | content: "\f010"; 239 | } 240 | .fa-power-off:before { 241 | content: "\f011"; 242 | } 243 | .fa-signal:before { 244 | content: "\f012"; 245 | } 246 | .fa-gear:before, 247 | .fa-cog:before { 248 | content: "\f013"; 249 | } 250 | .fa-trash-o:before { 251 | content: "\f014"; 252 | } 253 | .fa-home:before { 254 | content: "\f015"; 255 | } 256 | .fa-file-o:before { 257 | content: "\f016"; 258 | } 259 | .fa-clock-o:before { 260 | content: "\f017"; 261 | } 262 | .fa-road:before { 263 | content: "\f018"; 264 | } 265 | .fa-download:before { 266 | content: "\f019"; 267 | } 268 | .fa-arrow-circle-o-down:before { 269 | content: "\f01a"; 270 | } 271 | .fa-arrow-circle-o-up:before { 272 | content: "\f01b"; 273 | } 274 | .fa-inbox:before { 275 | content: "\f01c"; 276 | } 277 | .fa-play-circle-o:before { 278 | content: "\f01d"; 279 | } 280 | .fa-rotate-right:before, 281 | .fa-repeat:before { 282 | content: "\f01e"; 283 | } 284 | .fa-refresh:before { 285 | content: "\f021"; 286 | } 287 | .fa-list-alt:before { 288 | content: "\f022"; 289 | } 290 | .fa-lock:before { 291 | content: "\f023"; 292 | } 293 | .fa-flag:before { 294 | content: "\f024"; 295 | } 296 | .fa-headphones:before { 297 | content: "\f025"; 298 | } 299 | .fa-volume-off:before { 300 | content: "\f026"; 301 | } 302 | .fa-volume-down:before { 303 | content: "\f027"; 304 | } 305 | .fa-volume-up:before { 306 | content: "\f028"; 307 | } 308 | .fa-qrcode:before { 309 | content: "\f029"; 310 | } 311 | .fa-barcode:before { 312 | content: "\f02a"; 313 | } 314 | .fa-tag:before { 315 | content: "\f02b"; 316 | } 317 | .fa-tags:before { 318 | content: "\f02c"; 319 | } 320 | .fa-book:before { 321 | content: "\f02d"; 322 | } 323 | .fa-bookmark:before { 324 | content: "\f02e"; 325 | } 326 | .fa-print:before { 327 | content: "\f02f"; 328 | } 329 | .fa-camera:before { 330 | content: "\f030"; 331 | } 332 | .fa-font:before { 333 | content: "\f031"; 334 | } 335 | .fa-bold:before { 336 | content: "\f032"; 337 | } 338 | .fa-italic:before { 339 | content: "\f033"; 340 | } 341 | .fa-text-height:before { 342 | content: "\f034"; 343 | } 344 | .fa-text-width:before { 345 | content: "\f035"; 346 | } 347 | .fa-align-left:before { 348 | content: "\f036"; 349 | } 350 | .fa-align-center:before { 351 | content: "\f037"; 352 | } 353 | .fa-align-right:before { 354 | content: "\f038"; 355 | } 356 | .fa-align-justify:before { 357 | content: "\f039"; 358 | } 359 | .fa-list:before { 360 | content: "\f03a"; 361 | } 362 | .fa-dedent:before, 363 | .fa-outdent:before { 364 | content: "\f03b"; 365 | } 366 | .fa-indent:before { 367 | content: "\f03c"; 368 | } 369 | .fa-video-camera:before { 370 | content: "\f03d"; 371 | } 372 | .fa-picture-o:before { 373 | content: "\f03e"; 374 | } 375 | .fa-pencil:before { 376 | content: "\f040"; 377 | } 378 | .fa-map-marker:before { 379 | content: "\f041"; 380 | } 381 | .fa-adjust:before { 382 | content: "\f042"; 383 | } 384 | .fa-tint:before { 385 | content: "\f043"; 386 | } 387 | .fa-edit:before, 388 | .fa-pencil-square-o:before { 389 | content: "\f044"; 390 | } 391 | .fa-share-square-o:before { 392 | content: "\f045"; 393 | } 394 | .fa-check-square-o:before { 395 | content: "\f046"; 396 | } 397 | .fa-arrows:before { 398 | content: "\f047"; 399 | } 400 | .fa-step-backward:before { 401 | content: "\f048"; 402 | } 403 | .fa-fast-backward:before { 404 | content: "\f049"; 405 | } 406 | .fa-backward:before { 407 | content: "\f04a"; 408 | } 409 | .fa-play:before { 410 | content: "\f04b"; 411 | } 412 | .fa-pause:before { 413 | content: "\f04c"; 414 | } 415 | .fa-stop:before { 416 | content: "\f04d"; 417 | } 418 | .fa-forward:before { 419 | content: "\f04e"; 420 | } 421 | .fa-fast-forward:before { 422 | content: "\f050"; 423 | } 424 | .fa-step-forward:before { 425 | content: "\f051"; 426 | } 427 | .fa-eject:before { 428 | content: "\f052"; 429 | } 430 | .fa-chevron-left:before { 431 | content: "\f053"; 432 | } 433 | .fa-chevron-right:before { 434 | content: "\f054"; 435 | } 436 | .fa-plus-circle:before { 437 | content: "\f055"; 438 | } 439 | .fa-minus-circle:before { 440 | content: "\f056"; 441 | } 442 | .fa-times-circle:before { 443 | content: "\f057"; 444 | } 445 | .fa-check-circle:before { 446 | content: "\f058"; 447 | } 448 | .fa-question-circle:before { 449 | content: "\f059"; 450 | } 451 | .fa-info-circle:before { 452 | content: "\f05a"; 453 | } 454 | .fa-crosshairs:before { 455 | content: "\f05b"; 456 | } 457 | .fa-times-circle-o:before { 458 | content: "\f05c"; 459 | } 460 | .fa-check-circle-o:before { 461 | content: "\f05d"; 462 | } 463 | .fa-ban:before { 464 | content: "\f05e"; 465 | } 466 | .fa-arrow-left:before { 467 | content: "\f060"; 468 | } 469 | .fa-arrow-right:before { 470 | content: "\f061"; 471 | } 472 | .fa-arrow-up:before { 473 | content: "\f062"; 474 | } 475 | .fa-arrow-down:before { 476 | content: "\f063"; 477 | } 478 | .fa-mail-forward:before, 479 | .fa-share:before { 480 | content: "\f064"; 481 | } 482 | .fa-expand:before { 483 | content: "\f065"; 484 | } 485 | .fa-compress:before { 486 | content: "\f066"; 487 | } 488 | .fa-plus:before { 489 | content: "\f067"; 490 | } 491 | .fa-minus:before { 492 | content: "\f068"; 493 | } 494 | .fa-asterisk:before { 495 | content: "\f069"; 496 | } 497 | .fa-exclamation-circle:before { 498 | content: "\f06a"; 499 | } 500 | .fa-gift:before { 501 | content: "\f06b"; 502 | } 503 | .fa-leaf:before { 504 | content: "\f06c"; 505 | } 506 | .fa-fire:before { 507 | content: "\f06d"; 508 | } 509 | .fa-eye:before { 510 | content: "\f06e"; 511 | } 512 | .fa-eye-slash:before { 513 | content: "\f070"; 514 | } 515 | .fa-warning:before, 516 | .fa-exclamation-triangle:before { 517 | content: "\f071"; 518 | } 519 | .fa-plane:before { 520 | content: "\f072"; 521 | } 522 | .fa-calendar:before { 523 | content: "\f073"; 524 | } 525 | .fa-random:before { 526 | content: "\f074"; 527 | } 528 | .fa-comment:before { 529 | content: "\f075"; 530 | } 531 | .fa-magnet:before { 532 | content: "\f076"; 533 | } 534 | .fa-chevron-up:before { 535 | content: "\f077"; 536 | } 537 | .fa-chevron-down:before { 538 | content: "\f078"; 539 | } 540 | .fa-retweet:before { 541 | content: "\f079"; 542 | } 543 | .fa-shopping-cart:before { 544 | content: "\f07a"; 545 | } 546 | .fa-folder:before { 547 | content: "\f07b"; 548 | } 549 | .fa-folder-open:before { 550 | content: "\f07c"; 551 | } 552 | .fa-arrows-v:before { 553 | content: "\f07d"; 554 | } 555 | .fa-arrows-h:before { 556 | content: "\f07e"; 557 | } 558 | .fa-bar-chart-o:before { 559 | content: "\f080"; 560 | } 561 | .fa-twitter-square:before { 562 | content: "\f081"; 563 | } 564 | .fa-facebook-square:before { 565 | content: "\f082"; 566 | } 567 | .fa-camera-retro:before { 568 | content: "\f083"; 569 | } 570 | .fa-key:before { 571 | content: "\f084"; 572 | } 573 | .fa-gears:before, 574 | .fa-cogs:before { 575 | content: "\f085"; 576 | } 577 | .fa-comments:before { 578 | content: "\f086"; 579 | } 580 | .fa-thumbs-o-up:before { 581 | content: "\f087"; 582 | } 583 | .fa-thumbs-o-down:before { 584 | content: "\f088"; 585 | } 586 | .fa-star-half:before { 587 | content: "\f089"; 588 | } 589 | .fa-heart-o:before { 590 | content: "\f08a"; 591 | } 592 | .fa-sign-out:before { 593 | content: "\f08b"; 594 | } 595 | .fa-linkedin-square:before { 596 | content: "\f08c"; 597 | } 598 | .fa-thumb-tack:before { 599 | content: "\f08d"; 600 | } 601 | .fa-external-link:before { 602 | content: "\f08e"; 603 | } 604 | .fa-sign-in:before { 605 | content: "\f090"; 606 | } 607 | .fa-trophy:before { 608 | content: "\f091"; 609 | } 610 | .fa-github-square:before { 611 | content: "\f092"; 612 | } 613 | .fa-upload:before { 614 | content: "\f093"; 615 | } 616 | .fa-lemon-o:before { 617 | content: "\f094"; 618 | } 619 | .fa-phone:before { 620 | content: "\f095"; 621 | } 622 | .fa-square-o:before { 623 | content: "\f096"; 624 | } 625 | .fa-bookmark-o:before { 626 | content: "\f097"; 627 | } 628 | .fa-phone-square:before { 629 | content: "\f098"; 630 | } 631 | .fa-twitter:before { 632 | content: "\f099"; 633 | } 634 | .fa-facebook:before { 635 | content: "\f09a"; 636 | } 637 | .fa-github:before { 638 | content: "\f09b"; 639 | } 640 | .fa-unlock:before { 641 | content: "\f09c"; 642 | } 643 | .fa-credit-card:before { 644 | content: "\f09d"; 645 | } 646 | .fa-rss:before { 647 | content: "\f09e"; 648 | } 649 | .fa-hdd-o:before { 650 | content: "\f0a0"; 651 | } 652 | .fa-bullhorn:before { 653 | content: "\f0a1"; 654 | } 655 | .fa-bell:before { 656 | content: "\f0f3"; 657 | } 658 | .fa-certificate:before { 659 | content: "\f0a3"; 660 | } 661 | .fa-hand-o-right:before { 662 | content: "\f0a4"; 663 | } 664 | .fa-hand-o-left:before { 665 | content: "\f0a5"; 666 | } 667 | .fa-hand-o-up:before { 668 | content: "\f0a6"; 669 | } 670 | .fa-hand-o-down:before { 671 | content: "\f0a7"; 672 | } 673 | .fa-arrow-circle-left:before { 674 | content: "\f0a8"; 675 | } 676 | .fa-arrow-circle-right:before { 677 | content: "\f0a9"; 678 | } 679 | .fa-arrow-circle-up:before { 680 | content: "\f0aa"; 681 | } 682 | .fa-arrow-circle-down:before { 683 | content: "\f0ab"; 684 | } 685 | .fa-globe:before { 686 | content: "\f0ac"; 687 | } 688 | .fa-wrench:before { 689 | content: "\f0ad"; 690 | } 691 | .fa-tasks:before { 692 | content: "\f0ae"; 693 | } 694 | .fa-filter:before { 695 | content: "\f0b0"; 696 | } 697 | .fa-briefcase:before { 698 | content: "\f0b1"; 699 | } 700 | .fa-arrows-alt:before { 701 | content: "\f0b2"; 702 | } 703 | .fa-group:before, 704 | .fa-users:before { 705 | content: "\f0c0"; 706 | } 707 | .fa-chain:before, 708 | .fa-link:before { 709 | content: "\f0c1"; 710 | } 711 | .fa-cloud:before { 712 | content: "\f0c2"; 713 | } 714 | .fa-flask:before { 715 | content: "\f0c3"; 716 | } 717 | .fa-cut:before, 718 | .fa-scissors:before { 719 | content: "\f0c4"; 720 | } 721 | .fa-copy:before, 722 | .fa-files-o:before { 723 | content: "\f0c5"; 724 | } 725 | .fa-paperclip:before { 726 | content: "\f0c6"; 727 | } 728 | .fa-save:before, 729 | .fa-floppy-o:before { 730 | content: "\f0c7"; 731 | } 732 | .fa-square:before { 733 | content: "\f0c8"; 734 | } 735 | .fa-bars:before { 736 | content: "\f0c9"; 737 | } 738 | .fa-list-ul:before { 739 | content: "\f0ca"; 740 | } 741 | .fa-list-ol:before { 742 | content: "\f0cb"; 743 | } 744 | .fa-strikethrough:before { 745 | content: "\f0cc"; 746 | } 747 | .fa-underline:before { 748 | content: "\f0cd"; 749 | } 750 | .fa-table:before { 751 | content: "\f0ce"; 752 | } 753 | .fa-magic:before { 754 | content: "\f0d0"; 755 | } 756 | .fa-truck:before { 757 | content: "\f0d1"; 758 | } 759 | .fa-pinterest:before { 760 | content: "\f0d2"; 761 | } 762 | .fa-pinterest-square:before { 763 | content: "\f0d3"; 764 | } 765 | .fa-google-plus-square:before { 766 | content: "\f0d4"; 767 | } 768 | .fa-google-plus:before { 769 | content: "\f0d5"; 770 | } 771 | .fa-money:before { 772 | content: "\f0d6"; 773 | } 774 | .fa-caret-down:before { 775 | content: "\f0d7"; 776 | } 777 | .fa-caret-up:before { 778 | content: "\f0d8"; 779 | } 780 | .fa-caret-left:before { 781 | content: "\f0d9"; 782 | } 783 | .fa-caret-right:before { 784 | content: "\f0da"; 785 | } 786 | .fa-columns:before { 787 | content: "\f0db"; 788 | } 789 | .fa-unsorted:before, 790 | .fa-sort:before { 791 | content: "\f0dc"; 792 | } 793 | .fa-sort-down:before, 794 | .fa-sort-asc:before { 795 | content: "\f0dd"; 796 | } 797 | .fa-sort-up:before, 798 | .fa-sort-desc:before { 799 | content: "\f0de"; 800 | } 801 | .fa-envelope:before { 802 | content: "\f0e0"; 803 | } 804 | .fa-linkedin:before { 805 | content: "\f0e1"; 806 | } 807 | .fa-rotate-left:before, 808 | .fa-undo:before { 809 | content: "\f0e2"; 810 | } 811 | .fa-legal:before, 812 | .fa-gavel:before { 813 | content: "\f0e3"; 814 | } 815 | .fa-dashboard:before, 816 | .fa-tachometer:before { 817 | content: "\f0e4"; 818 | } 819 | .fa-comment-o:before { 820 | content: "\f0e5"; 821 | } 822 | .fa-comments-o:before { 823 | content: "\f0e6"; 824 | } 825 | .fa-flash:before, 826 | .fa-bolt:before { 827 | content: "\f0e7"; 828 | } 829 | .fa-sitemap:before { 830 | content: "\f0e8"; 831 | } 832 | .fa-umbrella:before { 833 | content: "\f0e9"; 834 | } 835 | .fa-paste:before, 836 | .fa-clipboard:before { 837 | content: "\f0ea"; 838 | } 839 | .fa-lightbulb-o:before { 840 | content: "\f0eb"; 841 | } 842 | .fa-exchange:before { 843 | content: "\f0ec"; 844 | } 845 | .fa-cloud-download:before { 846 | content: "\f0ed"; 847 | } 848 | .fa-cloud-upload:before { 849 | content: "\f0ee"; 850 | } 851 | .fa-user-md:before { 852 | content: "\f0f0"; 853 | } 854 | .fa-stethoscope:before { 855 | content: "\f0f1"; 856 | } 857 | .fa-suitcase:before { 858 | content: "\f0f2"; 859 | } 860 | .fa-bell-o:before { 861 | content: "\f0a2"; 862 | } 863 | .fa-coffee:before { 864 | content: "\f0f4"; 865 | } 866 | .fa-cutlery:before { 867 | content: "\f0f5"; 868 | } 869 | .fa-file-text-o:before { 870 | content: "\f0f6"; 871 | } 872 | .fa-building-o:before { 873 | content: "\f0f7"; 874 | } 875 | .fa-hospital-o:before { 876 | content: "\f0f8"; 877 | } 878 | .fa-ambulance:before { 879 | content: "\f0f9"; 880 | } 881 | .fa-medkit:before { 882 | content: "\f0fa"; 883 | } 884 | .fa-fighter-jet:before { 885 | content: "\f0fb"; 886 | } 887 | .fa-beer:before { 888 | content: "\f0fc"; 889 | } 890 | .fa-h-square:before { 891 | content: "\f0fd"; 892 | } 893 | .fa-plus-square:before { 894 | content: "\f0fe"; 895 | } 896 | .fa-angle-double-left:before { 897 | content: "\f100"; 898 | } 899 | .fa-angle-double-right:before { 900 | content: "\f101"; 901 | } 902 | .fa-angle-double-up:before { 903 | content: "\f102"; 904 | } 905 | .fa-angle-double-down:before { 906 | content: "\f103"; 907 | } 908 | .fa-angle-left:before { 909 | content: "\f104"; 910 | } 911 | .fa-angle-right:before { 912 | content: "\f105"; 913 | } 914 | .fa-angle-up:before { 915 | content: "\f106"; 916 | } 917 | .fa-angle-down:before { 918 | content: "\f107"; 919 | } 920 | .fa-desktop:before { 921 | content: "\f108"; 922 | } 923 | .fa-laptop:before { 924 | content: "\f109"; 925 | } 926 | .fa-tablet:before { 927 | content: "\f10a"; 928 | } 929 | .fa-mobile-phone:before, 930 | .fa-mobile:before { 931 | content: "\f10b"; 932 | } 933 | .fa-circle-o:before { 934 | content: "\f10c"; 935 | } 936 | .fa-quote-left:before { 937 | content: "\f10d"; 938 | } 939 | .fa-quote-right:before { 940 | content: "\f10e"; 941 | } 942 | .fa-spinner:before { 943 | content: "\f110"; 944 | } 945 | .fa-circle:before { 946 | content: "\f111"; 947 | } 948 | .fa-mail-reply:before, 949 | .fa-reply:before { 950 | content: "\f112"; 951 | } 952 | .fa-github-alt:before { 953 | content: "\f113"; 954 | } 955 | .fa-folder-o:before { 956 | content: "\f114"; 957 | } 958 | .fa-folder-open-o:before { 959 | content: "\f115"; 960 | } 961 | .fa-smile-o:before { 962 | content: "\f118"; 963 | } 964 | .fa-frown-o:before { 965 | content: "\f119"; 966 | } 967 | .fa-meh-o:before { 968 | content: "\f11a"; 969 | } 970 | .fa-gamepad:before { 971 | content: "\f11b"; 972 | } 973 | .fa-keyboard-o:before { 974 | content: "\f11c"; 975 | } 976 | .fa-flag-o:before { 977 | content: "\f11d"; 978 | } 979 | .fa-flag-checkered:before { 980 | content: "\f11e"; 981 | } 982 | .fa-terminal:before { 983 | content: "\f120"; 984 | } 985 | .fa-code:before { 986 | content: "\f121"; 987 | } 988 | .fa-reply-all:before { 989 | content: "\f122"; 990 | } 991 | .fa-mail-reply-all:before { 992 | content: "\f122"; 993 | } 994 | .fa-star-half-empty:before, 995 | .fa-star-half-full:before, 996 | .fa-star-half-o:before { 997 | content: "\f123"; 998 | } 999 | .fa-location-arrow:before { 1000 | content: "\f124"; 1001 | } 1002 | .fa-crop:before { 1003 | content: "\f125"; 1004 | } 1005 | .fa-code-fork:before { 1006 | content: "\f126"; 1007 | } 1008 | .fa-unlink:before, 1009 | .fa-chain-broken:before { 1010 | content: "\f127"; 1011 | } 1012 | .fa-question:before { 1013 | content: "\f128"; 1014 | } 1015 | .fa-info:before { 1016 | content: "\f129"; 1017 | } 1018 | .fa-exclamation:before { 1019 | content: "\f12a"; 1020 | } 1021 | .fa-superscript:before { 1022 | content: "\f12b"; 1023 | } 1024 | .fa-subscript:before { 1025 | content: "\f12c"; 1026 | } 1027 | .fa-eraser:before { 1028 | content: "\f12d"; 1029 | } 1030 | .fa-puzzle-piece:before { 1031 | content: "\f12e"; 1032 | } 1033 | .fa-microphone:before { 1034 | content: "\f130"; 1035 | } 1036 | .fa-microphone-slash:before { 1037 | content: "\f131"; 1038 | } 1039 | .fa-shield:before { 1040 | content: "\f132"; 1041 | } 1042 | .fa-calendar-o:before { 1043 | content: "\f133"; 1044 | } 1045 | .fa-fire-extinguisher:before { 1046 | content: "\f134"; 1047 | } 1048 | .fa-rocket:before { 1049 | content: "\f135"; 1050 | } 1051 | .fa-maxcdn:before { 1052 | content: "\f136"; 1053 | } 1054 | .fa-chevron-circle-left:before { 1055 | content: "\f137"; 1056 | } 1057 | .fa-chevron-circle-right:before { 1058 | content: "\f138"; 1059 | } 1060 | .fa-chevron-circle-up:before { 1061 | content: "\f139"; 1062 | } 1063 | .fa-chevron-circle-down:before { 1064 | content: "\f13a"; 1065 | } 1066 | .fa-html5:before { 1067 | content: "\f13b"; 1068 | } 1069 | .fa-css3:before { 1070 | content: "\f13c"; 1071 | } 1072 | .fa-anchor:before { 1073 | content: "\f13d"; 1074 | } 1075 | .fa-unlock-alt:before { 1076 | content: "\f13e"; 1077 | } 1078 | .fa-bullseye:before { 1079 | content: "\f140"; 1080 | } 1081 | .fa-ellipsis-h:before { 1082 | content: "\f141"; 1083 | } 1084 | .fa-ellipsis-v:before { 1085 | content: "\f142"; 1086 | } 1087 | .fa-rss-square:before { 1088 | content: "\f143"; 1089 | } 1090 | .fa-play-circle:before { 1091 | content: "\f144"; 1092 | } 1093 | .fa-ticket:before { 1094 | content: "\f145"; 1095 | } 1096 | .fa-minus-square:before { 1097 | content: "\f146"; 1098 | } 1099 | .fa-minus-square-o:before { 1100 | content: "\f147"; 1101 | } 1102 | .fa-level-up:before { 1103 | content: "\f148"; 1104 | } 1105 | .fa-level-down:before { 1106 | content: "\f149"; 1107 | } 1108 | .fa-check-square:before { 1109 | content: "\f14a"; 1110 | } 1111 | .fa-pencil-square:before { 1112 | content: "\f14b"; 1113 | } 1114 | .fa-external-link-square:before { 1115 | content: "\f14c"; 1116 | } 1117 | .fa-share-square:before { 1118 | content: "\f14d"; 1119 | } 1120 | .fa-compass:before { 1121 | content: "\f14e"; 1122 | } 1123 | .fa-toggle-down:before, 1124 | .fa-caret-square-o-down:before { 1125 | content: "\f150"; 1126 | } 1127 | .fa-toggle-up:before, 1128 | .fa-caret-square-o-up:before { 1129 | content: "\f151"; 1130 | } 1131 | .fa-toggle-right:before, 1132 | .fa-caret-square-o-right:before { 1133 | content: "\f152"; 1134 | } 1135 | .fa-euro:before, 1136 | .fa-eur:before { 1137 | content: "\f153"; 1138 | } 1139 | .fa-gbp:before { 1140 | content: "\f154"; 1141 | } 1142 | .fa-dollar:before, 1143 | .fa-usd:before { 1144 | content: "\f155"; 1145 | } 1146 | .fa-rupee:before, 1147 | .fa-inr:before { 1148 | content: "\f156"; 1149 | } 1150 | .fa-cny:before, 1151 | .fa-rmb:before, 1152 | .fa-yen:before, 1153 | .fa-jpy:before { 1154 | content: "\f157"; 1155 | } 1156 | .fa-ruble:before, 1157 | .fa-rouble:before, 1158 | .fa-rub:before { 1159 | content: "\f158"; 1160 | } 1161 | .fa-won:before, 1162 | .fa-krw:before { 1163 | content: "\f159"; 1164 | } 1165 | .fa-bitcoin:before, 1166 | .fa-btc:before { 1167 | content: "\f15a"; 1168 | } 1169 | .fa-file:before { 1170 | content: "\f15b"; 1171 | } 1172 | .fa-file-text:before { 1173 | content: "\f15c"; 1174 | } 1175 | .fa-sort-alpha-asc:before { 1176 | content: "\f15d"; 1177 | } 1178 | .fa-sort-alpha-desc:before { 1179 | content: "\f15e"; 1180 | } 1181 | .fa-sort-amount-asc:before { 1182 | content: "\f160"; 1183 | } 1184 | .fa-sort-amount-desc:before { 1185 | content: "\f161"; 1186 | } 1187 | .fa-sort-numeric-asc:before { 1188 | content: "\f162"; 1189 | } 1190 | .fa-sort-numeric-desc:before { 1191 | content: "\f163"; 1192 | } 1193 | .fa-thumbs-up:before { 1194 | content: "\f164"; 1195 | } 1196 | .fa-thumbs-down:before { 1197 | content: "\f165"; 1198 | } 1199 | .fa-youtube-square:before { 1200 | content: "\f166"; 1201 | } 1202 | .fa-youtube:before { 1203 | content: "\f167"; 1204 | } 1205 | .fa-xing:before { 1206 | content: "\f168"; 1207 | } 1208 | .fa-xing-square:before { 1209 | content: "\f169"; 1210 | } 1211 | .fa-youtube-play:before { 1212 | content: "\f16a"; 1213 | } 1214 | .fa-dropbox:before { 1215 | content: "\f16b"; 1216 | } 1217 | .fa-stack-overflow:before { 1218 | content: "\f16c"; 1219 | } 1220 | .fa-instagram:before { 1221 | content: "\f16d"; 1222 | } 1223 | .fa-flickr:before { 1224 | content: "\f16e"; 1225 | } 1226 | .fa-adn:before { 1227 | content: "\f170"; 1228 | } 1229 | .fa-bitbucket:before { 1230 | content: "\f171"; 1231 | } 1232 | .fa-bitbucket-square:before { 1233 | content: "\f172"; 1234 | } 1235 | .fa-tumblr:before { 1236 | content: "\f173"; 1237 | } 1238 | .fa-tumblr-square:before { 1239 | content: "\f174"; 1240 | } 1241 | .fa-long-arrow-down:before { 1242 | content: "\f175"; 1243 | } 1244 | .fa-long-arrow-up:before { 1245 | content: "\f176"; 1246 | } 1247 | .fa-long-arrow-left:before { 1248 | content: "\f177"; 1249 | } 1250 | .fa-long-arrow-right:before { 1251 | content: "\f178"; 1252 | } 1253 | .fa-apple:before { 1254 | content: "\f179"; 1255 | } 1256 | .fa-windows:before { 1257 | content: "\f17a"; 1258 | } 1259 | .fa-android:before { 1260 | content: "\f17b"; 1261 | } 1262 | .fa-linux:before { 1263 | content: "\f17c"; 1264 | } 1265 | .fa-dribbble:before { 1266 | content: "\f17d"; 1267 | } 1268 | .fa-skype:before { 1269 | content: "\f17e"; 1270 | } 1271 | .fa-foursquare:before { 1272 | content: "\f180"; 1273 | } 1274 | .fa-trello:before { 1275 | content: "\f181"; 1276 | } 1277 | .fa-female:before { 1278 | content: "\f182"; 1279 | } 1280 | .fa-male:before { 1281 | content: "\f183"; 1282 | } 1283 | .fa-gittip:before { 1284 | content: "\f184"; 1285 | } 1286 | .fa-sun-o:before { 1287 | content: "\f185"; 1288 | } 1289 | .fa-moon-o:before { 1290 | content: "\f186"; 1291 | } 1292 | .fa-archive:before { 1293 | content: "\f187"; 1294 | } 1295 | .fa-bug:before { 1296 | content: "\f188"; 1297 | } 1298 | .fa-vk:before { 1299 | content: "\f189"; 1300 | } 1301 | .fa-weibo:before { 1302 | content: "\f18a"; 1303 | } 1304 | .fa-renren:before { 1305 | content: "\f18b"; 1306 | } 1307 | .fa-pagelines:before { 1308 | content: "\f18c"; 1309 | } 1310 | .fa-stack-exchange:before { 1311 | content: "\f18d"; 1312 | } 1313 | .fa-arrow-circle-o-right:before { 1314 | content: "\f18e"; 1315 | } 1316 | .fa-arrow-circle-o-left:before { 1317 | content: "\f190"; 1318 | } 1319 | .fa-toggle-left:before, 1320 | .fa-caret-square-o-left:before { 1321 | content: "\f191"; 1322 | } 1323 | .fa-dot-circle-o:before { 1324 | content: "\f192"; 1325 | } 1326 | .fa-wheelchair:before { 1327 | content: "\f193"; 1328 | } 1329 | .fa-vimeo-square:before { 1330 | content: "\f194"; 1331 | } 1332 | .fa-turkish-lira:before, 1333 | .fa-try:before { 1334 | content: "\f195"; 1335 | } 1336 | .fa-plus-square-o:before { 1337 | content: "\f196"; 1338 | } 1339 | -------------------------------------------------------------------------------- /static/font-awesome/css/font-awesome.min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Font Awesome 4.0.3 by @davegandy - http://fontawesome.io - @fontawesome 3 | * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License) 4 | */@font-face{font-family:'FontAwesome';src:url('../fonts/fontawesome-webfont.eot?v=4.0.3');src:url('../fonts/fontawesome-webfont.eot?#iefix&v=4.0.3') format('embedded-opentype'),url('../fonts/fontawesome-webfont.woff?v=4.0.3') format('woff'),url('../fonts/fontawesome-webfont.ttf?v=4.0.3') format('truetype'),url('../fonts/fontawesome-webfont.svg?v=4.0.3#fontawesomeregular') format('svg');font-weight:normal;font-style:normal}.fa{display:inline-block;font-family:FontAwesome;font-style:normal;font-weight:normal;line-height:1;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.fa-lg{font-size:1.3333333333333333em;line-height:.75em;vertical-align:-15%}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-fw{width:1.2857142857142858em;text-align:center}.fa-ul{padding-left:0;margin-left:2.142857142857143em;list-style-type:none}.fa-ul>li{position:relative}.fa-li{position:absolute;left:-2.142857142857143em;width:2.142857142857143em;top:.14285714285714285em;text-align:center}.fa-li.fa-lg{left:-1.8571428571428572em}.fa-border{padding:.2em .25em .15em;border:solid .08em #eee;border-radius:.1em}.pull-right{float:right}.pull-left{float:left}.fa.pull-left{margin-right:.3em}.fa.pull-right{margin-left:.3em}.fa-spin{-webkit-animation:spin 2s infinite linear;-moz-animation:spin 2s infinite linear;-o-animation:spin 2s infinite linear;animation:spin 2s infinite linear}@-moz-keyframes spin{0%{-moz-transform:rotate(0deg)}100%{-moz-transform:rotate(359deg)}}@-webkit-keyframes spin{0%{-webkit-transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg)}}@-o-keyframes spin{0%{-o-transform:rotate(0deg)}100%{-o-transform:rotate(359deg)}}@-ms-keyframes spin{0%{-ms-transform:rotate(0deg)}100%{-ms-transform:rotate(359deg)}}@keyframes spin{0%{transform:rotate(0deg)}100%{transform:rotate(359deg)}}.fa-rotate-90{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=1);-webkit-transform:rotate(90deg);-moz-transform:rotate(90deg);-ms-transform:rotate(90deg);-o-transform:rotate(90deg);transform:rotate(90deg)}.fa-rotate-180{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=2);-webkit-transform:rotate(180deg);-moz-transform:rotate(180deg);-ms-transform:rotate(180deg);-o-transform:rotate(180deg);transform:rotate(180deg)}.fa-rotate-270{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=3);-webkit-transform:rotate(270deg);-moz-transform:rotate(270deg);-ms-transform:rotate(270deg);-o-transform:rotate(270deg);transform:rotate(270deg)}.fa-flip-horizontal{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=0,mirror=1);-webkit-transform:scale(-1,1);-moz-transform:scale(-1,1);-ms-transform:scale(-1,1);-o-transform:scale(-1,1);transform:scale(-1,1)}.fa-flip-vertical{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=2,mirror=1);-webkit-transform:scale(1,-1);-moz-transform:scale(1,-1);-ms-transform:scale(1,-1);-o-transform:scale(1,-1);transform:scale(1,-1)}.fa-stack{position:relative;display:inline-block;width:2em;height:2em;line-height:2em;vertical-align:middle}.fa-stack-1x,.fa-stack-2x{position:absolute;left:0;width:100%;text-align:center}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:#fff}.fa-glass:before{content:"\f000"}.fa-music:before{content:"\f001"}.fa-search:before{content:"\f002"}.fa-envelope-o:before{content:"\f003"}.fa-heart:before{content:"\f004"}.fa-star:before{content:"\f005"}.fa-star-o:before{content:"\f006"}.fa-user:before{content:"\f007"}.fa-film:before{content:"\f008"}.fa-th-large:before{content:"\f009"}.fa-th:before{content:"\f00a"}.fa-th-list:before{content:"\f00b"}.fa-check:before{content:"\f00c"}.fa-times:before{content:"\f00d"}.fa-search-plus:before{content:"\f00e"}.fa-search-minus:before{content:"\f010"}.fa-power-off:before{content:"\f011"}.fa-signal:before{content:"\f012"}.fa-gear:before,.fa-cog:before{content:"\f013"}.fa-trash-o:before{content:"\f014"}.fa-home:before{content:"\f015"}.fa-file-o:before{content:"\f016"}.fa-clock-o:before{content:"\f017"}.fa-road:before{content:"\f018"}.fa-download:before{content:"\f019"}.fa-arrow-circle-o-down:before{content:"\f01a"}.fa-arrow-circle-o-up:before{content:"\f01b"}.fa-inbox:before{content:"\f01c"}.fa-play-circle-o:before{content:"\f01d"}.fa-rotate-right:before,.fa-repeat:before{content:"\f01e"}.fa-refresh:before{content:"\f021"}.fa-list-alt:before{content:"\f022"}.fa-lock:before{content:"\f023"}.fa-flag:before{content:"\f024"}.fa-headphones:before{content:"\f025"}.fa-volume-off:before{content:"\f026"}.fa-volume-down:before{content:"\f027"}.fa-volume-up:before{content:"\f028"}.fa-qrcode:before{content:"\f029"}.fa-barcode:before{content:"\f02a"}.fa-tag:before{content:"\f02b"}.fa-tags:before{content:"\f02c"}.fa-book:before{content:"\f02d"}.fa-bookmark:before{content:"\f02e"}.fa-print:before{content:"\f02f"}.fa-camera:before{content:"\f030"}.fa-font:before{content:"\f031"}.fa-bold:before{content:"\f032"}.fa-italic:before{content:"\f033"}.fa-text-height:before{content:"\f034"}.fa-text-width:before{content:"\f035"}.fa-align-left:before{content:"\f036"}.fa-align-center:before{content:"\f037"}.fa-align-right:before{content:"\f038"}.fa-align-justify:before{content:"\f039"}.fa-list:before{content:"\f03a"}.fa-dedent:before,.fa-outdent:before{content:"\f03b"}.fa-indent:before{content:"\f03c"}.fa-video-camera:before{content:"\f03d"}.fa-picture-o:before{content:"\f03e"}.fa-pencil:before{content:"\f040"}.fa-map-marker:before{content:"\f041"}.fa-adjust:before{content:"\f042"}.fa-tint:before{content:"\f043"}.fa-edit:before,.fa-pencil-square-o:before{content:"\f044"}.fa-share-square-o:before{content:"\f045"}.fa-check-square-o:before{content:"\f046"}.fa-arrows:before{content:"\f047"}.fa-step-backward:before{content:"\f048"}.fa-fast-backward:before{content:"\f049"}.fa-backward:before{content:"\f04a"}.fa-play:before{content:"\f04b"}.fa-pause:before{content:"\f04c"}.fa-stop:before{content:"\f04d"}.fa-forward:before{content:"\f04e"}.fa-fast-forward:before{content:"\f050"}.fa-step-forward:before{content:"\f051"}.fa-eject:before{content:"\f052"}.fa-chevron-left:before{content:"\f053"}.fa-chevron-right:before{content:"\f054"}.fa-plus-circle:before{content:"\f055"}.fa-minus-circle:before{content:"\f056"}.fa-times-circle:before{content:"\f057"}.fa-check-circle:before{content:"\f058"}.fa-question-circle:before{content:"\f059"}.fa-info-circle:before{content:"\f05a"}.fa-crosshairs:before{content:"\f05b"}.fa-times-circle-o:before{content:"\f05c"}.fa-check-circle-o:before{content:"\f05d"}.fa-ban:before{content:"\f05e"}.fa-arrow-left:before{content:"\f060"}.fa-arrow-right:before{content:"\f061"}.fa-arrow-up:before{content:"\f062"}.fa-arrow-down:before{content:"\f063"}.fa-mail-forward:before,.fa-share:before{content:"\f064"}.fa-expand:before{content:"\f065"}.fa-compress:before{content:"\f066"}.fa-plus:before{content:"\f067"}.fa-minus:before{content:"\f068"}.fa-asterisk:before{content:"\f069"}.fa-exclamation-circle:before{content:"\f06a"}.fa-gift:before{content:"\f06b"}.fa-leaf:before{content:"\f06c"}.fa-fire:before{content:"\f06d"}.fa-eye:before{content:"\f06e"}.fa-eye-slash:before{content:"\f070"}.fa-warning:before,.fa-exclamation-triangle:before{content:"\f071"}.fa-plane:before{content:"\f072"}.fa-calendar:before{content:"\f073"}.fa-random:before{content:"\f074"}.fa-comment:before{content:"\f075"}.fa-magnet:before{content:"\f076"}.fa-chevron-up:before{content:"\f077"}.fa-chevron-down:before{content:"\f078"}.fa-retweet:before{content:"\f079"}.fa-shopping-cart:before{content:"\f07a"}.fa-folder:before{content:"\f07b"}.fa-folder-open:before{content:"\f07c"}.fa-arrows-v:before{content:"\f07d"}.fa-arrows-h:before{content:"\f07e"}.fa-bar-chart-o:before{content:"\f080"}.fa-twitter-square:before{content:"\f081"}.fa-facebook-square:before{content:"\f082"}.fa-camera-retro:before{content:"\f083"}.fa-key:before{content:"\f084"}.fa-gears:before,.fa-cogs:before{content:"\f085"}.fa-comments:before{content:"\f086"}.fa-thumbs-o-up:before{content:"\f087"}.fa-thumbs-o-down:before{content:"\f088"}.fa-star-half:before{content:"\f089"}.fa-heart-o:before{content:"\f08a"}.fa-sign-out:before{content:"\f08b"}.fa-linkedin-square:before{content:"\f08c"}.fa-thumb-tack:before{content:"\f08d"}.fa-external-link:before{content:"\f08e"}.fa-sign-in:before{content:"\f090"}.fa-trophy:before{content:"\f091"}.fa-github-square:before{content:"\f092"}.fa-upload:before{content:"\f093"}.fa-lemon-o:before{content:"\f094"}.fa-phone:before{content:"\f095"}.fa-square-o:before{content:"\f096"}.fa-bookmark-o:before{content:"\f097"}.fa-phone-square:before{content:"\f098"}.fa-twitter:before{content:"\f099"}.fa-facebook:before{content:"\f09a"}.fa-github:before{content:"\f09b"}.fa-unlock:before{content:"\f09c"}.fa-credit-card:before{content:"\f09d"}.fa-rss:before{content:"\f09e"}.fa-hdd-o:before{content:"\f0a0"}.fa-bullhorn:before{content:"\f0a1"}.fa-bell:before{content:"\f0f3"}.fa-certificate:before{content:"\f0a3"}.fa-hand-o-right:before{content:"\f0a4"}.fa-hand-o-left:before{content:"\f0a5"}.fa-hand-o-up:before{content:"\f0a6"}.fa-hand-o-down:before{content:"\f0a7"}.fa-arrow-circle-left:before{content:"\f0a8"}.fa-arrow-circle-right:before{content:"\f0a9"}.fa-arrow-circle-up:before{content:"\f0aa"}.fa-arrow-circle-down:before{content:"\f0ab"}.fa-globe:before{content:"\f0ac"}.fa-wrench:before{content:"\f0ad"}.fa-tasks:before{content:"\f0ae"}.fa-filter:before{content:"\f0b0"}.fa-briefcase:before{content:"\f0b1"}.fa-arrows-alt:before{content:"\f0b2"}.fa-group:before,.fa-users:before{content:"\f0c0"}.fa-chain:before,.fa-link:before{content:"\f0c1"}.fa-cloud:before{content:"\f0c2"}.fa-flask:before{content:"\f0c3"}.fa-cut:before,.fa-scissors:before{content:"\f0c4"}.fa-copy:before,.fa-files-o:before{content:"\f0c5"}.fa-paperclip:before{content:"\f0c6"}.fa-save:before,.fa-floppy-o:before{content:"\f0c7"}.fa-square:before{content:"\f0c8"}.fa-bars:before{content:"\f0c9"}.fa-list-ul:before{content:"\f0ca"}.fa-list-ol:before{content:"\f0cb"}.fa-strikethrough:before{content:"\f0cc"}.fa-underline:before{content:"\f0cd"}.fa-table:before{content:"\f0ce"}.fa-magic:before{content:"\f0d0"}.fa-truck:before{content:"\f0d1"}.fa-pinterest:before{content:"\f0d2"}.fa-pinterest-square:before{content:"\f0d3"}.fa-google-plus-square:before{content:"\f0d4"}.fa-google-plus:before{content:"\f0d5"}.fa-money:before{content:"\f0d6"}.fa-caret-down:before{content:"\f0d7"}.fa-caret-up:before{content:"\f0d8"}.fa-caret-left:before{content:"\f0d9"}.fa-caret-right:before{content:"\f0da"}.fa-columns:before{content:"\f0db"}.fa-unsorted:before,.fa-sort:before{content:"\f0dc"}.fa-sort-down:before,.fa-sort-asc:before{content:"\f0dd"}.fa-sort-up:before,.fa-sort-desc:before{content:"\f0de"}.fa-envelope:before{content:"\f0e0"}.fa-linkedin:before{content:"\f0e1"}.fa-rotate-left:before,.fa-undo:before{content:"\f0e2"}.fa-legal:before,.fa-gavel:before{content:"\f0e3"}.fa-dashboard:before,.fa-tachometer:before{content:"\f0e4"}.fa-comment-o:before{content:"\f0e5"}.fa-comments-o:before{content:"\f0e6"}.fa-flash:before,.fa-bolt:before{content:"\f0e7"}.fa-sitemap:before{content:"\f0e8"}.fa-umbrella:before{content:"\f0e9"}.fa-paste:before,.fa-clipboard:before{content:"\f0ea"}.fa-lightbulb-o:before{content:"\f0eb"}.fa-exchange:before{content:"\f0ec"}.fa-cloud-download:before{content:"\f0ed"}.fa-cloud-upload:before{content:"\f0ee"}.fa-user-md:before{content:"\f0f0"}.fa-stethoscope:before{content:"\f0f1"}.fa-suitcase:before{content:"\f0f2"}.fa-bell-o:before{content:"\f0a2"}.fa-coffee:before{content:"\f0f4"}.fa-cutlery:before{content:"\f0f5"}.fa-file-text-o:before{content:"\f0f6"}.fa-building-o:before{content:"\f0f7"}.fa-hospital-o:before{content:"\f0f8"}.fa-ambulance:before{content:"\f0f9"}.fa-medkit:before{content:"\f0fa"}.fa-fighter-jet:before{content:"\f0fb"}.fa-beer:before{content:"\f0fc"}.fa-h-square:before{content:"\f0fd"}.fa-plus-square:before{content:"\f0fe"}.fa-angle-double-left:before{content:"\f100"}.fa-angle-double-right:before{content:"\f101"}.fa-angle-double-up:before{content:"\f102"}.fa-angle-double-down:before{content:"\f103"}.fa-angle-left:before{content:"\f104"}.fa-angle-right:before{content:"\f105"}.fa-angle-up:before{content:"\f106"}.fa-angle-down:before{content:"\f107"}.fa-desktop:before{content:"\f108"}.fa-laptop:before{content:"\f109"}.fa-tablet:before{content:"\f10a"}.fa-mobile-phone:before,.fa-mobile:before{content:"\f10b"}.fa-circle-o:before{content:"\f10c"}.fa-quote-left:before{content:"\f10d"}.fa-quote-right:before{content:"\f10e"}.fa-spinner:before{content:"\f110"}.fa-circle:before{content:"\f111"}.fa-mail-reply:before,.fa-reply:before{content:"\f112"}.fa-github-alt:before{content:"\f113"}.fa-folder-o:before{content:"\f114"}.fa-folder-open-o:before{content:"\f115"}.fa-smile-o:before{content:"\f118"}.fa-frown-o:before{content:"\f119"}.fa-meh-o:before{content:"\f11a"}.fa-gamepad:before{content:"\f11b"}.fa-keyboard-o:before{content:"\f11c"}.fa-flag-o:before{content:"\f11d"}.fa-flag-checkered:before{content:"\f11e"}.fa-terminal:before{content:"\f120"}.fa-code:before{content:"\f121"}.fa-reply-all:before{content:"\f122"}.fa-mail-reply-all:before{content:"\f122"}.fa-star-half-empty:before,.fa-star-half-full:before,.fa-star-half-o:before{content:"\f123"}.fa-location-arrow:before{content:"\f124"}.fa-crop:before{content:"\f125"}.fa-code-fork:before{content:"\f126"}.fa-unlink:before,.fa-chain-broken:before{content:"\f127"}.fa-question:before{content:"\f128"}.fa-info:before{content:"\f129"}.fa-exclamation:before{content:"\f12a"}.fa-superscript:before{content:"\f12b"}.fa-subscript:before{content:"\f12c"}.fa-eraser:before{content:"\f12d"}.fa-puzzle-piece:before{content:"\f12e"}.fa-microphone:before{content:"\f130"}.fa-microphone-slash:before{content:"\f131"}.fa-shield:before{content:"\f132"}.fa-calendar-o:before{content:"\f133"}.fa-fire-extinguisher:before{content:"\f134"}.fa-rocket:before{content:"\f135"}.fa-maxcdn:before{content:"\f136"}.fa-chevron-circle-left:before{content:"\f137"}.fa-chevron-circle-right:before{content:"\f138"}.fa-chevron-circle-up:before{content:"\f139"}.fa-chevron-circle-down:before{content:"\f13a"}.fa-html5:before{content:"\f13b"}.fa-css3:before{content:"\f13c"}.fa-anchor:before{content:"\f13d"}.fa-unlock-alt:before{content:"\f13e"}.fa-bullseye:before{content:"\f140"}.fa-ellipsis-h:before{content:"\f141"}.fa-ellipsis-v:before{content:"\f142"}.fa-rss-square:before{content:"\f143"}.fa-play-circle:before{content:"\f144"}.fa-ticket:before{content:"\f145"}.fa-minus-square:before{content:"\f146"}.fa-minus-square-o:before{content:"\f147"}.fa-level-up:before{content:"\f148"}.fa-level-down:before{content:"\f149"}.fa-check-square:before{content:"\f14a"}.fa-pencil-square:before{content:"\f14b"}.fa-external-link-square:before{content:"\f14c"}.fa-share-square:before{content:"\f14d"}.fa-compass:before{content:"\f14e"}.fa-toggle-down:before,.fa-caret-square-o-down:before{content:"\f150"}.fa-toggle-up:before,.fa-caret-square-o-up:before{content:"\f151"}.fa-toggle-right:before,.fa-caret-square-o-right:before{content:"\f152"}.fa-euro:before,.fa-eur:before{content:"\f153"}.fa-gbp:before{content:"\f154"}.fa-dollar:before,.fa-usd:before{content:"\f155"}.fa-rupee:before,.fa-inr:before{content:"\f156"}.fa-cny:before,.fa-rmb:before,.fa-yen:before,.fa-jpy:before{content:"\f157"}.fa-ruble:before,.fa-rouble:before,.fa-rub:before{content:"\f158"}.fa-won:before,.fa-krw:before{content:"\f159"}.fa-bitcoin:before,.fa-btc:before{content:"\f15a"}.fa-file:before{content:"\f15b"}.fa-file-text:before{content:"\f15c"}.fa-sort-alpha-asc:before{content:"\f15d"}.fa-sort-alpha-desc:before{content:"\f15e"}.fa-sort-amount-asc:before{content:"\f160"}.fa-sort-amount-desc:before{content:"\f161"}.fa-sort-numeric-asc:before{content:"\f162"}.fa-sort-numeric-desc:before{content:"\f163"}.fa-thumbs-up:before{content:"\f164"}.fa-thumbs-down:before{content:"\f165"}.fa-youtube-square:before{content:"\f166"}.fa-youtube:before{content:"\f167"}.fa-xing:before{content:"\f168"}.fa-xing-square:before{content:"\f169"}.fa-youtube-play:before{content:"\f16a"}.fa-dropbox:before{content:"\f16b"}.fa-stack-overflow:before{content:"\f16c"}.fa-instagram:before{content:"\f16d"}.fa-flickr:before{content:"\f16e"}.fa-adn:before{content:"\f170"}.fa-bitbucket:before{content:"\f171"}.fa-bitbucket-square:before{content:"\f172"}.fa-tumblr:before{content:"\f173"}.fa-tumblr-square:before{content:"\f174"}.fa-long-arrow-down:before{content:"\f175"}.fa-long-arrow-up:before{content:"\f176"}.fa-long-arrow-left:before{content:"\f177"}.fa-long-arrow-right:before{content:"\f178"}.fa-apple:before{content:"\f179"}.fa-windows:before{content:"\f17a"}.fa-android:before{content:"\f17b"}.fa-linux:before{content:"\f17c"}.fa-dribbble:before{content:"\f17d"}.fa-skype:before{content:"\f17e"}.fa-foursquare:before{content:"\f180"}.fa-trello:before{content:"\f181"}.fa-female:before{content:"\f182"}.fa-male:before{content:"\f183"}.fa-gittip:before{content:"\f184"}.fa-sun-o:before{content:"\f185"}.fa-moon-o:before{content:"\f186"}.fa-archive:before{content:"\f187"}.fa-bug:before{content:"\f188"}.fa-vk:before{content:"\f189"}.fa-weibo:before{content:"\f18a"}.fa-renren:before{content:"\f18b"}.fa-pagelines:before{content:"\f18c"}.fa-stack-exchange:before{content:"\f18d"}.fa-arrow-circle-o-right:before{content:"\f18e"}.fa-arrow-circle-o-left:before{content:"\f190"}.fa-toggle-left:before,.fa-caret-square-o-left:before{content:"\f191"}.fa-dot-circle-o:before{content:"\f192"}.fa-wheelchair:before{content:"\f193"}.fa-vimeo-square:before{content:"\f194"}.fa-turkish-lira:before,.fa-try:before{content:"\f195"}.fa-plus-square-o:before{content:"\f196"} -------------------------------------------------------------------------------- /static/font-awesome/fonts/FontAwesome.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LittleVulpix/toxme/52c731993ad9bfb6eacde8ace91f896d718a2811/static/font-awesome/fonts/FontAwesome.otf -------------------------------------------------------------------------------- /static/font-awesome/fonts/fontawesome-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LittleVulpix/toxme/52c731993ad9bfb6eacde8ace91f896d718a2811/static/font-awesome/fonts/fontawesome-webfont.eot -------------------------------------------------------------------------------- /static/font-awesome/fonts/fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LittleVulpix/toxme/52c731993ad9bfb6eacde8ace91f896d718a2811/static/font-awesome/fonts/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /static/font-awesome/fonts/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LittleVulpix/toxme/52c731993ad9bfb6eacde8ace91f896d718a2811/static/font-awesome/fonts/fontawesome-webfont.woff -------------------------------------------------------------------------------- /static/fonts/Lato-Black.css: -------------------------------------------------------------------------------- 1 | /* Webfont: Lato-Black */@font-face { 2 | font-family: 'LatoBlack'; 3 | src: url('Lato-Black.eot'); /* IE9 Compat Modes */ 4 | src: url('Lato-Black.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */ 5 | url('Lato-Black.woff') format('woff'), /* Modern Browsers */ 6 | url('Lato-Black.ttf') format('truetype'); 7 | font-style: normal; 8 | font-weight: normal; 9 | text-rendering: optimizeLegibility; 10 | } 11 | 12 | -------------------------------------------------------------------------------- /static/fonts/Lato-Black.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LittleVulpix/toxme/52c731993ad9bfb6eacde8ace91f896d718a2811/static/fonts/Lato-Black.eot -------------------------------------------------------------------------------- /static/fonts/Lato-Black.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Lato Black - Web Font Specimen 7 | 8 | 11 | 12 | 13 |

The quick brown fox jumps over the lazy dog. $123.45!

14 | 15 | 16 | -------------------------------------------------------------------------------- /static/fonts/Lato-Black.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LittleVulpix/toxme/52c731993ad9bfb6eacde8ace91f896d718a2811/static/fonts/Lato-Black.ttf -------------------------------------------------------------------------------- /static/fonts/Lato-Black.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LittleVulpix/toxme/52c731993ad9bfb6eacde8ace91f896d718a2811/static/fonts/Lato-Black.woff -------------------------------------------------------------------------------- /static/fonts/Lato-BlackItalic.css: -------------------------------------------------------------------------------- 1 | /* Webfont: Lato-BlackItalic */@font-face { 2 | font-family: 'LatoBlack'; 3 | src: url('Lato-BlackItalic.eot'); /* IE9 Compat Modes */ 4 | src: url('Lato-BlackItalic.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */ 5 | url('Lato-BlackItalic.woff') format('woff'), /* Modern Browsers */ 6 | url('Lato-BlackItalic.ttf') format('truetype'); 7 | font-style: italic; 8 | font-weight: normal; 9 | text-rendering: optimizeLegibility; 10 | } 11 | 12 | -------------------------------------------------------------------------------- /static/fonts/Lato-BlackItalic.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LittleVulpix/toxme/52c731993ad9bfb6eacde8ace91f896d718a2811/static/fonts/Lato-BlackItalic.eot -------------------------------------------------------------------------------- /static/fonts/Lato-BlackItalic.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Lato Black Italic - Web Font Specimen 7 | 8 | 11 | 12 | 13 |

The quick brown fox jumps over the lazy dog. $123.45!

14 | 15 | 16 | -------------------------------------------------------------------------------- /static/fonts/Lato-BlackItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LittleVulpix/toxme/52c731993ad9bfb6eacde8ace91f896d718a2811/static/fonts/Lato-BlackItalic.ttf -------------------------------------------------------------------------------- /static/fonts/Lato-BlackItalic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LittleVulpix/toxme/52c731993ad9bfb6eacde8ace91f896d718a2811/static/fonts/Lato-BlackItalic.woff -------------------------------------------------------------------------------- /static/fonts/Lato-Bold.css: -------------------------------------------------------------------------------- 1 | /* Webfont: Lato-Bold */@font-face { 2 | font-family: 'Lato'; 3 | src: url('Lato-Bold.eot'); /* IE9 Compat Modes */ 4 | src: url('Lato-Bold.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */ 5 | url('Lato-Bold.woff') format('woff'), /* Modern Browsers */ 6 | url('Lato-Bold.ttf') format('truetype'); 7 | font-style: normal; 8 | font-weight: bold; 9 | text-rendering: optimizeLegibility; 10 | } 11 | 12 | -------------------------------------------------------------------------------- /static/fonts/Lato-Bold.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LittleVulpix/toxme/52c731993ad9bfb6eacde8ace91f896d718a2811/static/fonts/Lato-Bold.eot -------------------------------------------------------------------------------- /static/fonts/Lato-Bold.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Lato Bold - Web Font Specimen 7 | 8 | 11 | 12 | 13 |

The quick brown fox jumps over the lazy dog. $123.45!

14 | 15 | 16 | -------------------------------------------------------------------------------- /static/fonts/Lato-Bold.min.css: -------------------------------------------------------------------------------- 1 | @font-face{font-family:'Lato';src:url('Lato-Bold.eot');src:url('Lato-Bold.eot?#iefix') format('embedded-opentype'),url('Lato-Bold.woff') format('woff'),url('Lato-Bold.ttf') format('truetype');font-style:normal;font-weight:bold;text-rendering:optimizeLegibility} 2 | -------------------------------------------------------------------------------- /static/fonts/Lato-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LittleVulpix/toxme/52c731993ad9bfb6eacde8ace91f896d718a2811/static/fonts/Lato-Bold.ttf -------------------------------------------------------------------------------- /static/fonts/Lato-Bold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LittleVulpix/toxme/52c731993ad9bfb6eacde8ace91f896d718a2811/static/fonts/Lato-Bold.woff -------------------------------------------------------------------------------- /static/fonts/Lato-BoldItalic.css: -------------------------------------------------------------------------------- 1 | /* Webfont: Lato-BoldItalic */@font-face { 2 | font-family: 'Lato'; 3 | src: url('Lato-BoldItalic.eot'); /* IE9 Compat Modes */ 4 | src: url('Lato-BoldItalic.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */ 5 | url('Lato-BoldItalic.woff') format('woff'), /* Modern Browsers */ 6 | url('Lato-BoldItalic.ttf') format('truetype'); 7 | font-style: italic; 8 | font-weight: bold; 9 | text-rendering: optimizeLegibility; 10 | } 11 | 12 | -------------------------------------------------------------------------------- /static/fonts/Lato-BoldItalic.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LittleVulpix/toxme/52c731993ad9bfb6eacde8ace91f896d718a2811/static/fonts/Lato-BoldItalic.eot -------------------------------------------------------------------------------- /static/fonts/Lato-BoldItalic.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Lato Bold Italic - Web Font Specimen 7 | 8 | 11 | 12 | 13 |

The quick brown fox jumps over the lazy dog. $123.45!

14 | 15 | 16 | -------------------------------------------------------------------------------- /static/fonts/Lato-BoldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LittleVulpix/toxme/52c731993ad9bfb6eacde8ace91f896d718a2811/static/fonts/Lato-BoldItalic.ttf -------------------------------------------------------------------------------- /static/fonts/Lato-BoldItalic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LittleVulpix/toxme/52c731993ad9bfb6eacde8ace91f896d718a2811/static/fonts/Lato-BoldItalic.woff -------------------------------------------------------------------------------- /static/fonts/Lato-Hairline.css: -------------------------------------------------------------------------------- 1 | /* Webfont: Lato-Hairline */@font-face { 2 | font-family: 'LatoHairline'; 3 | src: url('Lato-Hairline.eot'); /* IE9 Compat Modes */ 4 | src: url('Lato-Hairline.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */ 5 | url('Lato-Hairline.woff') format('woff'), /* Modern Browsers */ 6 | url('Lato-Hairline.ttf') format('truetype'); 7 | font-style: normal; 8 | font-weight: normal; 9 | text-rendering: optimizeLegibility; 10 | } 11 | 12 | -------------------------------------------------------------------------------- /static/fonts/Lato-Hairline.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LittleVulpix/toxme/52c731993ad9bfb6eacde8ace91f896d718a2811/static/fonts/Lato-Hairline.eot -------------------------------------------------------------------------------- /static/fonts/Lato-Hairline.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Lato Hairline - Web Font Specimen 7 | 8 | 11 | 12 | 13 |

The quick brown fox jumps over the lazy dog. $123.45!

14 | 15 | 16 | -------------------------------------------------------------------------------- /static/fonts/Lato-Hairline.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LittleVulpix/toxme/52c731993ad9bfb6eacde8ace91f896d718a2811/static/fonts/Lato-Hairline.ttf -------------------------------------------------------------------------------- /static/fonts/Lato-Hairline.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LittleVulpix/toxme/52c731993ad9bfb6eacde8ace91f896d718a2811/static/fonts/Lato-Hairline.woff -------------------------------------------------------------------------------- /static/fonts/Lato-HairlineItalic.css: -------------------------------------------------------------------------------- 1 | /* Webfont: Lato-HairlineItalic */@font-face { 2 | font-family: 'LatoHairline'; 3 | src: url('Lato-HairlineItalic.eot'); /* IE9 Compat Modes */ 4 | src: url('Lato-HairlineItalic.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */ 5 | url('Lato-HairlineItalic.woff') format('woff'), /* Modern Browsers */ 6 | url('Lato-HairlineItalic.ttf') format('truetype'); 7 | font-style: italic; 8 | font-weight: normal; 9 | text-rendering: optimizeLegibility; 10 | } 11 | 12 | -------------------------------------------------------------------------------- /static/fonts/Lato-HairlineItalic.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LittleVulpix/toxme/52c731993ad9bfb6eacde8ace91f896d718a2811/static/fonts/Lato-HairlineItalic.eot -------------------------------------------------------------------------------- /static/fonts/Lato-HairlineItalic.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Lato Hairline Italic - Web Font Specimen 7 | 8 | 11 | 12 | 13 |

The quick brown fox jumps over the lazy dog. $123.45!

14 | 15 | 16 | -------------------------------------------------------------------------------- /static/fonts/Lato-HairlineItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LittleVulpix/toxme/52c731993ad9bfb6eacde8ace91f896d718a2811/static/fonts/Lato-HairlineItalic.ttf -------------------------------------------------------------------------------- /static/fonts/Lato-HairlineItalic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LittleVulpix/toxme/52c731993ad9bfb6eacde8ace91f896d718a2811/static/fonts/Lato-HairlineItalic.woff -------------------------------------------------------------------------------- /static/fonts/Lato-Heavy.css: -------------------------------------------------------------------------------- 1 | /* Webfont: Lato-Heavy */@font-face { 2 | font-family: 'LatoHeavy'; 3 | src: url('Lato-Heavy.eot'); /* IE9 Compat Modes */ 4 | src: url('Lato-Heavy.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */ 5 | url('Lato-Heavy.woff') format('woff'), /* Modern Browsers */ 6 | url('Lato-Heavy.ttf') format('truetype'); 7 | font-style: normal; 8 | font-weight: normal; 9 | text-rendering: optimizeLegibility; 10 | } 11 | 12 | -------------------------------------------------------------------------------- /static/fonts/Lato-Heavy.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LittleVulpix/toxme/52c731993ad9bfb6eacde8ace91f896d718a2811/static/fonts/Lato-Heavy.eot -------------------------------------------------------------------------------- /static/fonts/Lato-Heavy.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Lato Heavy - Web Font Specimen 7 | 8 | 11 | 12 | 13 |

The quick brown fox jumps over the lazy dog. $123.45!

14 | 15 | 16 | -------------------------------------------------------------------------------- /static/fonts/Lato-Heavy.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LittleVulpix/toxme/52c731993ad9bfb6eacde8ace91f896d718a2811/static/fonts/Lato-Heavy.ttf -------------------------------------------------------------------------------- /static/fonts/Lato-Heavy.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LittleVulpix/toxme/52c731993ad9bfb6eacde8ace91f896d718a2811/static/fonts/Lato-Heavy.woff -------------------------------------------------------------------------------- /static/fonts/Lato-HeavyItalic.css: -------------------------------------------------------------------------------- 1 | /* Webfont: Lato-HeavyItalic */@font-face { 2 | font-family: 'LatoHeavy'; 3 | src: url('Lato-HeavyItalic.eot'); /* IE9 Compat Modes */ 4 | src: url('Lato-HeavyItalic.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */ 5 | url('Lato-HeavyItalic.woff') format('woff'), /* Modern Browsers */ 6 | url('Lato-HeavyItalic.ttf') format('truetype'); 7 | font-style: italic; 8 | font-weight: normal; 9 | text-rendering: optimizeLegibility; 10 | } 11 | 12 | -------------------------------------------------------------------------------- /static/fonts/Lato-HeavyItalic.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LittleVulpix/toxme/52c731993ad9bfb6eacde8ace91f896d718a2811/static/fonts/Lato-HeavyItalic.eot -------------------------------------------------------------------------------- /static/fonts/Lato-HeavyItalic.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Lato Heavy Italic - Web Font Specimen 7 | 8 | 11 | 12 | 13 |

The quick brown fox jumps over the lazy dog. $123.45!

14 | 15 | 16 | -------------------------------------------------------------------------------- /static/fonts/Lato-HeavyItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LittleVulpix/toxme/52c731993ad9bfb6eacde8ace91f896d718a2811/static/fonts/Lato-HeavyItalic.ttf -------------------------------------------------------------------------------- /static/fonts/Lato-HeavyItalic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LittleVulpix/toxme/52c731993ad9bfb6eacde8ace91f896d718a2811/static/fonts/Lato-HeavyItalic.woff -------------------------------------------------------------------------------- /static/fonts/Lato-Italic.css: -------------------------------------------------------------------------------- 1 | /* Webfont: Lato-Italic */@font-face { 2 | font-family: 'Lato'; 3 | src: url('Lato-Italic.eot'); /* IE9 Compat Modes */ 4 | src: url('Lato-Italic.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */ 5 | url('Lato-Italic.woff') format('woff'), /* Modern Browsers */ 6 | url('Lato-Italic.ttf') format('truetype'); 7 | font-style: italic; 8 | font-weight: normal; 9 | text-rendering: optimizeLegibility; 10 | } 11 | 12 | -------------------------------------------------------------------------------- /static/fonts/Lato-Italic.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LittleVulpix/toxme/52c731993ad9bfb6eacde8ace91f896d718a2811/static/fonts/Lato-Italic.eot -------------------------------------------------------------------------------- /static/fonts/Lato-Italic.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Lato Italic - Web Font Specimen 7 | 8 | 11 | 12 | 13 |

The quick brown fox jumps over the lazy dog. $123.45!

14 | 15 | 16 | -------------------------------------------------------------------------------- /static/fonts/Lato-Italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LittleVulpix/toxme/52c731993ad9bfb6eacde8ace91f896d718a2811/static/fonts/Lato-Italic.ttf -------------------------------------------------------------------------------- /static/fonts/Lato-Italic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LittleVulpix/toxme/52c731993ad9bfb6eacde8ace91f896d718a2811/static/fonts/Lato-Italic.woff -------------------------------------------------------------------------------- /static/fonts/Lato-Light.css: -------------------------------------------------------------------------------- 1 | /* Webfont: Lato-Light */@font-face { 2 | font-family: 'LatoLight'; 3 | src: url('Lato-Light.eot'); /* IE9 Compat Modes */ 4 | src: url('Lato-Light.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */ 5 | url('Lato-Light.woff') format('woff'), /* Modern Browsers */ 6 | url('Lato-Light.ttf') format('truetype'); 7 | font-style: normal; 8 | font-weight: normal; 9 | text-rendering: optimizeLegibility; 10 | } 11 | 12 | -------------------------------------------------------------------------------- /static/fonts/Lato-Light.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LittleVulpix/toxme/52c731993ad9bfb6eacde8ace91f896d718a2811/static/fonts/Lato-Light.eot -------------------------------------------------------------------------------- /static/fonts/Lato-Light.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Lato Light - Web Font Specimen 7 | 8 | 11 | 12 | 13 |

The quick brown fox jumps over the lazy dog. $123.45!

14 | 15 | 16 | -------------------------------------------------------------------------------- /static/fonts/Lato-Light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LittleVulpix/toxme/52c731993ad9bfb6eacde8ace91f896d718a2811/static/fonts/Lato-Light.ttf -------------------------------------------------------------------------------- /static/fonts/Lato-Light.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LittleVulpix/toxme/52c731993ad9bfb6eacde8ace91f896d718a2811/static/fonts/Lato-Light.woff -------------------------------------------------------------------------------- /static/fonts/Lato-LightItalic.css: -------------------------------------------------------------------------------- 1 | /* Webfont: Lato-LightItalic */@font-face { 2 | font-family: 'LatoLight'; 3 | src: url('Lato-LightItalic.eot'); /* IE9 Compat Modes */ 4 | src: url('Lato-LightItalic.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */ 5 | url('Lato-LightItalic.woff') format('woff'), /* Modern Browsers */ 6 | url('Lato-LightItalic.ttf') format('truetype'); 7 | font-style: italic; 8 | font-weight: normal; 9 | text-rendering: optimizeLegibility; 10 | } 11 | 12 | -------------------------------------------------------------------------------- /static/fonts/Lato-LightItalic.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LittleVulpix/toxme/52c731993ad9bfb6eacde8ace91f896d718a2811/static/fonts/Lato-LightItalic.eot -------------------------------------------------------------------------------- /static/fonts/Lato-LightItalic.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Lato Light Italic - Web Font Specimen 7 | 8 | 11 | 12 | 13 |

The quick brown fox jumps over the lazy dog. $123.45!

14 | 15 | 16 | -------------------------------------------------------------------------------- /static/fonts/Lato-LightItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LittleVulpix/toxme/52c731993ad9bfb6eacde8ace91f896d718a2811/static/fonts/Lato-LightItalic.ttf -------------------------------------------------------------------------------- /static/fonts/Lato-LightItalic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LittleVulpix/toxme/52c731993ad9bfb6eacde8ace91f896d718a2811/static/fonts/Lato-LightItalic.woff -------------------------------------------------------------------------------- /static/fonts/Lato-Medium.css: -------------------------------------------------------------------------------- 1 | /* Webfont: Lato-Medium */@font-face { 2 | font-family: 'LatoMedium'; 3 | src: url('Lato-Medium.eot'); /* IE9 Compat Modes */ 4 | src: url('Lato-Medium.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */ 5 | url('Lato-Medium.woff') format('woff'), /* Modern Browsers */ 6 | url('Lato-Medium.ttf') format('truetype'); 7 | font-style: normal; 8 | font-weight: normal; 9 | text-rendering: optimizeLegibility; 10 | } 11 | 12 | -------------------------------------------------------------------------------- /static/fonts/Lato-Medium.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LittleVulpix/toxme/52c731993ad9bfb6eacde8ace91f896d718a2811/static/fonts/Lato-Medium.eot -------------------------------------------------------------------------------- /static/fonts/Lato-Medium.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Lato Medium - Web Font Specimen 7 | 8 | 11 | 12 | 13 |

The quick brown fox jumps over the lazy dog. $123.45!

14 | 15 | 16 | -------------------------------------------------------------------------------- /static/fonts/Lato-Medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LittleVulpix/toxme/52c731993ad9bfb6eacde8ace91f896d718a2811/static/fonts/Lato-Medium.ttf -------------------------------------------------------------------------------- /static/fonts/Lato-Medium.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LittleVulpix/toxme/52c731993ad9bfb6eacde8ace91f896d718a2811/static/fonts/Lato-Medium.woff -------------------------------------------------------------------------------- /static/fonts/Lato-MediumItalic.css: -------------------------------------------------------------------------------- 1 | /* Webfont: Lato-MediumItalic */@font-face { 2 | font-family: 'LatoMedium'; 3 | src: url('Lato-MediumItalic.eot'); /* IE9 Compat Modes */ 4 | src: url('Lato-MediumItalic.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */ 5 | url('Lato-MediumItalic.woff') format('woff'), /* Modern Browsers */ 6 | url('Lato-MediumItalic.ttf') format('truetype'); 7 | font-style: italic; 8 | font-weight: normal; 9 | text-rendering: optimizeLegibility; 10 | } 11 | 12 | -------------------------------------------------------------------------------- /static/fonts/Lato-MediumItalic.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LittleVulpix/toxme/52c731993ad9bfb6eacde8ace91f896d718a2811/static/fonts/Lato-MediumItalic.eot -------------------------------------------------------------------------------- /static/fonts/Lato-MediumItalic.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Lato Medium Italic - Web Font Specimen 7 | 8 | 11 | 12 | 13 |

The quick brown fox jumps over the lazy dog. $123.45!

14 | 15 | 16 | -------------------------------------------------------------------------------- /static/fonts/Lato-MediumItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LittleVulpix/toxme/52c731993ad9bfb6eacde8ace91f896d718a2811/static/fonts/Lato-MediumItalic.ttf -------------------------------------------------------------------------------- /static/fonts/Lato-MediumItalic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LittleVulpix/toxme/52c731993ad9bfb6eacde8ace91f896d718a2811/static/fonts/Lato-MediumItalic.woff -------------------------------------------------------------------------------- /static/fonts/Lato-Regular.css: -------------------------------------------------------------------------------- 1 | /* Webfont: Lato-Regular */@font-face { 2 | font-family: 'Lato'; 3 | src: url('Lato-Regular.eot'); /* IE9 Compat Modes */ 4 | src: url('Lato-Regular.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */ 5 | url('Lato-Regular.woff') format('woff'), /* Modern Browsers */ 6 | url('Lato-Regular.ttf') format('truetype'); 7 | font-style: normal; 8 | font-weight: normal; 9 | text-rendering: optimizeLegibility; 10 | } 11 | 12 | -------------------------------------------------------------------------------- /static/fonts/Lato-Regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LittleVulpix/toxme/52c731993ad9bfb6eacde8ace91f896d718a2811/static/fonts/Lato-Regular.eot -------------------------------------------------------------------------------- /static/fonts/Lato-Regular.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Lato Regular - Web Font Specimen 7 | 8 | 11 | 12 | 13 |

The quick brown fox jumps over the lazy dog. $123.45!

14 | 15 | 16 | -------------------------------------------------------------------------------- /static/fonts/Lato-Regular.min.css: -------------------------------------------------------------------------------- 1 | @font-face{font-family:'Lato';src:url('Lato-Regular.eot');src:url('Lato-Regular.eot?#iefix') format('embedded-opentype'),url('Lato-Regular.woff') format('woff'),url('Lato-Regular.ttf') format('truetype');font-style:normal;font-weight:normal;text-rendering:optimizeLegibility} 2 | -------------------------------------------------------------------------------- /static/fonts/Lato-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LittleVulpix/toxme/52c731993ad9bfb6eacde8ace91f896d718a2811/static/fonts/Lato-Regular.ttf -------------------------------------------------------------------------------- /static/fonts/Lato-Regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LittleVulpix/toxme/52c731993ad9bfb6eacde8ace91f896d718a2811/static/fonts/Lato-Regular.woff -------------------------------------------------------------------------------- /static/fonts/Lato-Semibold.css: -------------------------------------------------------------------------------- 1 | /* Webfont: Lato-Semibold */@font-face { 2 | font-family: 'LatoSemibold'; 3 | src: url('Lato-Semibold.eot'); /* IE9 Compat Modes */ 4 | src: url('Lato-Semibold.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */ 5 | url('Lato-Semibold.woff') format('woff'), /* Modern Browsers */ 6 | url('Lato-Semibold.ttf') format('truetype'); 7 | font-style: normal; 8 | font-weight: normal; 9 | text-rendering: optimizeLegibility; 10 | } 11 | 12 | -------------------------------------------------------------------------------- /static/fonts/Lato-Semibold.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LittleVulpix/toxme/52c731993ad9bfb6eacde8ace91f896d718a2811/static/fonts/Lato-Semibold.eot -------------------------------------------------------------------------------- /static/fonts/Lato-Semibold.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Lato Semibold - Web Font Specimen 7 | 8 | 11 | 12 | 13 |

The quick brown fox jumps over the lazy dog. $123.45!

14 | 15 | 16 | -------------------------------------------------------------------------------- /static/fonts/Lato-Semibold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LittleVulpix/toxme/52c731993ad9bfb6eacde8ace91f896d718a2811/static/fonts/Lato-Semibold.ttf -------------------------------------------------------------------------------- /static/fonts/Lato-Semibold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LittleVulpix/toxme/52c731993ad9bfb6eacde8ace91f896d718a2811/static/fonts/Lato-Semibold.woff -------------------------------------------------------------------------------- /static/fonts/Lato-SemiboldItalic.css: -------------------------------------------------------------------------------- 1 | /* Webfont: Lato-SemiboldItalic */@font-face { 2 | font-family: 'LatoSemibold'; 3 | src: url('Lato-SemiboldItalic.eot'); /* IE9 Compat Modes */ 4 | src: url('Lato-SemiboldItalic.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */ 5 | url('Lato-SemiboldItalic.woff') format('woff'), /* Modern Browsers */ 6 | url('Lato-SemiboldItalic.ttf') format('truetype'); 7 | font-style: italic; 8 | font-weight: normal; 9 | text-rendering: optimizeLegibility; 10 | } 11 | 12 | -------------------------------------------------------------------------------- /static/fonts/Lato-SemiboldItalic.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LittleVulpix/toxme/52c731993ad9bfb6eacde8ace91f896d718a2811/static/fonts/Lato-SemiboldItalic.eot -------------------------------------------------------------------------------- /static/fonts/Lato-SemiboldItalic.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Lato Semibold Italic - Web Font Specimen 7 | 8 | 11 | 12 | 13 |

The quick brown fox jumps over the lazy dog. $123.45!

14 | 15 | 16 | -------------------------------------------------------------------------------- /static/fonts/Lato-SemiboldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LittleVulpix/toxme/52c731993ad9bfb6eacde8ace91f896d718a2811/static/fonts/Lato-SemiboldItalic.ttf -------------------------------------------------------------------------------- /static/fonts/Lato-SemiboldItalic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LittleVulpix/toxme/52c731993ad9bfb6eacde8ace91f896d718a2811/static/fonts/Lato-SemiboldItalic.woff -------------------------------------------------------------------------------- /static/fonts/Lato-Thin.css: -------------------------------------------------------------------------------- 1 | /* Webfont: Lato-Thin */@font-face { 2 | font-family: 'LatoThin'; 3 | src: url('Lato-Thin.eot'); /* IE9 Compat Modes */ 4 | src: url('Lato-Thin.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */ 5 | url('Lato-Thin.woff') format('woff'), /* Modern Browsers */ 6 | url('Lato-Thin.ttf') format('truetype'); 7 | font-style: normal; 8 | font-weight: normal; 9 | text-rendering: optimizeLegibility; 10 | } 11 | 12 | -------------------------------------------------------------------------------- /static/fonts/Lato-Thin.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LittleVulpix/toxme/52c731993ad9bfb6eacde8ace91f896d718a2811/static/fonts/Lato-Thin.eot -------------------------------------------------------------------------------- /static/fonts/Lato-Thin.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Lato Thin - Web Font Specimen 7 | 8 | 11 | 12 | 13 |

The quick brown fox jumps over the lazy dog. $123.45!

14 | 15 | 16 | -------------------------------------------------------------------------------- /static/fonts/Lato-Thin.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LittleVulpix/toxme/52c731993ad9bfb6eacde8ace91f896d718a2811/static/fonts/Lato-Thin.ttf -------------------------------------------------------------------------------- /static/fonts/Lato-Thin.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LittleVulpix/toxme/52c731993ad9bfb6eacde8ace91f896d718a2811/static/fonts/Lato-Thin.woff -------------------------------------------------------------------------------- /static/fonts/Lato-ThinItalic.css: -------------------------------------------------------------------------------- 1 | /* Webfont: Lato-ThinItalic */@font-face { 2 | font-family: 'LatoThin'; 3 | src: url('Lato-ThinItalic.eot'); /* IE9 Compat Modes */ 4 | src: url('Lato-ThinItalic.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */ 5 | url('Lato-ThinItalic.woff') format('woff'), /* Modern Browsers */ 6 | url('Lato-ThinItalic.ttf') format('truetype'); 7 | font-style: italic; 8 | font-weight: normal; 9 | text-rendering: optimizeLegibility; 10 | } 11 | 12 | -------------------------------------------------------------------------------- /static/fonts/Lato-ThinItalic.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LittleVulpix/toxme/52c731993ad9bfb6eacde8ace91f896d718a2811/static/fonts/Lato-ThinItalic.eot -------------------------------------------------------------------------------- /static/fonts/Lato-ThinItalic.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Lato Thin Italic - Web Font Specimen 7 | 8 | 11 | 12 | 13 |

The quick brown fox jumps over the lazy dog. $123.45!

14 | 15 | 16 | -------------------------------------------------------------------------------- /static/fonts/Lato-ThinItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LittleVulpix/toxme/52c731993ad9bfb6eacde8ace91f896d718a2811/static/fonts/Lato-ThinItalic.ttf -------------------------------------------------------------------------------- /static/fonts/Lato-ThinItalic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LittleVulpix/toxme/52c731993ad9bfb6eacde8ace91f896d718a2811/static/fonts/Lato-ThinItalic.woff -------------------------------------------------------------------------------- /static/fonts/Lato2OFLWeb.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LittleVulpix/toxme/52c731993ad9bfb6eacde8ace91f896d718a2811/static/fonts/Lato2OFLWeb.zip -------------------------------------------------------------------------------- /static/fonts/OFL.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2010-2014, Łukasz Dziedzic (dziedzic@typoland.com), 2 | with Reserved Font Name Lato. 3 | 4 | This Font Software is licensed under the SIL Open Font License, Version 1.1. 5 | This license is copied below, and is also available with a FAQ at: 6 | http://scripts.sil.org/OFL 7 | 8 | 9 | ----------------------------------------------------------- 10 | SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 11 | ----------------------------------------------------------- 12 | 13 | PREAMBLE 14 | The goals of the Open Font License (OFL) are to stimulate worldwide 15 | development of collaborative font projects, to support the font creation 16 | efforts of academic and linguistic communities, and to provide a free and 17 | open framework in which fonts may be shared and improved in partnership 18 | with others. 19 | 20 | The OFL allows the licensed fonts to be used, studied, modified and 21 | redistributed freely as long as they are not sold by themselves. The 22 | fonts, including any derivative works, can be bundled, embedded, 23 | redistributed and/or sold with any software provided that any reserved 24 | names are not used by derivative works. The fonts and derivatives, 25 | however, cannot be released under any other type of license. The 26 | requirement for fonts to remain under this license does not apply 27 | to any document created using the fonts or their derivatives. 28 | 29 | DEFINITIONS 30 | "Font Software" refers to the set of files released by the Copyright 31 | Holder(s) under this license and clearly marked as such. This may 32 | include source files, build scripts and documentation. 33 | 34 | "Reserved Font Name" refers to any names specified as such after the 35 | copyright statement(s). 36 | 37 | "Original Version" refers to the collection of Font Software components as 38 | distributed by the Copyright Holder(s). 39 | 40 | "Modified Version" refers to any derivative made by adding to, deleting, 41 | or substituting -- in part or in whole -- any of the components of the 42 | Original Version, by changing formats or by porting the Font Software to a 43 | new environment. 44 | 45 | "Author" refers to any designer, engineer, programmer, technical 46 | writer or other person who contributed to the Font Software. 47 | 48 | PERMISSION & CONDITIONS 49 | Permission is hereby granted, free of charge, to any person obtaining 50 | a copy of the Font Software, to use, study, copy, merge, embed, modify, 51 | redistribute, and sell modified and unmodified copies of the Font 52 | Software, subject to the following conditions: 53 | 54 | 1) Neither the Font Software nor any of its individual components, 55 | in Original or Modified Versions, may be sold by itself. 56 | 57 | 2) Original or Modified Versions of the Font Software may be bundled, 58 | redistributed and/or sold with any software, provided that each copy 59 | contains the above copyright notice and this license. These can be 60 | included either as stand-alone text files, human-readable headers or 61 | in the appropriate machine-readable metadata fields within text or 62 | binary files as long as those fields can be easily viewed by the user. 63 | 64 | 3) No Modified Version of the Font Software may use the Reserved Font 65 | Name(s) unless explicit written permission is granted by the corresponding 66 | Copyright Holder. This restriction only applies to the primary font name as 67 | presented to the users. 68 | 69 | 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font 70 | Software shall not be used to promote, endorse or advertise any 71 | Modified Version, except to acknowledge the contribution(s) of the 72 | Copyright Holder(s) and the Author(s) or with their explicit written 73 | permission. 74 | 75 | 5) The Font Software, modified or unmodified, in part or in whole, 76 | must be distributed entirely under this license, and must not be 77 | distributed under any other license. The requirement for fonts to 78 | remain under this license does not apply to any document created 79 | using the Font Software. 80 | 81 | TERMINATION 82 | This license becomes null and void if any of the above conditions are 83 | not met. 84 | 85 | DISCLAIMER 86 | THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 87 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF 88 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT 89 | OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE 90 | COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 91 | INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL 92 | DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 93 | FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM 94 | OTHER DEALINGS IN THE FONT SOFTWARE. 95 | -------------------------------------------------------------------------------- /static/lato/Lato-Black.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LittleVulpix/toxme/52c731993ad9bfb6eacde8ace91f896d718a2811/static/lato/Lato-Black.ttf -------------------------------------------------------------------------------- /static/lato/Lato-BlackItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LittleVulpix/toxme/52c731993ad9bfb6eacde8ace91f896d718a2811/static/lato/Lato-BlackItalic.ttf -------------------------------------------------------------------------------- /static/lato/Lato-Bold.css: -------------------------------------------------------------------------------- 1 | /* Webfont: Lato-Bold */@font-face { 2 | font-family: 'Lato'; 3 | src: url('Lato-Bold.eot'); /* IE9 Compat Modes */ 4 | src: url('Lato-Bold.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */ 5 | url('Lato-Bold.woff') format('woff'), /* Modern Browsers */ 6 | url('Lato-Bold.ttf') format('truetype'); 7 | font-style: normal; 8 | font-weight: bold; 9 | text-rendering: optimizeLegibility; 10 | } 11 | 12 | -------------------------------------------------------------------------------- /static/lato/Lato-Bold.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LittleVulpix/toxme/52c731993ad9bfb6eacde8ace91f896d718a2811/static/lato/Lato-Bold.eot -------------------------------------------------------------------------------- /static/lato/Lato-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LittleVulpix/toxme/52c731993ad9bfb6eacde8ace91f896d718a2811/static/lato/Lato-Bold.ttf -------------------------------------------------------------------------------- /static/lato/Lato-Bold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LittleVulpix/toxme/52c731993ad9bfb6eacde8ace91f896d718a2811/static/lato/Lato-Bold.woff -------------------------------------------------------------------------------- /static/lato/Lato-BoldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LittleVulpix/toxme/52c731993ad9bfb6eacde8ace91f896d718a2811/static/lato/Lato-BoldItalic.ttf -------------------------------------------------------------------------------- /static/lato/Lato-Hairline.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LittleVulpix/toxme/52c731993ad9bfb6eacde8ace91f896d718a2811/static/lato/Lato-Hairline.ttf -------------------------------------------------------------------------------- /static/lato/Lato-HairlineItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LittleVulpix/toxme/52c731993ad9bfb6eacde8ace91f896d718a2811/static/lato/Lato-HairlineItalic.ttf -------------------------------------------------------------------------------- /static/lato/Lato-Italic.css: -------------------------------------------------------------------------------- 1 | /* Webfont: Lato-Italic */@font-face { 2 | font-family: 'Lato'; 3 | src: url('Lato-Italic.eot'); /* IE9 Compat Modes */ 4 | src: url('Lato-Italic.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */ 5 | url('Lato-Italic.woff') format('woff'), /* Modern Browsers */ 6 | url('Lato-Italic.ttf') format('truetype'); 7 | font-style: italic; 8 | font-weight: normal; 9 | text-rendering: optimizeLegibility; 10 | } 11 | 12 | -------------------------------------------------------------------------------- /static/lato/Lato-Italic.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LittleVulpix/toxme/52c731993ad9bfb6eacde8ace91f896d718a2811/static/lato/Lato-Italic.eot -------------------------------------------------------------------------------- /static/lato/Lato-Italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LittleVulpix/toxme/52c731993ad9bfb6eacde8ace91f896d718a2811/static/lato/Lato-Italic.ttf -------------------------------------------------------------------------------- /static/lato/Lato-Italic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LittleVulpix/toxme/52c731993ad9bfb6eacde8ace91f896d718a2811/static/lato/Lato-Italic.woff -------------------------------------------------------------------------------- /static/lato/Lato-Light.css: -------------------------------------------------------------------------------- 1 | /* Webfont: Lato-Light */@font-face { 2 | font-family: 'LatoLight'; 3 | src: url('Lato-Light.eot'); /* IE9 Compat Modes */ 4 | src: url('Lato-Light.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */ 5 | url('Lato-Light.woff') format('woff'), /* Modern Browsers */ 6 | url('Lato-Light.ttf') format('truetype'); 7 | font-style: normal; 8 | font-weight: normal; 9 | text-rendering: optimizeLegibility; 10 | } 11 | 12 | -------------------------------------------------------------------------------- /static/lato/Lato-Light.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LittleVulpix/toxme/52c731993ad9bfb6eacde8ace91f896d718a2811/static/lato/Lato-Light.eot -------------------------------------------------------------------------------- /static/lato/Lato-Light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LittleVulpix/toxme/52c731993ad9bfb6eacde8ace91f896d718a2811/static/lato/Lato-Light.ttf -------------------------------------------------------------------------------- /static/lato/Lato-Light.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LittleVulpix/toxme/52c731993ad9bfb6eacde8ace91f896d718a2811/static/lato/Lato-Light.woff -------------------------------------------------------------------------------- /static/lato/Lato-LightItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LittleVulpix/toxme/52c731993ad9bfb6eacde8ace91f896d718a2811/static/lato/Lato-LightItalic.ttf -------------------------------------------------------------------------------- /static/lato/Lato-Regular.css: -------------------------------------------------------------------------------- 1 | /* Webfont: Lato-Regular */@font-face { 2 | font-family: 'Lato'; 3 | src: url('Lato-Regular.eot'); /* IE9 Compat Modes */ 4 | src: url('Lato-Regular.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */ 5 | url('Lato-Regular.woff') format('woff'), /* Modern Browsers */ 6 | url('Lato-Regular.ttf') format('truetype'); 7 | font-style: normal; 8 | font-weight: normal; 9 | text-rendering: optimizeLegibility; 10 | } 11 | 12 | -------------------------------------------------------------------------------- /static/lato/Lato-Regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LittleVulpix/toxme/52c731993ad9bfb6eacde8ace91f896d718a2811/static/lato/Lato-Regular.eot -------------------------------------------------------------------------------- /static/lato/Lato-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LittleVulpix/toxme/52c731993ad9bfb6eacde8ace91f896d718a2811/static/lato/Lato-Regular.ttf -------------------------------------------------------------------------------- /static/lato/Lato-Regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LittleVulpix/toxme/52c731993ad9bfb6eacde8ace91f896d718a2811/static/lato/Lato-Regular.woff -------------------------------------------------------------------------------- /static/logo-head.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LittleVulpix/toxme/52c731993ad9bfb6eacde8ace91f896d718a2811/static/logo-head.png -------------------------------------------------------------------------------- /static/lookup.js: -------------------------------------------------------------------------------- 1 | function sc_showFailureOnUI(s) { 2 | "use strict"; 3 | document.getElementById("lookup_results").style.display = "none"; 4 | var message = document.getElementById("lookup_error"); 5 | message.style.display = "block"; 6 | message.textContent = s; 7 | } 8 | 9 | function sc_errorStringFromCode(ec) { 10 | "use strict"; 11 | switch (ec) { 12 | case -41: 13 | return "Lookup failed: unspecified error."; 14 | case -42: 15 | return "Lookup failed: user not found."; 16 | case -43: 17 | return "Lookup failed: internal error! Please report it on GitHub."; 18 | case -3: 19 | return "Lookup failed: the address wasn't valid."; 20 | } 21 | } 22 | 23 | function sc_showResultOnUI(payload) { 24 | "use strict"; 25 | var message, source, sigbox, sname; 26 | document.getElementById("lookup_error").style.display = "none"; 27 | message = document.getElementById("lookup_results"); 28 | message.style.display = "block"; 29 | document.getElementById("lu_oname").textContent = payload.name; 30 | document.getElementById("lu_regdomain").textContent = payload.regdomain; 31 | if (payload.source === 1) { 32 | source = "local user"; 33 | } else { 34 | source = "remote user"; 35 | } 36 | document.getElementById("lu_stype").textContent = source; 37 | document.getElementById("lu_rectype").textContent = payload.version; 38 | if (payload.tox_id.length === 64) { 39 | document.getElementById("results").className = "v2"; 40 | document.getElementById("lu_user_pk").textContent = payload.tox_id; 41 | } else { 42 | document.getElementById("results").className = "v1"; 43 | document.getElementById("lu_user_id").textContent = payload.tox_id; 44 | } 45 | sigbox = document.getElementById("lu_user_sig"); 46 | sname = encodeURIComponent(payload.name); 47 | sigbox.textContent = payload.verify.detail; 48 | sigbox.className = payload.verify.status === 1 ? "good" 49 | : (payload.verify.status === 2 ? "bad" : "undecided"); 50 | document.getElementById("lu_user_link").href = "/u/" + sname; 51 | } 52 | 53 | function sc_lookupStatusDidChange(sender) { 54 | "use strict"; 55 | var respload, ec 56 | if (sender.readyState === 4) { 57 | try { 58 | respload = JSON.parse(sender.responseText); 59 | } catch (e) { 60 | respload = null; 61 | } 62 | 63 | if (sender.status !== 200) { 64 | if (respload && (ec = parseInt(respload.c, 10))) { 65 | sc_showFailureOnUI(sc_errorStringFromCode(ec)); 66 | } else { 67 | sc_showFailureOnUI("Lookup failed: server responded \ 68 | with status code " + sender.status); 69 | } 70 | return; 71 | } 72 | 73 | sc_showResultOnUI(respload); 74 | } 75 | } 76 | 77 | function sc_performSearch(e) { 78 | "use strict"; 79 | // Check if the user pressed enter. 80 | if (e.type === "keydown" && e.keyCode !== 13) 81 | return; 82 | 83 | var query, xhr; 84 | query = document.getElementById("search_text").value.trim(); 85 | if (query === "") 86 | return; 87 | window.searchIsRunning = 1 88 | xhr = new XMLHttpRequest(); 89 | xhr.onreadystatechange = function() { 90 | sc_lookupStatusDidChange(xhr); 91 | } 92 | xhr.open("POST", "/api", true); 93 | xhr.send(JSON.stringify({ 94 | "action": 3, 95 | "name": query, 96 | })); 97 | sc_showFailureOnUI("Please wait...") 98 | } 99 | 100 | function sc_init() { 101 | "use strict"; 102 | var qbox = document.getElementById("search_text"); 103 | qbox.value = window.location.hash.substring(1); 104 | document.getElementById("search_go").addEventListener( 105 | "click", sc_performSearch, 1 106 | ); 107 | document.getElementById("search_text").addEventListener( 108 | "keydown", sc_performSearch, 1 109 | ); 110 | } 111 | -------------------------------------------------------------------------------- /static/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Allow: /$ 3 | Disallow: / 4 | -------------------------------------------------------------------------------- /static/tail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LittleVulpix/toxme/52c731993ad9bfb6eacde8ace91f896d718a2811/static/tail.png -------------------------------------------------------------------------------- /static/tail.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /static/tox.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'LatoLight'; 3 | src: url('/static/fonts/Lato-Light.eot'); /* IE9 Compat Modes */ 4 | src: url('/static/fonts/Lato-Light.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */ 5 | url('/static/fonts/Lato-Light.woff') format('woff'), /* Modern Browsers */ 6 | url('/static/fonts/Lato-Light.ttf') format('truetype'); 7 | font-style: normal; 8 | font-weight: normal; 9 | } 10 | 11 | @font-face { 12 | font-family: 'Lato'; 13 | src: url('/static/fonts/Lato-Bold.eot'); /* IE9 Compat Modes */ 14 | src: url('/static/fonts/Lato-Bold.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */ 15 | url('/static/fonts/Lato-Bold.woff') format('woff'), /* Modern Browsers */ 16 | url('/static/fonts/Lato-Bold.ttf') format('truetype'); 17 | font-style: normal; 18 | font-weight: bold; 19 | } 20 | 21 | @font-face { 22 | font-family: 'Lato'; 23 | src: url('/static/fonts/Lato-Regular.eot'); /* IE9 Compat Modes */ 24 | src: url('/static/fonts/Lato-Regular.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */ 25 | url('/static/fonts/Lato-Regular.woff') format('woff'), /* Modern Browsers */ 26 | url('/static/fonts/Lato-Regular.ttf') format('truetype'); 27 | font-style: normal; 28 | font-weight: normal; 29 | } 30 | 31 | @font-face { 32 | font-family: 'Lato'; 33 | src: url('/static/fonts/Lato-Italic.eot'); /* IE9 Compat Modes */ 34 | src: url('/static/fonts/Lato-Italic.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */ 35 | url('/static/fonts/Lato-Italic.woff') format('woff'), /* Modern Browsers */ 36 | url('/static/fonts/Lato-Italic.ttf') format('truetype'); 37 | font-style: italic; 38 | font-weight: normal; 39 | } 40 | 41 | body { 42 | background-color:#5e7c88; 43 | -webkit-font-smoothing:antialiased; 44 | font-family:Lato; 45 | margin:0; 46 | } 47 | .header_nav { 48 | text-align:left; 49 | padding:15px; 50 | background-color:#ecf0f1; 51 | } 52 | .header_nav > .icon { 53 | background-image:url(logo-head.png); 54 | background-size:109px 45px; 55 | height:45px; 56 | width:109px; 57 | display:block; 58 | } 59 | 60 | .main_content { 61 | margin:0 auto; 62 | text-align:center; 63 | color:white; 64 | } 65 | .main_content a { 66 | font-weight:bold; 67 | text-decoration:none; 68 | color:white; 69 | } 70 | .main_content a:hover { 71 | border-bottom:1px rgba(255, 255, 255, 0.90) dashed; 72 | } 73 | 74 | .chunk { 75 | padding:20px; 76 | } 77 | .chunk.hello { 78 | padding:50px 10px; 79 | } 80 | .chunk.dark { 81 | background-color:#24221f; 82 | } 83 | 84 | .tagline { 85 | font-size:40px; 86 | margin:0; 87 | color:white; 88 | } 89 | .chunk.dark .tagline { 90 | color:#feb41c; 91 | } 92 | .de-emphasize { 93 | color: rgb(230, 230, 230) 94 | } 95 | 96 | .lookup_form { 97 | font-size:0; 98 | } 99 | .lookup_form > input, 100 | .lookup_form > button { 101 | -webkit-font-smoothing:antialiased; 102 | font-size:20px; 103 | font-family:"Helvetica Neue", sans-serif; 104 | border:none; 105 | box-sizing:border-box; 106 | outline:none; 107 | margin:0; 108 | padding:10px 25px; 109 | } 110 | .lookup_form > input { 111 | border-radius:9001px 0 0 9001px; 112 | } 113 | .lookup_form > button { 114 | border-radius:0 9001px 9001px 0; 115 | background-color:#feb41c; 116 | } 117 | .chunk.report { 118 | max-width:800px; 119 | text-align:left; 120 | margin:0 auto; 121 | border-radius:8px; 122 | background-color:rgba(0, 0, 0, 0.19); 123 | padding:15px; 124 | box-sizing:border-box; 125 | } 126 | .chunk.report > p { 127 | margin:0; 128 | } 129 | table#results { 130 | table-layout:fixed; 131 | width:100%; 132 | } 133 | table#results td:first-child { 134 | font-weight:bold; 135 | width:170px; 136 | } 137 | table#results td { 138 | vertical-align: top; 139 | word-wrap:break-word; 140 | } 141 | table#results.v1 tr.v2only, 142 | table#results.v2 tr.v1only { 143 | display:none; 144 | } 145 | td#lu_user_sig.good::before { 146 | content:"\2713"; 147 | display:inline-block; 148 | padding-right:5px; 149 | color:#33FF33; 150 | } 151 | td#lu_user_sig.bad::before { 152 | content:"\2715"; 153 | display:inline-block; 154 | padding-right:5px; 155 | color:#FF6666; 156 | } 157 | 158 | .user_entry { 159 | text-align:left; 160 | max-width:400px; 161 | margin:0 auto; 162 | } 163 | .user_entry .quote { 164 | border-radius: 25px; 165 | background-color:rgba(255, 255, 255, 0.7); 166 | color:#4b6874; 167 | padding:15px 20px; 168 | font-style:italic; 169 | display:inline-block; 170 | margin-bottom:0; 171 | font-size:20px; 172 | min-width:30px; 173 | } 174 | .user_entry .quote.notext { 175 | color:#7e8c99; 176 | font-style:normal; 177 | } 178 | .user_entry .quote_tail { 179 | margin:0 0 10px 25px; 180 | display:block; 181 | width:20px; 182 | height:12px; 183 | background-image:url("tail.svg"); 184 | } 185 | .user_entry .public_id { 186 | word-wrap:break-word; 187 | } 188 | .username { 189 | font-size:20px; 190 | } 191 | .findfriends .user_entry { 192 | max-width:900px; 193 | } 194 | 195 | .addself_form { 196 | max-width:500px; 197 | font-size:0; 198 | margin:0 auto; 199 | } 200 | .addself_form .formbit { 201 | margin-bottom:10px; 202 | } 203 | .addself_form label { 204 | font-size:16px; 205 | margin-left:5px; 206 | } 207 | .addself_form input, 208 | .addself_form label.left { 209 | -webkit-font-smoothing:antialiased; 210 | font-size:20px; 211 | font-family:"Helvetica Neue", sans-serif; 212 | border:none; 213 | box-sizing:border-box; 214 | outline:none; 215 | margin:0; 216 | padding:10px 25px; 217 | display:inline-block; 218 | } 219 | .addself_form label.left { 220 | border-radius:9001px 0 0 9001px; 221 | background-color:#feb41c; 222 | color:black; 223 | width:135px; 224 | } 225 | .addself_form input { 226 | border-radius:0 9001px 9001px 0; 227 | } 228 | .addself_form .submit { 229 | border-radius:9001px 9001px; 230 | background-color:#feb41c; 231 | font-weight:bold; 232 | margin:0 5px; 233 | } 234 | 235 | .footer { 236 | padding:10px; 237 | } 238 | 239 | @media (max-width: 320px) { 240 | table#results td:first-child { 241 | font-weight:bold; 242 | width:140px; 243 | } 244 | .chunk { 245 | padding:10px; 246 | } 247 | .chunk.hello { 248 | padding:25px 10px!important; 249 | } 250 | .lookup_form > input, 251 | .lookup_form > button { 252 | padding:10px 15px; 253 | } 254 | .lookup_form > input { 255 | width:200px; 256 | } 257 | .lookup_form > button { 258 | width:55px; 259 | } 260 | .addself_form input, 261 | .addself_form label.left { 262 | padding:10px 15px; 263 | } 264 | .addself_form label.left { 265 | width:90px; 266 | } 267 | .addself_form input { 268 | width:190px; 269 | } 270 | } 271 | -------------------------------------------------------------------------------- /static/tox/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Tox ID Service 7 | 8 | 9 | 10 |
11 | 12 |
13 |
14 |
15 |

404.

16 |

17 | That requested resource could not be found. 18 |

Click here to file a bug, or click here to return to the home page. 19 |

20 |
21 | 28 |
29 | 30 | 31 | -------------------------------------------------------------------------------- /static/tox/500.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Tox ID Service 7 | 8 | 9 | 10 |
11 | 12 |
13 |
14 |
15 |

500.

16 |

17 | An unknown server error occurred. 18 |

Click here to file a bug, or click here to return to the home page. 19 |

20 |
21 | 28 |
29 | 30 | 31 | -------------------------------------------------------------------------------- /templates/tox/add_ui.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Tox ID Service 7 | 8 | 9 | 10 | 11 | 12 | 15 |
16 | 17 |
18 |
19 |
20 |

Add Yourself

21 |

22 | Use this to add your Tox ID to the public list.
23 | Note: If your client supports this, it's best to do it from within the client.
24 | Want to edit your entry? Click here. 25 |

26 |
27 |
28 | 29 | 30 |
31 |
32 | 33 | 34 |
35 |
36 | 37 | 38 |
39 |
40 | 41 | 42 |
43 |
44 | 45 |
46 |
47 |
48 | 54 |
55 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /templates/tox/addkeyweb_success.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Tox ID Service 7 | 8 | 9 | 10 |
11 | 12 |
13 |
14 |
15 |

Done!

16 |

17 | Successfully added {{ n }}@{{ regdomain }} to the database.
18 | Your password is {{ p }}. Don't forget it. 19 |

20 |
21 | 27 |
28 | 29 | 30 | -------------------------------------------------------------------------------- /templates/tox/api_error_pretty.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Tox ID Service 7 | 8 | 9 | 10 |
11 | 12 |
13 |
14 |
15 |

API Error

16 |

17 | {{ f }} ({{ payload["c"] }}) 18 |

19 |
20 | 27 |
28 | 29 | 30 | -------------------------------------------------------------------------------- /templates/tox/edit_ui.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Tox ID Service 7 | 8 | 9 | 10 | 11 | 12 | 15 |
16 | 17 |
18 |
19 |
20 |

Edit Record

21 |

22 | Edit the entry for an existing Tox ID. Only the name and password are required.
23 | If you don't want to change a certain field, leave it blank. 24 |

25 |
26 |
27 | 28 | 29 |
30 |
31 | 32 | 33 |
34 |
35 | 36 | 37 |
38 |
39 | 40 | 41 |
42 |
43 | 44 | 45 |
46 |
47 | 48 | 49 |
50 |
51 |
52 | 58 |
59 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /templates/tox/fourohfour.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Tox ID Service 7 | 8 | 9 | 10 |
11 | 12 |
13 |
14 |
15 |

404.

16 |

17 | {% if record %} 18 | I don't know anyone who goes by the name {{ record }}@{{ realm }}. 19 | {% else %} 20 | This page doesn't seem to exist. 21 | {% end %} 22 |

23 |
24 | 31 |
32 | 33 | 34 | -------------------------------------------------------------------------------- /templates/tox/lookup_home.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Tox ID Service 8 | 9 | 10 | 11 | 12 | 13 |
14 | 15 | 18 |
19 |
20 |
21 |

User Lookup

22 |

Type a name below to find out more about that user. 23 |

You can also sign up or edit your record 24 |

25 |
26 | 27 | 28 |
29 |

Want to test toxme? Add echobot! - tox://echobot@toxme.io 30 |

31 |
32 | 33 | 42 |
43 | 50 |
51 | 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /templates/tox/onemomentplease.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Tox ID Service 7 | 8 | 9 | 10 | 13 |
14 | 15 |
16 |
17 |
18 |

19 | Click here to add {{record.name}}. 20 |

You can also add {{ record.name }}@{{ realm }} in your client. 21 |

22 |
23 | {% if record.bio %} 24 |

{{ record.bio }}

25 |

26 | 27 | {{ record.name }}@{{ realm }} 28 | 29 | {% end %} 30 | {% if record.pin %} 31 |

Full Tox ID: {{ record.public_key }}{{ record.pin }}{{ record.checksum }}

32 | {% end %} 33 |
34 |
35 |
36 | 37 |

38 | (If you have a phone, you can scan this QR code to add {{ record.name }} instead.) 39 |

40 |
41 |

Don't have Tox? Click here to install it.

42 | 49 |
50 | 51 | 52 | -------------------------------------------------------------------------------- /templates/tox/public_userlist.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Tox ID Service 7 | 8 | 9 | 10 | 13 |
14 | 15 |
16 |
17 |
18 |

Find Friends

19 |

20 | Click on a name below to add them on Tox. 21 | Add yourself 22 |

23 |
24 |
25 | {% for record in results_set %} 26 |
27 | 28 | {{ record.name }}@{{ realm }} 29 | 30 |
31 | {% end %} 32 |
33 |
34 | {% if previous_page is not None %} 35 | « Previous 36 | {% end %} 37 | {% if previous_page is not None and next_page is not None %} 38 | || 39 | {% end %} 40 | {% if next_page is not None %} 41 | Next » 42 | {% end %} 43 |
44 | 50 |
51 | 52 | 53 | --------------------------------------------------------------------------------