Remove platform stuff.

This commit is contained in:
Michael Zhang 2016-05-05 17:54:52 -05:00
parent bb4d106130
commit ad713b8904
73 changed files with 0 additions and 7568 deletions

49
.gitignore vendored
View file

@ -1,49 +0,0 @@
.vagrant
.secret_key
.bundle/config
logs/
files/
pfp/
# Object files
*.o
*.ko
*.obj
*.elf
# Precompiled Headers
*.gch
*.pch
# Libraries
*.lib
*.a
*.la
*.lo
# Shared objects (inc. Windows DLLs)
*.dll
*.so
*.so.*
*.dylib
# Text Backup Files #
*.s[a-w][a-z]
*~
\#*\#
.\#*
# OS X Files #
.DS_Store
._*
.Spotlight*
.Trashes
# Windows Files #
Thumbs.db
ehthumbs.db
a.out
*.project
*.class
*.pyc

26
Vagrantfile vendored
View file

@ -1,26 +0,0 @@
# -*- mode: ruby -*-
# vi: set ft=ruby :
Vagrant.configure(2) do |config|
config.vm.box = "ubuntu/trusty64"
# config.vm.box_check_update = false
config.vm.network :forwarded_port, guest: 80, host: 8080, auto_correct: true
config.vm.network :forwarded_port, guest: 8000, host: 8000, auto_correct: true
config.vm.network :forwarded_port, guest: 27017, host: 27017, auto_correct: true
config.vm.synced_folder "server", "/home/vagrant/server"
config.vm.synced_folder "scripts", "/home/vagrant/scripts"
config.vm.synced_folder "web", "/srv/http/ctf"
config.vm.provision :shell, :path => "scripts/setup.sh"
config.ssh.forward_agent = true
config.vm.provider "virtualbox" do |vb|
vb.customize ["modifyvm", :id, "--memory", "2048"]
end
config.vm.provider :virtualbox do |vb|
vb.customize ["modifyvm", :id, "--natdnshostresolver1", "on"]
vb.customize ["modifyvm", :id, "--natdnsproxy1", "on"]
end
end

View file

@ -1,41 +0,0 @@
server {
listen 80 default_server;
listen [::]:80 default_server ipv6only=on;
sendfile off;
root /srv/http/ctf;
index index.html index.htm;
server_name localhost;
error_page 404 /index.html;
# location / {
# try_files $uri $uri/ =404;
# }
# Put all the pages here so Angular doesn't fail.
location ~^/(about|chat|help|learn|login|logout|profile|register|scoreboard|settings|team|forgot)$ {
default_type text/html;
try_files /index.html /index.html;
}
location ~^/admin/(problems|settings|stats|teams)$ {
default_type text/html;
try_files /index.html /index.html;
}
location ~^/(profile|team|forgot)/(.*)$ {
default_type text/html;
try_files /index.html /index.html;
}
location /static {
alias /srv/http/static;
}
location ~ /api {
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_pass http://localhost:8000;
proxy_redirect off;
}
}

13
deploy
View file

@ -1,13 +0,0 @@
#!/bin/bash
echo "Stopping the server..."
pkill gunicorn
sudo service nginx stop
tmux kill-session -t ctf 2> /dev/null
sudo cp /vagrant/ctf.nginx /etc/nginx/sites-enabled/ctf
echo "Starting the server..."
cd /home/vagrant/server
sudo service nginx start
tmux new-session -s ctf -d 'tmux set remain-on-exit on && gunicorn "app:app" -c /home/vagrant/scripts/gunicorn.py.ini'

View file

@ -1 +0,0 @@
Help! I started growing this array culture, but it outgrew its petri dish! Using standard array syntax (either JavaScript or Python), indicate which element of the array contains the value ${value}.

View file

@ -1,12 +0,0 @@
{
"pid": "cancer",
"title": "Cancer",
"hint": "No hint!",
"category": "Miscellaneous",
"autogen": true,
"programming": false,
"value": 100,
"bonus": 0,
"threshold": 0,
"weightmap": { }
}

View file

@ -1,4 +0,0 @@
from multiprocessing import cpu_count
bind = "0.0.0.0:8000"
workers = cpu_count() * 2 + 1

View file

@ -1,9 +0,0 @@
Flask
mysql-python
Flask-SQLAlchemy
SQLAlchemy
gunicorn
requests
voluptuous
Pillow
markdown2

View file

@ -1,31 +0,0 @@
#!/bin/bash
MYSQL_ROOT_PASSWORD="i_hate_passwords"
echo "Updating system..."
apt-get -y update
apt-get -y upgrade
echo "Preparing for MySQL installation..."
sudo debconf-set-selections <<< "mysql-server mysql-server/root_password password $MYSQL_ROOT_PASSWORD"
sudo debconf-set-selections <<< "mysql-server mysql-server/root_password_again password $MYSQL_ROOT_PASSWORD"
echo "Installing dependencies..."
apt-get -y install python
apt-get -y install python-pip libjpeg-dev
apt-get -y install python-dev libmysqlclient-dev
apt-get -y install nginx
apt-get -y install mysql-server
apt-get -y install tmux
echo "Installing pip dependencies..."
pip install -r /vagrant/scripts/requirements.txt
echo "PATH=$PATH:/vagrant" >> /etc/profile
source /etc/profile
cp /vagrant/ctf.nginx /etc/nginx/sites-enabled/ctf
rm /etc/nginx/sites-*/default
sudo service nginx restart
mysql -u root -p"$MYSQL_ROOT_PASSWORD" -e "CREATE DATABASE easyctf;"

View file

@ -1,8 +0,0 @@
import admin
import logger
import models
import problem
import user
import stats
import team
import utils

View file

@ -1,45 +0,0 @@
from flask import Blueprint, jsonify
from decorators import admins_only, api_wrapper
from models import db, Problems, Files
from schemas import verify_to_schema, check
import cPickle as pickle
blueprint = Blueprint("admin", __name__)
@blueprint.route("/problems/list", methods=["GET"])
@api_wrapper
@admins_only
def problem_data():
problems = Problems.query.order_by(Problems.value).all()
problems_return = [ ]
for problem in problems:
problems_return.append({
"pid": problem.pid,
"title": problem.title,
"category": problem.category,
"description": problem.description,
"hint": problem.hint,
"value": problem.value,
"threshold": problem.threshold,
"weightmap": problem.weightmap
})
problems_return.sort(key=lambda prob: prob["value"])
return { "success": 1, "problems": problems_return }
"""
@blueprint.route("/problems/submit", methods=["POST"])
@api_wrapper
@admins_only
def problem_submit():
params = utils.flat_multi(request.form)
verify_to_schema(UserSchema, params)
title = params.get("title")
ProblemSubmissionSchema = Schema({
Required("title"): check(
([str, Length(min=4, max=64)], "The title should be between 4 and 64 characters long."),
),
}, extra=True)
"""

View file

@ -1,68 +0,0 @@
from functools import wraps
from flask import abort, request, session, make_response
import json
import traceback
import utils
class WebException(Exception): pass
class InternalException(Exception): pass
response_header = { "Content-Type": "application/json; charset=utf-8" }
def api_wrapper(f):
@wraps(f)
def wrapper(*args, **kwds):
if request.method == "POST":
try:
token = str(session.pop("csrf_token"))
provided_token = str(request.form.get("csrf_token"))
if not token or token != provided_token:
raise Exception
except Exception, e:
response = make_response(json.dumps({ "success": 0, "message": "Token has been tampered with." }), 403, response_header)
token = utils.generate_string()
response.set_cookie("csrf_token", token)
session["csrf_token"] = token
return response
web_result = {}
response = 200
try:
web_result = f(*args, **kwds)
except WebException as error:
response = 200
web_result = { "success": 0, "message": str(error) }
except Exception as error:
response = 200
traceback.print_exc()
web_result = { "success": 0, "message": "Something went wrong! Please notify us about this immediately.", "error": [ str(error), traceback.format_exc() ] }
result = (json.dumps(web_result), response, response_header)
response = make_response(result)
# Setting CSRF token
if "csrf_token" not in session:
token = utils.generate_string()
response.set_cookie("csrf_token", token)
session["csrf_token"] = token
return response
return wrapper
def login_required(f):
@wraps(f)
def decorated_function(*args, **kwargs):
if not user.is_logged_in():
return { "success": 0, "message": "Not logged in." }
return f(*args, **kwargs)
return decorated_function
import user # Must go below api_wrapper to prevent import loops
def admins_only(f):
@wraps(f)
def decorated_function(*args, **kwargs):
if not user.is_admin():
return { "success": 0, "message": "Not authorized." }
return f(*args, **kwargs)
return decorated_function

View file

@ -1,34 +0,0 @@
import datetime
import logging
import logging.handlers
import os
import pkgutil
import api
NOTSET = 0
DEBUG = 10
INFO = 20
WARNING = 30
ERROR = 40
CRITICAL = 50
def initialize_logs():
def create_logger(name):
new_logger = logging.getLogger(name)
new_logger.setLevel(logging.INFO)
base = os.path.dirname(__file__).strip("api")
log_path = os.path.join(base, "logs")
if not os.path.exists(log_path):
os.mkdir(log_path)
log_handler = logging.handlers.RotatingFileHandler(os.path.join(log_path, name + ".log"))
new_logger.addHandler(log_handler)
for importer, modname, ispkg in pkgutil.walk_packages(path="../api"):
create_logger(modname)
def log(logname, message, level=INFO):
logger = logging.getLogger(logname)
message = "[%s] %s" % (datetime.datetime.now().strftime("%m/%d/%Y %X"), message)
logger.log(level, message)

View file

@ -1,212 +0,0 @@
from flask.ext.sqlalchemy import SQLAlchemy
import time
import traceback
import utils
import cPickle as pickle
db = SQLAlchemy()
class Users(db.Model):
uid = db.Column(db.Integer, unique=True, primary_key=True)
tid = db.Column(db.Integer)
name = db.Column(db.String(64))
username = db.Column(db.String(64), unique=True)
username_lower = db.Column(db.String(64), unique=True)
email = db.Column(db.String(64), unique=True)
password = db.Column(db.String(128))
admin = db.Column(db.Boolean)
utype = db.Column(db.Integer)
tid = db.Column(db.Integer)
registertime = db.Column(db.Integer)
reset_token = db.Column(db.String(64))
def __init__(self, name, username, email, password, utype=1):
self.name = name
self.username = username
self.username_lower = username.lower()
self.email = email.lower()
self.password = utils.hash_password(password)
self.utype = utype
self.admin = False
self.registertime = int(time.time())
def get_invitations(self):
invitations = db.session.query(TeamInvitations).filter_by(rtype=0, toid=self.uid).all()
result = [ ]
for inv in invitations:
team = db.session.query(Teams).filter_by(tid=inv.frid).first()
result.append({
"team": team.teamname,
"tid": team.tid
})
return result
class Teams(db.Model):
tid = db.Column(db.Integer, primary_key=True)
teamname = db.Column(db.String(64), unique=True)
teamname_lower = db.Column(db.String(64), unique=True)
school = db.Column(db.Text)
owner = db.Column(db.Integer)
observer = db.Column(db.Boolean)
def __init__(self, teamname, school, owner, observer):
self.teamname = teamname
self.teamname_lower = teamname.lower()
self.school = school
self.owner = owner
self.observer = observer
def get_members(self):
members = [ ]
for member in Users.query.filter_by(tid=self.tid).all():
members.append({
"username": member.username,
"name": member.name,
"captain": member.uid == self.owner,
"type": member.utype,
"admin": member.admin == True,
"observer": member.utype == 3
})
return members
def points(self):
""" TODO: Implement scoring with Bonus Points """
return 0
def place(self, ranked=True):
# score = db.func.sum(Problems.value).label("score")
# quickest = db.func.max(Solves.date).label("quickest")
# teams = db.session.query(Solves.tid).join(Teams).join(Problems).filter().group_by(Solves.tid).order_by(score.desc(), quickest).all()
teams = [ self.tid ]
try:
i = teams.index((self.tid,)) + 1
k = i % 10
return (i, "%d%s" % (i, "tsnrhtdd"[(i / 10 % 10 != 1) * (k < 4) * k::4]))
except ValueError:
return (-1, "--")
def get_invitation_requests(self, frid=None):
if frid is not None:
req = db.session.query(TeamInvitations).filter_by(rtype=1, frid=frid, toid=self.tid).first()
if req is None:
return None
else:
user = db.session.query(Users).filter_by(uid=req.frid).first()
return { "username": user.username, "name": user.name, "uid": user.uid }
result = [ ]
requests = db.session.query(TeamInvitations).filter_by(rtype=1, toid=self.tid).all()
for req in requests:
user = db.session.query(Users).filter_by(uid=req.frid).first()
result.append({
"username": user.username,
"name": user.name,
"uid": user.uid
})
return result
def get_pending_invitations(self, toid=None):
if toid is not None:
invitation = db.session.query(TeamInvitations).filter_by(rtype=0, frid=self.tid, toid=toid).first()
if invitation is None:
return None
else:
user = db.session.query(Users).filter_by(uid=invitation.toid).first()
return { "username": user.username, "name": user.name, "uid": user.uid }
result = [ ]
invitations = db.session.query(TeamInvitations).filter_by(rtype=0, frid=self.tid).all()
for invitation in invitations:
user = db.session.query(Users).filter_by(uid=invitation.toid).first()
result.append({
"username": user.username,
"name": user.name,
"uid": user.uid
})
return result
def is_observer(self):
members = self.get_members()
for member in members:
if member["observer"] == True or member["admin"] == True:
return True
return False
class Problems(db.Model):
pid = db.Column(db.String(128), primary_key=True, autoincrement=False)
title = db.Column(db.String(128))
category = db.Column(db.String(128))
description = db.Column(db.Text)
value = db.Column(db.Integer)
hint = db.Column(db.Text)
autogen = db.Column(db.Boolean)
bonus = db.Column(db.Integer)
threshold = db.Column(db.Integer)
weightmap = db.Column(db.PickleType)
def __init__(self, pid, title, category, description, value, hint="", autogen=False, bonus=0, threshold=0, weightmap={}):
self.pid = pid
self.title = title
self.category = category
self.description = description
self.value = value
self.hint = hint
self.autogen = autogen
self.bonus = bonus
self.threshold = threshold
self.weightmap = weightmap
class Files(db.Model):
fid = db.Column(db.Integer, primary_key=True)
pid = db.Column(db.Integer)
location = db.Column(db.Text)
def __init__(self, pid, location):
self.pid = pid
self.location = location
class Solves(db.Model):
__table_args__ = (db.UniqueConstraint("pid", "tid"), {})
sid = db.Column(db.Integer, primary_key=True)
pid = db.Column(db.Integer)
tid = db.Column(db.Integer)
date = db.Column(db.Integer, default=utils.get_time_since_epoch())
correct = db.Column(db.Boolean)
flag = db.Column(db.Text)
def __init__(self, pid, tid, flag, correct):
self.pid = pid
self.tid = tid
self.flag = flag
self.correct = correct
class LoginTokens(db.Model):
sid = db.Column(db.String(64), unique=True, primary_key=True)
uid = db.Column(db.Integer)
username = db.Column(db.String(32))
active = db.Column(db.Boolean)
issued = db.Column(db.Integer)
expiry = db.Column(db.Integer)
ua = db.Column(db.String(128))
ip = db.Column(db.String(16))
def __init__(self, uid, username, expiry=int(time.time()), active=True, ua=None, ip=None):
self.sid = utils.generate_string()
self.uid = uid
self.username = username
self.issued = int(time.time())
self.expiry = expiry
self.active = active
self.ua = ua
self.ip = ip
class TeamInvitations(db.Model):
rid = db.Column(db.Integer, primary_key=True)
rtype = db.Column(db.Integer)
frid = db.Column(db.Integer)
toid = db.Column(db.Integer)
date = db.Column(db.Integer, default=utils.get_time_since_epoch())
def __init__(self, rtype, frid, toid):
self.rtype = rtype
self.frid = frid
self.toid = toid

View file

@ -1,163 +0,0 @@
import hashlib
import logger
import os
from flask import Blueprint, jsonify, session, request
from flask import current_app as app
from werkzeug import secure_filename
from models import db, Files, Problems, Solves, Teams
from decorators import admins_only, api_wrapper, login_required, InternalException, WebException
blueprint = Blueprint("problem", __name__)
@blueprint.route("/add", methods=["POST"])
@admins_only
@api_wrapper
def problem_add():
name = request.form["name"]
category = request.form["category"]
description = request.form["description"]
hint = request.form["problem-hint"]
flag = request.form["flag"]
value = request.form["value"]
name_exists = Problems.query.filter_by(name=name).first()
if name_exists:
raise WebException("Problem name already taken.")
problem = Problems(name, category, description, hint, flag, value)
db.session.add(problem)
db.session.commit()
files = request.files.getlist("files[]")
for _file in files:
filename = secure_filename(_file.filename)
if len(filename) == 0:
continue
file_path = os.path.join(app.config["UPLOAD_FOLDER"], filename)
_file.save(file_path)
db_file = Files(problem.pid, "/".join(file_path.split("/")[2:]))
db.session.add(db_file)
db.session.commit()
return { "success": 1, "message": "Success!" }
@blueprint.route("/delete", methods=["POST"])
@admins_only
@api_wrapper
def problem_delete():
pid = request.form["pid"]
problem = Problems.query.filter_by(pid=pid).first()
if problem:
Solves.query.filter_by(pid=pid).delete()
Problems.query.filter_by(pid=pid).delete()
db.session.commit()
return { "success": 1, "message": "Success!" }
raise WebException("Problem does not exist!")
@blueprint.route("/update", methods=["POST"])
@admins_only
@api_wrapper
def problem_update():
pid = request.form["pid"]
name = request.form["name"]
category = request.form["category"]
description = request.form["description"]
hint = request.form["hint"]
flag = request.form["flag"]
disabled = request.form.get("disabled", 0)
value = request.form["value"]
problem = Problems.query.filter_by(pid=pid).first()
if problem:
problem.name = name
problem.category = category
problem.description = description
problem.hint = hint
problem.flag = flag
problem.disabled = disabled
problem.value = value
db.session.add(problem)
db.session.commit()
return { "success": 1, "message": "Success!" }
raise WebException("Problem does not exist!")
@blueprint.route("/submit", methods=["POST"])
@api_wrapper
@login_required
def problem_submit():
pid = request.form["pid"]
flag = request.form["flag"]
tid = session["tid"]
problem = Problems.query.filter_by(pid=pid).first()
team = Teams.query.filter_by(tid=tid).first()
if problem:
if flag == problem.flag:
solve = Solves(pid, tid)
team.score += problem.value
problem.solves += 1
db.session.add(solve)
db.session.add(team)
db.session.add(problem)
db.session.commit()
logger.log(__name__, logger.WARNING, "%s has solved %s by submitting %s" % (team.name, problem.name, flag))
return { "success": 1, "message": "Correct!" }
else:
logger.log(__name__, logger.WARNING, "%s has incorrectly submitted %s to %s" % (team.name, flag, problem.name))
raise WebException("Incorrect.")
else:
raise WebException("Problem does not exist!")
@blueprint.route("/data", methods=["POST"])
#@api_wrapper # Disable atm due to json serialization issues: will fix
@login_required
def problem_data():
problems = Problems.query.add_columns("pid", "name", "category", "description", "hint", "value", "solves").order_by(Problems.value).filter_by(disabled=False).all()
jason = []
for problem in problems:
problem_files = [ str(_file.location) for _file in Files.query.filter_by(pid=int(problem.pid)).all() ]
jason.append({"pid": problem[1], "name": problem[2] ,"category": problem[3], "description": problem[4], "hint": problem[5], "value": problem[6], "solves": problem[7], "files": problem_files})
return jsonify(data=jason)
def insert_problem(data, force=False):
with app.app_context():
if len(list(get_problem(pid=data["pid"]).all())) > 0:
if force == True:
_problem = Problems.query.filter_by(pid=data["pid"]).first()
db.session.delete(_problem)
db.session.commit()
else:
raise InternalException("Problem already exists.")
insert = Problems(data["pid"], data["title"], data["category"], data["description"], data["value"])
if "hint" in data: insert.hint = data["hint"]
if "autogen" in data: insert.autogen = data["autogen"]
if "bonus" in data: insert.bonus = data["bonus"]
if "threshold" in data: insert.threshold = data["threshold"]
if "weightmap" in data: insert.weightmap = data["weightmap"]
db.session.add(insert)
db.session.commit()
return True
def get_problem(title=None, pid=None):
match = {}
if title != None:
match.update({ "title": title })
elif pid != None:
match.update({ "pid": pid })
with app.app_context():
result = Problems.query.filter_by(**match)
return result

View file

@ -1,24 +0,0 @@
import api
import re
from voluptuous import Required, Length, Schema, Invalid, MultipleInvalid
from decorators import WebException
def check(*callback_tuples):
def v(value):
for callbacks, msg in callback_tuples:
for callback in callbacks:
try:
result = callback(value)
if not result and type(result) == bool:
raise Invalid(msg)
except Exception:
raise WebException(msg)
return value
return v
def verify_to_schema(schema, data):
try:
schema(data)
except MultipleInvalid as error:
raise WebException(str(error))

View file

@ -1,25 +0,0 @@
from flask import Blueprint, request, session
from flask import current_app as app
from decorators import api_wrapper
import team
blueprint = Blueprint("stats", __name__)
@blueprint.route("/scoreboard")
@api_wrapper
def all_teams_stats():
teams = team.get_team().all()
result = [ ]
count = 0
for _team in teams:
count += 1
result.append({
"rank": count,
"teamname": _team.teamname,
"tid": _team.tid,
"school": _team.school,
"points": _team.points()
})
return { "success": 1, "scoreboard": result }

View file

@ -1,329 +0,0 @@
from flask import Blueprint, request, session
from flask import current_app as app
from voluptuous import Schema, Length, Required
from models import db, Teams, Users, TeamInvitations
from decorators import api_wrapper, login_required, WebException
from schemas import verify_to_schema, check
import user
import utils
blueprint = Blueprint("team", __name__)
###############
# TEAM ROUTES #
###############
@blueprint.route("/create", methods=["POST"])
@api_wrapper
@login_required
def team_create():
params = utils.flat_multi(request.form)
_user = user.get_user().first()
if (_user.tid is not None and _user.tid >= 0) or get_team(owner=_user.uid).first() is not None:
raise WebException("You're already in a team!")
verify_to_schema(TeamSchema, params)
teamname = params.get("teamname")
school = params.get("school")
team = Teams(teamname, school, _user.uid, _user.utype != 1)
tid = team.tid
with app.app_context():
db.session.add(team)
db.session.commit()
Users.query.filter_by(uid=_user.uid).update({ "tid": team.tid })
db.session.commit()
db.session.close()
session["tid"] = team.tid
return { "success": 1, "message": "Success!" }
@blueprint.route("/delete", methods=["POST"])
@api_wrapper
@login_required
def team_delete():
username = session["username"]
tid = session["tid"]
team = Teams.query.filter_by(tid=tid).first()
usr = Users.query.filter_by(username=username).first()
owner = team.owner
if usr.uid == owner or usr.admin:
with app.app_context():
for member in Users.query.filter_by(tid=tid).all():
member.tid = -1
db.session.add(member)
db.session.delete(team)
db.session.commit()
db.session.close()
session.pop("tid")
return { "success": 1, "message": "Success!" }
else:
raise WebException("Not authorized.")
@blueprint.route("/remove_member", methods=["POST"])
@api_wrapper
@login_required
def team_remove_member():
username = session["username"]
tid = session["tid"]
team = Teams.query.filter_by(tid=tid).first()
usr = Users.query.filter_by(username=username).first()
owner = team.owner
if usr.uid == owner or usr.admin:
params = utils.flat_multi(request.form)
user_to_remove = Users.query.filter_by(username=params.get("user"))
user_to_remove.tid = -1
with app.app_context():
db.session.add(user_to_remove)
db.session.commit()
db.session.close()
return { "success": 1, "message": "Success!" }
else:
raise WebException("Not authorized.")
@blueprint.route("/invite", methods=["POST"])
@api_wrapper
@login_required
def team_invite():
params = utils.flat_multi(request.form)
_user = user.get_user().first()
if not user.in_team(_user):
raise WebException("You must be in a team!")
_team = get_team(tid=_user.tid).first()
if _user.uid != _team.owner:
raise WebException("You must be the captain of your team to invite members!")
new_member = params.get("new_member")
if new_member is None:
raise WebException("Please provide a username!")
_user2 = user.get_user(username_lower=new_member.lower()).first()
if _user2 is None:
raise WebException("User doesn't exist!")
if _user2.tid > 0:
raise WebException("This user is already a part of a team!")
if _team.get_pending_invitations(toid=_user2.uid) is not None:
raise WebException("You've already invited this member!")
req = TeamInvitations(0, _team.tid, _user2.uid)
with app.app_context():
db.session.add(req)
db.session.commit()
db.session.close()
return { "success": 1, "message": "Success!" }
@blueprint.route("/invite/rescind", methods=["POST"])
@api_wrapper
@login_required
def team_invite_rescind():
params = utils.flat_multi(request.form)
_user = user.get_user().first()
if not user.in_team(_user):
raise WebException("You must be in a team!")
_team = get_team(tid=_user.tid).first()
if _user.uid != _team.owner:
raise WebException("You must be the captain of your team to rescind invitations!")
uid = params.get("uid")
if uid is None:
raise WebException("Please provide a user.")
invitation = TeamInvitations.query.filter_by(rtype=0, frid=_team.tid, toid=uid).first()
if invitation is None:
raise WebException("Invitation doesn't exist.")
with app.app_context():
db.session.delete(invitation)
db.session.commit()
db.session.close()
return { "success": 1, "message": "Success!" }
@blueprint.route("/invite/request", methods=["POST"])
@api_wrapper
@login_required
def team_invite_request():
params = utils.flat_multi(request.form)
_user = user.get_user().first()
if user.in_team(_user):
raise WebException("You're already in a team!")
tid = params.get("tid")
_team = get_team(tid=tid).first()
if _team is None:
raise WebException("Team not found.")
if _team.get_invitation_requests(frid=_user.uid) is not None:
raise WebException("You've already requested to join this team!")
req = TeamInvitations(1, _user.uid, _team.tid)
with app.app_context():
db.session.add(req)
db.session.commit()
db.session.close()
return { "success": 1, "message": "Success!" }
@blueprint.route("/invite/accept", methods=["POST"])
@api_wrapper
def team_accept_invite():
params = utils.flat_multi(request.form)
_user = user.get_user().first()
if user.in_team(_user):
raise WebException("You're already in a team!")
tid = params.get("tid")
_team = get_team(tid=tid).first()
if _team is None:
raise WebException("Team not found.")
if len(_team.get_members()) >= 5:
raise WebException("This team is full.")
invitation = TeamInvitations.query.filter_by(rtype=0, frid=tid, toid=_user.uid).first()
if invitation is None:
raise WebException("Invitation doesn't exist.")
with app.app_context():
_user = Users.query.filter_by(uid=_user.uid).first()
_user.tid = tid
db.session.delete(invitation)
invitation2 = TeamInvitations.query.filter_by(rtype=1, frid=_user.uid, toid=tid).first()
if invitation2 is not None:
db.session.delete(invitation2)
db.session.commit()
db.session.close()
return { "success": 1, "message": "Success!" }
@blueprint.route("/invite/request/accept", methods=["POST"])
@api_wrapper
def team_accept_invite_request():
params = utils.flat_multi(request.form)
_user = user.get_user().first()
if not user.in_team(_user):
raise WebException("You must be in a team!")
_team = get_team(tid=_user.tid).first()
tid = _team.tid
if _user.uid != _team.owner:
raise WebException("You must be the captain of your team to rescind invitations!")
if len(_team.get_members()) >= 5:
raise WebException("Your team is full.")
uid = params.get("uid")
_user2 = user.get_user(uid=uid).first()
if user.in_team(_user2):
raise WebException("This user is already in a team!")
invitation = TeamInvitations.query.filter_by(rtype=1, frid=_user2.uid, toid=tid).first()
if invitation is None:
raise WebException("Invitation doesn't exist.")
with app.app_context():
_user2 = Users.query.filter_by(uid=_user2.uid).first()
_user2.tid = tid
db.session.delete(invitation)
invitation2 = TeamInvitations.query.filter_by(rtype=0, frid=tid, toid=_user2.uid).first()
if invitation2 is not None:
db.session.delete(invitation2)
db.session.commit()
db.session.close()
return { "success": 1, "message": "Success!" }
@blueprint.route("/info", methods=["GET"])
@api_wrapper
def team_info():
logged_in = user.is_logged_in()
in_team = False
owner = False
_user = None
teamdata = { }
search = { }
teamname = utils.flat_multi(request.args).get("teamname")
if teamname:
search.update({ "teamname_lower": teamname.lower() })
if logged_in:
_user = user.get_user().first()
if user.in_team(_user):
if "teamname_lower" not in search:
search.update({ "tid": _user.tid })
in_team = True
if bool(search) != False:
team = get_team(**search).first()
teamdata = get_team_info(**search)
if logged_in:
in_team = teamdata["tid"] == _user.tid
owner = teamdata["captain"] == _user.uid
teamdata["in_team"] = in_team
if in_team:
teamdata["is_owner"] = owner
if owner:
teamdata["pending_invitations"] = team.get_pending_invitations()
teamdata["invitation_requests"] = team.get_invitation_requests()
else:
if logged_in:
teamdata["invited"] = team.get_pending_invitations(toid=_user.uid) is not None
teamdata["requested"] = team.get_invitation_requests(frid=_user.uid) is not None
else:
if logged_in:
teamdata["invitations"] = _user.get_invitations()
return { "success": 1, "team": teamdata }
##################
# TEAM FUNCTIONS #
##################
__check_teamname = lambda teamname: get_team(teamname_lower=teamname.lower()).first() is None
TeamSchema = Schema({
Required("teamname"): check(
([str, Length(min=4, max=32)], "Your teamname should be between 4 and 32 characters long."),
([utils.__check_ascii], "Please only use ASCII characters in your teamname."),
([__check_teamname], "This teamname is taken, did you forget your password?")
),
Required("school"): check(
([str, Length(min=4, max=40)], "Your school name should be between 4 and 40 characters long."),
([utils.__check_ascii], "Please only use ASCII characters in your school name."),
),
}, extra=True)
def get_team_info(tid=None, teamname=None, teamname_lower=None, owner=None):
team = get_team(tid=tid, teamname=teamname, teamname_lower=teamname_lower, owner=owner).first()
if team is None:
raise WebException("Team not found.")
place_number, place = team.place()
result = {
"tid": team.tid,
"teamname": team.teamname,
"school": team.school,
"place": place,
"place_number": place_number,
"points": team.points(),
"members": team.get_members(),
"captain": team.owner,
"observer": team.is_observer()
}
return result
def get_team(tid=None, teamname=None, teamname_lower=None, owner=None):
match = {}
if teamname != None:
match.update({ "teamname": teamname })
elif teamname_lower != None:
match.update({ "teamname_lower": teamname_lower })
elif tid != None:
match.update({ "tid": tid })
elif owner != None:
match.update({ "owner": owner })
elif user.is_logged_in():
_user = user.get_user().first()
if _user.tid is not None:
match.update({ "tid": _user.tid })
with app.app_context():
result = Teams.query.filter_by(**match)
return result

View file

@ -1,334 +0,0 @@
from flask import Blueprint, make_response, session, request, redirect, url_for, send_file, abort
from werkzeug import secure_filename
from flask import current_app as app
from voluptuous import Schema, Length, Required
from models import db, LoginTokens, Users
from decorators import api_wrapper, WebException
from schemas import verify_to_schema, check
import datetime
import logger, logging
import os
import re
import requests
import team
import utils
from PIL import Image
###############
# USER ROUTES #
###############
blueprint = Blueprint("user", __name__)
@blueprint.route("/forgot", methods=["POST"])
@blueprint.route("/forgot/<token>", methods=["GET", "POST"])
@api_wrapper
def user_forgot_password(token=None):
params = utils.flat_multi(request.form)
if token is not None:
user = get_user(reset_token=token).first()
if user is None:
raise WebException("Invalid reset token.")
# We are viewing the actual reset form
if request.method == "GET":
return { "success": 1, "message": ""}
# Submission of actual reset form
if request.method == "POST":
password = params.get("password")
confirm_password = params.get("confirm_password")
if password != confirm_password:
raise WebException("Passwords do not match.")
else:
user.password = utils.hash_password(password)
user.reset_token = None
current_session = db.session.object_session(user)
current_session.add(user)
current_session.commit()
return { "success": 1, "message": "Success!" }
else:
email = params.get("email").lower()
user = get_user(email=email).first()
if user is None:
raise WebException("User with that email does not exist.")
token = utils.generate_string(length=64)
user.reset_token = token
current_session = db.session.object_session(user)
current_session.add(user)
current_session.commit()
reset_link = "%s/forgot/%s" % ("127.0.0.1:8000", token)
subject = "EasyCTF password reset"
body = """%s,\n\nA request to reset your EasyCTF password has been made. If you did not request this password reset, you may safely ignore this email and delete it.\n\nYou may reset your password by clicking this link or pasting it to your browser.\n\n%s\n\nThis link can only be used once, and will lead you to a page where you can reset your password.\n\nGood luck!\n\n- The EasyCTF Team""" % (user.username, reset_link)
response = utils.send_email(email, subject, body).json()
if "Queued" in response["message"]:
return { "success": 1, "message": "Email sent to %s" % email }
else:
raise WebException(response["message"])
@blueprint.route("/register", methods=["POST"])
@api_wrapper
def user_register():
params = utils.flat_multi(request.form)
name = params.get("name")
email = params.get("email")
username = params.get("username")
password = params.get("password")
password_confirm = params.get("password_confirm")
utype = int(params.get("type"))
if password != password_confirm:
raise WebException("Passwords do not match.")
verify_to_schema(UserSchema, params)
user = Users(name, username, email, password, utype)
with app.app_context():
db.session.add(user)
db.session.commit()
utils.generate_identicon(email, user.uid)
logger.log(__name__, "%s registered with %s" % (name.encode("utf-8"), email.encode("utf-8")))
login_user(username, password)
return { "success": 1, "message": "Success!" }
@blueprint.route("/logout", methods=["GET"])
@api_wrapper
def user_logout():
sid = session["sid"]
username = session["username"]
with app.app_context():
expired = LoginTokens.query.filter_by(username=username).all()
for expired_token in expired: db.session.delete(expired_token)
db.session.commit()
session.clear()
@blueprint.route("/login", methods=["POST"])
@api_wrapper
def user_login():
params = utils.flat_multi(request.form)
username = params.get("username")
password = params.get("password")
result = login_user(username, password)
if result != True:
raise WebException("Please check if your username/password are correct.")
return { "success": 1, "message": "Success!" }
@blueprint.route("/status", methods=["GET"])
@api_wrapper
def user_status():
logged_in = is_logged_in()
result = {
"success": 1,
"logged_in": logged_in,
"admin": is_admin(),
"competition": is_admin(),
"in_team": in_team(get_user()),
"username": session["username"] if logged_in else "",
}
if logged_in:
result["has_team"] = in_team(get_user().first())
return result
@blueprint.route("/info", methods=["GET"])
@api_wrapper
def user_info():
logged_in = is_logged_in()
username = utils.flat_multi(request.args).get("username")
if username is None:
if logged_in:
username = session["username"]
if username is None:
raise WebException("No user specified.")
me = False if not("username" in session) else username.lower() == session["username"].lower()
user = get_user(username_lower=username.lower()).first()
if user is None:
raise WebException("User not found.")
show_email = me if logged_in else False
user_in_team = in_team(user)
userdata = {
"user_found": True,
"name": user.name,
"username": user.username,
"type": ["Student", "Instructor", "Observer"][user.utype - 1],
"admin": user.admin,
"registertime": datetime.datetime.fromtimestamp(user.registertime).isoformat() + "Z",
"me": me,
"show_email": show_email,
"in_team": user_in_team,
"uid": user.uid
}
if show_email:
userdata["email"] = user.email
if user_in_team:
userdata["team"] = team.get_team_info(tid=user.tid)
if me and not(user_in_team):
invitations = user.get_invitations()
userdata["invitations"] = invitations
return { "success": 1, "user": userdata }
@blueprint.route("/avatar/<uid>", methods=["GET"])
def user_avatar(uid):
uid = int(uid)
try:
return send_file("pfp/%d.png" % uid, mimetype="image/png")
except:
user = get_user(uid=uid).first()
if user is not None:
utils.generate_identicon(user.email, user.uid)
return send_file("pfp/%d.png" % uid, mimetype="image/png")
return abort(404)
@blueprint.route("/avatar/upload", methods=["POST"])
@api_wrapper
def user_avatar_upload():
logged_in = is_logged_in()
if not logged_in:
raise WebException("You're not logged in.")
_user = get_user().first()
f = request.files["file"]
if f is None:
raise WebException("Please upload something.")
fname = "/tmp/" + secure_filename(utils.generate_string())
f.save(fname)
try:
pfp = "pfp/%d.png" % _user.uid
os.remove(pfp)
im = Image.open(fname)
im = im.resize((256, 256), Image.ANTIALIAS)
im.save(open(pfp, "w"), "PNG")
return { "success": 1, "message": "Uploaded!" }
except Exception, e:
raise WebException(str(e))
@blueprint.route("/avatar/remove", methods=["POST"])
@api_wrapper
def user_avatar_remove():
logged_in = is_logged_in()
if not logged_in:
raise WebException("You're not logged in.")
_user = get_user().first()
try:
pfp = "pfp/%d.png" % _user.uid
os.remove(pfp)
return { "success": 1, "message": "Removed!" }
except Exception, e:
raise WebException(str(e))
##################
# USER FUNCTIONS #
##################
__check_username = lambda username: get_user(username_lower=username.lower()).first() is None
__check_email = lambda email: get_user(email=email.lower()).first() is None
UserSchema = Schema({
Required("email"): check(
([str, Length(min=4, max=128)], "Your email should be between 4 and 128 characters long."),
([__check_email], "Someone already registered this email."),
([utils.__check_email_format], "Please enter a legit email.")
),
Required("name"): check(
([str, Length(min=4, max=128)], "Your name should be between 4 and 128 characters long.")
),
Required("username"): check(
([str, Length(min=4, max=32)], "Your username should be between 4 and 32 characters long."),
([utils.__check_alphanumeric], "Please only use alphanumeric characters in your username."),
([__check_username], "This username is taken, did you forget your password?")
),
Required("password"): check(
([str, Length(min=4, max=64)], "Your password should be between 4 and 64 characters long."),
([utils.__check_ascii], "Please only use ASCII characters in your password."),
),
Required("type"): check(
([str, lambda x: x.isdigit()], "Please use the online form.")
),
"notify": str
}, extra=True)
def login_user(username, password):
user = get_user(username_lower=username.lower()).first()
if user is None: return False
correct = utils.check_password(user.password, password)
if not correct: return False
useragent = request.headers.get("User-Agent")
ip = request.remote_addr
with app.app_context():
expired = LoginTokens.query.filter_by(username=username).all()
for expired_token in expired: db.session.delete(expired_token)
token = LoginTokens(user.uid, user.username, ua=useragent, ip=ip)
db.session.add(token)
db.session.commit()
session["sid"] = token.sid
session["username"] = token.username
session["admin"] = user.admin == True
if user.tid is not None and user.tid >= 0:
session["tid"] = user.tid
return True
def is_logged_in():
if not("sid" in session and "username" in session): return False
sid = session["sid"]
username = session["username"]
token = LoginTokens.query.filter_by(sid=sid).first()
if token is None: return False
useragent = request.headers.get("User-Agent")
ip = request.remote_addr
if token.username != username: return False
if token.ua != useragent: return False
return True
def get_user(username=None, username_lower=None, email=None, uid=None, reset_token=None):
match = {}
if username != None:
match.update({ "username": username })
elif username_lower != None:
match.update({ "username_lower": username_lower })
elif uid != None:
match.update({ "uid": uid })
elif email != None:
match.update({ "email": email })
elif is_logged_in():
match.update({ "username": session["username"] })
elif reset_token != None:
match.update({ "reset_token": reset_token })
with app.app_context():
result = Users.query.filter_by(**match)
return result
def is_admin():
return is_logged_in() and "admin" in session and session["admin"]
def in_team(user):
return hasattr(user, "tid") and user.tid >= 0
def validate_captcha(form):
if "captcha_response" not in form:
return False
captcha_response = form["captcha_response"]
data = {"secret": "6Lc4xhMTAAAAACFaG2NyuKoMdZQtSa_1LI76BCEu", "response": captcha_response}
response = requests.post("https://www.google.com/recaptcha/api/siteverify", data=data)
return response.json()["success"]

View file

@ -1,102 +0,0 @@
import datetime
import hashlib
import json
import random
import re
import requests
import string
import traceback
import unicodedata
from PIL import Image, ImageDraw
from flask import current_app as app
from functools import wraps
from werkzeug.security import generate_password_hash, check_password_hash
__check_email_format = lambda email: re.match(".+@.+\..{2,}", email) is not None
__check_ascii = lambda s: all(c in string.printable for c in s)
__check_alphanumeric = lambda s: all(c in string.digits + string.ascii_uppercase + string.ascii_lowercase for c in s)
def hash_password(s):
return generate_password_hash(s)
def check_password(hashed_password, try_password):
return check_password_hash(hashed_password, try_password)
def generate_string(length=32, alpha=string.hexdigits):
return "".join([random.choice(alpha) for x in range(length)])
def unix_time_millis(dt):
epoch = datetime.datetime.utcfromtimestamp(0)
return (dt - epoch).total_seconds() * 1000.0
def get_time_since_epoch():
return unix_time_millis(datetime.datetime.now())
def flat_multi(multidict):
flat = {}
for key, values in multidict.items():
value = values[0] if type(values) == list and len(values) == 1 else values
flat[key] = value.encode("utf-8")
return flat
def send_email(recipient, subject, body):
api_key = app.config["MG_API_KEY"]
data = {"from": "EasyCTF Administrator <%s>" % (app.config["ADMIN_EMAIL"]),
"to": recipient,
"subject": subject,
"text": body
}
auth = ("api", api_key)
return requests.post("https://api.mailgun.net/v3/%s/messages" % (app.config["MG_HOST"]), auth=auth, data=data)
def generate_identicon(email, filename):
email = email.strip().lower()
h = hashlib.sha1(email).hexdigest()
size = 256
margin = 0.08
baseMargin = int(size * margin)
cell = int((size - baseMargin * 2.0) / 5)
margin = int((size - cell * 5.0) / 2)
image = Image.new("RGB", (size, size))
draw = ImageDraw.Draw(image)
def hsl2rgb(h, s, b):
h *= 6
s1 = []
s *= b if b < 0.5 else 1-b
b += s
s1.append(b)
s1.append(b - h % 1 * s * 2)
s *= 2
b -= s
s1.append(b)
s1.append(b)
s1.append(b + h % 1 * s)
s1.append(b + s)
return [
s1[~~h % 6], s1[(h|16) % 6], s1[(h|8) % 6]
]
rgb = hsl2rgb(int(h[-7:], 16) & 0xfffffff, 0.5, 0.7)
bg = (255, 255, 255)
fg = (int(rgb[0] * 255), int(rgb[1] * 255), int(rgb[2] * 255))
# print fg, bg
draw.rectangle([(0, 0), (size, size)], fill=bg)
for i in range(15):
c = bg if int(h[i], 16) % 2 == 1 else fg
if i < 5:
draw.rectangle([(2*cell + margin, i*cell + margin), (3*cell + margin, (i+1)*cell + margin)], fill=c)
elif i < 10:
draw.rectangle([(1*cell + margin, (i-5)*cell + margin), (2*cell + margin, (i-4)*cell + margin)], fill=c)
draw.rectangle([(3*cell + margin, (i-5)*cell + margin), (4*cell + margin, (i-4)*cell + margin)], fill=c)
elif i < 15:
draw.rectangle([(0*cell + margin, (i-10)*cell + margin), (1*cell + margin, (i-9)*cell + margin)], fill=c)
draw.rectangle([(4*cell + margin, (i-10)*cell + margin), (5*cell + margin, (i-9)*cell + margin)], fill=c)
image.save(open("pfp/%s.png" % filename, "w"), "PNG")
return

View file

@ -1,120 +0,0 @@
#!/usr/bin/python
from argparse import ArgumentParser
from flask import Flask, send_file
app = Flask(__name__)
import api
import config
import json
import logging
import os
import traceback
from api.decorators import api_wrapper
app.config.from_object(config)
if not os.path.exists(app.config["UPLOAD_FOLDER"]):
os.makedirs(app.config["UPLOAD_FOLDER"])
if not os.path.exists("pfp"):
os.makedirs("pfp")
with app.app_context():
from api.models import db, Files, Teams, Problems, Solves, Users
db.init_app(app)
db.create_all()
app.db = db
app.secret_key = config.SECRET_KEY
app.register_blueprint(api.admin.blueprint, url_prefix="/api/admin")
app.register_blueprint(api.problem.blueprint, url_prefix="/api/problem")
app.register_blueprint(api.stats.blueprint, url_prefix="/api/stats")
app.register_blueprint(api.team.blueprint, url_prefix="/api/team")
app.register_blueprint(api.user.blueprint, url_prefix="/api/user")
api.logger.initialize_logs()
@app.route("/api")
@api_wrapper
def api_main():
return { "success": 1, "message": "The API is online." }
@app.errorhandler(404)
def page_not_found(e):
return send_file("../web/index.html")
def run(args=None):
with app.app_context():
try:
keyword_args = dict(args._get_kwargs())
app.debug = keyword_args["debug"] if "debug" in keyword_args else False
except: pass
app.run(host="0.0.0.0", port=8000)
def load_problems(args):
keyword_args = dict(args._get_kwargs())
force = keyword_args["force"] if "force" in keyword_args else False
if not os.path.exists(config.PROBLEM_DIR):
api.logger.log("api.problem.log", "Problems directory doesn't exist.")
return
for (dirpath, dirnames, filenames) in os.walk(config.PROBLEM_DIR):
if "problem.json" in filenames:
json_file = os.path.join(dirpath, "problem.json")
contents = open(json_file).read()
try:
data = json.loads(contents)
except ValueError as e:
api.logger.log("api.problem.log", "Invalid JSON format in file {filename} ({exception})".format(filename=json_file, exception=e))
continue
if not isinstance(data, dict):
api.logger.log("api.problem.log", "{filename} is not a dict.".format(filename=json_file))
continue
missing_keys = []
for key in ["pid", "title", "category", "value"]:
if key not in data:
missing_keys.append(key)
if len(missing_keys) > 0:
api.logger.log("api.problem.log", "{filename} is missing the following keys: {keys}".format(filename=json_file, keys=", ".join(missing_keys)))
continue
relative_path = os.path.relpath(dirpath, config.PROBLEM_DIR)
data["description"] = open(os.path.join(dirpath, "description.md"), "r").read()
api.logger.log("api.problem.log", "Found problem '{}'".format(data["title"]))
with app.app_context():
try:
api.problem.insert_problem(data, force=force)
except Exception as e:
api.logger.log("api.problem.log", "Problem '{}' was not added to the database. Error: {}".format(data["title"], e))
api.logger.log("api.problem.log", "{}".format(traceback.format_exc()))
api.logger.log("api.problem.log", "Finished.")
def main():
parser = ArgumentParser(description="EasyCTF Server Management")
subparser = parser.add_subparsers(help="Select one of the following actions.")
parser_problems = subparser.add_parser("problems", help="Manage problems.")
subparser_problems = parser_problems.add_subparsers(help="Select one of the following actions.")
parser_problems_load = subparser_problems.add_parser("load", help="Load all problems into database.")
parser_problems_load.add_argument("-f", "--force", action="store_true", help="Force overwrite problems.", default=False)
parser_problems_load.set_defaults(func=load_problems)
parser_run = subparser.add_parser("run", help="Run the server.")
parser_run.add_argument("-d", "--debug", action="store_true", help="Run the server in debug mode.", default=False)
parser_run.set_defaults(func=run)
args = parser.parse_args()
if "func" in args:
args.func(args)
else:
parser.print_help()
if __name__ == "__main__":
main()

View file

@ -1,27 +0,0 @@
import os
secret = open(".secret_key", "a+b")
contents = secret.read()
if not contents:
key = os.urandom(128)
secret.write(key)
secret.flush()
else:
key = contents
secret.close()
SECRET_KEY = key
SQLALCHEMY_DATABASE_URI = "mysql://root:i_hate_passwords@localhost/easyctf"
SQLALCHEMY_TRACK_MODIFICATIONS = False
UPLOAD_FOLDER = os.path.normpath("../web/files")
CTF_BEGIN = 0 # To be used later
CTF_END = 0 # To be used later
MG_HOST = ""
MG_API_KEY = ""
ADMIN_EMAIL = ""
PROBLEM_DIR = "../problems"

View file

@ -1,158 +0,0 @@
/*-- Chart --*/
.c3 svg {
font: 10px sans-serif; }
.c3 path, .c3 line {
fill: none;
stroke: #000; }
.c3 text {
-webkit-user-select: none;
-moz-user-select: none;
user-select: none; }
.c3-legend-item-tile, .c3-xgrid-focus, .c3-ygrid, .c3-event-rect, .c3-bars path {
shape-rendering: crispEdges; }
.c3-chart-arc path {
stroke: #fff; }
.c3-chart-arc text {
fill: #fff;
font-size: 13px; }
/*-- Axis --*/
/*-- Grid --*/
.c3-grid line {
stroke: #aaa; }
.c3-grid text {
fill: #aaa; }
.c3-xgrid, .c3-ygrid {
stroke-dasharray: 3 3; }
/*-- Text on Chart --*/
.c3-text.c3-empty {
fill: #808080;
font-size: 2em; }
/*-- Line --*/
.c3-line {
stroke-width: 1px; }
/*-- Point --*/
.c3-circle._expanded_ {
stroke-width: 1px;
stroke: white; }
.c3-selected-circle {
fill: white;
stroke-width: 2px; }
/*-- Bar --*/
.c3-bar {
stroke-width: 0; }
.c3-bar._expanded_ {
fill-opacity: 0.75; }
/*-- Focus --*/
.c3-target.c3-focused {
opacity: 1; }
.c3-target.c3-focused path.c3-line, .c3-target.c3-focused path.c3-step {
stroke-width: 2px; }
.c3-target.c3-defocused {
opacity: 0.3 !important; }
/*-- Region --*/
.c3-region {
fill: steelblue;
fill-opacity: 0.1; }
/*-- Brush --*/
.c3-brush .extent {
fill-opacity: 0.1; }
/*-- Select - Drag --*/
/*-- Legend --*/
.c3-legend-item {
font-size: 12px; }
.c3-legend-item-hidden {
opacity: 0.15; }
.c3-legend-background {
opacity: 0.75;
fill: white;
stroke: lightgray;
stroke-width: 1; }
/*-- Tooltip --*/
.c3-tooltip-container {
z-index: 10; }
.c3-tooltip {
border-collapse: collapse;
border-spacing: 0;
background-color: #fff;
empty-cells: show;
-webkit-box-shadow: 7px 7px 12px -9px #777777;
-moz-box-shadow: 7px 7px 12px -9px #777777;
box-shadow: 7px 7px 12px -9px #777777;
opacity: 0.9; }
.c3-tooltip tr {
border: 1px solid #CCC; }
.c3-tooltip th {
background-color: #aaa;
font-size: 14px;
padding: 2px 5px;
text-align: left;
color: #FFF; }
.c3-tooltip td {
font-size: 13px;
padding: 3px 6px;
background-color: #fff;
border-left: 1px dotted #999; }
.c3-tooltip td > span {
display: inline-block;
width: 10px;
height: 10px;
margin-right: 6px; }
.c3-tooltip td.value {
text-align: right; }
/*-- Area --*/
.c3-area {
stroke-width: 0;
opacity: 0.2; }
/*-- Arc --*/
.c3-chart-arcs-title {
dominant-baseline: middle;
font-size: 1.3em; }
.c3-chart-arcs .c3-chart-arcs-background {
fill: #e0e0e0;
stroke: none; }
.c3-chart-arcs .c3-chart-arcs-gauge-unit {
fill: #000;
font-size: 16px; }
.c3-chart-arcs .c3-chart-arcs-gauge-max {
fill: #777; }
.c3-chart-arcs .c3-chart-arcs-gauge-min {
fill: #777; }
.c3-chart-arc .c3-gauge-value {
fill: #000;
/* font-size: 28px !important;*/ }

View file

@ -1,31 +0,0 @@
@font-face {
font-family: "Proxima Nova";
src: url("/fonts/ProximaNova.woff2");
}
@font-face {
font-family: "Proxima Nova";
src: url("/fonts/ProximaNovaBold.woff2");
font-weight: bold;
}
body {
font-family: "Proxima Nova", Helvetica Neue, Helvetica, Arial, sans-serif;
}
.tab-content {
padding: 15px;
> .tab-pane {
display: none;
}
> .active {
display: block;
}
}
.NO_HOVER_UNDERLINE_DAMMIT:hover, .NO_HOVER_UNDERLINE_DAMMIT:focus, .NO_HOVER_UNDERLINE_DAMMIT:active {
text-decoration:none;
}
#avatar {
border: 1px solid rgb(221, 221, 221);
}

View file

@ -1,192 +0,0 @@
@import url(https://fonts.googleapis.com/css?family=Exo+2);
.heading1 {
font-size: 75px !important;
color: #62AC5B !important;
font-family: "Exo 2" !important;
}
.charts {
width:100%;
border:4px double black;
}
.charted {
width:5em;
height:5em;
}
#map {
height:45em;
background-image: url(../images/placeholdermap.jpg);
background-size: cover;
width:97%;
margin-right:auto;
margin-left:auto;
}
::-webkit-input-placeholder, :-moz-placeholder, ::-moz-placeholder, :-ms-input-placeholder {
color: #999999;
}
.navbar-default .navbar-nav>.open>a, .navbar-default .navbar-nav>.open>a:focus, .navbar-default .navbar-nav>.open>a:hover, .dropdown-menu>li>a:focus, .dropdown-menu>li>a:hover {
background-color:#0066CC;
}
#menubox {
border:none;
}
.pointvalue {
float:right;
}
.panel.panel-info {
margin: 2.5em;
}
.paragraph {
width:50em;
text-align: justify;
margin-left:auto;
margin-right:auto;
}
.prizes {
width:100%;
}
#hint {
background:#0080FF;
border-color:#0180FF;
}
#check {
border-color:#449D44;
}
.probfile {
display:inline;
color:#62AC5B;
margin-right:1em;
margin-left:1em;
}
.filelink, .filelink:hover {
text-decoration:none;
}
#style1 {
background-color: #0080FF;
margin: -0.1em;
}
#style1 .dropdown-menu {
background-color: #1a8dff;
}
#email, #password {
margin-bottom: 1em;
}
.style4 {
margin-left: 10em;
margin-right: 10em;
}
.icn {
position: relative;
bottom: 1px;
}
.style2 {
margin: 0px 0px 0px 0px;
padding: 0px 0px 0px 0px;
}
.style3 {
margin-top: 1em;
}
#question-text {
height: 4em;
}
#question-title {
background-color: #D9EDF7;
text-align: center;
padding: 0px 0px 0px 0px;
font-size: 1.2em;
}
.intro {
text-align: center;
}
.column {
max-width: 450px;
}
.mainbody {
background-color: #FAFAFA;
}
.ui.menu .item img.logo {
margin-right: 1.5em;
}
.main.container {
margin-top: 7em;
}
.wireframe {
margin-top: 2em;
}
.ui.footer.segment {
margin: 5em 0em 0em;
padding: 5em 0em;
}
.heading2, table {
color: #226C1B;
}
li {
transition-duration: 0.5s;
}
li:hover {
background-color: #0066cc;
}
tr {
transition-duration: 0.5s;
}
tr:hover {
background-color: #DDDDDD;
}
li a, .navbar-brand {
color: white !important;
}
#imp-files {
margin-right: auto;
padding-left: 2em;
}
@-webkit-keyframes fadeIn {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
@-moz-keyframes fadeIn {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
@keyframes fadeIn {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
.fade_in {
opacity: 0;
-webkit-animation: fadeIn ease-in-out 1;
-moz-animation: fadeIn ease-in-out 1;
animation: fadeIn ease-in-out 1;
-webkit-animation-fill-mode: forwards;
-moz-animation-fill-mode: forwards;
animation-fill-mode: forwards;
-webkit-animation-duration: 1s;
-moz-animation-duration: 1s;
animation-duration: 1s;
}
.modal {
text-align: center;
}
@media screen and (min-width: 768px) {
.modal:before {
display: inline-block;
vertical-align: middle;
content: " ";
height: 100%;
}
}
.modal-dialog {
display: inline-block;
text-align: left;
vertical-align: middle;
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 516 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 466 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 293 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 592 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 458 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 461 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 349 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 430 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 335 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 466 B

View file

@ -1,35 +0,0 @@
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="theme-color" content="#FA6900">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="description" content="EasyCTF is a national, online, high school hacking competition run by students. EasyCTF aims to open the door to computer science and cybersecurity for students all around the country.">
<meta name="keywords" content="ctf, capture the flag, competition, high school, hacking, students, teachers, computers, computer science, education, flag, capture, school, cryptography, web, forensics, steganography">
<meta name="subject" content="Computer Science Education">
<meta name="copyright" content="EasyCTF Team">
<meta name="language" content="EN">
<meta name="robots" content="Index, NoFollow">
<meta name="revised" content="Monday, December 21st, 2015, 21:00">
<meta name="abstract" content="Online capture the flag competition for high school students">
<meta name="topic" content="Computer Science Education">
<meta name="summary" content="Computer science education online hacking capture the flag competition for high school students">
<meta name="classification" content="Education">
<meta name="author" content="Thomas Gerot, Fox Wilson, Jared Zell, James Wang, Charles, Jacob Wood, Michael Zhang">
<meta name="designer" content="Thomas Gerot, Charles">
<meta name="reply-to" content="team@easyctf.com">
<meta name="owner" content="EasyCTF Team">
<meta name="url" content="http://easyctf.com">
<meta name="identifier-URL" content="http://easyctf.com">
<meta name="pagename" content="index.html">
<meta name="category" content="Education">
<meta name="coverage" content="worldwide">
<meta name="distribution" content="global">
<meta name="rating" content="general">
<meta name="revisit-after" content="3 days">
<meta name="subtitle" content="High School Hacking Competition">
<meta name="target" content="all">
<meta name="HandheldFriendly" content="True">
<meta name="MobileOptimized" content="320">
<meta name="date" content="Dec. 21, 2015">
<meta name="search_date" content="Dec. 21, 2015">
<meta name="medium" content="Online Competition">
<meta http-equiv="Cache-Control" content="no-cache">

View file

@ -1,96 +0,0 @@
<html ng-app="easyctf">
<head>
<base href="/">
<meta charset="utf-8">
<title>EasyCTF 2016</title>
<link href="css/c3.css" rel="stylesheet" type="text/css">
<script src="js/d3.v3.min.js" charset="utf-8"></script>
<script src="js/c3.min.js"></script>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css" integrity="sha384-1q8mTJOASx8j1Au+a5WDVnPi2lkFfwwEAa8hDDdjZlpLegxhjVME1fgjWPGmkzs7" crossorigin="anonymous">
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.3.0/css/font-awesome.min.css" integrity="sha384-yNuQMX46Gcak2eQsUzmBYgJ3eBeWYNKhnjyiBqLd1vvtE9kuMtgw6bjwN8J0JauQ" crossorigin="anonymous">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-select/1.9.3/css/bootstrap-select.css" integrity="sha384-X6BCz/5YN8CudtAN21w+htVJP5lJNqXP0VBfbGzrX7A/FhjpPIoMtiRRHxRYiKU6" crossorigin="anonymous">
<link rel="stylesheet" href="/css/easyctf.css" />
<script src="https://code.jquery.com/jquery-2.1.4.min.js" integrity="sha384-R4/ztc4ZlRqWjqIuvf6RX5yb/v90qNGx6fS48N0tRxiGkqveZETq72KgDVJCp2TC" crossorigin="anonymous"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/js/bootstrap.min.js" integrity="sha384-0mSbJDEHialfmuBBQP6A4Qrprq5OVfW37PRR3j5ELqxss1yVqOtnepnHVP9aJ7xS" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.4.8/angular.min.js" integrity="sha384-r1y8TJcloKTvouxnYsi4PJAx+nHNr90ibsEn3zznzDzWBN9X3o3kbHLSgcIPtzAp" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.5.0-rc.0/angular-route.min.js" integrity="sha384-9MZDoFf10trgrfsQOs9GJhf/mP/sh5weVp3FDSi8h/4TEaV6dloEDkpxGTaOmAs6" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-select/1.9.3/js/bootstrap-select.min.js" integrity="sha384-1qZEXZBmj54fSiiWT8bZQGEpCumJWDrAoEqMdg6N5bTTLCkU5RXoNeUsKWekRYob" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-timeago/1.4.3/jquery.timeago.min.js" integrity="sha384-Bap3DetwPgo4GEFvaIDVSIrz5G0mUAUsfCUcEsi+JrrNu7dyj3gBmuAG4hDIGg/4" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/smooth-scroll/7.1.1/js/smooth-scroll.min.js" integrity="sha384-bznoxhRX5dRiE60JhQSru8t7g2RPG9lwqvyut8sjFFWmsAlp+R38e7DiATv1YyIu" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-cookie/1.4.1/jquery.cookie.min.js" integrity="sha384-tSi+YsgNwyohDGfW/VhY51IK3RKAPYDcj1sNXJ16oRAyDP++K0NCzSCUW78EMFmf" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/ace/1.2.2/ace.js" integrity="sha384-niN+bRlaXMBoq/f5L4zKOEYGxuD0YRGBocto9LP2fB1UiMfxrymATRN4GqjVUt6J" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/epiceditor/0.2.2/js/epiceditor.min.js" integrity="sha384-jV5EM0s4BWbqIJxNwLbyMfgHMQWPkSn2oytRUMPSne2YaT5TAYcl7R0m1c1XsfAb" crossorigin="anonymous"></script>
<script src="/js/easyctf.js"></script>
<script src="/js/admin.js"></script>
</head>
<body ng-controller="mainController">
<div id="site-message" style="margin: 0"></div>
<nav class="navbar navbar-default">
<div class="container">
<div class="navbar-header">
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="main-navbar" aria-expanded="false">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="/">EasyCTF</a>
</div>
<div id="navbar" class="navbar-collapse collapse">
<ul class="nav navbar-nav">
<li><a href="/about">About</a></li>
<li><a href="/scoreboard">Scoreboard</a></li>
<li><a href="/learn">Learn</a></li>
</ul>
<ul class="nav navbar-nav navbar-right" ng-show="config.navbar['logged_in']==false">
<li><a href="/register">Register</a></li>
<li><a href="/login">Login</a></li>
</ul>
<ul class="nav navbar-nav navbar-right" ng-show="config.navbar['logged_in']==true">
<div ng-show="config.navbar['competition_started']==true">
</div>
<li><a href="/chat">Chat</a></li>
<li class="dropdown" ng-show="config.navbar['competition']==true">
<a href="javascript:void(0);" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">Competition <span class="caret"></span></a>
<ul class="dropdown-menu">
<li><a href="/problems">Problems</a></li>
<li><a href="/programming">Programming</a></li>
<li><a href="/shell">Shell</a></li>
</ul>
</li>
<li ng-show="config.navbar['competition']!=true&&config.navbar['in_team']!=true"><a href="/findteam">Find Team</a></li>
<li class="dropdown" ng-show="config.navbar['admin']==true">
<a href="javascript:void(0);" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">Admin <span class="caret"></span></a>
<ul class="dropdown-menu">
<li><a href="/admin/stats">Statistics</a></li>
<li><a href="/admin/problems">Problem Editor</a></li>
<li><a href="/admin/teams">Team Management</a></li>
<li role="separator" class="divider"></li>
<li><a href="/admin/settings">CTF Settings</a></li>
</ul>
</li>
<li class="dropdown">
<a href="javascript:void(0);" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">{{ config.navbar["username"] }} <span class="caret"></span></a>
<ul class="dropdown-menu">
<li><a href="/profile">Profile</a></li>
<li><a href="/team">Team</a></li>
<li><a href="/help">Help</a></li>
<li role="separator" class="divider"></li>
<li><a href="/settings">Settings</a></li>
<li><a href="/logout">Logout</a></li>
</ul>
</li>
</ul>
</div>
</div>
</nav>
<div id="mainContent" class="container">
<div ng-view></div>
</div>
</body>
</html>

View file

@ -1,5 +0,0 @@
$(document).ready(function() {
$(".panel-title > a[data-toggle=collapse]").click(function(e) {
e.preventDefault();
});
});

View file

@ -1,121 +0,0 @@
function render_problems() {
$.post("/api/admin/problem/data", {
}, function(data) {
data = data["data"];
for (var i = 0; i < data.length; i++) {
files = data[i]["files"];
var checked = "";
if (data[i]["disabled"]) {
checked = "checked";
}
problem =
`<div class="panel panel-info">
<form method="POST" onsubmit="return false;">
<input type="hidden" name="pid" value="` + data[i]["pid"] + `">
<div class="panel-heading">
<div class="row">
<div class="col-md-6">
<input type="text" name="name" placeholder="Name" autocomplete="on" class="form-control" value="` + data[i]["name"] + `">
</div>
<div class="col-md-6">
<input type="text" name="category" placeholder="Category" autocomplete="on" class="form-control" value="` + data[i]["category"] + `">
</div>
</div>
</div>
<div class="panel-body">
<textarea type="text" name="description" placeholder="Description" autocomplete="on" class="form-control">` + data[i]["description"] + `</textarea>
<br><br>
<div class="row">
<div class="col-md-6">
<input type="text" name="flag" placeholder="Flag" autocomplete="off" class="form-control" value="` + data[i]["flag"] + `">
</div>
<div class="col-md-6">
<input type="text" name="hint" placeholder="Hint" autocomplete="off" class="form-control" value="` + data[i]["hint"] + `">
</div>
</div>
<br>
<div class="row">
<input type="number" name="value" placeholder="Value" autocomplete="off" class="form-control-number" value="` + data[i]["value"] + `">
<label><input type="checkbox" name="disabled" value="1"` + checked + `>Disabled</label>
</div>
</div>
<div class="panel-footer">`
for (var j = 0; j < files.length; j++) {
file_name = files[j].split("/").pop();
problem +=
`<a href="` + files[j] + `" class="filelink" target="_blank">
<h4 class="probfile">` + file_name + `</h4>
</a>`
}
problem += `<br>
<div id="hint_` + data[i]["pid"] + `" style="display:none">` + data[i]["hint"] + `</div>
<div class="row" id="status_` + data[i]["pid"] + `"></div><br>
<input class="btn btn-success" type="submit" name="update" value="Update">
<input class="btn btn-danger" name="delete-modal" type="button" data-toggle="modal" data-target="#delete-modal" value="Delete">
</div></form></div>`
$("#problems").append(problem);
}
$("[name=update]").click(function(e) {
var problem = $(this).parents("form:first");
var pid = $("input[name=pid]", problem).val();
var name = $("input[name=name]", problem).val();
var description = $("textarea[name=description]", problem).val();
var hint = $("input[name=hint]", problem).val();
var category = $("input[name=category]", problem).val();
var value = $("input[name=value]", problem).val();
var flag = $("input[name=flag]", problem).val();
var disabled = $("input[name=disabled]", problem).prop("checked") ? 1 : 0;
update_problem(pid, name, category, description, hint, flag, disabled, value);
});
$("[name=delete-modal]").click(function(e) {
var problem = $(this).parents("form:first");
var pid = $("input[name=pid]", problem).val();
var div = $(this).closest("div.panel");
$("#delete").off().click(function(e) {
delete_problem(pid, div);
});
});
});
}
function update_problem(pid, name, category, description, hint, flag, disabled, value) {
$.post("/api/problem/update", {
pid: pid,
name: name,
category: category,
description: description,
hint: hint,
flag: flag,
disabled: disabled,
value: value
}, function(data) {
if (data.success == 1) {
display_message("status_" + pid, "success", data.message, function() {});
} else {
display_message("status_" + pid, "danger", data.message, function() {});
}
});
}
function delete_problem(pid, div) {
$.post("/api/problem/delete", {
pid: pid
}, function(data) {
if (data.success == 1) {
display_message("delete_status", "success", data.message, function() {
div.slideUp("normal", function() {
$(this).remove();
$("#delete-modal").modal("hide");
} );
});
} else {
display_message("delete_status", "warning", data.message, function() {});
}
});
}
$(function() {
render_problems();
});

5
web/js/c3.min.js vendored

File diff suppressed because one or more lines are too long

5
web/js/d3.v3.min.js vendored

File diff suppressed because one or more lines are too long

View file

@ -1,423 +0,0 @@
var app = angular.module("easyctf", [ "ngRoute" ]);
app.config(["$compileProvider", function($compileProvider) {
$compileProvider.aHrefSanitizationWhitelist(/^\s*(https?|ftp|mailto|file|javascript):/);
}]);
app.config(function($routeProvider, $locationProvider) {
$routeProvider.when("/", {
templateUrl: "pages/home.html",
controller: "mainController"
})
.when("/about", {
templateUrl: "pages/about.html",
controller: "mainController"
})
.when("/chat", {
templateUrl: "pages/chat.html",
controller: "mainController"
})
.when("/learn", {
templateUrl: "pages/learn.html",
controller: "mainController"
})
.when("/login", {
templateUrl: "pages/login.html",
controller: "mainController"
})
.when("/logout", {
templateUrl: "pages/blank.html",
controller: "logoutController"
})
.when("/profile", {
templateUrl: "pages/profile.html",
controller: "profileController"
})
.when("/profile/:username", {
templateUrl: "pages/profile.html",
controller: "profileController"
})
.when("/register", {
templateUrl: "pages/register.html",
controller: "mainController"
})
.when("/scoreboard", {
templateUrl: "pages/scoreboard.html",
controller: "scoreboardController"
})
.when("/settings", {
templateUrl: "pages/settings.html",
controller: "settingsController"
})
.when("/forgot", {
templateUrl: "pages/forgot.html",
controller: "resetController"
})
.when("/forgot/:token", {
templateUrl: "pages/forgot.html",
controller: "resetController"
})
.when("/team", {
templateUrl: "pages/team.html",
controller: "teamController"
})
.when("/team/:teamname", {
templateUrl: "pages/team.html",
controller: "teamController"
})
.when("/admin/problems", {
templateUrl: "pages/admin/problems.html",
controller: "adminProblemsController"
})
.otherwise({
templateUrl: "pages/404.html",
controller: "mainController"
});
$locationProvider.html5Mode(true);
});
function api_call(method, url, data, callback_success, callback_fail) {
if (method.toLowerCase() == "post") {
data["csrf_token"] = $.cookie("csrf_token");
}
$.ajax({
"type": method,
"datatype": "json",
"data": data,
"url": url,
"cache": false
}).done(callback_success).fail(callback_fail);
}
function permanent_message(containerId, alertType, message, callback) {
$("#" + containerId).html("<div class=\"alert alert-" + alertType + "\" style=\"margin:0;\">" + message + "</div>");
$("#" + containerId).hide().slideDown("fast", "swing");
};
function display_message(containerId, alertType, message, callback) {
$("#" + containerId).html("<div class=\"alert alert-" + alertType + "\">" + message + "</div>");
$("#" + containerId).hide().slideDown("fast", "swing", function() {
window.setTimeout(function () {
$("#" + containerId).slideUp("fast", "swing", callback);
}, message.length * 55);
});
};
app.controller("mainController", ["$scope", "$http", function($scope, $http) {
$scope.config = { navbar: { } };
$scope.timestamp = Date.now();
api_call("GET", "/api/user/status", {}, function(result) {
if (result["success"] == 1) {
delete result["success"];
$scope.config.navbar = result;
$scope.$emit("loginStatus");
} else {
$scope.config.navbar.logged_in = false;
}
$scope.$apply();
}, function() {
$scope.config.navbar.logged_in = false;
$scope.$apply();
permanent_message("site-message", "danger", "<div class='container'>The EasyCTF API server is currently down. We're working to fix this error right away. Follow <a href='http://twitter.com/easyctf' target='_blank'>@easyctf</a> for status updates.</div>");
});
}]);
app.controller("logoutController", function() {
api_call("GET", "/api/user/logout", {}, function(result) {
location.href = "/";
});
});
app.controller("profileController", ["$controller", "$scope", "$http", "$routeParams", function($controller, $scope, $http, $routeParams) {
var data = { };
if ("username" in $routeParams) data["username"] = $routeParams["username"];
$controller("mainController", { $scope: $scope });
api_call("GET", "/api/user/info", data, function(result) {
if (result["success"] == 1) {
$scope.user = result["user"];
}
$scope.$apply();
$(".timeago").timeago();
});
}]);
app.controller("loginController", ["$controller", "$scope", "$http", function($controller, $scope, $http) {
$controller("mainController", { $scope: $scope });
$scope.$on("loginStatus", function() {
if ($scope.config["navbar"].logged_in != true) {
location.href = "/login";
return;
}
});
}]);
app.controller("teamController", ["$controller", "$scope", "$http", "$routeParams", function($controller, $scope, $http, $routeParams) {
var data = { };
if ("teamname" in $routeParams) {
data["teamname"] = $routeParams["teamname"];
} else {
$controller("loginController", { $scope: $scope });
}
api_call("GET", "/api/team/info", data, function(result) {
if (result["success"] == 1) {
$scope.team = result["team"];
}
$scope.$apply();
$(".timeago").timeago();
});
}]);
app.controller("scoreboardController", ["$controller", "$scope", "$http", function($controller, $scope, $http) {
$controller("mainController", { $scope: $scope });
api_call("GET", "/api/stats/scoreboard", { }, function(result) {
if (result["success"] == 1) {
$scope.scoreboard = result["scoreboard"];
$scope.$apply();
}
});
}]);
app.controller("resetController", ["$controller", "$scope", "$http", "$routeParams", function($controller, $scope, $http, $routeParams) {
var data = { };
$scope.token = false;
data["csrf_token"] = $.cookie("csrf_token");
if ("token" in $routeParams) {
$scope.token = true;
token = $routeParams["token"];
api_call("GET", "/api/user/forgot/" + token, data, function(data) {
$scope.body = data["message"];
$scope.success = data["success"]
$scope.$apply();
});
} else {
$controller("mainController", { $scope: $scope });
}
}]);
app.controller("adminController", ["$controller", "$scope", "$http", function($controller, $scope, $http) {
$controller("mainController", { $scope: $scope });
$scope.$on("loginStatus", function() {
if ($scope.config["navbar"].logged_in != true) {
location.href = "/login";
return;
}
if ($scope.config["navbar"].admin != true) {
location.href = "/profile";
return;
}
});
}]);
app.controller("adminProblemsController", ["$controller", "$scope", "$http", function($controller, $scope, $http) {
$controller("adminController", { $scope: $scope });
api_call("GET", "/api/admin/problems/list", {}, function(result) {
if (result["success"] == 1) {
$scope.problems = result["problems"];
} else {
$scope.problems = [];
}
$scope.$apply();
});
}]);
app.controller("settingsController", ["$controller", "$scope", "$http", function($controller, $scope, $http) {
$controller("loginController", { $scope: $scope });
api_call("GET", "/api/user/info", {}, function(result) {
if (result["success"] == 1) {
$scope.user = result["user"];
}
$scope.$apply();
});
}]);
$.fn.serializeObject = function() {
var a, o;
o = {};
a = this.serializeArray();
$.each(a, function() {
if (o[this.name]) {
if (!o[this.name].push) {
o[this.name] = [o[this.name]];
}
return o[this.name].push(this.value || "");
} else {
return o[this.name] = this.value || "";
}
});
return o;
};
// register page
var register_form = function() {
var input = "#register_form input";
var data = $("#register_form").serializeObject();
$(input).attr("disabled", "disabled");
api_call("POST", "/api/user/register", data, function(result) {
if (result["success"] == 1) {
location.href = "/profile";
} else {
display_message("register_msg", "danger", result["message"], function() {
$(input).removeAttr("disabled");
});
}
}, function(jqXHR, status, error) {
var result = jqXHR["responseText"];
display_message("register_msg", "danger", "Error " + jqXHR["status"] + ": " + result["message"], function() {
$(input).removeAttr("disabled");
});
});
};
// password reset
var request_reset_form = function() {
var data = $("#request_reset_form").serializeObject();
$(input).attr("disabled", "disabled");
api_call("POST", "/api/user/forgot", data, function(result) {
if (result["success"] == 1) {
display_message("reset_msg", "success", result["message"]);
} else {
display_message("reset_msg", "danger", result["message"], function() {
$(input).removeAttr("disabled");
});
}
}, function(jqXHR, status, error) {
var result = jqXHR["responseText"];
display_message("reset_msg", "danger", "Error " + jqXHR["status"] + ": " + result["message"], function() {
$(input).removeAttr("disabled");
});
});
}
var reset_form = function() {
var data = $("#reset_form").serializeObject();
data["csrf_token"] = $.cookie("csrf_token");
var url = window.location.href;
var token = url.substr(url.lastIndexOf("/")+1);
$(input).attr("disabled", "disabled");
api_call("POST", "/api/user/forgot/" + token, data, function(result) {
if (result["success"] == 1) {
display_message("reset_msg", "success", result["message"], function() {
location.href = "/login";
});
} else {
display_message("reset_msg", "danger", result["message"], function() {
$(input).removeAttr("disabled");
});
}
}, function(jqXHR, status, error) {
var result = jqXHR["responseText"];
display_message("reset_msg", "danger", "Error " + jqXHR["status"] + ": " + result["message"], function() {
$(input).removeAttr("disabled");
});
});
}
// login page
var login_form = function() {
var input = "#login_form input";
var data = $("#login_form").serializeObject();
$(input).attr("disabled", "disabled");
api_call("POST", "/api/user/login", data, function(result) {
if (result["success"] == 1) {
location.href = "/profile";
} else {
display_message("login_msg", "danger", result["message"], function() {
$(input).removeAttr("disabled");
});
}
}, function(jqXHR, status, error) {
var result = jqXHR["responseText"];
display_message("login_msg", "danger", "Error " + jqXHR["status"] + ": " + result["message"], function() {
$(input).removeAttr("disabled");
});
});
};
// team page
var create_team = function() {
var input = "#create_team input";
var data = $("#create_team").serializeObject();
$(input).attr("disabled", "disabled");
api_call("POST", "/api/team/create", data, function(result) {
if (result["success"] == 1) {
location.reload(true);
} else {
display_message("create_team_msg", "danger", result["message"], function() {
$(input).removeAttr("disabled");
});
}
}, function(jqXHR, status, error) {
var result = JSON.parse(jqXHR["responseText"]);
display_message("create_team_msg", "danger", "Error " + jqXHR["status"] + ": " + result["message"], function() {
$(input).removeAttr("disabled");
});
});
};
var add_member = function() {
var input = "#add_member input";
var data = $("#add_member").serializeObject();
$(input).attr("disabled", "disabled");
api_call("POST", "/api/team/invite", data, function(result) {
if (result["success"] == 1) {
location.reload(true);
} else {
$(input).removeAttr("disabled");
}
}, function(jqXHR, status, error) {
var result = JSON.parse(jqXHR["responseText"]);
display_message("create_team_msg", "danger", "Error " + jqXHR["status"] + ": " + result["message"], function() {
$(input).removeAttr("disabled");
});
});
};
var rescind_invitation = function(uid) {
var input = "#add_member input";
var data = { "uid": uid };
api_call("POST", "/api/team/invite/rescind", data, function(result) {
if (result["success"] == 1) {
location.reload(true);
}
});
};
var request_invitation = function(tid) {
var input = "#add_member input";
var data = { "tid": tid };
api_call("POST", "/api/team/invite/request", data, function(result) {
if (result["success"] == 1) {
location.reload(true);
}
});
};
var accept_invitation = function(tid) {
var data = { "tid": tid };
api_call("POST", "/api/team/invite/accept", data, function(result) {
if (result["success"] == 1) {
location.reload(true);
}
});
};
var accept_invitation_request = function(uid) {
var data = { "uid": uid };
api_call("POST", "/api/team/invite/request/accept", data, function(result) {
if (result["success"] == 1) {
location.reload(true);
}
});
};
// settings page
var remove_profile_picture = function() {
api_call("POST", "/api/user/avatar/remove", { }, function(result) {
if (result["success"] == 1) {
location.reload(true);
}
});
};

File diff suppressed because it is too large Load diff

File diff suppressed because one or more lines are too long

View file

@ -1,44 +0,0 @@
function render_problems() {
$.post("/api/problem/data", {
}, function(data) {
data = data["data"];
for (var i = 0; i < data.length; i++) {
files = data[i]["files"];
problem =
`<div class="panel panel-info">
<div class="panel-heading">
<h3 class="panel-title">` + data[i]["name"] + ` | ` + data[i]["category"] + `<span style="float: right">` + data[i]["value"] + ` points</span></h3>
</div>
<div class="panel-body">
<p>` + data[i]["description"] + `</p>
<div class="input-group">
<input type="text" class="form-control" placeholder="Flag">
<span class="input-group-btn">
<button class="btn btn-success" id="hint" type="button" onclick="show_hint(\'` + data[i]["pid"] + `\');">Hint</button>
<button class="btn btn-success" type="button">Submit!</button>
</span>
</div>
</div>
<div class="panel-footer">`
for (var j = 0; j < files.length; j++) {
file_name = files[j].split("/").pop();
problem +=
`<a href="` + files[j] + `" class="filelink" target="_blank">
<h4 class="probfile">` + file_name + `</h4>
</a>`
}
problem += `<br>
<div id="hint_` + data[i]["pid"] + `" style="display:none">` + data[i]["hint"] + `</div>
</div></div>`
$("#problems").append(problem);
}
});
}
function show_hint(pid) {
$("#hint_" + pid).slideToggle(120, "swing");
}
$(document).ready( render_problems() );

View file

@ -1,45 +0,0 @@
var chart = c3.generate({
data: {
x: 'x',
columns: [
['x', '2013-01-01', '2013-01-02', '2013-01-03', '2013-01-04', '2013-01-05', '2013-01-06'],
['Team Thomas', 30, 250, 380, 500, 620, 740],
]
},
axis: {
x: {
type: 'timeseries',
tick: {
format: '%Y-%m-%d'
}
}
}
});
setTimeout(function() {
chart.load({
columns: [
['Team Charles', 100, 210, 320, 430, 540, 650]
]
});
}, 1000);
setTimeout(function() {
chart.load({
columns: [
['Team Zach', 10, 120, 230, 340, 450, 560]
]
});
}, 2000);
setTimeout(function() {
chart.load({
columns: [
['Team Michael', 0, 80, 190, 300, 410, 520]
]
});
}, 3000);
setTimeout(function() {
chart.load({
columns: [
['Team James', 0, 100, 250, 300, 450, 600]
]
});
}, 4000);

View file

@ -1,5 +0,0 @@
<div class="page-header">
<h1>404: Page Not Found</h1>
</div>
<p>Ok, ok, here's your flag: <code>bm8gZWFzdGVyIGVnZyBoZXJl</code>.</p>

View file

@ -1,93 +0,0 @@
<script type="text/javascript">
var chart1 = c3.generate({
colors: {
data1: '#1F77B4',
data2: '#62AC5B',
},
data: {
columns: [
['US High School/Middle School ', 33.33],
['Non-US/Non-Student', 66.66],
],
type: 'donut',
onclick: function(d, i) {
console.log("onclick", d, i);
},
onmouseover: function(d, i) {
console.log("onmouseover", d, i);
},
onmouseout: function(d, i) {
console.log("onmouseout", d, i);
}
},
donut: {
title: "Competitors"
},
bindto: "#chart1"
});
/*var chart2 = c3.generate({
colors: {
data1: '#1F77B4',
data2: '#62AC5B',
},
data: {
columns: [
['Solved ', 33.33],
['Unsolved', 66.66],
],
type: 'donut',
onclick: function(d, i) {
console.log("onclick", d, i);
},
onmouseover: function(d, i) {
console.log("onmouseover", d, i);
},
onmouseout: function(d, i) {
console.log("onmouseout", d, i);
}
},
donut: {
title: "Silly Ceasar"
},
bindto: "#chart2"
});
var chart3 = c3.generate({
colors: {
data1: '#1F77B4',
data2: '#62AC5B',
},
data: {
columns: [
['Solved ', 16],
['Unsolved', 25],
],
type: 'donut',
onclick: function(d, i) {
console.log("onclick", d, i);
},
onmouseover: function(d, i) {
console.log("onmouseover", d, i);
},
onmouseout: function(d, i) {
console.log("onmouseout", d, i);
}
},
donut: {
title: "Hard Binary"
},
bindto: "#chart3"
});*/
</script>
<div class="fade_in text-center">
<h1 class="heading1">About</h1>
<iframe frameborder="0" scrolling="no" marginheight="0" marginwidth="0" width="1000em" height="500em" src="https://maps.google.com/maps?hl=en&q=Lebanon, Kansas&ie=UTF8&t=hybrid&z=3&iwloc=B&output=embed"></iframe>
<br>
<div id="chart1"></div>
<br>
<!--<div id="chart2"></div>
<br>
<div id="chart3"></div>-->
<br>
</div>

View file

@ -1,71 +0,0 @@
<div class="page-header">
<h1>Problem Editor</h1>
</div>
<style>
.ace_editor {
width: 100%;
height: 200px;
}
.epiceditor-edit-mode {
border: 1px solid #999 !important;
}
</style>
<div class="row">
<div class="tabbable">
<ul class="nav nav-pills nav-stacked col-md-3">
<li class="active"><a data-target="#new" data-toggle="tab">New</a></li>
<li ng-repeat="problem in problems"><a data-target="#problem_{{ problem['pid'] }}" data-toggle="tab">{{ problem["title"] }} ({{ problem["value"] }} points)</a></li>
</ul>
<div class="tab-content col-md-9">
<div class="tab-pane active" id="new">
<div class="panel panel-default">
<div class="panel-heading">
<h4 class="panel-title">New Problem</h4>
</div>
<div class="panel-body">
</div>
</div>
</div>
<div class="tab-pane" ng-repeat="problem in problems" id="problem_{{ problem['pid'] }}">
<div class="panel panel-default">
<div class="panel-heading">
<h4 class="panel-title">{{ problem["title"] }}</h4>
</div>
<div class="panel-body">
</div>
</div>
</div>
</div>
</div>
</div>
<script type="text/javascript">
$(document).ready(function() {
/*$(".selectpicker").selectpicker();
var config = {
toolbar: [
{ name: "basicstyles", items: [ "Bold", "Italic", "Underline" ] },
{ name: "links", items: [ "Link" ] },
{ name: "paragraph", items: [ "NumberedList", "BulletedList", "-", "Outdent", "Indent", "-", "Blockquote" ] },
{ name: "tools", items: [ "Maximize" ] },
{ name: "document", items: [ "Source" ] },
]
};
var editor = new EpicEditor({
container: "description",
theme: {
base: "https://cdnjs.cloudflare.com/ajax/libs/epiceditor/0.2.2/themes/base/epiceditor.css",
preview: "https://cdnjs.cloudflare.com/ajax/libs/epiceditor/0.2.2/themes/preview/github.css",
editor: "https://cdnjs.cloudflare.com/ajax/libs/epiceditor/0.2.2/themes/editor/epic-light.css"
},
button: {
bar: "show"
}
}).load();
var new_grader = ace.edit("new_grader");
new_grader.setTheme("ace/theme/tomorrow");
new_grader.getSession().setMode("ace/mode/python");*/
});
</script>

View file

View file

@ -1,6 +0,0 @@
<div class="fade_in text-center">
<h1 class="heading1">EasyCTF IRC</h1>
<div class="irc">
<iframe src="https://kiwiirc.com/client/irc.kiwiirc.com/?&theme=mini#easyctf" style="border: none; width:100%; height:70%; border-radius:1em;"></iframe>
</div>
</div>

View file

@ -1,92 +0,0 @@
<div class="container">
<p>&nbsp;</p>
<div ng-switch on="token">
<div ng-switch-when="true">
{{ body }}
<div ng-switch on="success">
<div ng-switch-when="1">
<div class="row">
<div class="col-md-6 col-md-offset-3 col-sm-10 col-sm-offset-1">
<div class="panel panel-default">
<div class="panel-heading">
<h2 class="panel-title">Reset Password</h2>
</div>
<div class="panel-body">
<form class="form-horizontal" onsubmit="reset_form(); return false;" id="reset_form">
<fieldset>
<div id="reset_msg"></div>
</fieldset>
<fieldset class="container-fluid">
<div class="row">
<p>Reset your password here</p>
<div class="col-sm-12 form-group">
<label class="col-sm-12" for="password"><small>Password</small></label>
<div class="col-sm-12">
<input class="form-control" type="password" required name="password" id="password" placeholder="New password" autocomplete="off" />
</div>
</div>
<div class="col-sm-12 form-group">
<label class="col-sm-12" for="confirm_password"><small>Confirm Password</small></label>
<div class="col-sm-12">
<input class="form-control" type="password" required name="confirm_password" id="confirm_password" placeholder="Confirm new password" autocomplete="off" />
</div>
</div>
</div>
<div class="row">
<div class="col-sm-12 form-group">
<center>
<input type="hidden" id="_csrf" value="{{ csrf_token }}" />
<input type="submit" class="btn btn-success btn-lg" value="Reset password" />
</center>
</div>
</div>
</fieldset>
</form>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div ng-switch-default>
<div class="row">
<div class="col-md-6 col-md-offset-3 col-sm-10 col-sm-offset-1">
<div class="panel panel-default">
<div class="panel-heading">
<h2 class="panel-title">Reset Password</h2>
</div>
<div class="panel-body">
<form class="form-horizontal" onsubmit="request_reset_form(); return false;" id="request_reset_form">
<fieldset>
<div id="reset_msg"></div>
</fieldset>
<fieldset class="container-fluid">
<div class="row">
<p>Enter the email you used to sign up with, and we'll send you an an email with a link to reset your password.</p>
<div class="col-sm-12 form-group">
<label class="col-sm-12" for="email"><small>Email</small></label>
<div class="col-sm-12">
<input class="form-control" type="email" required name="email" id="email" placeholder="Email" autocomplete="off" />
</div>
</div>
</div>
<div class="row">
<div class="col-sm-12 form-group">
<center>
<input type="hidden" id="_csrf" value="{{ csrf_token }}" />
<input type="submit" class="btn btn-success btn-lg" value="Send email" />
</center>
</div>
</div>
</fieldset>
</form>
</div>
</div>
</div>
</div>
</div>
</div>
</div>

View file

@ -1,5 +0,0 @@
<div class="fade_in text-center">
<br>
<h1 class="heading1">EasyCTF 3</h1>
<br>
</div>

View file

View file

@ -1,54 +0,0 @@
<div class="container">
<p>&nbsp;</p>
<div class="row">
<div class="col-md-6 col-md-offset-3 col-sm-10 col-sm-offset-1">
<div class="panel panel-default">
<div class="panel-heading">
<h2 class="panel-title">Login</h2>
</div>
<div class="panel-body">
<form class="form-horizontal" onsubmit="login_form(); return false;" id="login_form">
<fieldset>
<div id="login_msg"></div>
</fieldset>
<fieldset class="container-fluid">
<div class="row">
<div class="col-sm-12 form-group">
<label class="col-sm-12" for="username"><small>Username</small></label>
<div class="col-sm-12">
<input class="form-control" type="text" required name="username" id="username" placeholder="Username" autocomplete="off" />
</div>
</div>
</div>
<div class="row">
<div class="col-sm-12 form-group">
<label class="col-sm-12" for="password"><small>Password</small></label>
<div class="col-sm-12">
<input class="form-control" type="password" required name="password" id="password" placeholder="Password" autocomplete="off" />
</div>
</div>
</div>
<div class="row">
<div class="col-sm-12 form-group">
<center>
<input type="hidden" id="_csrf" value="{{ csrf_token }}" />
<input type="submit" class="btn btn-success btn-lg" value="Login" />
</center>
</div>
</div>
<div class="row">
<div class="col-sm-12 form-group">
<center>
<small>
<a href="/forgot">Forgot Password?</a>
</small>
</center>
</div>
</div>
</fieldset>
</form>
</div>
</div>
</div>
</div>
</div>

View file

@ -1,11 +0,0 @@
<!--
Adding files:
1) put file in /files
2) create an "<a href="/files/example.txt" target="_blank"><h4>Example File</h4></a>"
3) remove these instructions (:P)
-->
<div class="fade_in">
<h1 class="heading1 text-center">Problems</h1>
<div id="problems"></div>
</div>
<script src="js/problems.js">

View file

@ -1,115 +0,0 @@
<div ng-show="user['user_found']==true">
<div class="row">
<div class="col-sm-3 col-xs-12">
<div class="panel panel-default">
<div class="panel-body">
<div style="width:100%;max-width:100%;">
<img src="/api/user/avatar/{{ user.uid }}?{{ timestamp }}" id="avatar" style="width:100%;max-height:256px;" />
<small style="display:block;text-align:right;" ng-show="user['me']==true"><a href="/settings#profile">Edit Picture</a></small>
</div>
<h2 style="margin:0px;font-weight:bold;font-size:2em;">{{ user.name }}</h2>
<small style="display:block;font-size:1.5em;color:#999;">@{{ user.username }}</small>
<hr>
<div>
<i class="fa fa-fw fa-user"></i>
{{ user.type }}
<div class="label label-info" ng-show="user['admin']==true">ADMIN</div>
</div>
<div ng-show="user['show_email']==true">
<i class="fa fa-fw fa-envelope"></i>
<a style="color:#666;" href="mailto:{{ user.email }}">
<span id="email">{{ user.email }}</span>
</a>
</div>
<div>
<i class="fa fa-fw fa-clock-o"></i>
Joined <time class="timeago" datetime="{{ user.registertime }}"></time>
</div>
</div>
</div>
</div>
<div class="col-sm-9 col-xs-12">
<ul class="nav nav-tabs" role="tablist">
<li role="presentation" class="active"><a href="#profile" aria-controls="profile" role="tab" data-toggle="tab">Profile</a></li>
<li role="presentation"><a href="#activity" aria-controls="activity" role="tab" data-toggle="tab">Activity</a></li>
<a href="/settings" class="btn btn-primary" style="float:right;" ng-show="user['me']==true"><i class="fa fa-fw fa-pencil"></i> Edit Profile</a>
</ul>
<div class="tab-content">
<div role="tabpanel" class="tab-pane active" id="profile">
<div class="panel panel-default">
<div class="panel-heading">
<h4 class="panel-title">Team Information</h4>
</div>
<div class="table-responsive" ng-show="user['in_team']==true">
<table class="table table-bordered">
<tr>
<th>Team Name</th>
<td><a ng-href="/team/{{ user.team['teamname'] }}">{{ user.team['teamname'] }}</a></td>
</tr>
<tr>
<th>School</th>
<td>{{ user.team['school'] }}</td>
</tr>
<tr>
<th>Points</th>
<td>{{ user.team['points'] }}</td>
</tr>
<tr>
<th>Rank</th>
<td>{{ user.team['place'] }}</td>
</tr>
</table>
</div>
<div class="panel-body" ng-show="user['in_team']!=true">
<div class="alert alert-success" ng-show="user['me']==true && user['invitations'].length>0">You have {{ user['invitations'].length }} invitation{{ user['invitations'].length==1 ? "" : "s" }}! <a href="/team">View &raquo;</a></div>
<p>{{ user['me']==true ? "You're" : "This user is" }} not a part of a team.</p>
<a href="/team" class="btn btn-primary" ng-show="user['me']==true">Join or create one now &raquo;</a>
</div>
</div>
<div class="panel panel-default">
<div class="panel-heading">
<h4 class="panel-title">Statistics</h4>
</div>
<div class="panel-body">
Hi.
</div>
</div>
<div class="panel panel-default">
<div class="panel-heading">
<h4 class="panel-title">Top Solves</h4>
</div>
<div class="panel-body">
Hi.
</div>
</div>
<div class="panel panel-default">
<div class="panel-heading">
<h4 class="panel-title">Achievements</h4>
</div>
<div class="panel-body">
Hi.
</div>
</div>
</div>
<div role="tabpanel" class="tab-pane" id="activity">
</div>
</div>
</div>
</div>
</div>
<div ng-show="user['user_found']!=true">
<div class="page-header">
<h1>User Not Found</h1>
</div>
<p>The user you were looking for doesn't exist. Check to make sure you've spelled the name right.</p>
</div>
<script type="text/javascript">
$(document).ready(function() {
$("ul[role=tablist]").tab();
$("a[role=tab]").click(function(e) {
e.preventDefault();
});
});
</script>

View file

@ -1,11 +0,0 @@
<div class="fade_in text-center">
<h1 class="heading1">Programming</h1>
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">Problems Avalible: NaN?</h3>
</div>
<div class="panel-body">
<h3>Insert Programming Client Here</h3>
</div>
</div>
</div>

View file

@ -1,103 +0,0 @@
<div class="container">
<p>&nbsp;</p>
<div class="row">
<div class="col-md-6 col-md-offset-3 col-sm-10 col-sm-offset-1">
<div class="panel panel-default">
<div class="panel-heading">
<h2 class="panel-title">Register</h2>
</div>
<div class="panel-body">
<form class="form-horizontal" onsubmit="register_form(); return false;" id="register_form">
<fieldset>
<div id="register_msg"></div>
</fieldset>
<fieldset class="container-fluid">
<p>Register your individual account here. Make sure that members of your team also register accounts. You'll be able to create teams and add members after you register!</p>
<div class="row">
<div class="col-sm-12 form-group">
<label class="col-sm-12" for="name"><small>Your Name</small></label>
<div class="col-sm-12">
<input class="form-control" type="text" required name="name" id="name" placeholder="Your Name" autocomplete="off" />
</div>
</div>
</div>
<div class="row">
<div class="col-sm-12 form-group">
<label class="col-sm-12" for="email"><small>Email</small></label>
<div class="col-sm-12">
<input class="form-control" type="email" required name="email" id="email" placeholder="michael@example.com" autocomplete="off" />
</div>
</div>
</div>
<div class="row">
<div class="col-sm-12 form-group">
<label class="col-sm-12" for="username"><small>Username</small></label>
<div class="col-sm-12">
<input class="form-control" type="text" required name="username" id="username" placeholder="Username" autocomplete="off" />
</div>
</div>
</div>
<div class="row">
<div class="col-sm-12 form-group">
<label class="col-sm-12" for="password"><small>Password</small></label>
<div class="col-sm-6">
<input class="form-control" type="password" required name="password" id="password" placeholder="Password" autocomplete="off" />
</div>
<div class="col-sm-6">
<input class="form-control" type="password" required name="password_confirm" id="password_confirm" placeholder="Confirm Password" autocomplete="off" />
</div>
</div>
</div>
<div class="row">
<div class="col-sm-12 form-group">
<label class="col-sm-12" for="type"><small>Who are you?</small></label>
<div class="col-sm-12">
<select name="type" id="type" class="selectpicker" data-width="100%">
<option value="1">US Middle/High School Student</option>
<option value="2">Middle/High School Teacher</option>
<option value="3">Non-US/Non-Student/Observer</option>
</select>
</div>
</div>
</div>
<div class="row">
<div class="col-sm-12 form-group">
<label class="col-sm-12"><small>Receive email notifications about future CTFs?</small></label>
<div class="col-sm-12">
<div class="btn-group" data-toggle="buttons">
<label class="btn btn-default active" id="notify_btn">
<input type="checkbox" autocomplete="off" checked name="notify" id="notify"><span id="notify_box" class="fa fa-fw fa-check-square"></span>&nbsp;Get notified about future CTFs
</label>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-sm-12 form-group">
<center>
<input type="submit" class="btn btn-primary btn-lg" value="Register" />
</center>
</div>
</div>
</fieldset>
</form>
</div>
</div>
</div>
</div>
</div>
<script type="text/javascript">
$(".selectpicker").selectpicker();
var checkedClass = "fa fa-check-square";
var uncheckedClass = "fa fa-square";
$("#notify_btn").click(function() {
if($("#notify").is(":checked")) {
$("#notify_box").removeClass(checkedClass);
$("#notify_box").addClass(uncheckedClass);
} else {
$("#notify_box").removeClass(uncheckedClass);
$("#notify_box").addClass(checkedClass);
}
});
</script>

View file

@ -1,43 +0,0 @@
<div class="fade_in text-center">
<h1 class="heading1"> Info </h1>
<h2 class="heading2">Rules</h2>
<p class="paragraph">
Below lie the rules regarding EasyCTF. These rules have been specifically formulated in order to preserve the integrity of the competition for every participant. These rules exist only serve as a auxillary to your own common sense. One of EasyCTF's core
goals is to gauge your team's understanding of the relevant computer science principles. If we wanted to measure the magnitude of your teacher's knowledge of computer science, we would attend a local CSTA meeting. Due to the nature of the competition's
content, the whole of the world wide web is free game. Do not, however, use forums or other services in which the problem is forwarded to unregistered member of EasyCTF. Sharing flags is obviously against rules, and will not be permitted. Do not
confuse EasyCTF with an attack-defense CTF platform; if you attack either the EasyCTF infrastructure or another team, you will be subject to complete team explusion, as well as the possibility of legal pursuance. All decisions made by an administrator
from the EasyCTF Team are final.
</p>
<h2 class="heading2">Flag Format</h2>
<p class="paragraph">
Similar to previous years, our flags will be formatted like so:
<code>EasyCTF{example_flag}</code>. Although our magical judge programs are expecting only the text in between the curly braces (
<code>example_flag</code>), EasyCTF will ignore the
<code>EasyCTF{}</code> and mark your submission correct or incorrect. Rapid, repeated guesses (brute forcing) will serve as sufficient grounds for team penalization. As forementioned, sharing of flags outside members of your own team can, and will have
severe penalties or explusion from the CTF competition. Finally, creating writeups for your solved problems is highly encouraged. However, for the integrity of the competition, writeups may not be posted before the end of the competition. After
the competition has successfuly ended, the EasyCTF team will post writeups for all of the problems.
</p>
<h2 class="heading2">Scoring</h2>
<p class="paragraph">In EasyCTF, every problem has an assigned point value. This point value is assigned based on the difficulty or the extent of time required to solve the problem. Your team's total score is the sum of the points you obtain from every problem you solve.
A few problems may have speed bonuses, meaning that the first solve on a problem will earn a bonus percent of the original problem value. These bonus points will also count toward your team total. Here is a screenshot from the problems page to
demonstrate. Teams with higher points will outrank teams with lower points. Should a tie occur between two teams, the time of the last submission will be used to break the tie. For example, if two teams are tied at 100 points, the team that reached
100 points first will outrank the team that reached 100 points afterwards.</p>
<h2 class="heading2">Eligibility and Observer Accounts/Teams</h2>
<p class="paragraph">EasyCTF is targeted at students enrolled in high schools or middle schools across the United States. Therefore, only students who are enrolled in high schools or middle schools in the United States are eligible for prizes. The EasyCTF Team will verify
the winning teams meet these conditions; if a winning team does not meet these conditions, then the prizes will be given to the next highest team. If you don't live in the United States, or you're not a high school or middle school student, you
are still encouraged to compete, but under an Observer account. Teams with at least one Observer member will be considered an Observer team. Observer teams still appear in the scoreboard, but are not eligible for prizes. Teachers also wishing
to take part in the CTF may also create an Observer team.</p>
<h2 class="heading2">Prizes</h2>
<p class="prizes"><b>1st Place: $512</b>
<br>2nd Place: $256
<br>3rd Place: $128
<br>Top 20: Laptop Stickers</p>
<br>
</div>
<br>
<br>

View file

@ -1,22 +0,0 @@
<div class="page-header">
<h1>Scoreboard</h1>
</div>
<table class="table table-striped">
<thead>
<tr>
<th>Rank</th>
<th>Team Name</th>
<th>School</th>
<th>Points</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="team in scoreboard">
<td>{{ team['rank'] }}</td>
<td><a ng-href="/team/{{ team['teamname'] }}">{{ team['teamname'] }}</a></td>
<td>{{ team['school'] }}</td>
<td>{{ team['points'] }}</td>
</tr>
</tbody>
</table>

View file

@ -1,192 +0,0 @@
<style>
.btn-file {
position: relative;
overflow: hidden;
}
.btn-file input[type=file] {
position: absolute;
top: 0;
right: 0;
min-width: 100%;
min-height: 100%;
font-size: 100px;
text-align: right;
filter: alpha(opacity=0);
opacity: 0;
background: red;
cursor: inherit;
display: block;
}
</style>
<div class="row">
<div class="col-sm-3 col-xs-12">
<div class="panel panel-default">
<div class="panel-heading">
<h4 class="panel-title">Account Settings</h4>
</div>
<div class="list-group">
<a href="#profile" class="list-group-item" data-scroll>Profile</a>
<a href="#email" class="list-group-item" data-scroll>Email</a>
<a href="#security" class="list-group-item" data-scroll>Security</a>
</div>
</div>
</div>
<div class="col-sm-9 col-xs-12">
<section id="profile">
<div class="panel panel-default">
<div class="panel-heading">
<h4 class="panel-title">Public Profile</h4>
</div>
<div class="panel-body">
<label>Profile Picture</label>
<div class="row">
<div class="col-sm-2">
<img src="/api/user/avatar/{{ user.uid }}?{{ timestamp }}" id="avatar" style="width:100%;max-height:256px;" />
</div>
<div class="col-sm-10">
<div class="row">
<div class="btn-group">
<div class="btn btn-primary btn-file" id="file_upload">
<i class="fa fa-fw fa-upload"></i> Upload new picture
<input type="file" name="file" id="file" accept="image/*" />
</div>
<a class="btn btn-default" href="javascript:remove_profile_picture();">
<i class="fa fa-fw fa-trash"></i> Remove profile picture
</a>
</div>
</div>
<div class="row">
<p><i>If you remove your picture, an automatically generated <a href="http://en.wikipedia.org/wiki/Identicon" target="_blank">identicon</a> will be used.</i></p>
</div1>
</div>
</div>
</div>
</div>
</section>
<section id="email">
<div class="panel panel-default">
<div class="panel-heading">
<h4 class="panel-title">Email</h4>
</div>
<div class="panel-body">
<p>Hi.</p>
</div>
</div>
<div class="panel panel-default">
<div class="panel-heading">
<h4 class="panel-title">Email Preferences</h4>
</div>
<div class="panel-body">
<p>Hi.</p>
</div>
</div>
</section>
<section id="security">
<div class="panel panel-default">
<div class="panel-heading">
<h4 class="panel-title">Change Password</h4>
</div>
<div class="panel-body">
<p>Hi.</p>
</div>
</div>
<div class="panel panel-default">
<div class="panel-heading">
<h4 class="panel-title">Active Sessions</h4>
</div>
<div class="panel-body">
<p>Hi.</p>
</div>
</div>
</section>
</div>
</div>
<script type="text/javascript">
smoothScroll.init({
speed: 480,
easing: "easeOutCubic",
offset: $("#navbar").outerHeight(),
updateURL: false
});
var dataURItoBlob = function(dataURI) {
var byteString = atob(dataURI.split(",")[1]);
var mimeString = dataURI.split(",")[0].split(":")[1].split(";")[0];
var ab = new ArrayBuffer(byteString.length);
var ia = new Uint8Array(ab);
for (var i = 0; i < byteString.length; i++) {
ia[i] = byteString.charCodeAt(i);
}
var blob = new Blob([ ab ], { type: mimeString });
return blob;
}
var MAX_SIZE = 256;
var uploadListener = function(e) {
try {
$("#file_upload").attr("disabled", "disabled");
$("#file_upload").html("<i class='fa fa-fw fa-circle-o-notch fa-spin'></i> Uploading...");
var file = e.target.files[0];
var reader = new FileReader();
reader.onload = (function(_file) {
return function(progress) {
var img = document.createElement("img");
img.src = progress.target.result;
var width = img.width;
var height = img.height;
if (width > height) {
if (width > MAX_SIZE) {
height *= MAX_SIZE / width;
width = MAX_SIZE;
}
} else {
if (height > MAX_SIZE) {
width *= MAX_SIZE / height;
height = MAX_SIZE;
}
}
img.cwidth = width;
img.cheight = height;
var data = new FormData();
data.append("file", dataURItoBlob(img.src));
data.append("csrf_token", $.cookie("csrf_token"));
$.ajax({
type: "POST",
url: "/api/user/avatar/upload",
data: data,
processData: false,
contentType: false,
}).done(function(result) {
if (result["success"] == 1) {
$("#file_upload").removeClass("btn-primary");
$("#file_upload").addClass("btn-success");
$("#file_upload").html("<i class='fa fa-fw fa-check'></i> Done! Reloading...");
location.reload(true);
}
}).fail(function() {
$("#file_upload").removeClass("btn-primary");
$("#file_upload").addClass("btn-danger");
$("#file_upload").html("<i class='fa fa-fw fa-ban'></i> Error. Reloading...");
location.reload(true);
});
};
})(file);
reader.readAsDataURL(file);
} catch (e) {
console.log(e);
$("#file_upload").removeClass("btn-primary");
$("#file_upload").addClass("btn-danger");
$("#file_upload").html("<i class='fa fa-fw fa-ban'></i> Error. Reloading...");
location.reload(true);
}
};
$(document).ready(function() {
if (!(window.File && window.FileReader && window.FileList && window.Blob)) {
console.log("Your browser doesn't support file upload.");
} else {
document.getElementById("file").addEventListener("change", uploadListener, false);
};
});
</script>

View file

@ -1,11 +0,0 @@
<div class="fade_in">
<h1 class="heading1">Shell</h1>
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">Username: (insert username) | Password: (insert password)</h3>
</div>
<div class="panel-body">
<h3>Insert Shell Here</h3>
</div>
</div>
</div>

View file

@ -1,224 +0,0 @@
<style>
.editable {
outline: none;
border: 1px solid rgba(0, 0, 0, 0);
}
.editable:hover {
border: 1px solid #999;
}
.editable:focus {
border: 1px solid #999;
background-color: #FFF;
}
.padded {
display: inline-block;
padding: 15px;
}
</style>
<div ng-show="team['tid'] >= 0">
<div class="jumbotron">
<center>
<div ng-show="team['in_team']==true">
<div ng-show="team['is_owner']==true">
<h1><span data-toggle="tooltip" data-placement="top" title="Click to edit team name." id="teamname_edit" class="padded editable" contenteditable>{{ team['teamname'] }}</span></h1>
<h4><i class="fa fa-fw fa-university"></i> <span data-toggle="tooltip" data-placement="top" title="Click to edit school." id="school_edit" class="padded editable" contenteditable>{{ team['school'] || 'Add School' }}</span></h4>
</div>
<div ng-show="team['is_owner']==false">
<h1><span class="padded">{{ team['teamname'] }}</span></h1>
<h4><i class="fa fa-fw fa-university"></i> <span class="padded">{{ team['school'] || 'Unknown Affiliation' }}</span></h4>
</div>
<div class="row">
<div class="label label-success">
<i class="fa fa-fw fa-flag"></i>
I'm in the team!
</div>
<div class="label label-warning" ng-show="team['observer']==true">
<i class="fa fa-fw fa-globe"></i>
OBSERVER
</div>
</div>
</div>
<div ng-show="team['in_team']!=true">
<h1><span class="padded">{{ team['teamname'] }}</span></h1>
<h4><i class="fa fa-fw fa-university"></i> <span class="padded">{{ team['school'] || 'Unknown Affiliation' }}</span></h4>
<div class="row">
<div class="label label-warning" ng-show="team['observer']==true">
<i class="fa fa-fw fa-globe"></i>
OBSERVER
</div>
</div>
</div>
</center>
</div>
<div class="row">
<div class="col-sm-3 col-xs-12">
<div class="panel panel-default">
<div class="panel-heading">
<h4 class="panel-title">Team Members</h4>
</div>
<div class="list-group">
<div class="list-group-item" ng-repeat="member in team['members']">
<h4 class="list-group-item-heading" style="display:inline-block;">{{ member['name'] }}</h4>
<div class="label label-info" ng-show="member['captain']==true">Owner</div>
<p class="list-group-item-text"><a href="/profile/{{ member['username'] }}">@{{ member['username'] }}</a></p>
</div>
</div>
<div class="panel-footer" ng-show="team['is_owner']==true">
<form id="add_member" onsubmit="add_member(); return false;" style="margin: 0;">
<div class="row">
<div class="col-xs-12">
<div class="input-group">
<input type="text" class="form-control" id="new_member" name="new_member" placeholder="Add member..." autocomplete="off">
<span class="input-group-btn">
<button class="btn btn-success" type="submit">&nbsp;<i class="fa fa-fw fa-plus"></i>&nbsp;</button>
</span>
</div>
</div>
</div>
</form>
</div>
<div class="panel-footer" ng-show="team['in_team']!=true && config.navbar['logged_in']==true">
<div class="row">
<div class="col-xs-12">
<a ng-href="javascript:request_invitation({{ team['tid'] }});" class="btn btn-primary col-xs-12" ng-show="team['invited']!=true && team['requested']!=true"><i class="fa fa-fw fa-plus"></i> Join this team</a>
<a class="btn btn-primary col-xs-12 disabled" ng-show="team['invited']!=true && team['requested']==true"><i class="fa fa-fw fa-check"></i> Sent Request</a>
<a ng-href="javascript:accept_invitation({{ team['tid'] }});" class="btn btn-success col-xs-12" ng-show="team['invited']==true"><i class="fa fa-fw fa-check"></i> Accept Invitation</a>
</div>
</div>
</div>
</div>
<div class="panel panel-default" ng-show="team['is_owner']==true && team['pending_invitations'].length > 0">
<div class="panel-heading">
<h4 class="panel-title">Pending Invitations</h4>
</div>
<div class="list-group">
<div class="list-group-item" ng-repeat="member in team['pending_invitations']" href="/profile/{{ member['username'] }}">
<a class="badge" ng-href="javascript:rescind_invitation({{ member['uid'] }});"><i class="fa fa-fw fa-times"></i></a>
<h4 class="list-group-item-heading">{{ member['name'] }}</h4>
<p class="list-group-item-text"><a href="/profile/{{ member['username'] }}">@{{ member['username'] }}</a></p>
</div>
</div>
</div>
<div class="panel panel-default" ng-show="team['is_owner']==true && team['invitation_requests'].length > 0">
<div class="panel-heading">
<h4 class="panel-title">Invitation Requests</h4>
</div>
<div class="list-group">
<div class="list-group-item" ng-repeat="member in team['invitation_requests']" href="/profile/{{ member['username'] }}">
<a class="badge" ng-href="javascript:accept_invitation_request({{ member['uid'] }});"><i class="fa fa-fw fa-check"></i></a>
<h4 class="list-group-item-heading">{{ member['name'] }}</h4>
<p class="list-group-item-text"><a href="/profile/{{ member['username'] }}">@{{ member['username'] }}</a></p>
</div>
</div>
</div>
</div>
<div class="col-sm-9 col-xs-12">
<ul class="nav nav-tabs" role="tablist">
<li role="presentation" class="active"><a href="#profile" aria-controls="profile" role="tab" data-toggle="tab">Profile</a></li>
<li role="presentation"><a href="#activity" aria-controls="activity" role="tab" data-toggle="tab">Activity</a></li>
</ul>
<div class="tab-content">
<div role="tabpanel" class="tab-pane active" id="profile">
</div>
<div role="tabpanel" class="tab-pane" id="activity">
</div>
</div>
</div>
</div>
</div>
<div ng-show="!(team['tid'] >= 0) && config.navbar['logged_in']==true">
<div class="page-header">
<h1>Team</h1>
</div>
<div class="row">
<div class="col-md-6">
<div class="page-header">
<h3>New Team</h3>
</div>
<p>To participate in EasyCTF, you must be on a <b>team</b>. If you'd like to go solo, just create a team by yourself. Read about team eligibility in the <a href="/rules">rules</a>.</p>
<form class="form-horizontal" onsubmit="create_team(); return false;" id="create_team">
<fieldset>
<div id="create_team_msg"></div>
</fieldset>
<fieldset class="container-fluid">
<div class="col-md-12">
<div class="panel panel-default">
<div class="panel-heading">
<h2 class="panel-title">Create a Team</h2>
</div>
<div class="panel-body">
<form class="form-horizontal" onsubmit="login_form(); return false;" id="login_form">
<fieldset>
<div id="login_msg"></div>
</fieldset>
<fieldset class="container-fluid">
<div class="row">
<div class="col-sm-12 form-group">
<label class="col-sm-12" for="teamname"><small>Team Name</small></label>
<div class="col-sm-12">
<input class="form-control" type="text" required name="teamname" id="teamname" placeholder="Create a team name..." autocomplete="off" />
</div>
</div>
</div>
<div class="row">
<div class="col-sm-12 form-group">
<label class="col-sm-12" for="school"><small>School Name</small></label>
<div class="col-sm-12">
<input class="form-control" type="text" required name="school" id="school" placeholder="School Name" autocomplete="off" />
</div>
</div>
</div>
<div class="row">
<div class="col-sm-12 form-group">
<center>
<input type="submit" class="btn btn-success btn-lg" value="Create Team" />
</center>
</div>
</div>
</fieldset>
</form>
</div>
</div>
</div>
</fieldset>
</form>
</div>
<div class="col-md-6">
<div class="page-header">
<h3>Invitations</h3>
</div>
<p ng-show="team['invitations'].length==0">You need an invitation to join another team. If you'd like to request to be a member of their team, go to their team page and click the Request button.</p>
<div ng-show="team['invitations'].length>0" class="list-group">
<div class="list-group-item" ng-repeat="invitation in team['invitations']">
<a ng-href="/team/{{ invitation['team'] }}">{{ invitation['team'] }}</a>
<a ng-href="javascript:accept_invitation({{ invitation['tid'] }});" class="badge">Accept &raquo;</a>
</div>
</div>
</div>
</div>
</div>
<div ng-show="!(team['tid'] >= 0) && config.navbar['logged_in']!=true">
<div class="page-header">
<h1>Team Not Found</h1>
</div>
<p>The team you were looking for doesn't exist. Check to make sure you've spelled the name right.</p>
</div>
<script type="text/javascript">
$("#teamname_edit").on("keypress", function(e) {
if (e.keyCode == 13) {
e.preventDefault();
}
});
$(document).ready(function() {
$("[data-toggle=tooltip]").tooltip();
$("ul[role=tablist]").tab();
$("a[role=tab]").click(function(e) {
e.preventDefault();
});
});
</script>

View file

@ -1,18 +0,0 @@
<div class="fade_in text-center">
<h1 class="heading1">News / Updates</h1>
<h4>This is where updates about the competition and clarifications about the problems will be posted.
<br>Make sure you check this page frequently during the competition!</h4>
<a class="twitter-timeline" href="https://twitter.com/easyctf" data-widget-id="681250456908120064">Tweets by @easyctf</a>
<script>
! function(d, s, id) {
var js, fjs = d.getElementsByTagName(s)[0],
p = /^http:/.test(d.location) ? 'http' : 'https';
if (!d.getElementById(id)) {
js = d.createElement(s);
js.id = id;
js.src = p + "://platform.twitter.com/widgets.js";
fjs.parentNode.insertBefore(js, fjs);
}
}(document, "script", "twitter-wjs");
</script>
</div>

View file

@ -1,70 +0,0 @@
html, body, iframe, div {
margin:0;
padding:0;
}
#epiceditor-utilbar {
position:fixed;
bottom:10px;
right:10px;
}
#epiceditor-utilbar button {
display:block;
float:left;
width:30px;
height:30px;
border:none;
background:none;
}
#epiceditor-utilbar button.epiceditor-toggle-preview-btn {
background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB4AAAAeCAYAAAA7MK6iAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAf9JREFUeNrsVu1twjAQLZmAEcIGYYKGDWACkgkSkPiQ+CifAoEgyQTQDegEpROQDZoRMgH0HToj1yIglKr90Vg62T6f/e4d9gu50+n09BctlwFnwP8TOAgCA10VRr2ZELaHhbBXx3HCVMC+71voXmD6g4Qi2NB13e1DwAAkZhtmmKYRcxsJhHeBPc8bMMufbDU0PxF4vV4TSyth8477csI6lTV/a71er9tioonBarXaIAmLErliNWyqoM8nrJPpHFNLWLcI4xvj5XKpMo2ZgcvzIvs+75S0wKwPPB/CnpWXsG00Gra2WCwshekOVoC9Sb6IGN1ge2HNsWK+B0iJqxAL5oSpYeDJJW02mxVYLAWSGfDtebylA68Bc4wh+ahK5PcxLh6PR5GUpym/iTOfz89PqNVqhRI4iQf1/o174HNMVYDSGeTDmXQ3znogCGrtdpsYVBhER1aH2Wzm8iE7UR74DMTWGNxUQWmNYqTEzq8APoo9sJ8wKoR5eU7T6VQVjZAvx4YvDJWt1Ol0QsTqkppF8EW8/12OhTnSpT2LCe2/KiCTyUQVkJgPuwgb6XG32w05Xui4q0imLLNDxA/uSuZ4PNaZqZlSsejDYfd6veihj8RoNDK5XOUHAen3Dfr9/j7VZxEJ6AwuxCCvhMTM7oNAARhl/0Ay4Az419qXAAMAfBdK7281j6YAAAAASUVORK5CYII=);
}
#epiceditor-utilbar button.epiceditor-toggle-edit-btn {
background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB4AAAAeCAYAAAA7MK6iAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAnVJREFUeNrsV+lt4lAQNtEWAB2QCtZ0ABWsqYB1BU5AgDgkQOISl9cV4FQQtgLYCuKtIN4K4gogM9H3pMmLTTh24c+O9An7+XnOb8aP1G63M64hN8aV5GqGvxyyyfO8Y3S6hDtc2wTfcZyLRPwdvxEbPSviIwjIRtO49s9ONSRLMIGvwkBI+EPYEEqyQmcZdl2Xo3BgcJ90xPUGDh1veLFYWCBK9oQ6P4BgiXVO6fUjg5zCJcE6kVxsLEN4QTlWzO5yuRwlGp7P55zOxz1RRqK2SfKD8BvOG4IHxUqlEnxop9lsZpITa0KWndGwIeQIXswzHbynpK2xzjXbeBfxdDrlhbUWjYyuiJS9fBJxgL3PItKsprNQrVaD1GQyYUVP2oYelDziPoPnT5+k2QajleEC3usI/exM7oYiXor0hpxS8qhLv5FIVVrcBwkp5ueclbS27qNMvkj7kg3nZX1qtVqAaSUN5OGUwn2M4RUb3263plh700E6I7wTKn1suCAW3PF4zL1r1Ov1SBhXZLEJFqGTQCq5N1BZIp3s+DOi5fXcG7lGo1Ea5DIFSWzcq7a4R6uYqJmlkSqHoeGKeq+w905MtGKj0Yje9TE5ID9pqictQQwmXTfh82cIJ0NML0d0QY8MdhMn13A4zENB0hAJEYn6EnGL3MZ0hsyG3Ww2g70jU8lgMOhqHicJz+KfovVkz3J5/FardfhBgDZzS90SeoJ8cXjQJlUAEmZUCx30kYiTfr9vgFRc72+ChCHSzNH+Qgk+fA7b7fZZJ5AAiIRhT4zUv3/Y07LiaPW9yPE2L5jrI/p/d7wVEZe0U8bJkvr/F+ZS8irAAIorRozUvI0gAAAAAElFTkSuQmCC);
}
#epiceditor-utilbar button.epiceditor-fullscreen-btn {
background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB4AAAAeCAYAAAA7MK6iAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAY5JREFUeNrsV8tthDAQhYgCtoV04BKghK2En5AQBw4IITjwq2RLgBJcwpawHZAxMpGDYGwTFPaQkUbG1sybj58NmNM0GVfIh3GRXBbYEid932N9d1zXHWWAGAb4m+9VsUA0H5SubKkKIGA4qyUC2qKBxSCe541HKln7dV2nVbHRtq0Nw7CXGNtz/jzwqjZ5odtqTOagQRC82KRpGkcS/L2Ok7lXZV3Xp7Q6DMNR2mqNthMhKXLqzcUCc6Wgn3wU1wlX1PboHs8tjaLoyVtLT7JFK2ZZM6CZvWyE+X1Voaj3la3DMA63epGqqm4wfyCBH8xmz1+Z1WVZ2vyqU2HvHtv9OI4PsRpjL91YV2a7pcB8onmOifYFUhSFLQCTnQtkDpokyYv73JBtcAQsA3zGTXJBEgNXgrF3CcrBZGwnC+4uqxHnH+zN8/ybvexZwvZNhlsHbrt5CyCgDtu1XosUe58K4kuOF9EKnKYp20eVrxDUJssyreM0bDg4kIw0EfCbftvqQ6KKYf7/wvyVfAkwAMjqoFSaWbrBAAAAAElFTkSuQmCC);
}
@media
only screen and (-webkit-min-device-pixel-ratio: 2),
only screen and ( min--moz-device-pixel-ratio: 2),
only screen and ( -o-min-device-pixel-ratio: 2/1),
only screen and ( min-device-pixel-ratio: 2),
only screen and ( min-resolution: 192dpi),
only screen and ( min-resolution: 2dppx) {
#epiceditor-utilbar button.epiceditor-toggle-preview-btn {
background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADwAAAA8CAMAAAANIilAAAABrVBMVEWIiIiHh4eIiIiJiYmEhISEhISEhISMjIyNjY2MjIyNjY2MjIyFhYWDg4OFhYWKioqKioqBgYGJiYmJiYmJiYmKioqKioqFhYWIiIiJiYmIiIiJiYmIiIiGhoaIiIiGhoaIiIiHh4eHh4eGhoaFhYWKioqFhYWEhISFhYWEhISDg4ONjY2NjY2MjIyHh4eMjIyLi4uCgoKCgoKCgoKBgYGBgYGOjo6Ojo6BgYGPj4+Ojo6Hh4eNjY2Pj4+BgYGNjY2BgYGHh4eQkJCNjY2FhYWHh4eDg4OQkJCQkJCAgICCgoKAgICAgICQkJCQkJCQkJCAgICQkJCAgICQkJCFhYWHh4eHh4eGhoaKioqGhoaMjIyDg4OEhISDg4OEhISDg4OEhISKioqBgYGJiYmKioqKioqJiYmMjIyEhISCgoKGhoaLi4uPj4+Pj4+BgYGKioqDg4OGhoaGhoaMjIyHh4eEhISEhISDg4OIiIiJiYmGhoaHh4eJiYkAAACAgICFhYWQkJCIiIiEhISLi4uDg4OHh4eGhoaCgoKBgYGJiYmMjIyOjo6KioqNjY2Pj4+o6Lr0AAAAfnRSTlPfvxDPEL8gv5/Pr3AQ3++vn8+/cCCA34/vj8/vMJ+vgJ+vYN/fMIBAv4/v38/v39+PgBBg74DPgGAwMHDvrzBQj1AggJ8wIHC/IM9gjxBQgO+vv89Qz0C/70Bgz89ggIDvYN/fII8wIHDvMJ+/QBBAMBDPnxBgML9AEI/vQACT8cYwAAACXElEQVR42u2WV1caURRGJ73H9J7YW+y99957ARREURAFQRFEOFGMkybxN+eec2cWUwBnrTwli/1yvvnm7PvEuoMAf0FGzsj/nDwxOTUzdyoxNzM1OWFU7h0aPtUxPNRrQPZ0XKagw3OF7Nm9TMOuJ43cQmpavSWV3HRhgKakcmvjhSEaW/VyV/tvg7R3aeU+9ULZ/fLEQ/ndMvXbPrV88EvBvQdOMCsLMzjNd5TFgSTr3SpWOCuUTYWTVVV6m+Sdr0qqqVGxw6pqXUPyZlziFaU9Vi3HVSyzag+D/YlcbXLZLm+85AsOgMK4hkIABz/YkSVVdpS3fnKevQCIYgCcGqKslGd0g3dbIIR5fP8cZCkMdOANWeSLEJZklt5StxEWcmLIuw+AHGE+YiEWE67jGxnlO8xv8OGTEBGRRSACmNtYyBUjgcCCKJPLqjYMAeD04ENEGIjQ7AGikuUFNhdF8Vogj0T5bDyqEjh55AwI4N7/hmRTe4zRxEM+ZuKYFSYeEP9HzPtuEFjm9pKf9W5KQLbKhSVMbkymfHL90i+s/wR5PM9iCaYiLOcLTogCrKEIYwkLD19T25/4bR+unSG3bkOQwiG1QZfV6gryBaqL5c01XCCZ9lbOCOvNUpouUOGishSK+dpKUHMZ2M6Jz7ZHNEO+hOoLUWVZZROx6a8hn+VcRWh1EOtBUuhcPiy+pBdg3fZ3DaOj2ma7LtXVW1uj0XVqTW2aS9/bUP8jJfUN3is+N97mp8nV9Wavka9kZ/e6zuzuNP59Hhkbn53+QkzPjo+NZP6TZOSM/L/LfwAJ69Ba3fXmtwAAAABJRU5ErkJggg==);
background-size: 30px 30px;
}
#epiceditor-utilbar button.epiceditor-toggle-edit-btn {
background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADwAAAA8CAMAAAANIilAAAACNFBMVEWIiIiHh4eIiIiGhoaEhISDg4OCgoKDg4OMjIyNjY2MjIyMjIyLi4uMjIyKioqCgoKNjY2Ojo6Dg4ONjY2FhYWFhYWCgoKKioqKioqLi4uLi4uMjIyLi4uFhYWKioqKioqEhISJiYmJiYmEhISEhISFhYWEhISGhoaHh4eJiYmNjY2Hh4eKioqDg4ODg4OCgoKOjo6Ojo6Dg4OOjo6BgYGOjo6AgICCgoKNjY2Dg4OPj4+BgYGBgYGAgICOjo6BgYGOjo6AgICOjo6BgYGBgYGAgICPj4+QkJCQkJCQkJCQkJCPj4+NjY2IiIiIiIiFhYWFhYWEhISEhISGhoaDg4OGhoaDg4OHh4eDg4OCgoKMjIyLi4uLi4uKioqNjY2KioqNjY2NjY2NjY2Dg4OKioqGhoaJiYmIiIiGhoaFhYWEhISFhYWFhYWOjo6Pj4+FhYWGhoaPj4+Hh4eMjIyMjIyMjIyJiYmIiIiIiIiOjo6Ojo6CgoKBgYGPj4+QkJCHh4eHh4eEhISIiIiDg4OEhISDg4ODg4OLi4uHh4eEhISMjIyFhYWGhoaHh4eOjo6IiIiKioqKioqMjIyGhoaIiIiJiYmIiIiFhYWGhoaGhoaHh4eKioqJiYmLi4uIiIiGhoaJiYmHh4eGhoaGhoaJiYmFhYWKioqPj4+IiIiGhoYAAACAgICCgoKQkJCPj4+BgYGDg4OFhYWHh4eOjo6JiYmGhoaLi4uEhISIiIiNjY2MjIyKioqRGRgVAAAAq3RSTlNQz7+Pz4AQr4/vIK+Av99QICBQzyCv37/PcM+f378QgHAwgIBgnxBQMK+PgFCf34Bwn0BQv2Ag7zBwEFDvj4CAv1DvYK/f398gUICA39+fYHAwQHAQn78gz0DfYO8wgO+/YECPIK9gz89Av8/f31AwgK9A72AwIGCAMM+PICCvrxCfr+/fYCAgYO9QgGBQjyCPYM8QcM8wEEAgn58Qn+8w7+/fv0DvQEBA7wCxres+AAAC50lEQVR42u2XVUMbQRSFU3d3d3d3d8EpUKRoKbS40+LuDsEtQOFQKA2FEvhz3XsnS5IlJLB9a/s95J49M99sXpJsNPgD/st/oZwxJJGhTn4+RLxQJ78k9xnUyU+HJV6pk6OGiXR1ciy5sZizfPMrwzl9mIiak5x6/sLnUZmH9+9eqqAQCftyakXkqFUq7MpX6JbW2WBHDnxtmJUAIMiGfD3AYINArDdsCppNfmewxQqx4aRVObFm0ibLAW+aNYkz5ZL4SdusRIkI8SVKOcFyp/cu5ftYA6ySc4Kl3DZuxs4dhfAZV+CDQtNFm0lWuLsBFPoqXF9g9bjSZrlqypwqAC1TClqAtYprIVfLjT+f0gfAXyn7oY9GNZ/KiWVumX17OYYAIUp3O+AnDu7bZqxOk5zU+ZOpPwD0UABNBaEAaPYACBVCZ5Ik14vlg5ClVjFpC0NZ6lplGa0nxN2gSZkg2vtB9FOmKDUNCyemcTStMXXcpmh4yGUl5ToAORMOaGiflhtkoRKCfq41yTw+GFsHKeeIRUfwwbwKOo/eIATi3GQNivREVxyIZsqeADL1+swuvZEiAJ4UmsEUsVEODRAnNp2iulzekrVAbyJLPrYctJTJ7nGQjI7uMULXBIBjIyQWUWLeAGik0E39sQGKYU0QMmp1vGnADSjj0EFtk5uOjuKzOtgok8r34rxasMzEjDFhjdDJNsE7u2VXh9oYDgNllp/n8IgfzJbwXhq9zlRu5soZWtFFl/Zy8SkaljK0R6inJTEinLQo5aSFE889kkqUWvsCdM37ZcnHYnrNBhably5QyoJDtFuJK1xMF3mHgVlkHM2e4eYB02Xxfts/NwXBuSMWLIG7sTmbb/+Hzj3fy1wuQD6N3DMX5/g0VHDDQ3aXAR4jXsEb5/co9fbLN2KdlJbO/zmM5a0qH+KukXxOnfzoO5GmTr5M7mOoktP4xrfUyffIvQ118pNBiTvq5AeDxNV//W+CFX4Doxg1lHC8FNUAAAAASUVORK5CYII=);
background-size: 30px 30px;
}
#epiceditor-utilbar button.epiceditor-fullscreen-btn {
background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADwAAAA8CAMAAAANIilAAAAA5FBMVEWPj4+JiYmJiYmCgoKGhoaPj4+Li4uEhISAgICNjY2Li4uNjY2QkJCFhYWEhISLi4uHh4eHh4eIiIiHh4eGhoaHh4eEhISOjo6EhISJiYmFhYWJiYmFhYWNjY2NjY2Hh4eOjo6CgoKGhoaKioqKioqJiYmKioqDg4ODg4OBgYGGhoaKioqKioqLi4uMjIyMjIyIiIiIiIiMjIyOjo6GhoaDg4ONjY2EhISPj4+CgoIAAACJiYmHh4eIiIiFhYWCgoKPj4+Li4uAgICKioqEhISGhoaBgYGOjo6MjIyQkJCNjY2Dg4O2Jy/OAAAAO3RSTlMQz0DPEHDPz0DvMDBAQBBAQBAQz88wMM/v7zAw788Q7xAQQM9AEBDPEEAwMO/v7zDvMEBA70BAQEBAAAENmbYAAAG1SURBVHja7dftM0JREMdxD5GkEEqKpBJRSc8rQq7Q////2Dnm3t85jtkTmmGG75t7Z3c/t9fN0Tf6x1/Dz0ZrZGZf/BJ8a9QjM/vCwks9Px5aBcslC+P3nPVmi8dcczrcHHM2flug1CHRYcoYNd0YFlrAL1yHqPOC9g9IdbAfjHAjYFjoT+FI1MfRiAM/cZdEl0+oVidVvRaMcAOMgJWGBUYSVhrWjdfvjKqrq1Vzsu7Cy1Vo7XXZhYuj0ahwfHY+sjo/Oy7woyhitkTQsESsZawstG6VlvDCfIlUmfSVVjpDqtL8goBLbKFhsRcwalxcB100CDkxLLQbJxKwpvb3At7Y2iRuJzd4V26HuM2tDQkPWMOamu1Awkeetx2qtDy/lvZaCW173pGIWetA/xBbF+ZgiVgjGcdutLJ7xO1l9VnMiWGhJdzl4vx4CNpN+ifJXUy7RPEuZ2C1AIY1NG5kHI4Dx8MynnBtovYkqHzi25OyP8ONiKFh3djQsCIecn2i/lBvMU+UXxwi3HyE832jvD0RsLuP8CN3Oh0+feRmixE+g7CdLb43WiEz++Jb+M//x/gB/ArCl0G4nsFuEQAAAABJRU5ErkJggg==);
background-size: 30px 30px;
}
}
#epiceditor-utilbar button:last-child {
margin-left:15px;
}
#epiceditor-utilbar button:hover {
cursor:pointer;
}
.epiceditor-edit-mode #epiceditor-utilbar button.epiceditor-toggle-edit-btn {
display:none;
}
.epiceditor-preview-mode #epiceditor-utilbar button.epiceditor-toggle-preview-btn {
display:none;
}

View file

@ -1,13 +0,0 @@
html { padding:10px; }
body {
border:0;
background:rgb(41,41,41);
font-family:monospace;
font-size:14px;
padding:10px;
color:#ddd;
line-height:1.35em;
margin:0;
padding:0;
}

View file

@ -1,12 +0,0 @@
html { padding:10px; }
body {
border:0;
background:#fcfcfc;
font-family:monospace;
font-size:14px;
padding:10px;
line-height:1.35em;
margin:0;
padding:0;
}

View file

@ -1,167 +0,0 @@
body {
font-family: Georgia, "Times New Roman", Times, serif;
line-height: 1.5;
font-size: 87.5%;
word-wrap: break-word;
margin: 2em;
padding: 0;
border: 0;
outline: 0;
background: #fff;
}
h1,
h2,
h3,
h4,
h5,
h6 {
margin: 1.0em 0 0.5em;
font-weight: inherit;
}
h1 {
font-size: 1.357em;
color: #000;
}
h2 {
font-size: 1.143em;
}
p {
margin: 0 0 1.2em;
}
del {
text-decoration: line-through;
}
tr:nth-child(odd) {
background-color: #dddddd;
}
img {
outline: 0;
}
code {
background-color: #f2f2f2;
background-color: rgba(40, 40, 0, 0.06);
}
pre {
background-color: #f2f2f2;
background-color: rgba(40, 40, 0, 0.06);
margin: 10px 0;
overflow: hidden;
padding: 15px;
white-space: pre-wrap;
}
pre code {
font-size: 100%;
background-color: transparent;
}
blockquote {
background: #f7f7f7;
border-left: 1px solid #bbb;
font-style: italic;
margin: 1.5em 10px;
padding: 0.5em 10px;
}
blockquote:before {
color: #bbb;
content: "\201C";
font-size: 3em;
line-height: 0.1em;
margin-right: 0.2em;
vertical-align: -.4em;
}
blockquote:after {
color: #bbb;
content: "\201D";
font-size: 3em;
line-height: 0.1em;
vertical-align: -.45em;
}
blockquote > p:first-child {
display: inline;
}
table {
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
border: 0;
border-spacing: 0;
font-size: 0.857em;
margin: 10px 0;
width: 100%;
}
table table {
font-size: 1em;
}
table tr th {
background: #757575;
background: rgba(0, 0, 0, 0.51);
border-bottom-style: none;
}
table tr th,
table tr th a,
table tr th a:hover {
color: #FFF;
font-weight: bold;
}
table tbody tr th {
vertical-align: top;
}
tr td,
tr th {
padding: 4px 9px;
border: 1px solid #fff;
text-align: left; /* LTR */
}
tr:nth-child(odd) {
background: #e4e4e4;
background: rgba(0, 0, 0, 0.105);
}
tr,
tr:nth-child(even) {
background: #efefef;
background: rgba(0, 0, 0, 0.063);
}
a {
color: #0071B3;
}
a:hover,
a:focus {
color: #018fe2;
}
a:active {
color: #23aeff;
}
a:link,
a:visited {
text-decoration: none;
}
a:hover,
a:active,
a:focus {
text-decoration: underline;
}

View file

@ -1,368 +0,0 @@
html { padding:0 10px; }
body {
margin:0;
padding:0;
background:#fff;
}
#epiceditor-wrapper{
background:white;
}
#epiceditor-preview{
padding-top:10px;
padding-bottom:10px;
font-family: Helvetica,arial,freesans,clean,sans-serif;
font-size:13px;
line-height:1.6;
}
#epiceditor-preview>*:first-child{
margin-top:0!important;
}
#epiceditor-preview>*:last-child{
margin-bottom:0!important;
}
#epiceditor-preview a{
color:#4183C4;
text-decoration:none;
}
#epiceditor-preview a:hover{
text-decoration:underline;
}
#epiceditor-preview h1,
#epiceditor-preview h2,
#epiceditor-preview h3,
#epiceditor-preview h4,
#epiceditor-preview h5,
#epiceditor-preview h6{
margin:20px 0 10px;
padding:0;
font-weight:bold;
-webkit-font-smoothing:antialiased;
}
#epiceditor-preview h1 tt,
#epiceditor-preview h1 code,
#epiceditor-preview h2 tt,
#epiceditor-preview h2 code,
#epiceditor-preview h3 tt,
#epiceditor-preview h3 code,
#epiceditor-preview h4 tt,
#epiceditor-preview h4 code,
#epiceditor-preview h5 tt,
#epiceditor-preview h5 code,
#epiceditor-preview h6 tt,
#epiceditor-preview h6 code{
font-size:inherit;
}
#epiceditor-preview h1{
font-size:28px;
color:#000;
}
#epiceditor-preview h2{
font-size:24px;
border-bottom:1px solid #ccc;
color:#000;
}
#epiceditor-preview h3{
font-size:18px;
}
#epiceditor-preview h4{
font-size:16px;
}
#epiceditor-preview h5{
font-size:14px;
}
#epiceditor-preview h6{
color:#777;
font-size:14px;
}
#epiceditor-preview p,
#epiceditor-preview blockquote,
#epiceditor-preview ul,
#epiceditor-preview ol,
#epiceditor-preview dl,
#epiceditor-preview li,
#epiceditor-preview table,
#epiceditor-preview pre{
margin:15px 0;
}
#epiceditor-preview hr{
background:transparent url('../../images/modules/pulls/dirty-shade.png') repeat-x 0 0;
border:0 none;
color:#ccc;
height:4px;
padding:0;
}
#epiceditor-preview>h2:first-child,
#epiceditor-preview>h1:first-child,
#epiceditor-preview>h1:first-child+h2,
#epiceditor-preview>h3:first-child,
#epiceditor-preview>h4:first-child,
#epiceditor-preview>h5:first-child,
#epiceditor-preview>h6:first-child{
margin-top:0;
padding-top:0;
}
#epiceditor-preview h1+p,
#epiceditor-preview h2+p,
#epiceditor-preview h3+p,
#epiceditor-preview h4+p,
#epiceditor-preview h5+p,
#epiceditor-preview h6+p{
margin-top:0;
}
#epiceditor-preview li p.first{
display:inline-block;
}
#epiceditor-preview ul,
#epiceditor-preview ol{
padding-left:30px;
}
#epiceditor-preview ul li>:first-child,
#epiceditor-preview ol li>:first-child{
margin-top:0;
}
#epiceditor-preview ul li>:last-child,
#epiceditor-preview ol li>:last-child{
margin-bottom:0;
}
#epiceditor-preview dl{
padding:0;
}
#epiceditor-preview dl dt{
font-size:14px;
font-weight:bold;
font-style:italic;
padding:0;
margin:15px 0 5px;
}
#epiceditor-preview dl dt:first-child{
padding:0;
}
#epiceditor-preview dl dt>:first-child{
margin-top:0;
}
#epiceditor-preview dl dt>:last-child{
margin-bottom:0;
}
#epiceditor-preview dl dd{
margin:0 0 15px;
padding:0 15px;
}
#epiceditor-preview dl dd>:first-child{
margin-top:0;
}
#epiceditor-preview dl dd>:last-child{
margin-bottom:0;
}
#epiceditor-preview blockquote{
border-left:4px solid #DDD;
padding:0 15px;
color:#777;
}
#epiceditor-preview blockquote>:first-child{
margin-top:0;
}
#epiceditor-preview blockquote>:last-child{
margin-bottom:0;
}
#epiceditor-preview table{
padding:0;
border-collapse: collapse;
border-spacing: 0;
font-size: 100%;
font: inherit;
}
#epiceditor-preview table tr{
border-top:1px solid #ccc;
background-color:#fff;
margin:0;
padding:0;
}
#epiceditor-preview table tr:nth-child(2n){
background-color:#f8f8f8;
}
#epiceditor-preview table tr th{
font-weight:bold;
}
#epiceditor-preview table tr th,
#epiceditor-preview table tr td{
border:1px solid #ccc;
text-align:left;
margin:0;
padding:6px 13px;
}
#epiceditor-preview table tr th>:first-child,
#epiceditor-preview table tr td>:first-child{
margin-top:0;
}
#epiceditor-preview table tr th>:last-child,
#epiceditor-preview table tr td>:last-child{
margin-bottom:0;
}
#epiceditor-preview img{
max-width:100%;
}
#epiceditor-preview span.frame{
display:block;
overflow:hidden;
}
#epiceditor-preview span.frame>span{
border:1px solid #ddd;
display:block;
float:left;
overflow:hidden;
margin:13px 0 0;
padding:7px;
width:auto;
}
#epiceditor-preview span.frame span img{
display:block;
float:left;
}
#epiceditor-preview span.frame span span{
clear:both;
color:#333;
display:block;
padding:5px 0 0;
}
#epiceditor-preview span.align-center{
display:block;
overflow:hidden;
clear:both;
}
#epiceditor-preview span.align-center>span{
display:block;
overflow:hidden;
margin:13px auto 0;
text-align:center;
}
#epiceditor-preview span.align-center span img{
margin:0 auto;
text-align:center;
}
#epiceditor-preview span.align-right{
display:block;
overflow:hidden;
clear:both;
}
#epiceditor-preview span.align-right>span{
display:block;
overflow:hidden;
margin:13px 0 0;
text-align:right;
}
#epiceditor-preview span.align-right span img{
margin:0;
text-align:right;
}
#epiceditor-preview span.float-left{
display:block;
margin-right:13px;
overflow:hidden;
float:left;
}
#epiceditor-preview span.float-left span{
margin:13px 0 0;
}
#epiceditor-preview span.float-right{
display:block;
margin-left:13px;
overflow:hidden;
float:right;
}
#epiceditor-preview span.float-right>span{
display:block;
overflow:hidden;
margin:13px auto 0;
text-align:right;
}
#epiceditor-preview code,
#epiceditor-preview tt{
margin:0 2px;
padding:0 5px;
white-space:nowrap;
border:1px solid #eaeaea;
background-color:#f8f8f8;
border-radius:3px;
}
#epiceditor-preview pre>code{
margin:0;
padding:0;
white-space:pre;
border:none;
background:transparent;
}
#epiceditor-preview .highlight pre,
#epiceditor-preview pre{
background-color:#f8f8f8;
border:1px solid #ccc;
font-size:13px;
line-height:19px;
overflow:auto;
padding:6px 10px;
border-radius:3px;
}
#epiceditor-preview pre code,
#epiceditor-preview pre tt{
background-color:transparent;
border:none;
}

View file

@ -1,121 +0,0 @@
html { padding:0 10px; }
body {
margin:0;
padding:10px 0;
background:#000;
}
#epiceditor-preview h1,
#epiceditor-preview h2,
#epiceditor-preview h3,
#epiceditor-preview h4,
#epiceditor-preview h5,
#epiceditor-preview h6,
#epiceditor-preview p,
#epiceditor-preview blockquote {
margin: 0;
padding: 0;
}
#epiceditor-preview {
background:#000;
font-family: "Helvetica Neue", Helvetica, "Hiragino Sans GB", Arial, sans-serif;
font-size: 13px;
line-height: 18px;
color: #ccc;
}
#epiceditor-preview a {
color: #fff;
}
#epiceditor-preview a:hover {
color: #00ff00;
text-decoration: none;
}
#epiceditor-preview a img {
border: none;
}
#epiceditor-preview p {
margin-bottom: 9px;
}
#epiceditor-preview h1,
#epiceditor-preview h2,
#epiceditor-preview h3,
#epiceditor-preview h4,
#epiceditor-preview h5,
#epiceditor-preview h6 {
color: #cdcdcd;
line-height: 36px;
}
#epiceditor-preview h1 {
margin-bottom: 18px;
font-size: 30px;
}
#epiceditor-preview h2 {
font-size: 24px;
}
#epiceditor-preview h3 {
font-size: 18px;
}
#epiceditor-preview h4 {
font-size: 16px;
}
#epiceditor-preview h5 {
font-size: 14px;
}
#epiceditor-preview h6 {
font-size: 13px;
}
#epiceditor-preview hr {
margin: 0 0 19px;
border: 0;
border-bottom: 1px solid #ccc;
}
#epiceditor-preview blockquote {
padding: 13px 13px 21px 15px;
margin-bottom: 18px;
font-family:georgia,serif;
font-style: italic;
}
#epiceditor-preview blockquote:before {
content:"\201C";
font-size:40px;
margin-left:-10px;
font-family:georgia,serif;
color:#eee;
}
#epiceditor-preview blockquote p {
font-size: 14px;
font-weight: 300;
line-height: 18px;
margin-bottom: 0;
font-style: italic;
}
#epiceditor-preview code, #epiceditor-preview pre {
font-family: Monaco, Andale Mono, Courier New, monospace;
}
#epiceditor-preview code {
background-color: #000;
color: #f92672;
padding: 1px 3px;
font-size: 12px;
-webkit-border-radius: 3px;
-moz-border-radius: 3px;
border-radius: 3px;
}
#epiceditor-preview pre {
display: block;
padding: 14px;
color:#66d9ef;
margin: 0 0 18px;
line-height: 16px;
font-size: 11px;
border: 1px solid #d9d9d9;
white-space: pre-wrap;
word-wrap: break-word;
}
#epiceditor-preview pre code {
background-color: #000;
color:#ccc;
font-size: 11px;
padding: 0;
}