Signed-off-by: Greg Wilkins <gregw@webtide.com>
This commit is contained in:
Greg Wilkins 2018-07-08 14:44:38 +00:00
parent c45ca9e38b
commit b42017b942
6 changed files with 104 additions and 671 deletions

View File

@ -1,618 +0,0 @@
#!/usr/bin/env bash
# LSB Tags
### BEGIN INIT INFO
# Provides: jetty
# Required-Start: $local_fs $network
# Required-Stop: $local_fs $network
# Default-Start: 2 3 4 5
# Default-Stop: 0 1 6
# Short-Description: Jetty start script.
# Description: Start Jetty web server.
### END INIT INFO
# Startup script for jetty under *nix systems (it works under NT/cygwin too).
##################################################
# Set the name which is used by other variables.
# Defaults to the file name without extension.
##################################################
NAME=$(echo $(basename $0) | sed -e 's/^[SK][0-9]*//' -e 's/\.sh$//')
# To get the service to restart correctly on reboot, uncomment below (3 lines):
# ========================
# chkconfig: 3 99 99
# description: Jetty 9 webserver
# processname: jetty
# ========================
# Configuration files
#
# /etc/default/$NAME
# If it exists, this is read at the start of script. It may perform any
# sequence of shell commands, like setting relevant environment variables.
#
# $HOME/.$NAMErc (e.g. $HOME/.jettyrc)
# If it exists, this is read at the start of script. It may perform any
# sequence of shell commands, like setting relevant environment variables.
#
# /etc/$NAME.conf
# If found, and no configurations were given on the command line,
# the file will be used as this script's configuration.
# Each line in the file may contain:
# - A comment denoted by the pound (#) sign as first non-blank character.
# - The path to a regular file, which will be passed to jetty as a
# config.xml file.
# - The path to a directory. Each *.xml file in the directory will be
# passed to jetty as a config.xml file.
# - All other lines will be passed, as-is to the start.jar
#
# The files will be checked for existence before being passed to jetty.
#
# Configuration variables
#
# JAVA
# Command to invoke Java. If not set, java (from the PATH) will be used.
#
# JAVA_OPTIONS
# Extra options to pass to the JVM
#
# JETTY_HOME
# Where Jetty is installed. If not set, the script will try go
# guess it by looking at the invocation path for the script
# The java system property "jetty.home" will be
# set to this value for use by configure.xml files, f.e.:
#
# <Arg><Property name="jetty.home" default="."/>/webapps/jetty.war</Arg>
#
# JETTY_BASE
# Where your Jetty base directory is. If not set, the value from
# $JETTY_HOME will be used.
#
# JETTY_RUN
# Where the $NAME.pid file should be stored. It defaults to the
# first available of /var/run, /usr/var/run, JETTY_BASE and /tmp
# if not set.
#
# JETTY_PID
# The Jetty PID file, defaults to $JETTY_RUN/$NAME.pid
#
# JETTY_ARGS
# The default arguments to pass to jetty.
# For example
# JETTY_ARGS=jetty.http.port=8080 jetty.ssl.port=8443
#
# JETTY_USER
# if set, then used as a username to run the server as
#
# JETTY_SHELL
# If set, then used as the shell by su when starting the server. Will have
# no effect if start-stop-daemon exists. Useful when JETTY_USER does not
# have shell access, e.g. /bin/false
#
# JETTY_START_TIMEOUT
# Time spent waiting to see if startup was successful/failed. Defaults to 60 seconds
#
usage()
{
echo "Usage: ${0##*/} [-d] {start|stop|run|restart|check|supervise} [ CONFIGS ... ] "
exit 1
}
[ $# -gt 0 ] || usage
##################################################
# Some utility functions
##################################################
findDirectory()
{
local L OP=$1
shift
for L in "$@"; do
[ "$OP" "$L" ] || continue
printf %s "$L"
break
done
}
running()
{
if [ -f "$1" ]
then
local PID=$(cat "$1" 2>/dev/null) || return 1
kill -0 "$PID" 2>/dev/null
return
fi
rm -f "$1"
return 1
}
started()
{
# wait for 60s to see "STARTED" in PID file, needs jetty-started.xml as argument
for ((T = 0; T < $(($3 / 4)); T++))
do
sleep 4
[ -z "$(grep STARTED $1 2>/dev/null)" ] || return 0
[ -z "$(grep STOPPED $1 2>/dev/null)" ] || return 1
[ -z "$(grep FAILED $1 2>/dev/null)" ] || return 1
local PID=$(cat "$2" 2>/dev/null) || return 1
kill -0 "$PID" 2>/dev/null || return 1
echo -n ". "
done
return 1;
}
readConfig()
{
(( DEBUG )) && echo "Reading $1.."
source "$1"
}
dumpEnv()
{
echo "JAVA = $JAVA"
echo "JAVA_OPTIONS = ${JAVA_OPTIONS[*]}"
echo "JETTY_HOME = $JETTY_HOME"
echo "JETTY_BASE = $JETTY_BASE"
echo "START_D = $START_D"
echo "START_INI = $START_INI"
echo "JETTY_START = $JETTY_START"
echo "JETTY_CONF = $JETTY_CONF"
echo "JETTY_ARGS = ${JETTY_ARGS[*]}"
echo "JETTY_RUN = $JETTY_RUN"
echo "JETTY_PID = $JETTY_PID"
echo "JETTY_START_LOG = $JETTY_START_LOG"
echo "JETTY_STATE = $JETTY_STATE"
echo "JETTY_START_TIMEOUT = $JETTY_START_TIMEOUT"
echo "RUN_CMD = ${RUN_CMD[*]}"
}
##################################################
# Get the action & configs
##################################################
CONFIGS=()
NO_START=0
DEBUG=0
while [[ $1 = -* ]]; do
case $1 in
-d) DEBUG=1 ;;
esac
shift
done
ACTION=$1
shift
##################################################
# Read any configuration files
##################################################
ETC=/etc
if [ $UID != 0 ]
then
ETC=$HOME/etc
fi
for CONFIG in {/etc,~/etc}/default/${NAME}{,9} $HOME/.${NAME}rc; do
if [ -f "$CONFIG" ] ; then
readConfig "$CONFIG"
fi
done
##################################################
# Set tmp if not already set.
##################################################
TMPDIR=${TMPDIR:-/tmp}
##################################################
# Jetty's hallmark
##################################################
JETTY_INSTALL_TRACE_FILE="start.jar"
##################################################
# Try to determine JETTY_HOME if not set
##################################################
if [ -z "$JETTY_HOME" ]
then
JETTY_SH=$0
case "$JETTY_SH" in
/*) JETTY_HOME=${JETTY_SH%/*/*} ;;
./*/*) JETTY_HOME=${JETTY_SH%/*/*} ;;
./*) JETTY_HOME=.. ;;
*/*/*) JETTY_HOME=./${JETTY_SH%/*/*} ;;
*/*) JETTY_HOME=. ;;
*) JETTY_HOME=.. ;;
esac
if [ ! -f "$JETTY_HOME/$JETTY_INSTALL_TRACE_FILE" ]
then
JETTY_HOME=
fi
fi
##################################################
# No JETTY_HOME yet? We're out of luck!
##################################################
if [ -z "$JETTY_HOME" ]; then
echo "** ERROR: JETTY_HOME not set, you need to set it or install in a standard location"
exit 1
fi
cd "$JETTY_HOME"
JETTY_HOME=$PWD
##################################################
# Set JETTY_BASE
##################################################
if [ -z "$JETTY_BASE" ]; then
JETTY_BASE=$JETTY_HOME
fi
cd "$JETTY_BASE"
JETTY_BASE=$PWD
#####################################################
# Check that jetty is where we think it is
#####################################################
if [ ! -r "$JETTY_HOME/$JETTY_INSTALL_TRACE_FILE" ]
then
echo "** ERROR: Oops! Jetty doesn't appear to be installed in $JETTY_HOME"
echo "** ERROR: $JETTY_HOME/$JETTY_INSTALL_TRACE_FILE is not readable!"
exit 1
fi
##################################################
# Try to find this script's configuration file,
# but only if no configurations were given on the
# command line.
##################################################
if [ -z "$JETTY_CONF" ]
then
if [ -f $ETC/${NAME}.conf ]
then
JETTY_CONF=$ETC/${NAME}.conf
elif [ -f "$JETTY_BASE/etc/jetty.conf" ]
then
JETTY_CONF=$JETTY_BASE/etc/jetty.conf
elif [ -f "$JETTY_HOME/etc/jetty.conf" ]
then
JETTY_CONF=$JETTY_HOME/etc/jetty.conf
fi
fi
#####################################################
# Find a location for the pid file
#####################################################
if [ -z "$JETTY_RUN" ]
then
JETTY_RUN=$(findDirectory -w /var/run /usr/var/run $JETTY_BASE /tmp)/jetty
[ -d "$JETTY_RUN" ] || mkdir $JETTY_RUN
fi
#####################################################
# define start log location
#####################################################
if [ -z "$JETTY_START_LOG" ]
then
JETTY_START_LOG="$JETTY_RUN/$NAME-start.log"
fi
#####################################################
# Find a pid and state file
#####################################################
if [ -z "$JETTY_PID" ]
then
JETTY_PID="$JETTY_RUN/${NAME}.pid"
fi
if [ -z "$JETTY_STATE" ]
then
JETTY_STATE=$JETTY_BASE/${NAME}.state
fi
case "`uname`" in
CYGWIN*) JETTY_STATE="`cygpath -w $JETTY_STATE`";;
esac
JETTY_ARGS=(${JETTY_ARGS[*]} "jetty.state=$JETTY_STATE")
##################################################
# Get the list of config.xml files from jetty.conf
##################################################
if [ -f "$JETTY_CONF" ] && [ -r "$JETTY_CONF" ]
then
while read -r CONF
do
if expr "$CONF" : '#' >/dev/null ; then
continue
fi
if [ -d "$CONF" ]
then
# assume it's a directory with configure.xml files
# for example: /etc/jetty.d/
# sort the files before adding them to the list of JETTY_ARGS
for XMLFILE in "$CONF/"*.xml
do
if [ -r "$XMLFILE" ] && [ -f "$XMLFILE" ]
then
JETTY_ARGS=(${JETTY_ARGS[*]} "$XMLFILE")
else
echo "** WARNING: Cannot read '$XMLFILE' specified in '$JETTY_CONF'"
fi
done
else
# assume it's a command line parameter (let start.jar deal with its validity)
JETTY_ARGS=(${JETTY_ARGS[*]} "$CONF")
fi
done < "$JETTY_CONF"
fi
##################################################
# Setup JAVA if unset
##################################################
if [ -z "$JAVA" ]
then
JAVA=$(which java)
fi
if [ -z "$JAVA" ]
then
echo "Cannot find a Java JDK. Please set either set JAVA or put java (>=1.5) in your PATH." >&2
exit 1
fi
#####################################################
# See if Deprecated JETTY_LOGS is defined
#####################################################
if [ "$JETTY_LOGS" ]
then
echo "** WARNING: JETTY_LOGS is Deprecated. Please configure logging within the jetty base." >&2
fi
#####################################################
# Set STARTED timeout
#####################################################
if [ -z "$JETTY_START_TIMEOUT"]
then
JETTY_START_TIMEOUT=60
fi
#####################################################
# Are we running on Windows? Could be, with Cygwin/NT.
#####################################################
case "`uname`" in
CYGWIN*) PATH_SEPARATOR=";";;
*) PATH_SEPARATOR=":";;
esac
#####################################################
# Add jetty properties to Java VM options.
#####################################################
case "`uname`" in
CYGWIN*)
JETTY_HOME="`cygpath -w $JETTY_HOME`"
JETTY_BASE="`cygpath -w $JETTY_BASE`"
TMPDIR="`cygpath -w $TMPDIR`"
;;
esac
JAVA_OPTIONS=(${JAVA_OPTIONS[*]} "-Djetty.home=$JETTY_HOME" "-Djetty.base=$JETTY_BASE" "-Djava.io.tmpdir=$TMPDIR")
#####################################################
# This is how the Jetty server will be started
#####################################################
JETTY_START=$JETTY_HOME/start.jar
START_INI=$JETTY_BASE/start.ini
START_D=$JETTY_BASE/start.d
if [ ! -f "$START_INI" -a ! -d "$START_D" ]
then
echo "Cannot find a start.ini file or a start.d directory in your JETTY_BASE directory: $JETTY_BASE" >&2
exit 1
fi
case "`uname`" in
CYGWIN*) JETTY_START="`cygpath -w $JETTY_START`";;
esac
RUN_ARGS=(${JAVA_OPTIONS[@]} -jar "$JETTY_START" ${JETTY_ARGS[*]})
RUN_CMD=("$JAVA" ${RUN_ARGS[@]})
#####################################################
# Comment these out after you're happy with what
# the script is doing.
#####################################################
if (( DEBUG ))
then
dumpEnv
fi
##################################################
# Do the action
##################################################
case "$ACTION" in
start)
echo -n "Starting Jetty: "
if (( NO_START )); then
echo "Not starting ${NAME} - NO_START=1";
exit
fi
if [ $UID -eq 0 ] && type start-stop-daemon > /dev/null 2>&1
then
unset CH_USER
if [ -n "$JETTY_USER" ]
then
CH_USER="-c$JETTY_USER"
fi
start-stop-daemon -S -p"$JETTY_PID" $CH_USER -d"$JETTY_BASE" -b -m -a "$JAVA" -- "${RUN_ARGS[@]}" start-log-file="$JETTY_START_LOG"
else
if running $JETTY_PID
then
echo "Already Running $(cat $JETTY_PID)!"
exit 1
fi
if [ -n "$JETTY_USER" ] && [ `whoami` != "$JETTY_USER" ]
then
unset SU_SHELL
if [ "$JETTY_SHELL" ]
then
SU_SHELL="-s $JETTY_SHELL"
fi
touch "$JETTY_PID"
chown "$JETTY_USER" "$JETTY_PID"
# FIXME: Broken solution: wordsplitting, pathname expansion, arbitrary command execution, etc.
su - "$JETTY_USER" $SU_SHELL -c "
cd \"$JETTY_BASE\"
exec ${RUN_CMD[*]} start-log-file=\"$JETTY_START_LOG\" > /dev/null &
disown \$!
echo \$! > \"$JETTY_PID\""
else
"${RUN_CMD[@]}" > /dev/null &
disown $!
echo $! > "$JETTY_PID"
fi
fi
if expr "${JETTY_ARGS[*]}" : '.*jetty-started.xml.*' >/dev/null
then
if started "$JETTY_STATE" "$JETTY_PID" "$JETTY_START_TIMEOUT"
then
echo "OK `date`"
else
echo "FAILED `date`"
exit 1
fi
else
echo "ok `date`"
fi
;;
stop)
echo -n "Stopping Jetty: "
if [ $UID -eq 0 ] && type start-stop-daemon > /dev/null 2>&1; then
start-stop-daemon -K -p"$JETTY_PID" -d"$JETTY_HOME" -a "$JAVA" -s HUP
TIMEOUT=30
while running "$JETTY_PID"; do
if (( TIMEOUT-- == 0 )); then
start-stop-daemon -K -p"$JETTY_PID" -d"$JETTY_HOME" -a "$JAVA" -s KILL
fi
sleep 1
done
else
if [ ! -f "$JETTY_PID" ] ; then
echo "ERROR: no pid found at $JETTY_PID"
exit 1
fi
PID=$(cat "$JETTY_PID" 2>/dev/null)
if [ -z "$PID" ] ; then
echo "ERROR: no pid id found in $JETTY_PID"
exit 1
fi
kill "$PID" 2>/dev/null
TIMEOUT=30
while running $JETTY_PID; do
if (( TIMEOUT-- == 0 )); then
kill -KILL "$PID" 2>/dev/null
fi
sleep 1
done
fi
rm -f "$JETTY_PID"
rm -f "$JETTY_STATE"
echo OK
;;
restart)
JETTY_SH=$0
> "$JETTY_STATE"
if [ ! -f $JETTY_SH ]; then
if [ ! -f $JETTY_HOME/bin/jetty.sh ]; then
echo "$JETTY_HOME/bin/jetty.sh does not exist."
exit 1
fi
JETTY_SH=$JETTY_HOME/bin/jetty.sh
fi
"$JETTY_SH" stop "$@"
"$JETTY_SH" start "$@"
;;
supervise)
#
# Under control of daemontools supervise monitor which
# handles restarts and shutdowns via the svc program.
#
exec "${RUN_CMD[@]}"
;;
run|demo)
echo "Running Jetty: "
if running "$JETTY_PID"
then
echo Already Running $(cat "$JETTY_PID")!
exit 1
fi
exec "${RUN_CMD[@]}"
;;
check|status)
if running "$JETTY_PID"
then
echo "Jetty running pid=$(< "$JETTY_PID")"
else
echo "Jetty NOT running"
fi
echo
dumpEnv
echo
if running "$JETTY_PID"
then
exit 0
fi
exit 1
;;
*)
usage
;;
esac
exit 0

View File

@ -27,6 +27,7 @@ import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpStatus;
import org.eclipse.jetty.http.MetaData;
import org.eclipse.jetty.http2.hpack.HpackContext.Entry;
import org.eclipse.jetty.util.TypeUtil;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
@ -66,14 +67,15 @@ public class HpackDecoder
_localMaxDynamicTableSize=localMaxdynamciTableSize;
}
public MetaData decode(ByteBuffer buffer)
public MetaData decode(ByteBuffer buffer) throws HpackException
{
if (LOG.isDebugEnabled())
LOG.debug(String.format("CtxTbl[%x] decoding %d octets",_context.hashCode(),buffer.remaining()));
// If the buffer is big, don't even think about decoding it
if (buffer.remaining()>_builder.getMaxSize())
throw new BadMessageException(HttpStatus.REQUEST_HEADER_FIELDS_TOO_LARGE_431,"Header frame size "+buffer.remaining()+">"+_builder.getMaxSize());
throw new HpackException.Session("431 Request Header Fields too large",6);
//throw new BadMessageException(HttpStatus.REQUEST_HEADER_FIELDS_TOO_LARGE_431,"Header frame size "+buffer.remaining()+">"+_builder.getMaxSize());
while(buffer.hasRemaining())
{

View File

@ -29,18 +29,22 @@ import org.eclipse.jetty.http.HttpScheme;
import org.eclipse.jetty.http.HttpStatus;
import org.eclipse.jetty.http.HttpVersion;
import org.eclipse.jetty.http.MetaData;
import org.eclipse.jetty.http2.hpack.HpackException.Session;
public class MetaDataBuilder
{
private final int _maxSize;
private int _size;
private int _status;
private int _status=-1;
private String _method;
private HttpScheme _scheme;
private HostPortHttpField _authority;
private String _path;
private long _contentLength=Long.MIN_VALUE;
private HttpFields _fields = new HttpFields(10);
private HpackException.Stream _streamException;
private boolean _request;
private boolean _response;
/**
* @param maxHeadersSize The maximum size of the headers, expressed as total name and value characters.
@ -66,7 +70,7 @@ public class MetaDataBuilder
return _size;
}
public void emit(HttpField field)
public void emit(HttpField field) throws HpackException.Session
{
HttpHeader header = field.getHeader();
String name = field.getName();
@ -74,7 +78,7 @@ public class MetaDataBuilder
int field_size = name.length() + (value == null ? 0 : value.length());
_size+=field_size+32;
if (_size>_maxSize)
throw new BadMessageException(HttpStatus.REQUEST_HEADER_FIELDS_TOO_LARGE_431,"Header size "+_size+">"+_maxSize);
throw new HpackException.Session("Header Size %d > %d",_size,_maxSize);
if (field instanceof StaticTableHttpField)
{
@ -82,15 +86,21 @@ public class MetaDataBuilder
switch(header)
{
case C_STATUS:
_status=(Integer)staticField.getStaticValue();
if(checkHeader(header, _status))
_status = (Integer)staticField.getStaticValue();
_response = true;
break;
case C_METHOD:
_method=value;
if(checkPseudoHeader(header, _method))
_method = value;
_request = true;
break;
case C_SCHEME:
_scheme = (HttpScheme)staticField.getStaticValue();
if(checkPseudoHeader(header, _scheme))
_scheme = (HttpScheme)staticField.getStaticValue();
_request = true;
break;
default:
@ -102,23 +112,32 @@ public class MetaDataBuilder
switch(header)
{
case C_STATUS:
_status=field.getIntValue();
if(checkHeader(header, _status))
_status = field.getIntValue();
_response = true;
break;
case C_METHOD:
_method=value;
if(checkPseudoHeader(header, _method))
_method = value;
_request = true;
break;
case C_SCHEME:
if (value != null)
if(checkPseudoHeader(header, _scheme) && value != null)
_scheme = HttpScheme.CACHE.get(value);
_request = true;
break;
case C_AUTHORITY:
if (field instanceof HostPortHttpField)
_authority = (HostPortHttpField)field;
else if (value != null)
_authority = new AuthorityHttpField(value);
if(checkPseudoHeader(header, _authority))
{
if (field instanceof HostPortHttpField)
_authority = (HostPortHttpField)field;
else if (value != null)
_authority = new AuthorityHttpField(value);
}
_request = true;
break;
case HOST:
@ -134,29 +153,89 @@ public class MetaDataBuilder
break;
case C_PATH:
_path = value;
if(checkPseudoHeader(header, _path))
_path = value;
_request = true;
break;
case CONTENT_LENGTH:
_contentLength = field.getLongValue();
_fields.add(field);
break;
case TE:
if ("trailors".equalsIgnoreCase(value))
_fields.add(field);
else
streamException("unsupported TE value %s", value);
break;
case CONNECTION:
// TODO should other connection specific fields be listed here?
streamException("Connection specific field %s", header);
break;
default:
if (name.charAt(0)!=':')
default:
if (name.charAt(0)==':')
streamException("Unknown psuodo header %s", name);
else
_fields.add(field);
break;
}
}
else
{
if (name.charAt(0)!=':')
if (name.charAt(0)==':')
streamException("Unknown psuedo header %s",name);
else
_fields.add(field);
}
}
public MetaData build()
private void streamException(String messageFormat, Object... args)
{
HpackException.Stream stream = new HpackException.Stream(messageFormat, args);
if (_streamException==null)
_streamException = stream;
else
_streamException.addSuppressed(stream);
}
private boolean checkHeader(HttpHeader header, int value)
{
if (_fields.size()>0)
{
streamException("Psuedo header %s after fields", header.asString());
return false;
}
if (value==-1)
return true;
streamException("Duplicate psuedo header %s", header.asString());
return false;
}
private boolean checkPseudoHeader(HttpHeader header, Object value)
{
if (_fields.size()>0)
{
streamException("Psuedo header %s after fields", header.asString());
return false;
}
if (value==null)
return true;
streamException("Duplicate psuedo header %s", header.asString());
return false;
}
public MetaData build() throws HpackException.Stream
{
if (_streamException!=null)
throw _streamException;
if (_request && _response)
throw new HpackException.Stream("Request and Response headers");
try
{
HttpFields fields = _fields;
@ -189,13 +268,14 @@ public class MetaDataBuilder
* Check that the max size will not be exceeded.
* @param length the length
* @param huffman the huffman name
* @throws Session
*/
public void checkSize(int length, boolean huffman)
public void checkSize(int length, boolean huffman) throws Session
{
// Apply a huffman fudge factor
if (huffman)
length=(length*4)/3;
if ((_size+length)>_maxSize)
throw new BadMessageException(HttpStatus.REQUEST_HEADER_FIELDS_TOO_LARGE_431,"Header size "+(_size+length)+">"+_maxSize);
throw new HpackException.Session("Header too large %d > %d", _size+length, _maxSize);
}
}

View File

@ -1,21 +0,0 @@
Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: TestIt
Bundle-SymbolicName: TestIt
Bundle-Version: 1.0.0.qualifier
Bundle-Activator: testit.Activator
Import-Package: javax.servlet;version="2.6",
javax.servlet.http;version="2.6",
javax.servlet.jsp,
javax.servlet.jsp.tagext
Require-Bundle: org.eclipse.jetty.client,
org.eclipse.jetty.proxy,
org.eclipse.jetty.http,
org.eclipse.jetty.io,
org.eclipse.jetty.util
Bundle-ClassPath: WEB-INF/classes
Bundle-RequiredExecutionEnvironment: JavaSE-1.7
Bundle-ActivationPolicy: lazy
Web-ContextPath: /
Class-Path:

View File

@ -1,7 +0,0 @@
body {color: #2E2E2E; font-family:sans-serif; font-size:90%;}
h1 {font-variant: small-caps; font-size:130%; letter-spacing: 0.1em;}
h2 {font-variant: small-caps; font-size:100%; letter-spacing: 0.1em; margin-top:2em}
h3 {font-size:100%; letter-spacing: 0.1em;}
span.pass { color: green; }
span.fail { color:red; }

View File

@ -1,3 +0,0 @@
Manifest-Version: 1.0
Class-Path: