]> WPIA git - gigi.git/commitdiff
Merge remote-tracking branch 'origin/libs/scrypt/local'
authorFelix Dörre <felix@dogcraft.de>
Sun, 1 Mar 2015 00:33:57 +0000 (01:33 +0100)
committerFelix Dörre <felix@dogcraft.de>
Sun, 1 Mar 2015 00:33:57 +0000 (01:33 +0100)
723 files changed:
.classpath
.gitignore
.settings/org.eclipse.core.runtime.prefs [new file with mode: 0644]
.settings/org.eclipse.jdt.core.prefs [new file with mode: 0644]
.settings/org.eclipse.jdt.ui.prefs [new file with mode: 0644]
Gigi.MF [new file with mode: 0644]
LICENSE
LICENSE.BSD [new file with mode: 0644]
LICENSE.GPL [new file with mode: 0644]
README.md
build.xml [new file with mode: 0644]
config/.gitignore [new file with mode: 0644]
config/gigi.properties.template [new file with mode: 0644]
config/test.properties.template [new file with mode: 0644]
debian/.gitignore [new file with mode: 0644]
debian/cacert-gigi-testing.cacert-gigi-signer.default [new file with mode: 0644]
debian/cacert-gigi-testing.cacert-gigi-signer.init [new file with mode: 0644]
debian/cacert-gigi-testing.cacert-gigi.default [new file with mode: 0644]
debian/cacert-gigi-testing.cacert-gigi.init [new file with mode: 0644]
debian/cacert-gigi-testing.docs [new file with mode: 0644]
debian/cacert-gigi-testing.manpages [new file with mode: 0644]
debian/cacert-gigi-testing.postinst [new file with mode: 0644]
debian/cacert-gigi.cacert-gigi-signer.init [new file with mode: 0644]
debian/cacert-gigi.cacert-gigi.init [new file with mode: 0644]
debian/cacert-gigi.docs [new file with mode: 0644]
debian/cacert-gigi.manpages [new file with mode: 0644]
debian/cacert-gigi.postinst [new file with mode: 0644]
debian/changelog [new file with mode: 0644]
debian/compat [new file with mode: 0644]
debian/control [new file with mode: 0644]
debian/copyright [new file with mode: 0644]
debian/gigi.1 [new file with mode: 0644]
debian/rules [new file with mode: 0755]
debian/source/format [new file with mode: 0644]
doc/.gitignore [new file with mode: 0644]
doc/TemplateSyntax.txt [new file with mode: 0644]
doc/beforeYouStart.txt [new file with mode: 0644]
doc/exoticKeys.sh [new file with mode: 0644]
doc/jenkinsJob/README.txt [new file with mode: 0644]
doc/jenkinsJob/ci-tests-setup.txt [new file with mode: 0644]
doc/jenkinsJob/config.xml [new file with mode: 0644]
doc/jenkinsJob/dyn-txt.php [new file with mode: 0644]
doc/scripts/.gitignore [new file with mode: 0644]
doc/scripts/generateSomeCsrs.sh [new file with mode: 0755]
doc/scripts/getJetty.sh [new file with mode: 0644]
doc/scripts/gigi [new file with mode: 0755]
keys/.dirinfo [new file with mode: 0644]
keys/.gitignore [new file with mode: 0644]
keys/generateKeys.sh [new file with mode: 0755]
keys/generateTruststore.sh [new file with mode: 0755]
keys/selfsign.config [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/http/DateGenerator.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/http/DateParser.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/http/HttpContent.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/http/HttpCookie.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/http/HttpField.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/http/HttpFields.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/http/HttpGenerator.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/http/HttpHeader.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/http/HttpHeaderValue.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/http/HttpMethod.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/http/HttpParser.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/http/HttpScheme.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/http/HttpStatus.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/http/HttpTester.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/http/HttpTokens.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/http/HttpURI.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/http/HttpVersion.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/http/MimeTypes.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/http/PathMap.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/http/encoding.properties [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/http/mime.properties [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/http/package-info.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/http/useragents [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/io/AbstractConnection.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/io/AbstractEndPoint.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/io/ArrayByteBufferPool.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/io/ByteArrayEndPoint.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/io/ByteBufferPool.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/io/ChannelEndPoint.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/io/ClientConnectionFactory.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/io/Connection.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/io/EndPoint.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/io/EofException.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/io/FillInterest.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/io/IdleTimeout.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/io/LeakTrackingByteBufferPool.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/io/MappedByteBufferPool.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/io/NegotiatingClientConnection.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/io/NegotiatingClientConnectionFactory.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/io/NetworkTrafficListener.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/io/NetworkTrafficSelectChannelEndPoint.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/io/RuntimeIOException.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/io/SelectChannelEndPoint.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/io/SelectorManager.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/io/UncheckedPrintWriter.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/io/WriteFlusher.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/io/WriterOutputStream.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/io/package-info.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/io/ssl/SslClientConnectionFactory.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/io/ssl/SslConnection.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/io/ssl/SslReconfigurator.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/io/ssl/package-info.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/security/AbstractUserAuthentication.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/security/Authenticator.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/security/ConstraintAware.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/security/ConstraintMapping.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/security/ConstraintSecurityHandler.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/security/CrossContextPsuedoSession.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/security/DefaultAuthenticatorFactory.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/security/DefaultIdentityService.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/security/DefaultUserIdentity.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/security/HashCrossContextPsuedoSession.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/security/HashLoginService.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/security/IdentityService.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/security/JDBCLoginService.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/security/LoginService.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/security/MappedLoginService.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/security/PropertyUserStore.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/security/RoleInfo.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/security/RoleRunAsToken.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/security/RunAsToken.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/security/SecurityHandler.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/security/ServerAuthException.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/security/SpnegoLoginService.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/security/SpnegoUserIdentity.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/security/SpnegoUserPrincipal.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/security/UserAuthentication.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/security/UserDataConstraint.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/security/authentication/BasicAuthenticator.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/security/authentication/ClientCertAuthenticator.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/security/authentication/DeferredAuthentication.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/security/authentication/DigestAuthenticator.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/security/authentication/FormAuthenticator.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/security/authentication/LoginAuthenticator.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/security/authentication/LoginCallback.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/security/authentication/LoginCallbackImpl.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/security/authentication/SessionAuthentication.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/security/authentication/SpnegoAuthenticator.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/security/authentication/package-info.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/security/package-info.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/server/AbstractConnectionFactory.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/server/AbstractConnector.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/server/AbstractNCSARequestLog.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/server/AbstractNetworkConnector.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/server/AsyncContextEvent.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/server/AsyncContextState.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/server/AsyncNCSARequestLog.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/server/Authentication.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/server/ByteBufferQueuedHttpInput.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/server/ClassLoaderDump.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/server/ConnectionFactory.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/server/Connector.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/server/ConnectorStatistics.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/server/CookieCutter.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/server/Dispatcher.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/server/EncodingHttpWriter.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/server/ForwardedRequestCustomizer.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/server/Handler.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/server/HandlerContainer.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/server/HostHeaderCustomizer.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/server/HttpChannel.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/server/HttpChannelState.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/server/HttpConfiguration.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/server/HttpConnection.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/server/HttpConnectionFactory.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/server/HttpInput.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/server/HttpInputOverHTTP.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/server/HttpOutput.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/server/HttpTransport.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/server/HttpWriter.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/server/InclusiveByteRange.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/server/Iso88591HttpWriter.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/server/LocalConnector.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/server/LowResourceMonitor.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/server/NCSARequestLog.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/server/NegotiatingServerConnection.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/server/NegotiatingServerConnectionFactory.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/server/NetworkConnector.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/server/NetworkTrafficServerConnector.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/server/QueuedHttpInput.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/server/QuietServletException.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/server/Request.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/server/RequestLog.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/server/ResourceCache.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/server/Response.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/server/SecureRequestCustomizer.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/server/Server.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/server/ServerConnector.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/server/ServletRequestHttpWrapper.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/server/ServletResponseHttpWrapper.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/server/SessionIdManager.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/server/SessionManager.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/server/ShutdownMonitor.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/server/SslConnectionFactory.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/server/UserIdentity.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/server/Utf8HttpWriter.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/server/handler/AbstractHandler.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/server/handler/AbstractHandlerContainer.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/server/handler/AllowSymLinkAliasChecker.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/server/handler/ContextHandler.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/server/handler/ContextHandlerCollection.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/server/handler/DebugHandler.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/server/handler/DefaultHandler.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/server/handler/ErrorHandler.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/server/handler/HandlerCollection.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/server/handler/HandlerList.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/server/handler/HandlerWrapper.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/server/handler/HotSwapHandler.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/server/handler/IPAccessHandler.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/server/handler/IdleTimeoutHandler.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/server/handler/MovedContextHandler.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/server/handler/RequestLogHandler.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/server/handler/ResourceHandler.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/server/handler/ScopedHandler.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/server/handler/ShutdownHandler.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/server/handler/StatisticsHandler.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/server/handler/package-info.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/server/nio/NetworkTrafficSelectChannelConnector.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/server/nio/package-info.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/server/package-info.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/server/session/AbstractSession.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/server/session/AbstractSessionIdManager.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/server/session/AbstractSessionManager.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/server/session/HashSessionIdManager.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/server/session/HashSessionManager.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/server/session/HashedSession.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/server/session/JDBCSessionIdManager.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/server/session/JDBCSessionManager.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/server/session/MemSession.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/server/session/SessionHandler.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/server/session/package-info.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/servlet/BaseHolder.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/servlet/DefaultServlet.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/servlet/ErrorPageErrorHandler.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/servlet/FilterHolder.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/servlet/FilterMapping.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/servlet/Holder.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/servlet/Invoker.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/servlet/JspPropertyGroupServlet.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/servlet/ListenerHolder.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/servlet/NoJspServlet.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/servlet/ServletContextHandler.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/servlet/ServletHandler.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/servlet/ServletHolder.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/servlet/ServletMapping.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/servlet/ServletTester.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/servlet/StatisticsServlet.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/servlet/listener/ELContextCleaner.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/servlet/listener/IntrospectorCleaner.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/servlet/listener/package-info.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/servlet/package-info.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/AbstractTrie.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/ArrayQueue.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/ArrayTernaryTrie.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/ArrayTrie.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/ArrayUtil.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/Atomics.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/Attributes.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/AttributesMap.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/B64Code.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/BlockingArrayQueue.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/BlockingCallback.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/BufferUtil.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/ByteArrayISO8859Writer.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/ByteArrayOutputStream2.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/Callback.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/ClassLoadingObjectInputStream.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/CompletableCallback.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/ConcurrentArrayQueue.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/ConcurrentHashSet.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/DateCache.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/Fields.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/ForkInvoker.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/FutureCallback.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/FuturePromise.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/HostMap.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/HttpCookieStore.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/IO.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/IPAddressMap.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/IntrospectionUtil.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/IteratingCallback.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/IteratingNestedCallback.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/Jetty.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/LazyList.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/LeakDetector.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/Loader.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/MemoryUtils.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/MultiException.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/MultiMap.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/MultiPartInputStreamParser.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/MultiPartOutputStream.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/MultiPartWriter.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/PatternMatcher.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/Promise.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/QuotedStringTokenizer.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/ReadLineInputStream.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/RolloverFileOutputStream.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/Scanner.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/SharedBlockingCallback.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/SocketAddressResolver.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/StringUtil.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/TreeTrie.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/Trie.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/TypeUtil.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/URIUtil.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/UrlEncoded.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/Utf8Appendable.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/Utf8LineParser.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/Utf8StringBuffer.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/Utf8StringBuilder.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/annotation/ManagedAttribute.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/annotation/ManagedObject.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/annotation/ManagedOperation.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/annotation/Name.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/annotation/package-info.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/component/AbstractLifeCycle.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/component/Container.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/component/ContainerLifeCycle.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/component/Destroyable.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/component/Dumpable.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/component/FileDestroyable.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/component/FileNoticeLifeCycleListener.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/component/Graceful.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/component/LifeCycle.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/component/package-info.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/log/AbstractLogger.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/log/JavaUtilLog.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/log/Log.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/log/Logger.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/log/LoggerLog.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/log/StacklessLogging.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/log/StdErrLog.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/log/package-info.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/package-info.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/preventers/AWTLeakPreventer.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/preventers/AbstractLeakPreventer.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/preventers/AppContextLeakPreventer.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/preventers/DOMLeakPreventer.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/preventers/DriverManagerLeakPreventer.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/preventers/GCThreadLeakPreventer.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/preventers/Java2DLeakPreventer.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/preventers/LDAPLeakPreventer.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/preventers/LoginConfigurationLeakPreventer.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/preventers/SecurityProviderLeakPreventer.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/preventers/package-info.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/resource/BadResource.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/resource/EmptyResource.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/resource/FileResource.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/resource/JarFileResource.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/resource/JarResource.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/resource/Resource.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/resource/ResourceCollection.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/resource/ResourceFactory.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/resource/URLResource.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/resource/package-info.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/security/CertificateUtils.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/security/CertificateValidator.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/security/Constraint.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/security/Credential.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/security/Password.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/security/UnixCrypt.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/security/package-info.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/ssl/AliasedX509ExtendedKeyManager.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/ssl/AliasedX509KeyManager.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/ssl/SslContextFactory.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/ssl/package-info.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/statistic/CounterStatistic.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/statistic/SampleStatistic.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/statistic/package-info.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/thread/ExecutorThreadPool.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/thread/NonBlockingThread.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/thread/QueuedThreadPool.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/thread/ScheduledExecutorScheduler.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/thread/Scheduler.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/thread/ShutdownThread.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/thread/ThreadPool.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/thread/TimerScheduler.java [new file with mode: 0644]
lib/jetty/org/eclipse/jetty/util/thread/package-info.java [new file with mode: 0644]
lib/jtar/org/kamranzafar/jtar/Octal.java [new file with mode: 0644]
lib/jtar/org/kamranzafar/jtar/TarConstants.java [new file with mode: 0644]
lib/jtar/org/kamranzafar/jtar/TarEntry.java [new file with mode: 0644]
lib/jtar/org/kamranzafar/jtar/TarHeader.java [new file with mode: 0644]
lib/jtar/org/kamranzafar/jtar/TarInputStream.java [new file with mode: 0644]
lib/jtar/org/kamranzafar/jtar/TarOutputStream.java [new file with mode: 0644]
lib/jtar/org/kamranzafar/jtar/TarUtils.java [new file with mode: 0644]
lib/servlet-api/javax/servlet/AsyncContext.java [new file with mode: 0644]
lib/servlet-api/javax/servlet/AsyncEvent.java [new file with mode: 0644]
lib/servlet-api/javax/servlet/AsyncListener.java [new file with mode: 0644]
lib/servlet-api/javax/servlet/DispatcherType.java [new file with mode: 0644]
lib/servlet-api/javax/servlet/Filter.java [new file with mode: 0644]
lib/servlet-api/javax/servlet/FilterChain.java [new file with mode: 0644]
lib/servlet-api/javax/servlet/FilterConfig.java [new file with mode: 0644]
lib/servlet-api/javax/servlet/FilterRegistration.java [new file with mode: 0644]
lib/servlet-api/javax/servlet/GenericServlet.java [new file with mode: 0644]
lib/servlet-api/javax/servlet/HttpConstraintElement.java [new file with mode: 0644]
lib/servlet-api/javax/servlet/HttpMethodConstraintElement.java [new file with mode: 0644]
lib/servlet-api/javax/servlet/LocalStrings.properties [new file with mode: 0644]
lib/servlet-api/javax/servlet/LocalStrings_es.properties [new file with mode: 0644]
lib/servlet-api/javax/servlet/LocalStrings_fr.properties [new file with mode: 0644]
lib/servlet-api/javax/servlet/LocalStrings_ja.properties [new file with mode: 0644]
lib/servlet-api/javax/servlet/MultipartConfigElement.java [new file with mode: 0644]
lib/servlet-api/javax/servlet/ReadListener.java [new file with mode: 0644]
lib/servlet-api/javax/servlet/Registration.java [new file with mode: 0644]
lib/servlet-api/javax/servlet/RequestDispatcher.java [new file with mode: 0644]
lib/servlet-api/javax/servlet/Servlet.java [new file with mode: 0644]
lib/servlet-api/javax/servlet/ServletConfig.java [new file with mode: 0644]
lib/servlet-api/javax/servlet/ServletContainerInitializer.java [new file with mode: 0644]
lib/servlet-api/javax/servlet/ServletContext.java [new file with mode: 0644]
lib/servlet-api/javax/servlet/ServletContextAttributeEvent.java [new file with mode: 0644]
lib/servlet-api/javax/servlet/ServletContextAttributeListener.java [new file with mode: 0644]
lib/servlet-api/javax/servlet/ServletContextEvent.java [new file with mode: 0644]
lib/servlet-api/javax/servlet/ServletContextListener.java [new file with mode: 0644]
lib/servlet-api/javax/servlet/ServletException.java [new file with mode: 0644]
lib/servlet-api/javax/servlet/ServletInputStream.java [new file with mode: 0644]
lib/servlet-api/javax/servlet/ServletOutputStream.java [new file with mode: 0644]
lib/servlet-api/javax/servlet/ServletRegistration.java [new file with mode: 0644]
lib/servlet-api/javax/servlet/ServletRequest.java [new file with mode: 0644]
lib/servlet-api/javax/servlet/ServletRequestAttributeEvent.java [new file with mode: 0644]
lib/servlet-api/javax/servlet/ServletRequestAttributeListener.java [new file with mode: 0644]
lib/servlet-api/javax/servlet/ServletRequestEvent.java [new file with mode: 0644]
lib/servlet-api/javax/servlet/ServletRequestListener.java [new file with mode: 0644]
lib/servlet-api/javax/servlet/ServletRequestWrapper.java [new file with mode: 0644]
lib/servlet-api/javax/servlet/ServletResponse.java [new file with mode: 0644]
lib/servlet-api/javax/servlet/ServletResponseWrapper.java [new file with mode: 0644]
lib/servlet-api/javax/servlet/ServletSecurityElement.java [new file with mode: 0644]
lib/servlet-api/javax/servlet/SessionCookieConfig.java [new file with mode: 0644]
lib/servlet-api/javax/servlet/SessionTrackingMode.java [new file with mode: 0644]
lib/servlet-api/javax/servlet/SingleThreadModel.java [new file with mode: 0644]
lib/servlet-api/javax/servlet/UnavailableException.java [new file with mode: 0644]
lib/servlet-api/javax/servlet/WriteListener.java [new file with mode: 0644]
lib/servlet-api/javax/servlet/annotation/HandlesTypes.java [new file with mode: 0644]
lib/servlet-api/javax/servlet/annotation/HttpConstraint.java [new file with mode: 0644]
lib/servlet-api/javax/servlet/annotation/HttpMethodConstraint.java [new file with mode: 0644]
lib/servlet-api/javax/servlet/annotation/MultipartConfig.java [new file with mode: 0644]
lib/servlet-api/javax/servlet/annotation/ServletSecurity.java [new file with mode: 0644]
lib/servlet-api/javax/servlet/annotation/WebFilter.java [new file with mode: 0644]
lib/servlet-api/javax/servlet/annotation/WebInitParam.java [new file with mode: 0644]
lib/servlet-api/javax/servlet/annotation/WebListener.java [new file with mode: 0644]
lib/servlet-api/javax/servlet/annotation/WebServlet.java [new file with mode: 0644]
lib/servlet-api/javax/servlet/descriptor/JspConfigDescriptor.java [new file with mode: 0644]
lib/servlet-api/javax/servlet/descriptor/JspPropertyGroupDescriptor.java [new file with mode: 0644]
lib/servlet-api/javax/servlet/descriptor/TaglibDescriptor.java [new file with mode: 0644]
lib/servlet-api/javax/servlet/http/Cookie.java [new file with mode: 0644]
lib/servlet-api/javax/servlet/http/HttpServlet.java [new file with mode: 0644]
lib/servlet-api/javax/servlet/http/HttpServletRequest.java [new file with mode: 0644]
lib/servlet-api/javax/servlet/http/HttpServletRequestWrapper.java [new file with mode: 0644]
lib/servlet-api/javax/servlet/http/HttpServletResponse.java [new file with mode: 0644]
lib/servlet-api/javax/servlet/http/HttpServletResponseWrapper.java [new file with mode: 0644]
lib/servlet-api/javax/servlet/http/HttpSession.java [new file with mode: 0644]
lib/servlet-api/javax/servlet/http/HttpSessionActivationListener.java [new file with mode: 0644]
lib/servlet-api/javax/servlet/http/HttpSessionAttributeListener.java [new file with mode: 0644]
lib/servlet-api/javax/servlet/http/HttpSessionBindingEvent.java [new file with mode: 0644]
lib/servlet-api/javax/servlet/http/HttpSessionBindingListener.java [new file with mode: 0644]
lib/servlet-api/javax/servlet/http/HttpSessionContext.java [new file with mode: 0644]
lib/servlet-api/javax/servlet/http/HttpSessionEvent.java [new file with mode: 0644]
lib/servlet-api/javax/servlet/http/HttpSessionIdListener.java [new file with mode: 0644]
lib/servlet-api/javax/servlet/http/HttpSessionListener.java [new file with mode: 0644]
lib/servlet-api/javax/servlet/http/HttpUpgradeHandler.java [new file with mode: 0644]
lib/servlet-api/javax/servlet/http/HttpUtils.java [new file with mode: 0644]
lib/servlet-api/javax/servlet/http/LocalStrings.properties [new file with mode: 0644]
lib/servlet-api/javax/servlet/http/LocalStrings_es.properties [new file with mode: 0644]
lib/servlet-api/javax/servlet/http/LocalStrings_fr.properties [new file with mode: 0644]
lib/servlet-api/javax/servlet/http/LocalStrings_ja.properties [new file with mode: 0644]
lib/servlet-api/javax/servlet/http/Part.java [new file with mode: 0644]
lib/servlet-api/javax/servlet/http/WebConnection.java [new file with mode: 0644]
lib/servlet-api/javax/servlet/http/package.html [new file with mode: 0644]
lib/servlet-api/javax/servlet/package.html [new file with mode: 0644]
locale/.gitignore [new file with mode: 0644]
natives/.gitignore [new file with mode: 0644]
natives/Makefile [new file with mode: 0644]
natives/org_cacert_gigi_natives_SetUID.c [new file with mode: 0644]
src/org/cacert/gigi/DevelLauncher.java [new file with mode: 0644]
src/org/cacert/gigi/Gigi.java [new file with mode: 0644]
src/org/cacert/gigi/Gigi.templ [new file with mode: 0644]
src/org/cacert/gigi/GigiApiException.java [new file with mode: 0644]
src/org/cacert/gigi/GigiConfig.java [new file with mode: 0644]
src/org/cacert/gigi/Launcher.java [new file with mode: 0644]
src/org/cacert/gigi/PermissionCheckable.java [new file with mode: 0644]
src/org/cacert/gigi/PolicyRedirector.java [new file with mode: 0644]
src/org/cacert/gigi/api/GigiAPI.java [new file with mode: 0644]
src/org/cacert/gigi/crypto/SMIME.java [new file with mode: 0644]
src/org/cacert/gigi/crypto/SPKAC.java [new file with mode: 0644]
src/org/cacert/gigi/database/DatabaseConnection.java [new file with mode: 0644]
src/org/cacert/gigi/database/GigiPreparedStatement.java [new file with mode: 0644]
src/org/cacert/gigi/database/GigiResultSet.java [new file with mode: 0644]
src/org/cacert/gigi/database/SQLFileManager.java [new file with mode: 0644]
src/org/cacert/gigi/database/tableStructure.sql [new file with mode: 0644]
src/org/cacert/gigi/dbObjects/Assurance.java [new file with mode: 0644]
src/org/cacert/gigi/dbObjects/CPS.properties [new file with mode: 0644]
src/org/cacert/gigi/dbObjects/Certificate.java [new file with mode: 0644]
src/org/cacert/gigi/dbObjects/CertificateOwner.java [new file with mode: 0644]
src/org/cacert/gigi/dbObjects/CertificateProfile.java [new file with mode: 0644]
src/org/cacert/gigi/dbObjects/Digest.java [new file with mode: 0644]
src/org/cacert/gigi/dbObjects/Domain.java [new file with mode: 0644]
src/org/cacert/gigi/dbObjects/DomainPingConfiguration.java [new file with mode: 0644]
src/org/cacert/gigi/dbObjects/EmailAddress.java [new file with mode: 0644]
src/org/cacert/gigi/dbObjects/Group.java [new file with mode: 0644]
src/org/cacert/gigi/dbObjects/IdCachable.java [new file with mode: 0644]
src/org/cacert/gigi/dbObjects/Name.java [new file with mode: 0644]
src/org/cacert/gigi/dbObjects/ObjectCache.java [new file with mode: 0644]
src/org/cacert/gigi/dbObjects/Organisation.java [new file with mode: 0644]
src/org/cacert/gigi/dbObjects/SupportedUser.java [new file with mode: 0644]
src/org/cacert/gigi/dbObjects/User.java [new file with mode: 0644]
src/org/cacert/gigi/email/EmailProvider.java [new file with mode: 0644]
src/org/cacert/gigi/email/MailProbe.java [new file with mode: 0644]
src/org/cacert/gigi/email/Sendmail.java [new file with mode: 0644]
src/org/cacert/gigi/localisation/Language.java [new file with mode: 0644]
src/org/cacert/gigi/natives/SetUID.java [new file with mode: 0644]
src/org/cacert/gigi/output/AssurancesDisplay.java [new file with mode: 0644]
src/org/cacert/gigi/output/AssurancesDisplay.templ [new file with mode: 0644]
src/org/cacert/gigi/output/CertificateIterable.java [new file with mode: 0644]
src/org/cacert/gigi/output/CertificateTable.templ [new file with mode: 0644]
src/org/cacert/gigi/output/CertificateValiditySelector.java [new file with mode: 0644]
src/org/cacert/gigi/output/ClientCSRGenerate.java [new file with mode: 0644]
src/org/cacert/gigi/output/ClientCSRGenerate.templ [new file with mode: 0644]
src/org/cacert/gigi/output/ClientCSRGenerateIE.templ [new file with mode: 0644]
src/org/cacert/gigi/output/DateSelector.java [new file with mode: 0644]
src/org/cacert/gigi/output/HashAlgorithms.java [new file with mode: 0644]
src/org/cacert/gigi/output/IMenuItem.java [new file with mode: 0644]
src/org/cacert/gigi/output/Menu.java [new file with mode: 0644]
src/org/cacert/gigi/output/PageMenuItem.java [new file with mode: 0644]
src/org/cacert/gigi/output/SimpleMenuItem.java [new file with mode: 0644]
src/org/cacert/gigi/output/template/ForeachStatement.java [new file with mode: 0644]
src/org/cacert/gigi/output/template/Form.java [new file with mode: 0644]
src/org/cacert/gigi/output/template/IfStatement.java [new file with mode: 0644]
src/org/cacert/gigi/output/template/IterableDataset.java [new file with mode: 0644]
src/org/cacert/gigi/output/template/OutputVariableCommand.java [new file with mode: 0644]
src/org/cacert/gigi/output/template/Outputable.java [new file with mode: 0644]
src/org/cacert/gigi/output/template/OutputableArrayIterable.java [new file with mode: 0644]
src/org/cacert/gigi/output/template/Scope.java [new file with mode: 0644]
src/org/cacert/gigi/output/template/SprintfCommand.java [new file with mode: 0644]
src/org/cacert/gigi/output/template/Template.java [new file with mode: 0644]
src/org/cacert/gigi/output/template/TemplateBlock.java [new file with mode: 0644]
src/org/cacert/gigi/output/template/TranslateCommand.java [new file with mode: 0644]
src/org/cacert/gigi/pages/LoginPage.java [new file with mode: 0644]
src/org/cacert/gigi/pages/LoginPage.templ [new file with mode: 0644]
src/org/cacert/gigi/pages/LogoutPage.java [new file with mode: 0644]
src/org/cacert/gigi/pages/MainPage.java [new file with mode: 0644]
src/org/cacert/gigi/pages/MainPage.templ [new file with mode: 0644]
src/org/cacert/gigi/pages/MainPageNotLogin.templ [new file with mode: 0644]
src/org/cacert/gigi/pages/OneFormPage.java [new file with mode: 0644]
src/org/cacert/gigi/pages/Page.java [new file with mode: 0644]
src/org/cacert/gigi/pages/PolicyIndex.java [new file with mode: 0644]
src/org/cacert/gigi/pages/RootCertPage.java [new file with mode: 0644]
src/org/cacert/gigi/pages/RootCertPage.templ [new file with mode: 0644]
src/org/cacert/gigi/pages/StaticPage.java [new file with mode: 0644]
src/org/cacert/gigi/pages/TestSecure.java [new file with mode: 0644]
src/org/cacert/gigi/pages/Verify.java [new file with mode: 0644]
src/org/cacert/gigi/pages/Verify.templ [new file with mode: 0644]
src/org/cacert/gigi/pages/account/ChangeForm.java [new file with mode: 0644]
src/org/cacert/gigi/pages/account/ChangePasswordForm.templ [new file with mode: 0644]
src/org/cacert/gigi/pages/account/ChangePasswordPage.java [new file with mode: 0644]
src/org/cacert/gigi/pages/account/MyDetails.java [new file with mode: 0644]
src/org/cacert/gigi/pages/account/MyDetails.templ [new file with mode: 0644]
src/org/cacert/gigi/pages/account/MyDetailsForm.java [new file with mode: 0644]
src/org/cacert/gigi/pages/account/MyDetailsForm.templ [new file with mode: 0644]
src/org/cacert/gigi/pages/account/MyDetailsFormAssured.templ [new file with mode: 0644]
src/org/cacert/gigi/pages/account/MyListingForm.java [new file with mode: 0644]
src/org/cacert/gigi/pages/account/MyListingForm.templ [new file with mode: 0644]
src/org/cacert/gigi/pages/account/certs/CertificateAdd.java [new file with mode: 0644]
src/org/cacert/gigi/pages/account/certs/CertificateDisplay.templ [new file with mode: 0644]
src/org/cacert/gigi/pages/account/certs/CertificateIssueForm.java [new file with mode: 0644]
src/org/cacert/gigi/pages/account/certs/CertificateIssueForm.templ [new file with mode: 0644]
src/org/cacert/gigi/pages/account/certs/CertificateRequest.java [new file with mode: 0644]
src/org/cacert/gigi/pages/account/certs/Certificates.java [new file with mode: 0644]
src/org/cacert/gigi/pages/account/certs/RequestCertificate.templ [new file with mode: 0644]
src/org/cacert/gigi/pages/account/domain/DomainAddForm.java [new file with mode: 0644]
src/org/cacert/gigi/pages/account/domain/DomainAddForm.templ [new file with mode: 0644]
src/org/cacert/gigi/pages/account/domain/DomainManagementForm.java [new file with mode: 0644]
src/org/cacert/gigi/pages/account/domain/DomainManagementForm.templ [new file with mode: 0644]
src/org/cacert/gigi/pages/account/domain/DomainOverview.java [new file with mode: 0644]
src/org/cacert/gigi/pages/account/domain/DomainOverview.templ [new file with mode: 0644]
src/org/cacert/gigi/pages/account/domain/DomainPinglogForm.java [new file with mode: 0644]
src/org/cacert/gigi/pages/account/domain/DomainPinglogForm.templ [new file with mode: 0644]
src/org/cacert/gigi/pages/account/domain/PingConfigForm.java [new file with mode: 0644]
src/org/cacert/gigi/pages/account/domain/PingConfigForm.templ [new file with mode: 0644]
src/org/cacert/gigi/pages/account/mail/MailAddForm.java [new file with mode: 0644]
src/org/cacert/gigi/pages/account/mail/MailAddForm.templ [new file with mode: 0644]
src/org/cacert/gigi/pages/account/mail/MailManagementForm.java [new file with mode: 0644]
src/org/cacert/gigi/pages/account/mail/MailManagementForm.templ [new file with mode: 0644]
src/org/cacert/gigi/pages/account/mail/MailOverview.java [new file with mode: 0644]
src/org/cacert/gigi/pages/account/mail/MailOverview.templ [new file with mode: 0644]
src/org/cacert/gigi/pages/admin/TTPAdminForm.java [new file with mode: 0644]
src/org/cacert/gigi/pages/admin/TTPAdminForm.templ [new file with mode: 0644]
src/org/cacert/gigi/pages/admin/TTPAdminPage.java [new file with mode: 0644]
src/org/cacert/gigi/pages/admin/TTPAdminPage.templ [new file with mode: 0644]
src/org/cacert/gigi/pages/admin/support/FindDomainForm.java [new file with mode: 0644]
src/org/cacert/gigi/pages/admin/support/FindDomainForm.templ [new file with mode: 0644]
src/org/cacert/gigi/pages/admin/support/FindDomainPage.java [new file with mode: 0644]
src/org/cacert/gigi/pages/admin/support/FindUserForm.java [new file with mode: 0644]
src/org/cacert/gigi/pages/admin/support/FindUserForm.templ [new file with mode: 0644]
src/org/cacert/gigi/pages/admin/support/FindUserPage.java [new file with mode: 0644]
src/org/cacert/gigi/pages/admin/support/SupportEnterTicketForm.java [new file with mode: 0644]
src/org/cacert/gigi/pages/admin/support/SupportEnterTicketForm.templ [new file with mode: 0644]
src/org/cacert/gigi/pages/admin/support/SupportRevokeCertificatesForm.java [new file with mode: 0644]
src/org/cacert/gigi/pages/admin/support/SupportRevokeCertificatesForm.templ [new file with mode: 0644]
src/org/cacert/gigi/pages/admin/support/SupportUserDetailsForm.java [new file with mode: 0644]
src/org/cacert/gigi/pages/admin/support/SupportUserDetailsForm.templ [new file with mode: 0644]
src/org/cacert/gigi/pages/admin/support/SupportUserDetailsPage.java [new file with mode: 0644]
src/org/cacert/gigi/pages/admin/support/SupportUserDetailsPage.templ [new file with mode: 0644]
src/org/cacert/gigi/pages/error/AccessDenied.java [new file with mode: 0644]
src/org/cacert/gigi/pages/error/AccessDenied.templ [new file with mode: 0644]
src/org/cacert/gigi/pages/error/PageNotFound.java [new file with mode: 0644]
src/org/cacert/gigi/pages/error/PageNotFound.templ [new file with mode: 0644]
src/org/cacert/gigi/pages/main/RegisterPage.java [new file with mode: 0644]
src/org/cacert/gigi/pages/main/RegisterPage.templ [new file with mode: 0644]
src/org/cacert/gigi/pages/main/Signup.java [new file with mode: 0644]
src/org/cacert/gigi/pages/main/Signup.templ [new file with mode: 0644]
src/org/cacert/gigi/pages/orga/AffiliationForm.java [new file with mode: 0644]
src/org/cacert/gigi/pages/orga/AffiliationForm.templ [new file with mode: 0644]
src/org/cacert/gigi/pages/orga/CreateOrgForm.java [new file with mode: 0644]
src/org/cacert/gigi/pages/orga/CreateOrgForm.templ [new file with mode: 0644]
src/org/cacert/gigi/pages/orga/CreateOrgPage.java [new file with mode: 0644]
src/org/cacert/gigi/pages/orga/EditOrg.templ [new file with mode: 0644]
src/org/cacert/gigi/pages/orga/ViewOrgPage.java [new file with mode: 0644]
src/org/cacert/gigi/pages/orga/ViewOrgs.templ [new file with mode: 0644]
src/org/cacert/gigi/pages/wot/AssuranceForm.java [new file with mode: 0644]
src/org/cacert/gigi/pages/wot/AssuranceForm.templ [new file with mode: 0644]
src/org/cacert/gigi/pages/wot/AssurePage.java [new file with mode: 0644]
src/org/cacert/gigi/pages/wot/AssureeSearch.templ [new file with mode: 0644]
src/org/cacert/gigi/pages/wot/MyPoints.java [new file with mode: 0644]
src/org/cacert/gigi/pages/wot/MyPoints.templ [new file with mode: 0644]
src/org/cacert/gigi/pages/wot/RequestTTPForm.java [new file with mode: 0644]
src/org/cacert/gigi/pages/wot/RequestTTPForm.templ [new file with mode: 0644]
src/org/cacert/gigi/pages/wot/RequestTTPPage.java [new file with mode: 0644]
src/org/cacert/gigi/pages/wot/RequestTTPPage.templ [new file with mode: 0644]
src/org/cacert/gigi/pages/wot/Rules.templ [new file with mode: 0644]
src/org/cacert/gigi/ping/DNSPinger.java [new file with mode: 0644]
src/org/cacert/gigi/ping/DomainPinger.java [new file with mode: 0644]
src/org/cacert/gigi/ping/EmailPinger.java [new file with mode: 0644]
src/org/cacert/gigi/ping/HTTPFetch.java [new file with mode: 0644]
src/org/cacert/gigi/ping/PingerDaemon.java [new file with mode: 0644]
src/org/cacert/gigi/ping/SSLPinger.java [new file with mode: 0644]
src/org/cacert/gigi/util/CipherInfo.java [new file with mode: 0644]
src/org/cacert/gigi/util/DNSUtil.java [new file with mode: 0644]
src/org/cacert/gigi/util/HTMLEncoder.java [new file with mode: 0644]
src/org/cacert/gigi/util/Job.java [new file with mode: 0644]
src/org/cacert/gigi/util/KeyStorage.java [new file with mode: 0644]
src/org/cacert/gigi/util/Notary.java [new file with mode: 0644]
src/org/cacert/gigi/util/PEM.java [new file with mode: 0644]
src/org/cacert/gigi/util/PasswordHash.java [new file with mode: 0644]
src/org/cacert/gigi/util/PasswordStrengthChecker.java [new file with mode: 0644]
src/org/cacert/gigi/util/PublicSuffixes.java [new file with mode: 0644]
src/org/cacert/gigi/util/RandomToken.java [new file with mode: 0644]
src/org/cacert/gigi/util/ServerConstants.java [new file with mode: 0644]
static/static/default.css [new file with mode: 0644]
static/static/images/bit.png [new file with mode: 0644]
static/static/images/cacert4-test.png [new file with mode: 0644]
static/static/images/cacert4.png [new file with mode: 0644]
static/static/images/nlnet.png [new file with mode: 0644]
static/static/images/oan.png [new file with mode: 0644]
static/static/images/tunix.png [new file with mode: 0644]
static/static/keygenIE.js [new file with mode: 0644]
static/static/menu.js [new file with mode: 0644]
static/www/policy/AssurancePolicy.html [new file with mode: 0644]
static/www/policy/CAcertCommunityAgreement.html [new file with mode: 0644]
static/www/policy/CertificationPracticeStatement.html [new file with mode: 0644]
static/www/policy/DisputeResolutionPolicy.html [new file with mode: 0644]
static/www/policy/NRPDisclaimerAndLicence.html [new file with mode: 0644]
static/www/policy/OrganisationAssurancePolicy.html [new file with mode: 0644]
static/www/policy/PolicyOnPolicy.html [new file with mode: 0644]
static/www/policy/PrivacyPolicy.html [new file with mode: 0644]
static/www/policy/RootDistributionLicense.html [new file with mode: 0644]
static/www/policy/cacert-draft.png [new file with mode: 0644]
tests/org/cacert/gigi/DomainVerification.java [new file with mode: 0644]
tests/org/cacert/gigi/LoginTest.java [new file with mode: 0644]
tests/org/cacert/gigi/TestCertificate.java [new file with mode: 0644]
tests/org/cacert/gigi/TestCrossDomainAccess.java [new file with mode: 0644]
tests/org/cacert/gigi/TestDomain.java [new file with mode: 0644]
tests/org/cacert/gigi/TestLanguage.java [new file with mode: 0644]
tests/org/cacert/gigi/TestName.java [new file with mode: 0644]
tests/org/cacert/gigi/TestObjectCache.java [new file with mode: 0644]
tests/org/cacert/gigi/TestOrga.java [new file with mode: 0644]
tests/org/cacert/gigi/TestSSL.java [new file with mode: 0644]
tests/org/cacert/gigi/TestSecurityHeaders.java [new file with mode: 0644]
tests/org/cacert/gigi/TestSeparateSessionScope.java [new file with mode: 0644]
tests/org/cacert/gigi/TestUser.java [new file with mode: 0644]
tests/org/cacert/gigi/TestUserGroupMembership.java [new file with mode: 0644]
tests/org/cacert/gigi/api/IssueCert.java [new file with mode: 0644]
tests/org/cacert/gigi/crypto/TestSPKAC.java [new file with mode: 0644]
tests/org/cacert/gigi/crypto/sampleSPKAC.txt [new file with mode: 0644]
tests/org/cacert/gigi/email/TestEmailProviderClass.java [new file with mode: 0644]
tests/org/cacert/gigi/email/TestSendmail.java [new file with mode: 0644]
tests/org/cacert/gigi/pages/account/TestCertificateAdd.java [new file with mode: 0644]
tests/org/cacert/gigi/pages/account/TestCertificateRequest.java [new file with mode: 0644]
tests/org/cacert/gigi/pages/account/TestChangePassword.java [new file with mode: 0644]
tests/org/cacert/gigi/pages/account/TestContactInformation.java [new file with mode: 0644]
tests/org/cacert/gigi/pages/account/TestDomain.java [new file with mode: 0644]
tests/org/cacert/gigi/pages/account/TestMailManagement.java [new file with mode: 0644]
tests/org/cacert/gigi/pages/account/TestMyDetailsEdit.java [new file with mode: 0644]
tests/org/cacert/gigi/pages/main/RegisterPageTest.java [new file with mode: 0644]
tests/org/cacert/gigi/pages/orga/TestOrgaManagement.java [new file with mode: 0644]
tests/org/cacert/gigi/pages/wot/TestAssurance.java [new file with mode: 0644]
tests/org/cacert/gigi/pages/wot/TestTTP.java [new file with mode: 0644]
tests/org/cacert/gigi/pages/wot/TestTTPAdmin.java [new file with mode: 0644]
tests/org/cacert/gigi/ping/TestDNS.java [new file with mode: 0644]
tests/org/cacert/gigi/ping/TestHTTP.java [new file with mode: 0644]
tests/org/cacert/gigi/ping/TestSSL.java [new file with mode: 0644]
tests/org/cacert/gigi/template/TestTemplate.java [new file with mode: 0644]
tests/org/cacert/gigi/testUtils/ClientTest.java [new file with mode: 0644]
tests/org/cacert/gigi/testUtils/ConfiguredTest.java [new file with mode: 0644]
tests/org/cacert/gigi/testUtils/IOUtils.java [new file with mode: 0644]
tests/org/cacert/gigi/testUtils/InitTruststore.java [new file with mode: 0644]
tests/org/cacert/gigi/testUtils/ManagedTest.java [new file with mode: 0644]
tests/org/cacert/gigi/testUtils/PingTest.java [new file with mode: 0644]
tests/org/cacert/gigi/testUtils/RegisteredUser.java [new file with mode: 0644]
tests/org/cacert/gigi/testUtils/TestEmailReciever.java [new file with mode: 0644]
tests/org/cacert/gigi/util/TestHTMLEncoder.java [new file with mode: 0644]
tests/org/cacert/gigi/util/TestNotary.java [new file with mode: 0644]
tests/org/cacert/gigi/util/TestPasswordHash.java [new file with mode: 0644]
tests/org/cacert/gigi/util/TestPasswordMigration.java [new file with mode: 0644]
tests/org/cacert/gigi/util/TestPasswordStrengthChecker.java [new file with mode: 0644]
tests/org/cacert/gigi/util/TestPublicSuffixes.java [new file with mode: 0644]
tests/org/cacert/gigi/util/TestPublicSuffixes.txt [new file with mode: 0644]
util-testing/org/cacert/gigi/email/CommandlineEmailProvider.java [new file with mode: 0644]
util-testing/org/cacert/gigi/email/TestEmailProvider.java [new file with mode: 0644]
util-testing/org/cacert/gigi/pages/Manager.java [new file with mode: 0644]
util-testing/org/cacert/gigi/pages/Manager.templ [new file with mode: 0644]
util-testing/org/cacert/gigi/pages/ManagerMails.templ [new file with mode: 0644]
util-testing/org/cacert/gigi/util/SimpleSigner.java [new file with mode: 0644]
util/org/cacert/gigi/util/DatabaseManager.java [new file with mode: 0644]
util/org/cacert/gigi/util/FetchLocales.java [new file with mode: 0644]

index fb5011632c0ab8d6649a148c6fb5845a1b34c747..cab9630e50879b23c4ea0c49a44b2121e484d828 100644 (file)
@@ -1,6 +1,15 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <classpath>
+       <classpathentry kind="src" path="lib/servlet-api"/>
+       <classpathentry kind="src" path="lib/jetty"/>
+       <classpathentry kind="src" path="lib/jtar"/>
+       <classpathentry kind="src" path="lib/scrypt"/>
        <classpathentry kind="src" path="src"/>
+       <classpathentry kind="src" path="util"/>
+       <classpathentry kind="src" path="tests"/>
+       <classpathentry kind="src" path="util-testing"/>
        <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
+       <classpathentry kind="con" path="org.eclipse.jdt.junit.JUNIT_CONTAINER/4"/>
+       <classpathentry kind="con" path="org.eclipse.jdt.USER_LIBRARY/MySQL"/>
        <classpathentry kind="output" path="bin"/>
 </classpath>
index eda95824d8f50701da3552860c12a17429c78953..0c170e19bc9094e21614b27cb53380800ea9b344 100644 (file)
@@ -8,4 +8,13 @@
 # Generated Stuff
 *.manifest
 
+#OS stuff
+.DS_Store
+
 /bin
+/bintest
+/binutil
+/work
+static.tar.gz
+
+/src/org/cacert/gigi/util/effective_tld_names.dat
diff --git a/.settings/org.eclipse.core.runtime.prefs b/.settings/org.eclipse.core.runtime.prefs
new file mode 100644 (file)
index 0000000..5a0ad22
--- /dev/null
@@ -0,0 +1,2 @@
+eclipse.preferences.version=1
+line.separator=\n
diff --git a/.settings/org.eclipse.jdt.core.prefs b/.settings/org.eclipse.jdt.core.prefs
new file mode 100644 (file)
index 0000000..2a0716e
--- /dev/null
@@ -0,0 +1,296 @@
+eclipse.preferences.version=1
+org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
+org.eclipse.jdt.core.compiler.codegen.methodParameters=do not generate
+org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.7
+org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
+org.eclipse.jdt.core.compiler.compliance=1.7
+org.eclipse.jdt.core.compiler.debug.lineNumber=generate
+org.eclipse.jdt.core.compiler.debug.localVariable=generate
+org.eclipse.jdt.core.compiler.debug.sourceFile=generate
+org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
+org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
+org.eclipse.jdt.core.compiler.source=1.7
+org.eclipse.jdt.core.formatter.align_type_members_on_columns=false
+org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression=16
+org.eclipse.jdt.core.formatter.alignment_for_arguments_in_annotation=48
+org.eclipse.jdt.core.formatter.alignment_for_arguments_in_enum_constant=16
+org.eclipse.jdt.core.formatter.alignment_for_arguments_in_explicit_constructor_call=16
+org.eclipse.jdt.core.formatter.alignment_for_arguments_in_method_invocation=16
+org.eclipse.jdt.core.formatter.alignment_for_arguments_in_qualified_allocation_expression=16
+org.eclipse.jdt.core.formatter.alignment_for_assignment=0
+org.eclipse.jdt.core.formatter.alignment_for_binary_expression=16
+org.eclipse.jdt.core.formatter.alignment_for_compact_if=16
+org.eclipse.jdt.core.formatter.alignment_for_conditional_expression=48
+org.eclipse.jdt.core.formatter.alignment_for_enum_constants=0
+org.eclipse.jdt.core.formatter.alignment_for_expressions_in_array_initializer=80
+org.eclipse.jdt.core.formatter.alignment_for_method_declaration=0
+org.eclipse.jdt.core.formatter.alignment_for_multiple_fields=16
+org.eclipse.jdt.core.formatter.alignment_for_parameters_in_constructor_declaration=16
+org.eclipse.jdt.core.formatter.alignment_for_parameters_in_method_declaration=16
+org.eclipse.jdt.core.formatter.alignment_for_resources_in_try=80
+org.eclipse.jdt.core.formatter.alignment_for_selector_in_method_invocation=0
+org.eclipse.jdt.core.formatter.alignment_for_superclass_in_type_declaration=32
+org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_enum_declaration=16
+org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_type_declaration=32
+org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_constructor_declaration=16
+org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_method_declaration=16
+org.eclipse.jdt.core.formatter.alignment_for_union_type_in_multicatch=16
+org.eclipse.jdt.core.formatter.blank_lines_after_imports=1
+org.eclipse.jdt.core.formatter.blank_lines_after_package=1
+org.eclipse.jdt.core.formatter.blank_lines_before_field=1
+org.eclipse.jdt.core.formatter.blank_lines_before_first_class_body_declaration=1
+org.eclipse.jdt.core.formatter.blank_lines_before_imports=1
+org.eclipse.jdt.core.formatter.blank_lines_before_member_type=1
+org.eclipse.jdt.core.formatter.blank_lines_before_method=1
+org.eclipse.jdt.core.formatter.blank_lines_before_new_chunk=1
+org.eclipse.jdt.core.formatter.blank_lines_before_package=0
+org.eclipse.jdt.core.formatter.blank_lines_between_import_groups=1
+org.eclipse.jdt.core.formatter.blank_lines_between_type_declarations=1
+org.eclipse.jdt.core.formatter.brace_position_for_annotation_type_declaration=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_anonymous_type_declaration=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_array_initializer=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_block=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_block_in_case=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_constructor_declaration=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_enum_constant=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_enum_declaration=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_lambda_body=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_method_declaration=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_switch=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_type_declaration=end_of_line
+org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_block_comment=true
+org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_javadoc_comment=true
+org.eclipse.jdt.core.formatter.comment.format_block_comments=true
+org.eclipse.jdt.core.formatter.comment.format_header=true
+org.eclipse.jdt.core.formatter.comment.format_html=true
+org.eclipse.jdt.core.formatter.comment.format_javadoc_comments=true
+org.eclipse.jdt.core.formatter.comment.format_line_comments=true
+org.eclipse.jdt.core.formatter.comment.format_source_code=true
+org.eclipse.jdt.core.formatter.comment.indent_parameter_description=true
+org.eclipse.jdt.core.formatter.comment.indent_root_tags=true
+org.eclipse.jdt.core.formatter.comment.insert_new_line_before_root_tags=insert
+org.eclipse.jdt.core.formatter.comment.insert_new_line_for_parameter=insert
+org.eclipse.jdt.core.formatter.comment.line_length=80
+org.eclipse.jdt.core.formatter.comment.new_lines_at_block_boundaries=true
+org.eclipse.jdt.core.formatter.comment.new_lines_at_javadoc_boundaries=true
+org.eclipse.jdt.core.formatter.comment.preserve_white_space_between_code_and_line_comments=true
+org.eclipse.jdt.core.formatter.compact_else_if=true
+org.eclipse.jdt.core.formatter.continuation_indentation=2
+org.eclipse.jdt.core.formatter.continuation_indentation_for_array_initializer=2
+org.eclipse.jdt.core.formatter.disabling_tag=@formatter:off
+org.eclipse.jdt.core.formatter.enabling_tag=@formatter:on
+org.eclipse.jdt.core.formatter.format_guardian_clause_on_one_line=false
+org.eclipse.jdt.core.formatter.format_line_comment_starting_on_first_column=true
+org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_annotation_declaration_header=true
+org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_constant_header=true
+org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_declaration_header=true
+org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_type_header=true
+org.eclipse.jdt.core.formatter.indent_breaks_compare_to_cases=true
+org.eclipse.jdt.core.formatter.indent_empty_lines=false
+org.eclipse.jdt.core.formatter.indent_statements_compare_to_block=true
+org.eclipse.jdt.core.formatter.indent_statements_compare_to_body=true
+org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_cases=true
+org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_switch=false
+org.eclipse.jdt.core.formatter.indentation.size=4
+org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_field=insert
+org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_local_variable=insert
+org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_method=insert
+org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_package=insert
+org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_parameter=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_type=insert
+org.eclipse.jdt.core.formatter.insert_new_line_after_label=insert
+org.eclipse.jdt.core.formatter.insert_new_line_after_opening_brace_in_array_initializer=insert
+org.eclipse.jdt.core.formatter.insert_new_line_after_type_annotation=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_at_end_of_file_if_missing=insert
+org.eclipse.jdt.core.formatter.insert_new_line_before_catch_in_try_statement=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_before_closing_brace_in_array_initializer=insert
+org.eclipse.jdt.core.formatter.insert_new_line_before_else_in_if_statement=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_before_finally_in_try_statement=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_before_while_in_do_statement=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_in_empty_annotation_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_in_empty_anonymous_type_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_in_empty_block=insert
+org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_constant=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_declaration=insert
+org.eclipse.jdt.core.formatter.insert_new_line_in_empty_method_body=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_in_empty_type_declaration=insert
+org.eclipse.jdt.core.formatter.insert_space_after_and_in_type_parameter=insert
+org.eclipse.jdt.core.formatter.insert_space_after_assignment_operator=insert
+org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation_type_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_binary_operator=insert
+org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_arguments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_parameters=insert
+org.eclipse.jdt.core.formatter.insert_space_after_closing_brace_in_block=insert
+org.eclipse.jdt.core.formatter.insert_space_after_closing_paren_in_cast=insert
+org.eclipse.jdt.core.formatter.insert_space_after_colon_in_assert=insert
+org.eclipse.jdt.core.formatter.insert_space_after_colon_in_case=insert
+org.eclipse.jdt.core.formatter.insert_space_after_colon_in_conditional=insert
+org.eclipse.jdt.core.formatter.insert_space_after_colon_in_for=insert
+org.eclipse.jdt.core.formatter.insert_space_after_colon_in_labeled_statement=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_allocation_expression=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_annotation=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_array_initializer=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_parameters=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_throws=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_constant_arguments=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_declarations=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_explicitconstructorcall_arguments=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_increments=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_inits=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_parameters=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_throws=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_invocation_arguments=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_field_declarations=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_local_declarations=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_parameterized_type_reference=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_superinterfaces=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_arguments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_parameters=insert
+org.eclipse.jdt.core.formatter.insert_space_after_ellipsis=insert
+org.eclipse.jdt.core.formatter.insert_space_after_lambda_arrow=insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_parameterized_type_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_arguments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_parameters=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_brace_in_array_initializer=insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_allocation_expression=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_annotation=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_cast=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_catch=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_constructor_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_enum_constant=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_for=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_if=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_invocation=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_parenthesized_expression=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_switch=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_synchronized=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_try=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_while=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_postfix_operator=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_prefix_operator=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_question_in_conditional=insert
+org.eclipse.jdt.core.formatter.insert_space_after_question_in_wildcard=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_for=insert
+org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_try_resources=insert
+org.eclipse.jdt.core.formatter.insert_space_after_unary_operator=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_and_in_type_parameter=insert
+org.eclipse.jdt.core.formatter.insert_space_before_assignment_operator=insert
+org.eclipse.jdt.core.formatter.insert_space_before_at_in_annotation_type_declaration=insert
+org.eclipse.jdt.core.formatter.insert_space_before_binary_operator=insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_parameterized_type_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_arguments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_parameters=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_brace_in_array_initializer=insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_allocation_expression=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_annotation=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_cast=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_catch=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_constructor_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_enum_constant=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_for=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_if=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_invocation=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_parenthesized_expression=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_switch=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_synchronized=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_try=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_while=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_colon_in_assert=insert
+org.eclipse.jdt.core.formatter.insert_space_before_colon_in_case=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_colon_in_conditional=insert
+org.eclipse.jdt.core.formatter.insert_space_before_colon_in_default=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_colon_in_for=insert
+org.eclipse.jdt.core.formatter.insert_space_before_colon_in_labeled_statement=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_allocation_expression=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_annotation=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_array_initializer=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_parameters=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_throws=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_constant_arguments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_declarations=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_explicitconstructorcall_arguments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_increments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_inits=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_parameters=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_throws=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_invocation_arguments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_field_declarations=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_local_declarations=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_parameterized_type_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_superinterfaces=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_arguments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_parameters=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_ellipsis=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_lambda_arrow=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_parameterized_type_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_arguments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_parameters=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_annotation_type_declaration=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_anonymous_type_declaration=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_array_initializer=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_block=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_constructor_declaration=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_constant=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_declaration=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_method_declaration=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_switch=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_type_declaration=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_allocation_expression=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_type_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation_type_member_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_catch=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_constructor_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_enum_constant=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_for=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_if=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_invocation=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_parenthesized_expression=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_switch=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_synchronized=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_try=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_while=insert
+org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_return=insert
+org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_throw=insert
+org.eclipse.jdt.core.formatter.insert_space_before_postfix_operator=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_prefix_operator=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_question_in_conditional=insert
+org.eclipse.jdt.core.formatter.insert_space_before_question_in_wildcard=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_semicolon=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_for=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_try_resources=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_unary_operator=insert
+org.eclipse.jdt.core.formatter.insert_space_between_brackets_in_array_type_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_between_empty_braces_in_array_initializer=do not insert
+org.eclipse.jdt.core.formatter.insert_space_between_empty_brackets_in_array_allocation_expression=do not insert
+org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_annotation_type_member_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_constructor_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_enum_constant=do not insert
+org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_invocation=do not insert
+org.eclipse.jdt.core.formatter.join_lines_in_comments=true
+org.eclipse.jdt.core.formatter.join_wrapped_lines=true
+org.eclipse.jdt.core.formatter.keep_else_statement_on_same_line=false
+org.eclipse.jdt.core.formatter.keep_empty_array_initializer_on_one_line=false
+org.eclipse.jdt.core.formatter.keep_imple_if_on_one_line=false
+org.eclipse.jdt.core.formatter.keep_then_statement_on_same_line=false
+org.eclipse.jdt.core.formatter.lineSplit=8192
+org.eclipse.jdt.core.formatter.never_indent_block_comments_on_first_column=false
+org.eclipse.jdt.core.formatter.never_indent_line_comments_on_first_column=false
+org.eclipse.jdt.core.formatter.number_of_blank_lines_at_beginning_of_method_body=0
+org.eclipse.jdt.core.formatter.number_of_empty_lines_to_preserve=1
+org.eclipse.jdt.core.formatter.put_empty_statement_on_new_line=true
+org.eclipse.jdt.core.formatter.tabulation.char=space
+org.eclipse.jdt.core.formatter.tabulation.size=4
+org.eclipse.jdt.core.formatter.use_on_off_tags=true
+org.eclipse.jdt.core.formatter.use_tabs_only_for_leading_indentations=true
+org.eclipse.jdt.core.formatter.wrap_before_binary_operator=false
+org.eclipse.jdt.core.formatter.wrap_before_or_operator_multicatch=false
+org.eclipse.jdt.core.formatter.wrap_outer_expressions_when_nested=true
diff --git a/.settings/org.eclipse.jdt.ui.prefs b/.settings/org.eclipse.jdt.ui.prefs
new file mode 100644 (file)
index 0000000..4ef74ca
--- /dev/null
@@ -0,0 +1,66 @@
+eclipse.preferences.version=1
+editor_save_participant_org.eclipse.jdt.ui.postsavelistener.cleanup=true
+formatter_profile=_cacert-gigi
+formatter_settings_version=12
+org.eclipse.jdt.ui.ignorelowercasenames=true
+org.eclipse.jdt.ui.importorder=java;javax;org;com;
+org.eclipse.jdt.ui.ondemandthreshold=99
+org.eclipse.jdt.ui.staticondemandthreshold=1
+sp_cleanup.add_default_serial_version_id=true
+sp_cleanup.add_generated_serial_version_id=false
+sp_cleanup.add_missing_annotations=true
+sp_cleanup.add_missing_deprecated_annotations=true
+sp_cleanup.add_missing_methods=false
+sp_cleanup.add_missing_nls_tags=false
+sp_cleanup.add_missing_override_annotations=true
+sp_cleanup.add_missing_override_annotations_interface_methods=false
+sp_cleanup.add_serial_version_id=false
+sp_cleanup.always_use_blocks=true
+sp_cleanup.always_use_parentheses_in_expressions=false
+sp_cleanup.always_use_this_for_non_static_field_access=false
+sp_cleanup.always_use_this_for_non_static_method_access=false
+sp_cleanup.convert_functional_interfaces=false
+sp_cleanup.convert_to_enhanced_for_loop=false
+sp_cleanup.correct_indentation=false
+sp_cleanup.format_source_code=true
+sp_cleanup.format_source_code_changes_only=false
+sp_cleanup.insert_inferred_type_arguments=false
+sp_cleanup.make_local_variable_final=false
+sp_cleanup.make_parameters_final=false
+sp_cleanup.make_private_fields_final=true
+sp_cleanup.make_type_abstract_if_missing_method=false
+sp_cleanup.make_variable_declarations_final=false
+sp_cleanup.never_use_blocks=false
+sp_cleanup.never_use_parentheses_in_expressions=true
+sp_cleanup.on_save_use_additional_actions=true
+sp_cleanup.organize_imports=true
+sp_cleanup.qualify_static_field_accesses_with_declaring_class=false
+sp_cleanup.qualify_static_member_accesses_through_instances_with_declaring_class=true
+sp_cleanup.qualify_static_member_accesses_through_subtypes_with_declaring_class=true
+sp_cleanup.qualify_static_member_accesses_with_declaring_class=false
+sp_cleanup.qualify_static_method_accesses_with_declaring_class=false
+sp_cleanup.remove_private_constructors=true
+sp_cleanup.remove_redundant_type_arguments=false
+sp_cleanup.remove_trailing_whitespaces=false
+sp_cleanup.remove_trailing_whitespaces_all=true
+sp_cleanup.remove_trailing_whitespaces_ignore_empty=false
+sp_cleanup.remove_unnecessary_casts=true
+sp_cleanup.remove_unnecessary_nls_tags=false
+sp_cleanup.remove_unused_imports=true
+sp_cleanup.remove_unused_local_variables=false
+sp_cleanup.remove_unused_private_fields=true
+sp_cleanup.remove_unused_private_members=false
+sp_cleanup.remove_unused_private_methods=true
+sp_cleanup.remove_unused_private_types=true
+sp_cleanup.sort_members=false
+sp_cleanup.sort_members_all=false
+sp_cleanup.use_anonymous_class_creation=false
+sp_cleanup.use_blocks=true
+sp_cleanup.use_blocks_only_for_return_and_throw=false
+sp_cleanup.use_lambda=false
+sp_cleanup.use_parentheses_in_expressions=false
+sp_cleanup.use_this_for_non_static_field_access=false
+sp_cleanup.use_this_for_non_static_field_access_only_if_necessary=true
+sp_cleanup.use_this_for_non_static_method_access=false
+sp_cleanup.use_this_for_non_static_method_access_only_if_necessary=true
+sp_cleanup.use_type_arguments=false
diff --git a/Gigi.MF b/Gigi.MF
new file mode 100644 (file)
index 0000000..614d595
--- /dev/null
+++ b/Gigi.MF
@@ -0,0 +1,3 @@
+Manifest-Version: 1.0
+Main-Class: org.cacert.gigi.Launcher
+
diff --git a/LICENSE b/LICENSE
index d7f105139782ab695d86613e343916f7372f4ac0..eafe33ad1571a6643ab0e8b74e05c4842fd41a90 100644 (file)
--- a/LICENSE
+++ b/LICENSE
@@ -1,339 +1,4 @@
-GNU GENERAL PUBLIC LICENSE
-                       Version 2, June 1991
+The code is licensed under GPLv2.
+Parts of the code carry a dual license under GPLv2+BSD based on the wishes of their authors.
 
- Copyright (C) 1989, 1991 Free Software Foundation, Inc., <http://fsf.org/>
- 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
- Everyone is permitted to copy and distribute verbatim copies
- of this license document, but changing it is not allowed.
-
-                            Preamble
-
-  The licenses for most software are designed to take away your
-freedom to share and change it.  By contrast, the GNU General Public
-License is intended to guarantee your freedom to share and change free
-software--to make sure the software is free for all its users.  This
-General Public License applies to most of the Free Software
-Foundation's software and to any other program whose authors commit to
-using it.  (Some other Free Software Foundation software is covered by
-the GNU Lesser General Public License instead.)  You can apply it to
-your programs, too.
-
-  When we speak of free software, we are referring to freedom, not
-price.  Our General Public Licenses are designed to make sure that you
-have the freedom to distribute copies of free software (and charge for
-this service if you wish), that you receive source code or can get it
-if you want it, that you can change the software or use pieces of it
-in new free programs; and that you know you can do these things.
-
-  To protect your rights, we need to make restrictions that forbid
-anyone to deny you these rights or to ask you to surrender the rights.
-These restrictions translate to certain responsibilities for you if you
-distribute copies of the software, or if you modify it.
-
-  For example, if you distribute copies of such a program, whether
-gratis or for a fee, you must give the recipients all the rights that
-you have.  You must make sure that they, too, receive or can get the
-source code.  And you must show them these terms so they know their
-rights.
-
-  We protect your rights with two steps: (1) copyright the software, and
-(2) offer you this license which gives you legal permission to copy,
-distribute and/or modify the software.
-
-  Also, for each author's protection and ours, we want to make certain
-that everyone understands that there is no warranty for this free
-software.  If the software is modified by someone else and passed on, we
-want its recipients to know that what they have is not the original, so
-that any problems introduced by others will not reflect on the original
-authors' reputations.
-
-  Finally, any free program is threatened constantly by software
-patents.  We wish to avoid the danger that redistributors of a free
-program will individually obtain patent licenses, in effect making the
-program proprietary.  To prevent this, we have made it clear that any
-patent must be licensed for everyone's free use or not licensed at all.
-
-  The precise terms and conditions for copying, distribution and
-modification follow.
-
-                    GNU GENERAL PUBLIC LICENSE
-   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
-
-  0. This License applies to any program or other work which contains
-a notice placed by the copyright holder saying it may be distributed
-under the terms of this General Public License.  The "Program", below,
-refers to any such program or work, and a "work based on the Program"
-means either the Program or any derivative work under copyright law:
-that is to say, a work containing the Program or a portion of it,
-either verbatim or with modifications and/or translated into another
-language.  (Hereinafter, translation is included without limitation in
-the term "modification".)  Each licensee is addressed as "you".
-
-Activities other than copying, distribution and modification are not
-covered by this License; they are outside its scope.  The act of
-running the Program is not restricted, and the output from the Program
-is covered only if its contents constitute a work based on the
-Program (independent of having been made by running the Program).
-Whether that is true depends on what the Program does.
-
-  1. You may copy and distribute verbatim copies of the Program's
-source code as you receive it, in any medium, provided that you
-conspicuously and appropriately publish on each copy an appropriate
-copyright notice and disclaimer of warranty; keep intact all the
-notices that refer to this License and to the absence of any warranty;
-and give any other recipients of the Program a copy of this License
-along with the Program.
-
-You may charge a fee for the physical act of transferring a copy, and
-you may at your option offer warranty protection in exchange for a fee.
-
-  2. You may modify your copy or copies of the Program or any portion
-of it, thus forming a work based on the Program, and copy and
-distribute such modifications or work under the terms of Section 1
-above, provided that you also meet all of these conditions:
-
-    a) You must cause the modified files to carry prominent notices
-    stating that you changed the files and the date of any change.
-
-    b) You must cause any work that you distribute or publish, that in
-    whole or in part contains or is derived from the Program or any
-    part thereof, to be licensed as a whole at no charge to all third
-    parties under the terms of this License.
-
-    c) If the modified program normally reads commands interactively
-    when run, you must cause it, when started running for such
-    interactive use in the most ordinary way, to print or display an
-    announcement including an appropriate copyright notice and a
-    notice that there is no warranty (or else, saying that you provide
-    a warranty) and that users may redistribute the program under
-    these conditions, and telling the user how to view a copy of this
-    License.  (Exception: if the Program itself is interactive but
-    does not normally print such an announcement, your work based on
-    the Program is not required to print an announcement.)
-
-These requirements apply to the modified work as a whole.  If
-identifiable sections of that work are not derived from the Program,
-and can be reasonably considered independent and separate works in
-themselves, then this License, and its terms, do not apply to those
-sections when you distribute them as separate works.  But when you
-distribute the same sections as part of a whole which is a work based
-on the Program, the distribution of the whole must be on the terms of
-this License, whose permissions for other licensees extend to the
-entire whole, and thus to each and every part regardless of who wrote it.
-
-Thus, it is not the intent of this section to claim rights or contest
-your rights to work written entirely by you; rather, the intent is to
-exercise the right to control the distribution of derivative or
-collective works based on the Program.
-
-In addition, mere aggregation of another work not based on the Program
-with the Program (or with a work based on the Program) on a volume of
-a storage or distribution medium does not bring the other work under
-the scope of this License.
-
-  3. You may copy and distribute the Program (or a work based on it,
-under Section 2) in object code or executable form under the terms of
-Sections 1 and 2 above provided that you also do one of the following:
-
-    a) Accompany it with the complete corresponding machine-readable
-    source code, which must be distributed under the terms of Sections
-    1 and 2 above on a medium customarily used for software interchange; or,
-
-    b) Accompany it with a written offer, valid for at least three
-    years, to give any third party, for a charge no more than your
-    cost of physically performing source distribution, a complete
-    machine-readable copy of the corresponding source code, to be
-    distributed under the terms of Sections 1 and 2 above on a medium
-    customarily used for software interchange; or,
-
-    c) Accompany it with the information you received as to the offer
-    to distribute corresponding source code.  (This alternative is
-    allowed only for noncommercial distribution and only if you
-    received the program in object code or executable form with such
-    an offer, in accord with Subsection b above.)
-
-The source code for a work means the preferred form of the work for
-making modifications to it.  For an executable work, complete source
-code means all the source code for all modules it contains, plus any
-associated interface definition files, plus the scripts used to
-control compilation and installation of the executable.  However, as a
-special exception, the source code distributed need not include
-anything that is normally distributed (in either source or binary
-form) with the major components (compiler, kernel, and so on) of the
-operating system on which the executable runs, unless that component
-itself accompanies the executable.
-
-If distribution of executable or object code is made by offering
-access to copy from a designated place, then offering equivalent
-access to copy the source code from the same place counts as
-distribution of the source code, even though third parties are not
-compelled to copy the source along with the object code.
-
-  4. You may not copy, modify, sublicense, or distribute the Program
-except as expressly provided under this License.  Any attempt
-otherwise to copy, modify, sublicense or distribute the Program is
-void, and will automatically terminate your rights under this License.
-However, parties who have received copies, or rights, from you under
-this License will not have their licenses terminated so long as such
-parties remain in full compliance.
-
-  5. You are not required to accept this License, since you have not
-signed it.  However, nothing else grants you permission to modify or
-distribute the Program or its derivative works.  These actions are
-prohibited by law if you do not accept this License.  Therefore, by
-modifying or distributing the Program (or any work based on the
-Program), you indicate your acceptance of this License to do so, and
-all its terms and conditions for copying, distributing or modifying
-the Program or works based on it.
-
-  6. Each time you redistribute the Program (or any work based on the
-Program), the recipient automatically receives a license from the
-original licensor to copy, distribute or modify the Program subject to
-these terms and conditions.  You may not impose any further
-restrictions on the recipients' exercise of the rights granted herein.
-You are not responsible for enforcing compliance by third parties to
-this License.
-
-  7. If, as a consequence of a court judgment or allegation of patent
-infringement or for any other reason (not limited to patent issues),
-conditions are imposed on you (whether by court order, agreement or
-otherwise) that contradict the conditions of this License, they do not
-excuse you from the conditions of this License.  If you cannot
-distribute so as to satisfy simultaneously your obligations under this
-License and any other pertinent obligations, then as a consequence you
-may not distribute the Program at all.  For example, if a patent
-license would not permit royalty-free redistribution of the Program by
-all those who receive copies directly or indirectly through you, then
-the only way you could satisfy both it and this License would be to
-refrain entirely from distribution of the Program.
-
-If any portion of this section is held invalid or unenforceable under
-any particular circumstance, the balance of the section is intended to
-apply and the section as a whole is intended to apply in other
-circumstances.
-
-It is not the purpose of this section to induce you to infringe any
-patents or other property right claims or to contest validity of any
-such claims; this section has the sole purpose of protecting the
-integrity of the free software distribution system, which is
-implemented by public license practices.  Many people have made
-generous contributions to the wide range of software distributed
-through that system in reliance on consistent application of that
-system; it is up to the author/donor to decide if he or she is willing
-to distribute software through any other system and a licensee cannot
-impose that choice.
-
-This section is intended to make thoroughly clear what is believed to
-be a consequence of the rest of this License.
-
-  8. If the distribution and/or use of the Program is restricted in
-certain countries either by patents or by copyrighted interfaces, the
-original copyright holder who places the Program under this License
-may add an explicit geographical distribution limitation excluding
-those countries, so that distribution is permitted only in or among
-countries not thus excluded.  In such case, this License incorporates
-the limitation as if written in the body of this License.
-
-  9. The Free Software Foundation may publish revised and/or new versions
-of the General Public License from time to time.  Such new versions will
-be similar in spirit to the present version, but may differ in detail to
-address new problems or concerns.
-
-Each version is given a distinguishing version number.  If the Program
-specifies a version number of this License which applies to it and "any
-later version", you have the option of following the terms and conditions
-either of that version or of any later version published by the Free
-Software Foundation.  If the Program does not specify a version number of
-this License, you may choose any version ever published by the Free Software
-Foundation.
-
-  10. If you wish to incorporate parts of the Program into other free
-programs whose distribution conditions are different, write to the author
-to ask for permission.  For software which is copyrighted by the Free
-Software Foundation, write to the Free Software Foundation; we sometimes
-make exceptions for this.  Our decision will be guided by the two goals
-of preserving the free status of all derivatives of our free software and
-of promoting the sharing and reuse of software generally.
-
-                            NO WARRANTY
-
-  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
-FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
-OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
-PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
-OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
-MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
-TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
-PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
-REPAIR OR CORRECTION.
-
-  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
-WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
-REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
-INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
-OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
-TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
-YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
-PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
-POSSIBILITY OF SUCH DAMAGES.
-
-                     END OF TERMS AND CONDITIONS
-
-            How to Apply These Terms to Your New Programs
-
-  If you develop a new program, and you want it to be of the greatest
-possible use to the public, the best way to achieve this is to make it
-free software which everyone can redistribute and change under these terms.
-
-  To do so, attach the following notices to the program.  It is safest
-to attach them to the start of each source file to most effectively
-convey the exclusion of warranty; and each file should have at least
-the "copyright" line and a pointer to where the full notice is found.
-
-    {description}
-    Copyright (C) {year}  {fullname}
-
-    This program is free software; you can redistribute it and/or modify
-    it under the terms of the GNU General Public License as published by
-    the Free Software Foundation; either version 2 of the License, or
-    (at your option) any later version.
-
-    This program is distributed in the hope that it will be useful,
-    but WITHOUT ANY WARRANTY; without even the implied warranty of
-    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-    GNU General Public License for more details.
-
-    You should have received a copy of the GNU General Public License along
-    with this program; if not, write to the Free Software Foundation, Inc.,
-    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-
-Also add information on how to contact you by electronic and paper mail.
-
-If the program is interactive, make it output a short notice like this
-when it starts in an interactive mode:
-
-    Gnomovision version 69, Copyright (C) year name of author
-    Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
-    This is free software, and you are welcome to redistribute it
-    under certain conditions; type `show c' for details.
-
-The hypothetical commands `show w' and `show c' should show the appropriate
-parts of the General Public License.  Of course, the commands you use may
-be called something other than `show w' and `show c'; they could even be
-mouse-clicks or menu items--whatever suits your program.
-
-You should also get your employer (if you work as a programmer) or your
-school, if any, to sign a "copyright disclaimer" for the program, if
-necessary.  Here is a sample; alter the names:
-
-  Yoyodyne, Inc., hereby disclaims all copyright interest in the program
-  `Gnomovision' (which makes passes at compilers) written by James Hacker.
-
-  {signature of Ty Coon}, 1 April 1989
-  Ty Coon, President of Vice
-
-This General Public License does not permit incorporating your program into
-proprietary programs.  If your program is a subroutine library, you may
-consider it more useful to permit linking proprietary applications with the
-library.  If this is what you want to do, use the GNU Lesser General
-Public License instead of this License.
+For detailed license information see "debian/copyright" ...
diff --git a/LICENSE.BSD b/LICENSE.BSD
new file mode 100644 (file)
index 0000000..b7bf085
--- /dev/null
@@ -0,0 +1,24 @@
+Copyright (c) 2015, CAcert
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without 
+modification, are permitted provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright notice, 
+this list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright notice, 
+this list of conditions and the following disclaimer in the documentation 
+and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 
+ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 
+LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 
+CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 
+SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 
+INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
+CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
+ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF 
+THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/LICENSE.GPL b/LICENSE.GPL
new file mode 100644 (file)
index 0000000..d7f1051
--- /dev/null
@@ -0,0 +1,339 @@
+GNU GENERAL PUBLIC LICENSE
+                       Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc., <http://fsf.org/>
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+                            Preamble
+
+  The licenses for most software are designed to take away your
+freedom to share and change it.  By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users.  This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it.  (Some other Free Software Foundation software is covered by
+the GNU Lesser General Public License instead.)  You can apply it to
+your programs, too.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+  To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have.  You must make sure that they, too, receive or can get the
+source code.  And you must show them these terms so they know their
+rights.
+
+  We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+  Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software.  If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+  Finally, any free program is threatened constantly by software
+patents.  We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary.  To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+
+                    GNU GENERAL PUBLIC LICENSE
+   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+  0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License.  The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language.  (Hereinafter, translation is included without limitation in
+the term "modification".)  Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope.  The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+  1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+  2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+    a) You must cause the modified files to carry prominent notices
+    stating that you changed the files and the date of any change.
+
+    b) You must cause any work that you distribute or publish, that in
+    whole or in part contains or is derived from the Program or any
+    part thereof, to be licensed as a whole at no charge to all third
+    parties under the terms of this License.
+
+    c) If the modified program normally reads commands interactively
+    when run, you must cause it, when started running for such
+    interactive use in the most ordinary way, to print or display an
+    announcement including an appropriate copyright notice and a
+    notice that there is no warranty (or else, saying that you provide
+    a warranty) and that users may redistribute the program under
+    these conditions, and telling the user how to view a copy of this
+    License.  (Exception: if the Program itself is interactive but
+    does not normally print such an announcement, your work based on
+    the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole.  If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works.  But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+  3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+    a) Accompany it with the complete corresponding machine-readable
+    source code, which must be distributed under the terms of Sections
+    1 and 2 above on a medium customarily used for software interchange; or,
+
+    b) Accompany it with a written offer, valid for at least three
+    years, to give any third party, for a charge no more than your
+    cost of physically performing source distribution, a complete
+    machine-readable copy of the corresponding source code, to be
+    distributed under the terms of Sections 1 and 2 above on a medium
+    customarily used for software interchange; or,
+
+    c) Accompany it with the information you received as to the offer
+    to distribute corresponding source code.  (This alternative is
+    allowed only for noncommercial distribution and only if you
+    received the program in object code or executable form with such
+    an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it.  For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable.  However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+  4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License.  Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+  5. You are not required to accept this License, since you have not
+signed it.  However, nothing else grants you permission to modify or
+distribute the Program or its derivative works.  These actions are
+prohibited by law if you do not accept this License.  Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+  6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions.  You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+  7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all.  For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices.  Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+  8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded.  In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+  9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number.  If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation.  If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+  10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission.  For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this.  Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+                            NO WARRANTY
+
+  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+                     END OF TERMS AND CONDITIONS
+
+            How to Apply These Terms to Your New Programs
+
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    {description}
+    Copyright (C) {year}  {fullname}
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License along
+    with this program; if not, write to the Free Software Foundation, Inc.,
+    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+    Gnomovision version 69, Copyright (C) year name of author
+    Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+    This is free software, and you are welcome to redistribute it
+    under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License.  Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary.  Here is a sample; alter the names:
+
+  Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+  `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+  {signature of Ty Coon}, 1 April 1989
+  Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs.  If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library.  If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.
index a4c3425267360a664f6b70b0cb5d479c252ace62..8f4be3bef366b165526c235b6e03e07ccd40a7e7 100644 (file)
--- a/README.md
+++ b/README.md
@@ -2,3 +2,6 @@ Gigi
 =================
 
 Webserver Module for CAcert
+
+
+Contains source from jetty 9.1.0.RC0
diff --git a/build.xml b/build.xml
new file mode 100644 (file)
index 0000000..5d4634d
--- /dev/null
+++ b/build.xml
@@ -0,0 +1,325 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<project basedir="." default="develop" name="cacert-gigi" xmlns:jacoco="antlib:org.jacoco.ant">
+
+       <taskdef uri="antlib:org.jacoco.ant" resource="org/jacoco/ant/antlib.xml">
+               <classpath path="/usr/share/java/jacocoant.jar" />
+       </taskdef>
+
+       <property environment="env" />
+       <property name="PACKAGE" value="cacert-gigi" />
+       <property name="junit.output.dir" value="junit" />
+       <property name="debuglevel" value="source,lines,vars" />
+       <property name="target" value="1.8" />
+       <property name="source" value="1.8" />
+       <property name="mysqlconnector" value="/usr/share/java/mysql-connector-java.jar" />
+       <property name="juintexec" value="/usr/share/java" />
+       <path id="JUnit 4.libraryclasspath">
+               <pathelement location="${juintexec}/junit4.jar" />
+               <pathelement location="${juintexec}/hamcrest-core.jar" />
+       </path>
+       <path id="cacert-gigi.classpath">
+               <pathelement location="bin" />
+               <pathelement location="binutil" />
+               <pathelement location="${mysqlconnector}" />
+       </path>
+       <path id="cacert-gigi.test.classpath">
+               <pathelement location="bintest" />
+               <pathelement location="bin" />
+               <pathelement location="binutil" />
+               <pathelement location="binutil-testing" />
+               <path refid="JUnit 4.libraryclasspath" />
+               <pathelement location="${mysqlconnector}" />
+       </path>
+       <target name="init">
+               <mkdir dir="bin" />
+               <mkdir dir="binutil" />
+               <mkdir dir="binutil-testing" />
+               <mkdir dir="bintest" />
+
+               <copy includeemptydirs="false" todir="bin">
+                       <fileset dir="lib/servlet-api">
+                               <exclude name="**/*.launch" />
+                               <exclude name="**/*.java" />
+                       </fileset>
+                       <fileset dir="lib/jetty">
+                               <exclude name="**/*.launch" />
+                               <exclude name="**/*.java" />
+                       </fileset>
+                       <fileset dir="src">
+                               <exclude name="**/*.launch" />
+                               <exclude name="**/*.java" />
+                       </fileset>
+                       <fileset dir="util">
+                               <exclude name="**/*.launch" />
+                               <exclude name="**/*.java" />
+                       </fileset>
+               </copy>
+               <copy includeemptydirs="false" todir="binutil-testing">
+                       <fileset dir="util-testing">
+                               <exclude name="**/*.launch" />
+                               <exclude name="**/*.java" />
+                       </fileset>
+               </copy>
+               <copy includeemptydirs="false" todir="bintest">
+                       <fileset dir="tests">
+                               <exclude name="**/*.launch" />
+                               <exclude name="**/*.java" />
+                       </fileset>
+               </copy>
+       </target>
+       <target name="clean">
+               <delete dir="bin" />
+       </target>
+       <target name="clean-test">
+               <delete dir="bintest" />
+               <delete dir="cocoReport" failonerror="false"/>
+
+       </target>
+       <target depends="clean,clean-test" name="cleanall" />
+       <target depends="build-project, build-testing, native" name="build" />
+       <target depends="init" name="build-project">
+               <echo message="${ant.project.name}: ${ant.file}" />
+               <javac encoding="UTF-8" debug="true" debuglevel="${debuglevel}" destdir="bin"
+                       includeantruntime="false" source="${source}" target="${target}">
+                       <compilerarg value="-XDignore.symbol.file"/>
+                       <src path="lib/servlet-api" />
+                       <src path="lib/jetty" />
+                       <src path="lib/jtar" />
+                       <src path="lib/scrypt" />
+                       <src path="src" />
+                       <classpath refid="cacert-gigi.classpath" />
+               </javac>
+               <javac encoding="UTF-8" debug="true" debuglevel="${debuglevel}" destdir="binutil"
+                       includeantruntime="false" source="${source}" target="${target}">
+                       <compilerarg value="-XDignore.symbol.file"/>
+                       <src path="util" />
+                       <classpath refid="cacert-gigi.classpath" />
+               </javac>
+       </target>
+       <target depends="init, build-project" name="build-testing">
+               <javac encoding="UTF-8" debug="true" debuglevel="${debuglevel}" destdir="binutil-testing"
+                       includeantruntime="false" source="${source}" target="${target}">
+                       <src path="util-testing" />
+                       <classpath refid="cacert-gigi.classpath" />
+               </javac>
+       </target>
+
+       <target name="native">
+               <exec executable="make" dir="natives"/>
+       </target>
+
+       <target depends="build-project, update-effective-tlds" name="pack">
+               <jar destfile="gigi.jar" basedir="bin" manifest="Gigi.MF" update="false"/>
+               <jar destfile="gigi.jar" basedir="binutil" update="on"/>
+       </target>
+
+       <target depends="build-testing,update-effective-tlds" name="pack-testing">
+               <jar destfile="gigi-testing.jar" basedir="bin" manifest="Gigi.MF" update="false"/>
+               <jar destfile="gigi-testing.jar" basedir="binutil" update="on"/>
+               <jar destfile="gigi-testing.jar" basedir="binutil-testing" update="on"/>
+       </target>
+
+       <target depends="test,native" name="bundle">
+               <zip destfile="gigi-linux_amd64.zip" basedir="."
+                       includes="gigi.jar,native/*.so,src/org/cacert/gigi/database/tableStructure.sql,static/**,templates/**" />
+       </target>
+       <target name="static-bundle">
+               <mkdir dir="work"/>
+               <mkdir dir="work/static"/>
+               <copy todir="work/static">
+                       <fileset dir="static"/>
+               </copy>
+               <move file="work/static/static/images/cacert4-test.png" tofile="work/static/static/images/cacert4.png"/>
+               <delete file="work/static/static/image/cacert4-test.png"/>
+               <tar destfile="static.tar.gz" compression="gzip" basedir="work"
+                       includes="../src/org/cacert/gigi/database/tableStructure.sql,**,templates/**" />
+       </target>
+
+       <target name="static-bundle-release">
+               <mkdir dir="work"/>
+               <mkdir dir="work/static"/>
+               <copy todir="work/static">
+                       <fileset dir="static"/>
+               </copy>
+               <delete file="work/static/static/image/cacert4-test.png"/>
+               <tar destfile="static.tar.gz" compression="gzip" basedir="work"
+                       includes="../src/org/cacert/gigi/database/tableStructure.sql,**,templates/**" />
+       </target>
+
+       <target name="develop" depends="bundle,static-bundle" />
+
+       <target name="release" depends="bundle,static-bundle-release" />
+
+       <target depends="init,build-testing,update-effective-tlds" name="build-project-test">
+               <echo message="${ant.project.name}: ${ant.file}" />
+               <javac encoding="UTF-8" debug="true" debuglevel="${debuglevel}" destdir="bintest"
+                       includeantruntime="false" source="${source}" target="${target}">
+                       <compilerarg value="-XDignore.symbol.file"/>
+                       <src path="tests" />
+                       <classpath refid="cacert-gigi.test.classpath" />
+               </javac>
+               <concat destfile="bintest/org/cacert/gigi/util/effective_tld_names.dat">
+                       <path path="bin/org/cacert/gigi/util/effective_tld_names.dat"/>
+                       <footer>${test_nic}</footer>
+               </concat>
+       </target>
+       <target name="check-locale">
+               <available file="locale/de.xml" property="locale.present" />
+       </target>
+       <target name="FetchLocales" depends="check-locale" unless="locale.present">
+               <java classname="org.cacert.gigi.util.FetchLocales" failonerror="true"
+                       fork="yes">
+                       <classpath refid="cacert-gigi.classpath" />
+               </java>
+       </target>
+       <target name="check-generateKeys">
+               <available file="config/keystore.pkcs12" property="keystore.present" />
+       </target>
+       <target name="generateKeys" depends="check-generateKeys" unless="keystore.present">
+               <exec executable="./generateKeys.sh" dir="keys" />
+               <exec executable="./generateTruststore.sh" dir="keys">
+                       <arg value="-noprompt" />
+               </exec>
+       </target>
+       <target name="reset-db" depends="build-project">
+               <copy file="config/test.properties" tofile="config/gigi.properties"/>
+               <java classname="org.cacert.gigi.util.DatabaseManager">
+                       <arg value="--test"/>
+                       <classpath refid="cacert-gigi.test.classpath" />
+               </java>
+       </target>
+       <target name="test" depends="build-project-test,generateKeys,FetchLocales,pack-testing,pack,reset-db">
+               <delete failonerror="false">
+                       <fileset dir=".">
+                               <include name="jacoco.exec"/>
+                               <include name="tester.exec"/>
+                       </fileset>
+               </delete>
+               <mkdir dir="${junit.output.dir}" />
+               <junit maxmemory="2g" fork="yes" printsummary="withOutAndErr">
+                       <jvmarg value="-javaagent:/usr/share/java/jacocoagent.jar=destfile=tester.exec"/>
+                       <formatter type="xml" />
+                       <batchtest fork="yes" todir="${junit.output.dir}">
+                               <fileset dir="tests">
+                                       <include name="**/*.java" />
+                                       <exclude name="**/testUtils/**" />
+                                       <exclude name="**/com/lambdaworks/crypto/test/CryptoTestUtil.java"/>
+                               </fileset>
+                       </batchtest>
+                       <classpath refid="cacert-gigi.test.classpath" />
+               </junit>
+       </target>
+       <target name="junitreport">
+               <junitreport todir="${junit.output.dir}">
+                       <fileset dir="${junit.output.dir}">
+                               <include name="TEST-*.xml" />
+                       </fileset>
+                       <report format="frames" todir="${junit.output.dir}" />
+               </junitreport>
+       </target>
+       <target name="generatecoco">
+               <jacoco:merge destfile="merged.exec">
+                       <fileset dir="." includes="*.exec"/>
+               </jacoco:merge>
+               <jacoco:report>
+                       <executiondata>
+                               <file file="merged.exec" />
+                       </executiondata>
+
+                       <structure name="CAcert gigi">
+                               <group name="Server">
+                                       <classfiles>
+                                               <fileset dir="bin">
+                                                       <include name="org/cacert/gigi/**"/>
+                                               </fileset>
+                                       </classfiles>
+                                       <sourcefiles encoding="UTF-8">
+                                               <fileset dir="src">
+                                                       <include name="org/cacert/gigi/**"/>
+                                               </fileset>
+                                       </sourcefiles>
+                               </group>
+                               <group name="Testcases">
+                                       <classfiles>
+                                               <fileset dir="bintest">
+                                                       <include name="org/cacert/gigi/**"/>
+                                               </fileset>
+                                       </classfiles>
+                                       <sourcefiles encoding="UTF-8">
+                                               <fileset dir="tests">
+                                                       <include name="org/cacert/gigi/**"/>
+                                               </fileset>
+                                       </sourcefiles>
+                               </group>
+                       </structure>
+
+                       <html destdir="cocoReport"/>
+
+               </jacoco:report>
+       </target>
+       <target name="install-native" depends="native">
+               <mkdir dir="${env.DESTDIR}/usr/lib/jni"/>
+               <copy file="natives/libsetuid.so" todir="${env.DESTDIR}/usr/lib/jni"/>
+       </target>
+       <target name="install" depends="install-common">
+               <mkdir dir="${env.DESTDIR}/usr/share/cacert-gigi/static" />
+               <copy todir="${env.DESTDIR}/usr/share/cacert-gigi/static">
+                       <fileset dir="static" />
+               </copy>
+               <delete file="${env.DESTDIR}/usr/share/cacert-gigi/static/static/images/cacert4-test.png" />
+               <copy file="gigi.jar" tofile="${env.DESTDIR}/usr/share/java/gigi.jar"/>
+       </target>
+       <target name="install-testing" depends="install-common">
+               <mkdir dir="${env.DESTDIR}/usr/share/cacert-gigi/static" />
+               <copy todir="${env.DESTDIR}/usr/share/cacert-gigi/static">
+                       <fileset dir="static" />
+               </copy>
+               <move file="${env.DESTDIR}/usr/share/cacert-gigi/static/static/images/cacert4-test.png" tofile="${env.DESTDIR}/usr/share/cacert-gigi/static/static/images/cacert4.png" />
+               <delete file="${env.DESTDIR}/usr/share/cacert-gigi/static/static/images/cacert4-test.png" />
+               <copy file="gigi-testing.jar" tofile="${env.DESTDIR}/usr/share/java/gigi.jar"/>
+       </target>
+
+       <target name="install-common" depends="pack">
+               <mkdir dir="${env.DESTDIR}/usr/share/java" />
+
+               <mkdir dir="${env.DESTDIR}/usr/bin"/>
+               <copy file="doc/scripts/gigi" tofile="${env.DESTDIR}/usr/bin/gigi"/>
+
+               <chmod file="${env.DESTDIR}/usr/bin/gigi" perm="+x"/>
+               <mkdir dir="${env.DESTDIR}/usr/share/dbconfig-common/data/${PACKAGE}/install/"/>
+               <copy file="src/org/cacert/gigi/database/tableStructure.sql" tofile="${env.DESTDIR}/usr/share/dbconfig-common/data/${PACKAGE}/install/mysql.sql"/>
+
+               <mkdir dir="${env.DESTDIR}/var/lib/cacert-gigi/doc"/>
+               <exec executable="ln">
+                       <arg value="-s"/>
+                       <arg value="/usr/share/dbconfig-common/data/${PACKAGE}/install/mysql.sql"/>
+                       <arg value="${env.DESTDIR}/var/lib/cacert-gigi/doc/tableStructure.sql"/>
+               </exec>
+               <mkdir dir="${env.DESTDIR}/var/lib/cacert-gigi/natives"/>
+               <delete failonerror="false" file="${env.DESTDIR}/var/lib/cacert-gigi/static"/>
+               <exec executable="ln">
+                       <arg value="-s"/>
+                       <arg value="/usr/share/cacert-gigi/static"/>
+                       <arg value="${env.DESTDIR}/var/lib/cacert-gigi/static"/>
+               </exec>
+               <exec executable="ln">
+                       <arg value="-s"/>
+                       <arg value="/usr/lib/jni/libsetuid.so"/>
+                       <arg value="${env.DESTDIR}/var/lib/cacert-gigi/natives/libsetuid.so"/>
+               </exec>
+
+               <mkdir dir="${env.DESTDIR}/etc/cacert/gigi"/>
+               <!--<copy todir="${env.DESTDIR}/DEBIAN">
+                       <fileset dir="debian">
+                       </fileset>
+               </copy>-->
+       </target>
+       <target name="update-effective-tlds">
+               <mkdir dir="bin/org/cacert/gigi/util"/>
+               <exec executable="wget" dir="bin/org/cacert/gigi/util">
+                       <arg value="-N"/>
+                       <arg value="-q"/>
+                       <arg value="https://publicsuffix.org/list/effective_tld_names.dat"/>
+               </exec>
+       </target>
+</project>
diff --git a/config/.gitignore b/config/.gitignore
new file mode 100644 (file)
index 0000000..59f2d87
--- /dev/null
@@ -0,0 +1,5 @@
+
+keystore.pkcs12
+cacerts.jks
+gigi.properties
+test.properties
diff --git a/config/gigi.properties.template b/config/gigi.properties.template
new file mode 100644 (file)
index 0000000..ef794f6
--- /dev/null
@@ -0,0 +1,14 @@
+host=127.0.0.1
+name.static=static.cacert.local
+name.secure=secure.cacert.local
+name.www=www.cacert.local
+name.api=api.cacert.local
+
+https.port=443
+http.port=80
+#emailProvider=org.cacert.gigi.email.Sendmail
+emailProvider=org.cacert.gigi.email.CommandlineEmailProvider
+sql.driver=com.mysql.jdbc.Driver
+sql.url=jdbc:mysql://
+sql.user=
+sql.password=
diff --git a/config/test.properties.template b/config/test.properties.template
new file mode 100644 (file)
index 0000000..02cb4d4
--- /dev/null
@@ -0,0 +1,38 @@
+type=local
+serverPort.https=443
+serverPort.http=80
+mail=localhost:8474
+
+# ==== OR ===
+type=autonomous
+java=java -cp bin;/path/to/mysqlConnector.jar org.cacert.gigi.Launcher
+serverPort.https=4443
+serverPort.http=8098
+mailPort=8473
+
+
+
+
+# ==== ALL ===
+name.static=static.cacert.local
+name.secure=secure.cacert.local
+name.www=www.cacert.local
+name.api=api.cacert.local
+sql.driver=com.mysql.jdbc.Driver
+sql.url=jdbc:mysql://localhost:3306/cacert
+sql.user=cacert
+sql.password=<password>
+
+
+domain.manage=http://you-installation-of-the/index.php
+domain.http=you-intstallation-for-the-textfiles
+domain.dnstest=the.dns.zone
+domain.testns=the.authorativ.ns.for.domain.dnstest
+domain.local=a.domain.that.resolves.to.localhost
+
+
+email.address=somemail@yourdomain.org
+email.password=somemails-imap-password
+email.imap=imap.yourdomain.org
+email.imap.user=somemail-imap-useraccount
+email.non-address=some-non-existent-domain@yourdomain.org
diff --git a/debian/.gitignore b/debian/.gitignore
new file mode 100644 (file)
index 0000000..5a7dbaa
--- /dev/null
@@ -0,0 +1,6 @@
+*.debhelper
+*.substvars
+*.log
+cacert-gigi-setuid
+files
+cacert-gigi
diff --git a/debian/cacert-gigi-testing.cacert-gigi-signer.default b/debian/cacert-gigi-testing.cacert-gigi-signer.default
new file mode 100644 (file)
index 0000000..436a32a
--- /dev/null
@@ -0,0 +1 @@
+START_DAEMON=0
diff --git a/debian/cacert-gigi-testing.cacert-gigi-signer.init b/debian/cacert-gigi-testing.cacert-gigi-signer.init
new file mode 100644 (file)
index 0000000..b041f0d
--- /dev/null
@@ -0,0 +1,175 @@
+#!/bin/sh
+### BEGIN INIT INFO
+# Provides:          cacert-gigi-signer
+# Required-Start:    $local_fs $network $remote_fs $syslog mysql
+# Required-Stop:     $local_fs $network $remote_fs $syslog mysql
+# Default-Start:     2 3 4 5
+# Default-Stop:      0 1 6
+# Short-Description: <Enter a short description of the software>
+# Description:       <Enter a long description of the software>
+#                    <...>
+#                    <...>
+### END INIT INFO
+
+# Author: unknown <software@cacert.org>
+
+# Do NOT "set -e"
+
+# PATH should only include /usr/* if it runs after the mountnfs.sh script
+PATH=/sbin:/usr/sbin:/bin:/usr/bin
+DESC="cacert-gigi-signer"
+NAME=cacert-gigi-signer
+DAEMON=`which java`
+DAEMON_ARGS="-cp /usr/share/java/mysql-connector-java.jar:/usr/share/java/gigi.jar org.cacert.gigi.util.SimpleSigner"
+PIDFILE=/var/run/$NAME.pid
+SCRIPTNAME=/etc/init.d/$NAME
+DIR=/var/lib/cacert-gigi
+
+# Exit if the package is not installed
+[ -r "/usr/share/java/gigi.jar" ] || exit 0
+
+# Read configuration variable file if it is present
+[ -r /etc/default/$NAME ] && . /etc/default/$NAME
+if [ "$START_DAEMON" = "0" ]; then
+    echo "Not starting $NAME (as configured in /etc/default/$NAME)";
+    exit 0;
+fi
+
+# Load the VERBOSE setting and other rcS variables
+. /lib/init/vars.sh
+
+# Define LSB log_* functions.
+# Depend on lsb-base (>= 3.2-14) to ensure that this file is present
+# and status_of_proc is working.
+. /lib/lsb/init-functions
+
+#
+# Function that starts the daemon/service
+#
+do_start()
+{
+       if [ ! -f /var/lib/cacert-gigi/config/gigi.properties ]; then
+               echo Missing signer-configfile
+               return 2
+       fi
+       # Return
+       #   0 if daemon has been started
+       #   1 if daemon was already running
+       #   2 if daemon could not be started
+       start-stop-daemon --start --quiet --pidfile $PIDFILE -d $DIR --startas $DAEMON --test > /dev/null \
+               || return 1
+       start-stop-daemon -b --start --quiet --pidfile $PIDFILE --make-pidfile -d $DIR --startas $DAEMON -- \
+               $DAEMON_ARGS \
+               || return 2
+       # The above code will not work for interpreted scripts, use the next
+       # six lines below instead (Ref: #643337, start-stop-daemon(8) )
+       #start-stop-daemon --start --quiet --pidfile $PIDFILE --startas $DAEMON \
+       #       --name $NAME --test > /dev/null \
+       #       || return 1
+       #start-stop-daemon -b --start --quiet --pidfile $PIDFILE --startas $DAEMON \
+       #       --name $NAME -- $DAEMON_ARGS \
+       #       || return 2
+
+       # Add code here, if necessary, that waits for the process to be ready
+       # to handle requests from services started subsequently which depend
+       # on this one.  As a last resort, sleep for some time.
+}
+
+#
+# Function that stops the daemon/service
+#
+do_stop()
+{
+       # Return
+       #   0 if daemon has been stopped
+       #   1 if daemon was already stopped
+       #   2 if daemon could not be stopped
+       #   other if a failure occurred
+       start-stop-daemon --stop --quiet --retry=TERM/30/KILL/5 --pidfile $PIDFILE
+       RETVAL="$?"
+       [ "$RETVAL" = 2 ] && return 2
+       # Wait for children to finish too if this is a daemon that forks
+       # and if the daemon is only ever run from this initscript.
+       # If the above conditions are not satisfied then add some other code
+       # that waits for the process to drop all resources that could be
+       # needed by services started subsequently.  A last resort is to
+       # sleep for some time.
+       start-stop-daemon --stop --quiet --oknodo --retry=0/30/KILL/5 --pidfile $PIDFILE
+       [ "$?" = 2 ] && return 2
+       # Many daemons don't delete their pidfiles when they exit.
+       rm -f $PIDFILE
+       return "$RETVAL"
+}
+
+#
+# Function that sends a SIGHUP to the daemon/service
+#
+do_reload() {
+       #
+       # If the daemon can reload its configuration without
+       # restarting (for example, when it is sent a SIGHUP),
+       # then implement that here.
+       #
+       start-stop-daemon --stop --signal 1 --quiet --pidfile $PIDFILE --name $NAME
+       return 0
+}
+
+case "$1" in
+  start)
+       [ "$VERBOSE" != no ] && log_daemon_msg "Starting $DESC" "$NAME"
+       do_start
+       case "$?" in
+               0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;;
+               2) [ "$VERBOSE" != no ] && log_end_msg 1 ;;
+       esac
+       ;;
+  stop)
+       [ "$VERBOSE" != no ] && log_daemon_msg "Stopping $DESC" "$NAME"
+       do_stop
+       case "$?" in
+               0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;;
+               2) [ "$VERBOSE" != no ] && log_end_msg 1 ;;
+       esac
+       ;;
+  status)
+       status_of_proc "$DAEMON" "$NAME" && exit 0 || exit $?
+       ;;
+  #reload|force-reload)
+       #
+       # If do_reload() is not implemented then leave this commented out
+       # and leave 'force-reload' as an alias for 'restart'.
+       #
+       #log_daemon_msg "Reloading $DESC" "$NAME"
+       #do_reload
+       #log_end_msg $?
+       #;;
+  restart|force-reload)
+       #
+       # If the "reload" option is implemented then remove the
+       # 'force-reload' alias
+       #
+       log_daemon_msg "Restarting $DESC" "$NAME"
+       do_stop
+       case "$?" in
+         0|1)
+               do_start
+               case "$?" in
+                       0) log_end_msg 0 ;;
+                       1) log_end_msg 1 ;; # Old process is still running
+                       *) log_end_msg 1 ;; # Failed to start
+               esac
+               ;;
+         *)
+               # Failed to stop
+               log_end_msg 1
+               ;;
+       esac
+       ;;
+  *)
+       #echo "Usage: $SCRIPTNAME {start|stop|restart|reload|force-reload}" >&2
+       echo "Usage: $SCRIPTNAME {start|stop|status|restart|force-reload}" >&2
+       exit 3
+       ;;
+esac
+
+:
diff --git a/debian/cacert-gigi-testing.cacert-gigi.default b/debian/cacert-gigi-testing.cacert-gigi.default
new file mode 100644 (file)
index 0000000..436a32a
--- /dev/null
@@ -0,0 +1 @@
+START_DAEMON=0
diff --git a/debian/cacert-gigi-testing.cacert-gigi.init b/debian/cacert-gigi-testing.cacert-gigi.init
new file mode 100644 (file)
index 0000000..278f690
--- /dev/null
@@ -0,0 +1,175 @@
+#!/bin/sh
+### BEGIN INIT INFO
+# Provides:          cacert-gigi
+# Required-Start:    $local_fs $network $remote_fs $syslog mysql
+# Required-Stop:     $local_fs $network $remote_fs $syslog mysql
+# Default-Start:     2 3 4 5
+# Default-Stop:      0 1 6
+# Short-Description: <Enter a short description of the software>
+# Description:       <Enter a long description of the software>
+#                    <...>
+#                    <...>
+### END INIT INFO
+
+# Author: unknown <software@cacert.org>
+
+# Do NOT "set -e"
+
+# PATH should only include /usr/* if it runs after the mountnfs.sh script
+PATH=/sbin:/usr/sbin:/bin:/usr/bin
+DESC="cacert-gigi"
+NAME=cacert-gigi
+DAEMON=`which java`
+DAEMON_ARGS="-cp /usr/share/java/mysql-connector-java.jar:/usr/share/java/gigi.jar org.cacert.gigi.Launcher"
+PIDFILE=/var/run/$NAME.pid
+SCRIPTNAME=/etc/init.d/$NAME
+DIR=/var/lib/cacert-gigi
+
+# Exit if the package is not installed
+[ -r "/usr/share/java/gigi.jar" ] || exit 0
+
+# Read configuration variable file if it is present
+[ -r /etc/default/$NAME ] && . /etc/default/$NAME
+if [ "$START_DAEMON" = "0" ]; then
+    echo "Not starting $NAME (as configured in /etc/default/$NAME)";
+    exit 0;
+fi
+
+
+# Load the VERBOSE setting and other rcS variables
+. /lib/init/vars.sh
+
+# Define LSB log_* functions.
+# Depend on lsb-base (>= 3.2-14) to ensure that this file is present
+# and status_of_proc is working.
+. /lib/lsb/init-functions
+
+#
+# Function that starts the daemon/service
+#
+do_start()
+{
+       if [ ! -f /etc/cacert/gigi/conf.tar ]; then
+               echo Missing gigi-configfile
+               exit 2
+       fi
+       # Return
+       #   0 if daemon has been started
+       #   1 if daemon was already running
+       #   2 if daemon could not be started
+       start-stop-daemon --start --quiet --pidfile $PIDFILE -d $DIR --exec $DAEMON --test > /dev/null \
+               || return 1
+       start-stop-daemon -b --start --quiet --pidfile $PIDFILE -d $DIR --exec /usr/bin/gigi -- start-daemon \
+               || return 2
+       # The above code will not work for interpreted scripts, use the next
+       # six lines below instead (Ref: #643337, start-stop-daemon(8) )
+       # start-stop-daemon --start --quiet --pidfile $PIDFILE --startas $DAEMON \
+       #       --name $NAME --test > /dev/null \
+       #       || return 1
+       # start-stop-daemon --start --quiet --pidfile $PIDFILE --startas $DAEMON \
+       #       --name $NAME -- $DAEMON_ARGS \
+       #       || return 2
+
+       # Add code here, if necessary, that waits for the process to be ready
+       # to handle requests from services started subsequently which depend
+       # on this one.  As a last resort, sleep for some time.
+}
+
+#
+# Function that stops the daemon/service
+#
+do_stop()
+{
+       # Return
+       #   0 if daemon has been stopped
+       #   1 if daemon was already stopped
+       #   2 if daemon could not be stopped
+       #   other if a failure occurred
+       start-stop-daemon --stop --quiet --retry=TERM/30/KILL/5 --pidfile $PIDFILE
+       RETVAL="$?"
+       [ "$RETVAL" = 2 ] && return 2
+       # Wait for children to finish too if this is a daemon that forks
+       # and if the daemon is only ever run from this initscript.
+       # If the above conditions are not satisfied then add some other code
+       # that waits for the process to drop all resources that could be
+       # needed by services started subsequently.  A last resort is to
+       # sleep for some time.
+       start-stop-daemon --stop --quiet --oknodo --retry=0/30/KILL/5 --pidfile $PIDFILE
+       [ "$?" = 2 ] && return 2
+       # Many daemons don't delete their pidfiles when they exit.
+       rm -f $PIDFILE
+       return "$RETVAL"
+}
+
+#
+# Function that sends a SIGHUP to the daemon/service
+#
+do_reload() {
+       #
+       # If the daemon can reload its configuration without
+       # restarting (for example, when it is sent a SIGHUP),
+       # then implement that here.
+       #
+       start-stop-daemon --stop --signal 1 --quiet --pidfile $PIDFILE --name $NAME
+       return 0
+}
+
+case "$1" in
+  start)
+       [ "$VERBOSE" != no ] && log_daemon_msg "Starting $DESC" "$NAME"
+       do_start
+       case "$?" in
+               0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;;
+               2) [ "$VERBOSE" != no ] && log_end_msg 1 ;;
+       esac
+       ;;
+  stop)
+       [ "$VERBOSE" != no ] && log_daemon_msg "Stopping $DESC" "$NAME"
+       do_stop
+       case "$?" in
+               0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;;
+               2) [ "$VERBOSE" != no ] && log_end_msg 1 ;;
+       esac
+       ;;
+  status)
+       status_of_proc "$DAEMON" "$NAME" && exit 0 || exit $?
+       ;;
+  #reload|force-reload)
+       #
+       # If do_reload() is not implemented then leave this commented out
+       # and leave 'force-reload' as an alias for 'restart'.
+       #
+       #log_daemon_msg "Reloading $DESC" "$NAME"
+       #do_reload
+       #log_end_msg $?
+       #;;
+  restart|force-reload)
+       #
+       # If the "reload" option is implemented then remove the
+       # 'force-reload' alias
+       #
+       log_daemon_msg "Restarting $DESC" "$NAME"
+       do_stop
+       case "$?" in
+         0|1)
+               do_start
+               case "$?" in
+                       0) log_end_msg 0 ;;
+                       1) log_end_msg 1 ;; # Old process is still running
+                       *) log_end_msg 1 ;; # Failed to start
+               esac
+               ;;
+         *)
+               # Failed to stop
+               log_end_msg 1
+               ;;
+       esac
+       ;;
+  *)
+       #echo "Usage: $SCRIPTNAME {start|stop|restart|reload|force-reload}" >&2
+       echo "Usage: $SCRIPTNAME {start|stop|status|restart|force-reload}" >&2
+       exit 3
+       ;;
+esac
+
+:
diff --git a/debian/cacert-gigi-testing.docs b/debian/cacert-gigi-testing.docs
new file mode 100644 (file)
index 0000000..b43bf86
--- /dev/null
@@ -0,0 +1 @@
+README.md
diff --git a/debian/cacert-gigi-testing.manpages b/debian/cacert-gigi-testing.manpages
new file mode 100644 (file)
index 0000000..3de344b
--- /dev/null
@@ -0,0 +1 @@
+debian/gigi.1
diff --git a/debian/cacert-gigi-testing.postinst b/debian/cacert-gigi-testing.postinst
new file mode 100644 (file)
index 0000000..3a7ec74
--- /dev/null
@@ -0,0 +1,45 @@
+#!/bin/sh
+# postinst script for cacert-gigi
+#
+# see: dh_installdeb(1)
+
+set -e
+
+# summary of how this script can be called:
+#        * <postinst> `configure' <most-recently-configured-version>
+#        * <old-postinst> `abort-upgrade' <new version>
+#        * <conflictor's-postinst> `abort-remove' `in-favour' <package>
+#          <new-version>
+#        * <postinst> `abort-remove'
+#        * <deconfigured's-postinst> `abort-deconfigure' `in-favour'
+#          <failed-install-package> <version> `removing'
+#          <conflicting-package> <version>
+# for details, see http://www.debian.org/doc/debian-policy/ or
+# the debian-policy package
+
+
+case "$1" in
+    configure)
+    ;;
+
+    abort-upgrade|abort-remove|abort-deconfigure)
+    ;;
+
+    *)
+        echo "postinst called with unknown argument \`$1'" >&2
+        exit 1
+    ;;
+esac
+
+if [ -f /usr/share/debconf/confmodule ]; then
+    . /usr/share/debconf/confmodule
+fi
+
+gigi fetch-locales
+
+# dh_installdeb will replace this with shell code automatically
+# generated by other debhelper scripts.
+
+#DEBHELPER#
+
+exit 0
diff --git a/debian/cacert-gigi.cacert-gigi-signer.init b/debian/cacert-gigi.cacert-gigi-signer.init
new file mode 100644 (file)
index 0000000..7e6c085
--- /dev/null
@@ -0,0 +1,171 @@
+#!/bin/sh
+### BEGIN INIT INFO
+# Provides:          cacert-gigi-signer
+# Required-Start:    $local_fs $network $remote_fs $syslog mysql
+# Required-Stop:     $local_fs $network $remote_fs $syslog mysql
+# Default-Start:     2 3 4 5
+# Default-Stop:      0 1 6
+# Short-Description: <Enter a short description of the software>
+# Description:       <Enter a long description of the software>
+#                    <...>
+#                    <...>
+### END INIT INFO
+
+# Author: unknown <software@cacert.org>
+
+# Do NOT "set -e"
+
+# PATH should only include /usr/* if it runs after the mountnfs.sh script
+PATH=/sbin:/usr/sbin:/bin:/usr/bin
+DESC="cacert-gigi-signer"
+NAME=cacert-gigi-signer
+DAEMON=`which java`
+DAEMON_ARGS="-cp /usr/share/java/mysql-connector-java.jar:/usr/share/java/gigi.jar org.cacert.gigi.util.SimpleSigner"
+PIDFILE=/var/run/$NAME.pid
+SCRIPTNAME=/etc/init.d/$NAME
+DIR=/var/lib/cacert-gigi
+
+# Exit if the package is not installed
+[ -x "$DAEMON" ] || exit 0
+
+# Read configuration variable file if it is present
+[ -r /etc/default/$NAME ] && . /etc/default/$NAME
+
+# Load the VERBOSE setting and other rcS variables
+. /lib/init/vars.sh
+
+# Define LSB log_* functions.
+# Depend on lsb-base (>= 3.2-14) to ensure that this file is present
+# and status_of_proc is working.
+. /lib/lsb/init-functions
+
+#
+# Function that starts the daemon/service
+#
+do_start()
+{
+        if [ ! -f /var/lib/cacert-gigi/config/gigi.properties ]; then
+                echo Missing signer-configfile
+                return 0
+        fi
+       # Return
+       #   0 if daemon has been started
+       #   1 if daemon was already running
+       #   2 if daemon could not be started
+       start-stop-daemon --start --quiet --pidfile $PIDFILE -d $DIR --startas $DAEMON --test > /dev/null \
+               || return 1
+       start-stop-daemon -b --start --quiet --pidfile $PIDFILE --make-pidfile -d $DIR --startas $DAEMON -- \
+               $DAEMON_ARGS \
+               || return 2
+       # The above code will not work for interpreted scripts, use the next
+       # six lines below instead (Ref: #643337, start-stop-daemon(8) )
+       #start-stop-daemon --start --quiet --pidfile $PIDFILE --startas $DAEMON \
+       #       --name $NAME --test > /dev/null \
+       #       || return 1
+       #start-stop-daemon -b --start --quiet --pidfile $PIDFILE --startas $DAEMON \
+       #       --name $NAME -- $DAEMON_ARGS \
+       #       || return 2
+
+       # Add code here, if necessary, that waits for the process to be ready
+       # to handle requests from services started subsequently which depend
+       # on this one.  As a last resort, sleep for some time.
+}
+
+#
+# Function that stops the daemon/service
+#
+do_stop()
+{
+       # Return
+       #   0 if daemon has been stopped
+       #   1 if daemon was already stopped
+       #   2 if daemon could not be stopped
+       #   other if a failure occurred
+       start-stop-daemon --stop --quiet --retry=TERM/30/KILL/5 --pidfile $PIDFILE
+       RETVAL="$?"
+       [ "$RETVAL" = 2 ] && return 2
+       # Wait for children to finish too if this is a daemon that forks
+       # and if the daemon is only ever run from this initscript.
+       # If the above conditions are not satisfied then add some other code
+       # that waits for the process to drop all resources that could be
+       # needed by services started subsequently.  A last resort is to
+       # sleep for some time.
+       start-stop-daemon --stop --quiet --oknodo --retry=0/30/KILL/5 --pidfile $PIDFILE
+       [ "$?" = 2 ] && return 2
+       # Many daemons don't delete their pidfiles when they exit.
+       rm -f $PIDFILE
+       return "$RETVAL"
+}
+
+#
+# Function that sends a SIGHUP to the daemon/service
+#
+do_reload() {
+       #
+       # If the daemon can reload its configuration without
+       # restarting (for example, when it is sent a SIGHUP),
+       # then implement that here.
+       #
+       start-stop-daemon --stop --signal 1 --quiet --pidfile $PIDFILE --name $NAME
+       return 0
+}
+
+case "$1" in
+  start)
+       [ "$VERBOSE" != no ] && log_daemon_msg "Starting $DESC" "$NAME"
+       do_start
+       case "$?" in
+               0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;;
+               2) [ "$VERBOSE" != no ] && log_end_msg 1 ;;
+       esac
+       ;;
+  stop)
+       [ "$VERBOSE" != no ] && log_daemon_msg "Stopping $DESC" "$NAME"
+       do_stop
+       case "$?" in
+               0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;;
+               2) [ "$VERBOSE" != no ] && log_end_msg 1 ;;
+       esac
+       ;;
+  status)
+       status_of_proc "$DAEMON" "$NAME" && exit 0 || exit $?
+       ;;
+  #reload|force-reload)
+       #
+       # If do_reload() is not implemented then leave this commented out
+       # and leave 'force-reload' as an alias for 'restart'.
+       #
+       #log_daemon_msg "Reloading $DESC" "$NAME"
+       #do_reload
+       #log_end_msg $?
+       #;;
+  restart|force-reload)
+       #
+       # If the "reload" option is implemented then remove the
+       # 'force-reload' alias
+       #
+       log_daemon_msg "Restarting $DESC" "$NAME"
+       do_stop
+       case "$?" in
+         0|1)
+               do_start
+               case "$?" in
+                       0) log_end_msg 0 ;;
+                       1) log_end_msg 1 ;; # Old process is still running
+                       *) log_end_msg 1 ;; # Failed to start
+               esac
+               ;;
+         *)
+               # Failed to stop
+               log_end_msg 1
+               ;;
+       esac
+       ;;
+  *)
+       #echo "Usage: $SCRIPTNAME {start|stop|restart|reload|force-reload}" >&2
+       echo "Usage: $SCRIPTNAME {start|stop|status|restart|force-reload}" >&2
+       exit 3
+       ;;
+esac
+
+:
diff --git a/debian/cacert-gigi.cacert-gigi.init b/debian/cacert-gigi.cacert-gigi.init
new file mode 100644 (file)
index 0000000..d044355
--- /dev/null
@@ -0,0 +1,170 @@
+#!/bin/sh
+### BEGIN INIT INFO
+# Provides:          cacert-gigi
+# Required-Start:    $local_fs $network $remote_fs $syslog mysql
+# Required-Stop:     $local_fs $network $remote_fs $syslog mysql
+# Default-Start:     2 3 4 5
+# Default-Stop:      0 1 6
+# Short-Description: <Enter a short description of the software>
+# Description:       <Enter a long description of the software>
+#                    <...>
+#                    <...>
+### END INIT INFO
+
+# Author: unknown <software@cacert.org>
+
+# Do NOT "set -e"
+
+# PATH should only include /usr/* if it runs after the mountnfs.sh script
+PATH=/sbin:/usr/sbin:/bin:/usr/bin
+DESC="cacert-gigi"
+NAME=cacert-gigi
+DAEMON=`which java`
+DAEMON_ARGS="-cp /usr/share/java/mysql-connector-java.jar:/usr/share/java/gigi.jar org.cacert.gigi.Launcher"
+PIDFILE=/var/run/$NAME.pid
+SCRIPTNAME=/etc/init.d/$NAME
+DIR=/var/lib/cacert-gigi
+
+# Exit if the package is not installed
+[ -x "$DAEMON" ] || exit 0
+
+# Read configuration variable file if it is present
+[ -r /etc/default/$NAME ] && . /etc/default/$NAME
+
+# Load the VERBOSE setting and other rcS variables
+. /lib/init/vars.sh
+
+# Define LSB log_* functions.
+# Depend on lsb-base (>= 3.2-14) to ensure that this file is present
+# and status_of_proc is working.
+. /lib/lsb/init-functions
+
+#
+# Function that starts the daemon/service
+#
+do_start()
+{
+       if [ ! -f /etc/cacert-gigi/conf.tar ]; then
+               echo Missing gigi-configfile
+                exit 0
+        fi
+       # Return
+       #   0 if daemon has been started
+       #   1 if daemon was already running
+       #   2 if daemon could not be started
+        start-stop-daemon --start --quiet --pidfile $PIDFILE -d $DIR --exec $DAEMON --test > /dev/null \
+               || return 1
+       start-stop-daemon -b --start --quiet --pidfile $PIDFILE -d $DIR --exec /usr/bin/gigi -- start-daemon \
+               || return 2
+       # The above code will not work for interpreted scripts, use the next
+       # six lines below instead (Ref: #643337, start-stop-daemon(8) )
+       # start-stop-daemon --start --quiet --pidfile $PIDFILE --startas $DAEMON \
+       #       --name $NAME --test > /dev/null \
+       #       || return 1
+       # start-stop-daemon --start --quiet --pidfile $PIDFILE --startas $DAEMON \
+       #       --name $NAME -- $DAEMON_ARGS \
+       #       || return 2
+
+       # Add code here, if necessary, that waits for the process to be ready
+       # to handle requests from services started subsequently which depend
+       # on this one.  As a last resort, sleep for some time.
+}
+
+#
+# Function that stops the daemon/service
+#
+do_stop()
+{
+       # Return
+       #   0 if daemon has been stopped
+       #   1 if daemon was already stopped
+       #   2 if daemon could not be stopped
+       #   other if a failure occurred
+       start-stop-daemon --stop --quiet --retry=TERM/30/KILL/5 --pidfile $PIDFILE
+       RETVAL="$?"
+       [ "$RETVAL" = 2 ] && return 2
+       # Wait for children to finish too if this is a daemon that forks
+       # and if the daemon is only ever run from this initscript.
+       # If the above conditions are not satisfied then add some other code
+       # that waits for the process to drop all resources that could be
+       # needed by services started subsequently.  A last resort is to
+       # sleep for some time.
+       start-stop-daemon --stop --quiet --oknodo --retry=0/30/KILL/5 --pidfile $PIDFILE
+       [ "$?" = 2 ] && return 2
+       # Many daemons don't delete their pidfiles when they exit.
+       rm -f $PIDFILE
+       return "$RETVAL"
+}
+
+#
+# Function that sends a SIGHUP to the daemon/service
+#
+do_reload() {
+       #
+       # If the daemon can reload its configuration without
+       # restarting (for example, when it is sent a SIGHUP),
+       # then implement that here.
+       #
+       start-stop-daemon --stop --signal 1 --quiet --pidfile $PIDFILE --name $NAME
+       return 0
+}
+
+case "$1" in
+  start)
+       [ "$VERBOSE" != no ] && log_daemon_msg "Starting $DESC" "$NAME"
+       do_start
+       case "$?" in
+               0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;;
+               2) [ "$VERBOSE" != no ] && log_end_msg 1 ;;
+       esac
+       ;;
+  stop)
+       [ "$VERBOSE" != no ] && log_daemon_msg "Stopping $DESC" "$NAME"
+       do_stop
+       case "$?" in
+               0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;;
+               2) [ "$VERBOSE" != no ] && log_end_msg 1 ;;
+       esac
+       ;;
+  status)
+       status_of_proc "$DAEMON" "$NAME" && exit 0 || exit $?
+       ;;
+  #reload|force-reload)
+       #
+       # If do_reload() is not implemented then leave this commented out
+       # and leave 'force-reload' as an alias for 'restart'.
+       #
+       #log_daemon_msg "Reloading $DESC" "$NAME"
+       #do_reload
+       #log_end_msg $?
+       #;;
+  restart|force-reload)
+       #
+       # If the "reload" option is implemented then remove the
+       # 'force-reload' alias
+       #
+       log_daemon_msg "Restarting $DESC" "$NAME"
+       do_stop
+       case "$?" in
+         0|1)
+               do_start
+               case "$?" in
+                       0) log_end_msg 0 ;;
+                       1) log_end_msg 1 ;; # Old process is still running
+                       *) log_end_msg 1 ;; # Failed to start
+               esac
+               ;;
+         *)
+               # Failed to stop
+               log_end_msg 1
+               ;;
+       esac
+       ;;
+  *)
+       #echo "Usage: $SCRIPTNAME {start|stop|restart|reload|force-reload}" >&2
+       echo "Usage: $SCRIPTNAME {start|stop|status|restart|force-reload}" >&2
+       exit 3
+       ;;
+esac
+
+:
diff --git a/debian/cacert-gigi.docs b/debian/cacert-gigi.docs
new file mode 100644 (file)
index 0000000..b43bf86
--- /dev/null
@@ -0,0 +1 @@
+README.md
diff --git a/debian/cacert-gigi.manpages b/debian/cacert-gigi.manpages
new file mode 100644 (file)
index 0000000..3de344b
--- /dev/null
@@ -0,0 +1 @@
+debian/gigi.1
diff --git a/debian/cacert-gigi.postinst b/debian/cacert-gigi.postinst
new file mode 100644 (file)
index 0000000..3a7ec74
--- /dev/null
@@ -0,0 +1,45 @@
+#!/bin/sh
+# postinst script for cacert-gigi
+#
+# see: dh_installdeb(1)
+
+set -e
+
+# summary of how this script can be called:
+#        * <postinst> `configure' <most-recently-configured-version>
+#        * <old-postinst> `abort-upgrade' <new version>
+#        * <conflictor's-postinst> `abort-remove' `in-favour' <package>
+#          <new-version>
+#        * <postinst> `abort-remove'
+#        * <deconfigured's-postinst> `abort-deconfigure' `in-favour'
+#          <failed-install-package> <version> `removing'
+#          <conflicting-package> <version>
+# for details, see http://www.debian.org/doc/debian-policy/ or
+# the debian-policy package
+
+
+case "$1" in
+    configure)
+    ;;
+
+    abort-upgrade|abort-remove|abort-deconfigure)
+    ;;
+
+    *)
+        echo "postinst called with unknown argument \`$1'" >&2
+        exit 1
+    ;;
+esac
+
+if [ -f /usr/share/debconf/confmodule ]; then
+    . /usr/share/debconf/confmodule
+fi
+
+gigi fetch-locales
+
+# dh_installdeb will replace this with shell code automatically
+# generated by other debhelper scripts.
+
+#DEBHELPER#
+
+exit 0
diff --git a/debian/changelog b/debian/changelog
new file mode 100644 (file)
index 0000000..cc57cff
--- /dev/null
@@ -0,0 +1,5 @@
+cacert-gigi (0.1) unstable; urgency=low
+
+  * Initial Release
+
+ -- CAcert Software Team <cacert-devel@cacert.org>  Thu, 25 Sep 2014 03:19:20 +0200
diff --git a/debian/compat b/debian/compat
new file mode 100644 (file)
index 0000000..ec63514
--- /dev/null
@@ -0,0 +1 @@
+9
diff --git a/debian/control b/debian/control
new file mode 100644 (file)
index 0000000..001f80e
--- /dev/null
@@ -0,0 +1,29 @@
+Source: cacert-gigi
+Section: java
+Priority: optional
+Maintainer: unknown <software@cacert.org>
+Build-Depends: debhelper (>= 8.0.0), openjdk-8-jdk-gigi
+Standards-Version: 3.9.4
+Homepage: http://cacert.org
+#Vcs-Git: git://git.debian.org/collab-maint/cacert-gigi.git
+#Vcs-Browser: http://git.debian.org/?p=collab-maint/cacert-gigi.git;a=summary
+
+Package: cacert-gigi
+Architecture: all
+Depends: openjdk-8-jdk-gigi, cacert-gigi-setuid, ${shlibs:Depends}, ${misc:Depends}
+Conflicts: cacert-gigi-testing
+Description: CAcert Web-DB software.
+ This program is used to manage accounts and certificates.
+
+Package: cacert-gigi-testing
+Architecture: all
+Depends:  openjdk-8-jdk-gigi, cacert-gigi-setuid, ${shlibs:Depends}, ${misc:Depends}
+Conflicts: cacert-gigi
+Description: CAcert Web-DB software testing version.
+ This program is the release to the testing server.
+
+Package: cacert-gigi-setuid
+Architecture: any
+Depends: openjdk-8-jdk-gigi, ${shlibs:Depends}, ${misc:Depends}
+Description: CAcert Web-DB software's setuid native library.
+ It is used to drop privilleges after allocating ports.
diff --git a/debian/copyright b/debian/copyright
new file mode 100644 (file)
index 0000000..c454948
--- /dev/null
@@ -0,0 +1,65 @@
+Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
+Upstream-Name: gigi
+Source: <https://github.com/CAcertOrg/cacert-gigi>
+
+Files: *
+Copyright: 2014 CAcert Software Team <cacert-devel@cacert.org>
+License: GPL-2.0
+
+Files: debian/*
+Copyright: 2014 CAcert Software Team <cacert-devel@cacert.org>
+License: GPL-2.0
+
+Files: src/org/cacert/gigi/output/template
+Copyright: 2015 CAcert Software Team <cacert-devel@cacert.org>
+License: GPL-2.0 or BSD
+
+Files: src/org/cacert/gigi/localisation
+Copyright: 2015 CAcert Software Team <cacert-devel@cacert.org>
+License: GPL-2.0 or BSD
+
+Files: src/org/cacert/gigi/database
+Copyright: 2015 CAcert Software Team <cacert-devel@cacert.org>
+License: GPL-2.0 or BSD
+
+License: BSD
+ Copyright (c) 2015, CAcert
+ All rights reserved.
+ .
+ Redistribution and use in source and binary forms, with or without 
+ modification, are permitted provided that the following conditions are met:
+ .
+ 1. Redistributions of source code must retain the above copyright notice, 
+ this list of conditions and the following disclaimer.
+ .
+ 2. Redistributions in binary form must reproduce the above copyright notice, 
+ this list of conditions and the following disclaimer in the documentation 
+ and/or other materials provided with the distribution.
+ .
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 
+ ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 
+ LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 
+ CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 
+ SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 
+ INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
+ CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
+ ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF 
+ THE POSSIBILITY OF SUCH DAMAGE.
+
+License: GPL-2.0
+ This package is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; only version 2 of the License.
+ .
+ This package is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ GNU General Public License for more details.
+ .
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>
+ .
+ On Debian systems, the complete text of the GNU General
+ Public License version 2 can be found in "/usr/share/common-licenses/GPL-2".
diff --git a/debian/gigi.1 b/debian/gigi.1
new file mode 100644 (file)
index 0000000..61b41b6
--- /dev/null
@@ -0,0 +1,49 @@
+.\"                                      Hey, EMACS: -*- nroff -*-
+.\" (C) Copyright 2014 CAcert Software Team <software@cacert.org>,
+.\"
+.TH CACERT-GIGI 1 "September 25, 2014"
+.\" Please adjust this date whenever revising the manpage.
+.\"
+.\" Some roff macros, for reference:
+.\" .nh        disable hyphenation
+.\" .hy        enable hyphenation
+.\" .ad l      left justify
+.\" .ad b      justify to both left and right margins
+.\" .nf        disable filling
+.\" .fi        enable filling
+.\" .br        insert line break
+.\" .sp <n>    insert n+1 empty lines
+.\" for manpage-specific macros, see man(7)
+.SH NAME
+cacert-gigi \- a starter for the CAcert-gigi system
+.SH SYNOPSIS
+.B cacert-gigi
+.RI {start|signer|reset-database|fetch-locales}
+.SH DESCRIPTION
+.B cacert-gigi
+is the starter for the CAcert-gigi system.
+.\" TeX users may be more comfortable with the \fB<whatever>\fP and
+.\" \fI<whatever>\fP escape sequences to invode bold face and italics,
+.\" respectively.
+.SH OPTIONS
+.TP
+.B debug
+Run the usual webdb (not forking) with opening jdwp on port 8000. You will need to pipe the config into this program.
+.TP
+.B fetch-locales
+Fetch all Translations from http://translations.cacert.org/
+.TP
+.B reset-database
+Delete the whole database contents, resetting it to default.
+.TP
+.B signer
+Run the test-replacement signer (not forking).
+.TP
+.B signer-conf
+Configure the (internal) signer and the "reset-database"-tool with the config from stdin.
+.TP
+.B start
+Run the usual webdb (not forking). You will need to pipe the config into this program.
+.TP
+.B start-daemon
+Run the usual webdb (forking). You will not need to pipe the config into this program. It reads the config from /etc/cacert/gigi/conf.tar
diff --git a/debian/rules b/debian/rules
new file mode 100755 (executable)
index 0000000..f0e56fb
--- /dev/null
@@ -0,0 +1,26 @@
+#!/usr/bin/make -f
+# -*- makefile -*-
+
+# Uncomment this to turn on verbose mode.
+#export DH_VERBOSE=1
+
+%:
+       dh $@ 
+
+build:
+       ant pack
+
+override_dh_auto_clean:
+       echo i dont clean
+
+override_dh_installinit:
+       dh_installinit --name=cacert-gigi
+       dh_installinit --name=cacert-gigi-signer
+
+override_dh_auto_build: build
+
+override_dh_auto_install:
+       DESTDIR=debian/cacert-gigi ant install
+       DESTDIR=debian/cacert-gigi-testing ant install-testing
+       DESTDIR=debian/cacert-gigi-setuid ant install-native
+
diff --git a/debian/source/format b/debian/source/format
new file mode 100644 (file)
index 0000000..89ae9db
--- /dev/null
@@ -0,0 +1 @@
+3.0 (native)
diff --git a/doc/.gitignore b/doc/.gitignore
new file mode 100644 (file)
index 0000000..becafc3
--- /dev/null
@@ -0,0 +1 @@
+sampleData.sql
diff --git a/doc/TemplateSyntax.txt b/doc/TemplateSyntax.txt
new file mode 100644 (file)
index 0000000..ad8e09a
--- /dev/null
@@ -0,0 +1,29 @@
+A template is constructed from a charstream. Everything that is not in "<?" to "?>" will be outputted directly. Text in these markers will be interpreted is template scripting syntax. The following strings are valid:
+
+General remarks:
+- $variablename: a variablename matches the regex [a-zA-Z0-9_-]
+Syntax:
+- <?=$variablename?> will output "variablename".
+   if "variablename" is an Outputable output this thing recursively.
+   else turn it into a String (Object.toString()) and output it.
+
+- <?=$!variablename?> will output the variable "variablename" but not HTML-escaped
+   
+- <?=_This is free Text.?> will translate "This is free Text." into the users language, (escaped) and output it.
+       Text may not contain "?>".
+       If the text contains "$" or "!'" it is interpreted as "advanced replacement".
+          - ${variablename} is interpreted as "output this variable at this point"
+          - !'literal content' output "literal content" here and do not translate or escape. (literal content may not contain any of: {}'$   )
+       Then the whole text than also may not contain "{" and "}".
+
+- <? if($variable) { ?> ... <? } ?>
+  Output/execute the text until "<? } ?>" only if $variable is Boolean.TRUE (<=> !Boolean.FALSE) or not null. 
+- <? if(...) { ?> ... <? } else { ?> ... <? } ?>
+
+- <? foreach($variable) { ?> ... <? } ?>
+  If $variable is an "IterableDataset"
+  Output/execute the text until "<? } ?>" repeated as $variable suggests. 
+  Special variables that $variable defines can be used in the inner text.
+  
+  
\ No newline at end of file
diff --git a/doc/beforeYouStart.txt b/doc/beforeYouStart.txt
new file mode 100644 (file)
index 0000000..b51c5db
--- /dev/null
@@ -0,0 +1,15 @@
+Before you start using you might want to:
+
+- create a keypair for the server (scripts/generateKeys.sh)
+- create a truststore for the server (scripts/generateTruststore.sh)
+
+- download locales (util/ org.cacert.gigi.util.FetchLocales)
+- write your sql connection properties: config/gigi.properties.template -> config/gigi.properties
+- install "hosts" entries for the hosts you entered in "gigi.properties"
+   (be aware if you change the default ones you need to change the CN given in the certificates)
+
+- add the corresponding jdbc connector to your path.
+
+- on unix-like systems: to securely run on privileged ports <= 1024 build the native setuid library (run the makefile in natives/).
+  This expects JAVA_HOME to be set.
+
diff --git a/doc/exoticKeys.sh b/doc/exoticKeys.sh
new file mode 100644 (file)
index 0000000..caa4c76
--- /dev/null
@@ -0,0 +1,7 @@
+#!/bin/bash
+openssl ecparam -out ec.pem -name secp521r1 -genkey
+openssl req -new -key ec.pem -out ec.csr -subj "/CN=bla"
+
+openssl dsaparam -genkey 1024 -out dsa.pem
+openssl req -new -key dsa.pem -out dsa.csr -subj "/CN=bla"
+
diff --git a/doc/jenkinsJob/README.txt b/doc/jenkinsJob/README.txt
new file mode 100644 (file)
index 0000000..ec8e337
--- /dev/null
@@ -0,0 +1,12 @@
+you need the debian mysql-connector package (jar under /usr/share/java/mysql-connector.jar)
+
+<yourSqlPassword>
+a Password to the sql database to test in.
+
+/path/to/folder/with/junit/
+folder Containing:
+- junit.jar
+- org.hamcrest.core.jar
+
+
+Fill also all other variables that are marked with "$$$$"
diff --git a/doc/jenkinsJob/ci-tests-setup.txt b/doc/jenkinsJob/ci-tests-setup.txt
new file mode 100644 (file)
index 0000000..e51c280
--- /dev/null
@@ -0,0 +1,16 @@
+-you need 4 domains resolving to the ci server (or localhost)
+preferably
+static.DOMAIN, secure.DOMAIN, www.DOMAIN and api.DOMAIN.
+enter them in the jenkins job to write them to "keys/config" and "config/test.properties"
+
+-you need credentials to an acessabible mysql database.
+make jenkins write them to "config/test.properties"
+
+-you need a dynamically managable dns zone.
+Write the zone name to "domain.dnstest" in "test.properties"
+and a manage script (see dyn-txt.php). 
+- Put the url with password in "domain.manage"
+- Put the host with password in "domain.http"
+
+Setup with bind9:
+dnssec-keygen -a HMAC-SHA512 -b 512 -n HOST example.org.
diff --git a/doc/jenkinsJob/config.xml b/doc/jenkinsJob/config.xml
new file mode 100644 (file)
index 0000000..505a7f1
--- /dev/null
@@ -0,0 +1,138 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<project>
+  <actions/>
+  <description></description>
+  <logRotator class="hudson.tasks.LogRotator">
+    <daysToKeep>-1</daysToKeep>
+    <numToKeep>100</numToKeep>
+    <artifactDaysToKeep>-1</artifactDaysToKeep>
+    <artifactNumToKeep>-1</artifactNumToKeep>
+  </logRotator>
+  <keepDependencies>false</keepDependencies>
+  <properties>
+    <hudson.model.ParametersDefinitionProperty>
+      <parameterDefinitions>
+        <hudson.model.TextParameterDefinition>
+          <name>JAVA_HOME</name>
+          <description></description>
+          <defaultValue>/usr/lib/jvm/openjdk-8-jdk-gigi</defaultValue>
+        </hudson.model.TextParameterDefinition>
+        <hudson.model.TextParameterDefinition>
+          <name>BRANCH</name>
+          <description>The branch to build from.</description>
+          <defaultValue>master</defaultValue>
+        </hudson.model.TextParameterDefinition>
+        <hudson.model.ChoiceParameterDefinition>
+          <name>TARGET</name>
+          <description>The target.</description>
+          <choices class="java.util.Arrays$ArrayList">
+            <a class="string-array">
+              <string>develop</string>
+              <string>release</string>
+            </a>
+          </choices>
+        </hudson.model.ChoiceParameterDefinition>
+      </parameterDefinitions>
+    </hudson.model.ParametersDefinitionProperty>
+  </properties>
+  <scm class="hudson.plugins.git.GitSCM" plugin="git@2.2.5">
+    <configVersion>2</configVersion>
+    <userRemoteConfigs>
+      <hudson.plugins.git.UserRemoteConfig>
+        <url>$$$$YOUR_REFERENCE_GIT_REPO$$$$</url>
+      </hudson.plugins.git.UserRemoteConfig>
+    </userRemoteConfigs>
+    <branches>
+      <hudson.plugins.git.BranchSpec>
+        <name>$BRANCH</name>
+      </hudson.plugins.git.BranchSpec>
+    </branches>
+    <doGenerateSubmoduleConfigurations>false</doGenerateSubmoduleConfigurations>
+    <submoduleCfg class="list"/>
+    <extensions>
+      <hudson.plugins.git.extensions.impl.RelativeTargetDirectory>
+        <relativeTargetDir>cacert-gigi</relativeTargetDir>
+      </hudson.plugins.git.extensions.impl.RelativeTargetDirectory>
+    </extensions>
+  </scm>
+  <canRoam>true</canRoam>
+  <disabled>false</disabled>
+  <blockBuildWhenDownstreamBuilding>false</blockBuildWhenDownstreamBuilding>
+  <blockBuildWhenUpstreamBuilding>false</blockBuildWhenUpstreamBuilding>
+  <jdk>Java 8 OpenJDK</jdk>
+  <triggers>
+    <hudson.triggers.SCMTrigger>
+      <spec>@midnight</spec>
+      <ignorePostCommitHooks>false</ignorePostCommitHooks>
+    </hudson.triggers.SCMTrigger>
+  </triggers>
+  <concurrentBuild>false</concurrentBuild>
+  <builders>
+    <hudson.tasks.Shell>
+      <command>rm -f *.deb
+cd cacert-gigi
+cat &lt;&lt;EOT &gt;keys/config
+DOMAIN=$$$$YOUR_LOOKUP_DOMAIN$$$$
+KEYSIZE=4096
+EOT
+cat &lt;&lt;EOT &gt;config/test.properties
+type=autonomous
+java=/usr/lib/jvm/openjdk-8-jdk-gigi/bin/java -cp bintest:gigi-testing.jar:/usr/share/java/mysql-connector-java.jar -javaagent:/usr/share/java/jacocoagent.jar org.cacert.gigi.Launcher
+serverPort.https=4448
+serverPort.http=8098
+mailPort=8473
+sql.driver=com.mysql.jdbc.Driver
+sql.url=jdbc:mysql://localhost:3306/cacert
+sql.user=cacert
+sql.password=$$$$sql password$$$$
+name.static=static.$$$$YOUR_LOOKUP_DOMAIN$$$$
+name.secure=secure.$$$$YOUR_LOOKUP_DOMAIN$$$$
+name.www=www.$$$$YOUR_LOOKUP_DOMAIN$$$$
+name.api=api.$$$$YOUR_LOOKUP_DOMAIN$$$$
+
+domain.manage=http://$$$$YOUR_TESTSERVICE$$$$/dyn-txt.php?token=$$$$managementToken$$$$&amp;
+domain.http=$$$$YOUR_TESTSERVICE_HTTP$$$$
+domain.dnstest=$$$$YOUR_TESTSERVICE_ZONE$$$$
+domain.testns=$$$$YOUR_TESTSERVICE_AUTH_NAMESERVER$$$$
+domain.local=test.$$$$YOUR_LOOKUP_DOMAIN$$$$
+
+email.address=$$$$YOUR_IMAP_EMAIL$$$$
+email.password=$$$$YOUR_IMAP_PASSWORD$$$$
+email.imap=$$$$YOUR_IMAP_SERVER$$$$
+email.imap.user=$$$$YOUR_IMAP_USERNAME$$$$
+email.non-address=$$$$IMAP_NON_EXISTENT_ADDRESS$$$$
+
+EOT
+
+</command>
+    </hudson.tasks.Shell>
+    <hudson.tasks.Ant plugin="ant@1.2">
+      <targets>$TARGET generatecoco</targets>
+      <antOpts>-Dfile.encoding=UTF-8</antOpts>
+      <buildFile>cacert-gigi/build.xml</buildFile>
+      <properties>juintexec=$$$$JUNIT_PATH$$$$
+test_nic=$$$$YOUR_TESTSERVICE_NIC$$$$\n$$$$YOUR_LOOKUP_DOMAIN$$$$</properties>
+    </hudson.tasks.Ant>
+    <hudson.tasks.Shell>
+      <command>cd cacert-gigi
+dpkg-buildpackage -b -us -uc</command>
+    </hudson.tasks.Shell>
+  </builders>
+  <publishers>
+    <hudson.tasks.junit.JUnitResultArchiver>
+      <testResults>cacert-gigi/junit/*.xml</testResults>
+      <keepLongStdio>false</keepLongStdio>
+      <testDataPublishers/>
+    </hudson.tasks.junit.JUnitResultArchiver>
+    <hudson.tasks.ArtifactArchiver>
+      <artifacts>cacert-gigi/natives/*.so,cacert-gigi/gigi*.jar,cacert-gigi/gigi-linux_amd64.zip,*.deb</artifacts>
+      <latestOnly>false</latestOnly>
+      <allowEmptyArchive>false</allowEmptyArchive>
+    </hudson.tasks.ArtifactArchiver>
+    <hudson.tasks.Fingerprinter>
+      <targets></targets>
+      <recordBuildArtifacts>true</recordBuildArtifacts>
+    </hudson.tasks.Fingerprinter>
+  </publishers>
+  <buildWrappers/>
+</project>
\ No newline at end of file
diff --git a/doc/jenkinsJob/dyn-txt.php b/doc/jenkinsJob/dyn-txt.php
new file mode 100644 (file)
index 0000000..c7b6cfe
--- /dev/null
@@ -0,0 +1,65 @@
+<?php
+header("Content-type: text/plain");
+
+define("ZONENAME", "cacert.dyn.dogcraft.de");
+define("KEYNAME", "keys/Kcacert.dyn.dogcraft.de.+165+54687.key");
+
+if(!isset($_GET['token']) || !isset($_GET['t1']) || !isset($_GET['t2']) || !isset($_GET['action'])){
+  die("Error");
+}
+if($_GET['token'] != "rD1m3A9ew6Hs4DIv7lnTxNbR6dr"){
+  die ();
+}
+$t1 = $_GET['t1'];
+$t2 = $_GET['t2'];
+if(!preg_match("/^[a-zA-Z0-9]+$/", $t1) || !preg_match("/^[a-zA-Z0-9]+$/", $t2)){
+  die("Error");
+}
+$todelete = array();
+
+if(file_exists("data.php")){
+  include ("data.php");
+}
+
+$time = time()/60;
+if(!isset($todelete[$time])){
+  $todelete[$time] = array();
+}
+
+$dnscalls = "";
+
+if($_GET['action'] == "http"){
+  $todelete[$time][] = array("http", $t1);
+  file_put_contents("cacert-$t1.txt", $t2);
+} else if($_GET['action'] == "dns") {
+  $todelete[$time][] = array("dns", $t1);
+  $dnscalls .= "update delete {$t1}._cacert._auth." . ZONENAME . " TXT\n"
+    ."update add {$t1}._cacert._auth." . ZONENAME . " 60 TXT {$t2}\n";
+}
+$copy = $todelete;
+foreach($copy as $nt => $ar){
+  if($nt < $time - 2){
+    unset($todelete[$nt]);
+    foreach($ar as $act){
+      if($act[0] == "http"){
+        unlink("cacert-{$act[1]}.txt");
+      } else if($act[0] == "dns") {
+        $dnscalls .= "update delete {$act[1]}._cacert._auth." . ZONENAME . " TXT\n";
+      }
+    }
+  }
+}
+file_put_contents("data.php", "<?php \$todelete = ".var_export($todelete,true).";\n?>");
+
+if($dnscalls != ""){
+  dnsAction($dnscalls);
+}
+
+function dnsAction($command) {
+  $call = "server localhost\n$command\nsend\nquit\n";
+
+  $nsupdate = popen("/usr/bin/nsupdate -k " . KEYNAME, 'w');
+  fwrite($nsupdate, $call);
+  $retval = pclose($nsupdate); // nsupdate doesn't return anything useful when called this way
+}
+
diff --git a/doc/scripts/.gitignore b/doc/scripts/.gitignore
new file mode 100644 (file)
index 0000000..ea4975e
--- /dev/null
@@ -0,0 +1 @@
+/*.csr
diff --git a/doc/scripts/generateSomeCsrs.sh b/doc/scripts/generateSomeCsrs.sh
new file mode 100755 (executable)
index 0000000..758342a
--- /dev/null
@@ -0,0 +1,7 @@
+cd `dirname $0`
+
+for i in {4..100}; do
+openssl req -newkey rsa:1024 -nodes -keyout /dev/null \
+       -out $i.csr -subj "/CN=tmp.cacert.local" \
+       -config ../../keys/selfsign.config;
+done
diff --git a/doc/scripts/getJetty.sh b/doc/scripts/getJetty.sh
new file mode 100644 (file)
index 0000000..cbec366
--- /dev/null
@@ -0,0 +1,32 @@
+#!/bin/sh
+JETTY=C:/jars/jetty-distribution-9.1.0.RC0/org.eclipse.jetty.project
+
+pushd ../../lib/jetty/org/eclipse/jetty
+rm -fR *
+
+pushd $JETTY
+
+git checkout refs/tags/jetty-9.2.1.v20140609
+popd
+
+
+for package in http io security server servlet util
+do
+    cp -R $JETTY/jetty-$package/src/main/java/org/eclipse/jetty/$package .
+done
+
+cp -R $JETTY/jetty-http/src/main/resources/org/eclipse/jetty/http .
+
+
+
+cp -R $JETTY/jetty-$package/src/main/java/org/eclipse/jetty/$package .
+
+rm -R server/session/jmx
+rm -R server/handler/jmx
+rm -R server/jmx
+rm -R servlet/jmx
+
+rm util/log/JettyAwareLogger.java
+rm util/log/Slf4jLog.java
+rm server/Slf4jRequestLog.java
+popd
diff --git a/doc/scripts/gigi b/doc/scripts/gigi
new file mode 100755 (executable)
index 0000000..763af7c
--- /dev/null
@@ -0,0 +1,53 @@
+#!/bin/bash
+if [ "$JDBC_DRIVER" == "" ]
+then
+JDBC_DRIVER=/usr/share/java/mysql-connector-java.jar
+#echo "JDBC_DRIVER environment variable not set. Assumed path: $JDBC_DRIVER"
+fi
+if [ "$GIGI_EXEC" == "" ]
+then
+GIGI_EXEC=/usr/share/java/gigi.jar
+#echo "GIGI_EXEC environment variable not set. Assumed path: $GIGI_EXEC" 
+fi
+
+cd /var/lib/cacert-gigi
+
+if [ "$1" == "start" ]
+then
+       java -cp $JDBC_DRIVER:$GIGI_EXEC org.cacert.gigi.Launcher
+elif [ "$1" == "debug" ]
+then
+       java -cp $JDBC_DRIVER:$GIGI_EXEC -Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=8000 org.cacert.gigi.Launcher
+elif [ "$1" == "start-daemon" ]
+then
+       if [ ! -e /etc/cacert/gigi/conf.tar ]; then
+               echo "Config missing."
+               exit 1;
+       fi
+       java -cp $JDBC_DRIVER:$GIGI_EXEC org.cacert.gigi.Launcher < /etc/cacert/gigi/conf.tar >> /var/log/cacert-gigi.log 2>&1 &
+       echo $! > /var/run/cacert-gigi.pid
+elif [ "$1" == "signer" ]
+then
+       java -cp $JDBC_DRIVER:$GIGI_EXEC org.cacert.gigi.util.SimpleSigner
+elif [ "$1" == "reset-database" ]
+then
+       java -cp $JDBC_DRIVER:$GIGI_EXEC org.cacert.gigi.util.DatabaseManager
+elif [ "$1" == "fetch-locales" ]
+then
+       java -cp $JDBC_DRIVER:$GIGI_EXEC org.cacert.gigi.util.FetchLocales
+elif [ "$1" == "signer-conf" ]
+then
+       mkdir /var/lib/cacert-gigi/config
+       cd /var/lib/cacert-gigi/config
+       tar x gigi.properties
+else
+       echo "Usage: gigi <option>"
+       echo "debug - starts gigi in debug mode (on port 8000, with config from stdin)"
+       echo "fetch-locales - (re)fetch the localisation"
+       echo "reset-database - resets the database"
+       echo "signer - starts the simple signer"
+       echo "signer-conf - extract config for simple signer (and reset-database) from the tar from stdin"
+       echo "start - starts gigi"
+       echo "start-daemon - starts gigi in background (using config from /etc/cacert/gigi/conf.tar)"
+
+fi
diff --git a/keys/.dirinfo b/keys/.dirinfo
new file mode 100644 (file)
index 0000000..c9087a3
--- /dev/null
@@ -0,0 +1,9 @@
+This directoy will contain keys created to test CAcert-gigi.
+generate them with doc/scripts/generateKeys.sh
+
+It may contain:
+
+testca/*
+testca.crt
+testca.key
+{api,secure,static,www}.{crt,key,csr,pkcs12}
diff --git a/keys/.gitignore b/keys/.gitignore
new file mode 100644 (file)
index 0000000..83df620
--- /dev/null
@@ -0,0 +1,15 @@
+#generated keys
+*.crt
+*.csr
+*.key
+*.pkcs12
+*.ca
+*.crl
+csr
+crt
+signer_bundle.tar
+
+
+# user specific generation config
+config
+
diff --git a/keys/generateKeys.sh b/keys/generateKeys.sh
new file mode 100755 (executable)
index 0000000..e9f75a7
--- /dev/null
@@ -0,0 +1,136 @@
+#!/bin/sh
+# this script generates a set of sample keys
+DOMAIN="cacert.local"
+KEYSIZE=4096
+PRIVATEPW="changeit"
+
+[ -f config ] && . ./config
+
+
+rm -Rf *.csr *.crt *.key *.pkcs12 *.ca *.crl
+
+
+####### create various extensions files for the various certificate types ######
+cat <<TESTCA > test_ca.cnf
+subjectKeyIdentifier = hash
+#extendedKeyUsage = critical
+basicConstraints = CA:true
+keyUsage = digitalSignature, nonRepudiation, keyCertSign, cRLSign
+TESTCA
+
+cat <<TESTCA > test_subca.cnf
+subjectKeyIdentifier = hash
+#extendedKeyUsage = critical,
+basicConstraints = CA:true
+keyUsage = digitalSignature, nonRepudiation, keyCertSign, cRLSign
+TESTCA
+
+cat <<TESTCA > test_req.cnf
+basicConstraints = critical,CA:false
+keyUsage = keyEncipherment, digitalSignature
+extendedKeyUsage=serverAuth
+subjectKeyIdentifier = hash
+authorityKeyIdentifier = keyid:always,issuer:always
+#crlDistributionPoints=URI:http://www.my.host/ca.crl
+#authorityInfoAccess = OCSP;URI:http://ocsp.my.host/
+TESTCA
+
+cat <<TESTCA > test_reqClient.cnf
+basicConstraints = critical,CA:false
+keyUsage = keyEncipherment, digitalSignature
+extendedKeyUsage=clientAuth
+subjectKeyIdentifier = hash
+authorityKeyIdentifier = keyid:always,issuer:always
+#crlDistributionPoints=URI:http://www.my.host/ca.crl
+#authorityInfoAccess = OCSP;URI:http://ocsp.my.host/
+TESTCA
+
+cat <<TESTCA > test_reqMail.cnf
+basicConstraints = critical,CA:false
+keyUsage = keyEncipherment, digitalSignature
+extendedKeyUsage=emailProtection
+subjectKeyIdentifier = hash
+authorityKeyIdentifier = keyid:always,issuer:always
+#crlDistributionPoints=URI:http://www.my.host/ca.crl
+#authorityInfoAccess = OCSP;URI:http://ocsp.my.host/
+TESTCA
+
+
+genca(){ #subj, internalName
+
+    openssl genrsa -out $2.key ${KEYSIZE}
+    openssl req -new -key $2.key -out $2.csr -subj "$1/O=Test Environment CA Ltd./OU=Test Environment CAs"
+    
+    mkdir $2.ca
+    mkdir $2.ca/newcerts
+    echo 01 > $2.ca/serial
+    touch $2.ca/db
+    echo unique_subject = no >$2.ca/db.attr
+
+}
+
+caSign(){ # key,ca,config
+    cd $2.ca
+    openssl ca -cert ../$2.crt -keyfile ../$2.key -in ../$1.csr -out ../$1.crt -days 365 -batch -config ../selfsign.config -extfile ../$3
+    cd ..
+}
+
+rootSign(){ # key
+    caSign $1 root test_subca.cnf
+}
+
+genserver(){ #key, subject, config
+    openssl genrsa -out $1.key ${KEYSIZE}
+    openssl req -new -key $1.key -out $1.csr -subj "$2" -config selfsign.config
+    caSign $1 env "$3"
+    
+    openssl pkcs12 -inkey $1.key -in $1.crt -CAfile env.chain.crt -chain -name $1 -export -passout pass:changeit -out $1.pkcs12
+    
+    keytool -importkeystore -noprompt -srckeystore $1.pkcs12 -destkeystore ../config/keystore.pkcs12 -srcstoretype pkcs12 -deststoretype pkcs12 -srcstorepass "changeit" -deststorepass "$PRIVATEPW"
+}
+
+
+# Generate the super Root CA
+genca "/CN=Cacert-gigi testCA" root
+openssl x509 -req -days 365 -in root.csr -signkey root.key -out root.crt -extfile test_ca.cnf
+
+# generate the various sub-CAs
+genca "/CN=Environment" env
+rootSign env
+genca "/CN=Unassured" unassured
+rootSign unassured
+genca "/CN=Assured" assured
+rootSign assured
+genca "/CN=Codesigning" codesign
+rootSign codesign
+genca "/CN=Timestamping" timestamp
+rootSign timestamp
+genca "/CN=Orga" orga
+rootSign orga
+genca "/CN=Orga sign" orgaSign
+rootSign orgaSign
+
+
+cat env.crt root.crt > env.chain.crt
+
+# generate orga-keys specific to gigi.
+# first the server keys
+genserver www "/CN=www.${DOMAIN}" test_req.cnf
+genserver secure "/CN=secure.${DOMAIN}" test_req.cnf
+genserver static "/CN=static.${DOMAIN}" test_req.cnf
+genserver api "/CN=api.${DOMAIN}" test_req.cnf
+
+genserver signer_client "/CN=CAcert signer handler 1" test_reqClient.cnf
+genserver signer_server "/CN=CAcert signer 1" test_req.cnf
+
+# then the email signing key
+genserver mail "/emailAddress=support@${DOMAIN}" test_reqMail.cnf
+
+keytool -list -keystore ../config/keystore.pkcs12 -storetype pkcs12 -storepass "$PRIVATEPW"
+
+rm test_ca.cnf test_subca.cnf test_req.cnf test_reqMail.cnf test_reqClient.cnf
+rm env.chain.crt
+
+cat root.crt env.crt > ca.crt
+tar cf signer_bundle.tar root.crt env.crt signer_client.crt signer_client.key signer_server.crt signer_server.key ca.crt
+rm ca.crt
diff --git a/keys/generateTruststore.sh b/keys/generateTruststore.sh
new file mode 100755 (executable)
index 0000000..0c5aedc
--- /dev/null
@@ -0,0 +1,20 @@
+#!/bin/bash
+# this script imports the cacert root certs
+
+rm -f ../config/cacerts.jks
+
+#wget -N http://www.cacert.org/certs/root.crt
+#wget -N http://www.cacert.org/certs/class3.crt
+
+#keytool -importcert -keystore ../config/cacerts.jks -file root.crt -alias root -storepass "changeit" $1
+#keytool -importcert -keystore ../config/cacerts.jks -file class3.crt -alias class3 -storepass "changeit" $1
+
+function import(){
+  keytool -importcert -keystore ../config/cacerts.jks -file "$1.crt" -alias own -storepass "changeit" -alias "$1" $2
+}
+
+import root -noprompt
+import assured
+import unassured
+
+keytool -list -keystore ../config/cacerts.jks -storepass "changeit"
diff --git a/keys/selfsign.config b/keys/selfsign.config
new file mode 100644 (file)
index 0000000..2b0f5a7
--- /dev/null
@@ -0,0 +1,39 @@
+[req]
+distinguished_name=dn
+#req_extensions=ext
+
+[dn]
+[ext]
+subjectAltName=
+
+[ca]
+default_ca=ca1
+
+[ca1]
+new_certs_dir=newcerts
+database=db
+serial=serial
+default_md=sha256
+email_in_dn=salat
+policy=ca1_pol
+#default_days=365
+x509_extensions = v3_ca 
+
+
+
+[ v3_ca ]
+
+basicConstraints        = critical, CA:FALSE
+keyUsage                = critical, digitalSignature, keyEncipherment, keyAgreement
+extendedKeyUsage        = clientAuth, serverAuth, nsSGC, msSGC
+
+
+[ca1_pol]
+commonName              = optional
+subjectAltName          = optional
+organizationName       = optional
+organizationalUnitName = optional
+emailAddress           = optional
+countryName            = optional
+stateOrProvinceName    = optional
+localityName           = optional
diff --git a/lib/jetty/org/eclipse/jetty/http/DateGenerator.java b/lib/jetty/org/eclipse/jetty/http/DateGenerator.java
new file mode 100644 (file)
index 0000000..3ecaad8
--- /dev/null
@@ -0,0 +1,164 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.http;
+
+import java.util.Calendar;
+import java.util.GregorianCalendar;
+import java.util.TimeZone;
+
+import org.eclipse.jetty.util.StringUtil;
+
+/**
+ * ThreadLocal Date formatters for HTTP style dates.
+ *
+ */
+public class DateGenerator
+{
+    private static final TimeZone __GMT = TimeZone.getTimeZone("GMT");
+    static
+    {
+        __GMT.setID("GMT");
+    }
+    
+    static final String[] DAYS =
+        { "Sat", "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"};
+    static final String[] MONTHS =
+        { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", "Jan"};
+
+
+    private static final ThreadLocal<DateGenerator> __dateGenerator =new ThreadLocal<DateGenerator>()
+    {
+        @Override
+        protected DateGenerator initialValue()
+        {
+            return new DateGenerator();
+        }
+    };
+
+
+    public final static String __01Jan1970=DateGenerator.formatDate(0);
+    
+    /**
+     * Format HTTP date "EEE, dd MMM yyyy HH:mm:ss 'GMT'"
+     */
+    public static String formatDate(long date)
+    {
+        return __dateGenerator.get().doFormatDate(date);
+    }
+
+    /**
+     * Format "EEE, dd-MMM-yyyy HH:mm:ss 'GMT'" for cookies
+     */
+    public static void formatCookieDate(StringBuilder buf, long date)
+    {
+        __dateGenerator.get().doFormatCookieDate(buf,date);
+    }
+
+    /**
+     * Format "EEE, dd-MMM-yyyy HH:mm:ss 'GMT'" for cookies
+     */
+    public static String formatCookieDate(long date)
+    {
+        StringBuilder buf = new StringBuilder(28);
+        formatCookieDate(buf, date);
+        return buf.toString();
+    }
+    
+    private final StringBuilder buf = new StringBuilder(32);
+    private final GregorianCalendar gc = new GregorianCalendar(__GMT);
+
+    /**
+     * Format HTTP date "EEE, dd MMM yyyy HH:mm:ss 'GMT'"
+     */
+    public String doFormatDate(long date)
+    {
+        buf.setLength(0);
+        gc.setTimeInMillis(date);
+
+        int day_of_week = gc.get(Calendar.DAY_OF_WEEK);
+        int day_of_month = gc.get(Calendar.DAY_OF_MONTH);
+        int month = gc.get(Calendar.MONTH);
+        int year = gc.get(Calendar.YEAR);
+        int century = year / 100;
+        year = year % 100;
+
+        int hours = gc.get(Calendar.HOUR_OF_DAY);
+        int minutes = gc.get(Calendar.MINUTE);
+        int seconds = gc.get(Calendar.SECOND);
+
+        buf.append(DAYS[day_of_week]);
+        buf.append(',');
+        buf.append(' ');
+        StringUtil.append2digits(buf, day_of_month);
+
+        buf.append(' ');
+        buf.append(MONTHS[month]);
+        buf.append(' ');
+        StringUtil.append2digits(buf, century);
+        StringUtil.append2digits(buf, year);
+
+        buf.append(' ');
+        StringUtil.append2digits(buf, hours);
+        buf.append(':');
+        StringUtil.append2digits(buf, minutes);
+        buf.append(':');
+        StringUtil.append2digits(buf, seconds);
+        buf.append(" GMT");
+        return buf.toString();
+    }
+
+    /**
+     * Format "EEE, dd-MMM-yy HH:mm:ss 'GMT'" for cookies
+     */
+    public void doFormatCookieDate(StringBuilder buf, long date)
+    {
+        gc.setTimeInMillis(date);
+
+        int day_of_week = gc.get(Calendar.DAY_OF_WEEK);
+        int day_of_month = gc.get(Calendar.DAY_OF_MONTH);
+        int month = gc.get(Calendar.MONTH);
+        int year = gc.get(Calendar.YEAR);
+        year = year % 10000;
+
+        int epoch = (int) ((date / 1000) % (60 * 60 * 24));
+        int seconds = epoch % 60;
+        epoch = epoch / 60;
+        int minutes = epoch % 60;
+        int hours = epoch / 60;
+
+        buf.append(DAYS[day_of_week]);
+        buf.append(',');
+        buf.append(' ');
+        StringUtil.append2digits(buf, day_of_month);
+
+        buf.append('-');
+        buf.append(MONTHS[month]);
+        buf.append('-');
+        StringUtil.append2digits(buf, year/100);
+        StringUtil.append2digits(buf, year%100);
+
+        buf.append(' ');
+        StringUtil.append2digits(buf, hours);
+        buf.append(':');
+        StringUtil.append2digits(buf, minutes);
+        buf.append(':');
+        StringUtil.append2digits(buf, seconds);
+        buf.append(" GMT");
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/http/DateParser.java b/lib/jetty/org/eclipse/jetty/http/DateParser.java
new file mode 100644 (file)
index 0000000..1ede4ce
--- /dev/null
@@ -0,0 +1,109 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.http;
+
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Locale;
+import java.util.TimeZone;
+
+/**
+ * ThreadLocal data parsers for HTTP style dates
+ *
+ */
+class DateParser
+{
+    private static final TimeZone __GMT = TimeZone.getTimeZone("GMT");
+    static
+    {
+        __GMT.setID("GMT");
+    }
+    
+    final static String __dateReceiveFmt[] =
+    {
+        "EEE, dd MMM yyyy HH:mm:ss zzz",
+        "EEE, dd-MMM-yy HH:mm:ss",
+        "EEE MMM dd HH:mm:ss yyyy",
+
+        "EEE, dd MMM yyyy HH:mm:ss", "EEE dd MMM yyyy HH:mm:ss zzz",
+        "EEE dd MMM yyyy HH:mm:ss", "EEE MMM dd yyyy HH:mm:ss zzz", "EEE MMM dd yyyy HH:mm:ss",
+        "EEE MMM-dd-yyyy HH:mm:ss zzz", "EEE MMM-dd-yyyy HH:mm:ss", "dd MMM yyyy HH:mm:ss zzz",
+        "dd MMM yyyy HH:mm:ss", "dd-MMM-yy HH:mm:ss zzz", "dd-MMM-yy HH:mm:ss", "MMM dd HH:mm:ss yyyy zzz",
+        "MMM dd HH:mm:ss yyyy", "EEE MMM dd HH:mm:ss yyyy zzz",
+        "EEE, MMM dd HH:mm:ss yyyy zzz", "EEE, MMM dd HH:mm:ss yyyy", "EEE, dd-MMM-yy HH:mm:ss zzz",
+        "EEE dd-MMM-yy HH:mm:ss zzz", "EEE dd-MMM-yy HH:mm:ss",
+    };
+
+    public static long parseDate(String date)
+    {
+        return __dateParser.get().parse(date);
+    }
+
+    private static final ThreadLocal<DateParser> __dateParser =new ThreadLocal<DateParser>()
+    {
+        @Override
+        protected DateParser initialValue()
+        {
+            return new DateParser();
+        }
+    };
+    
+    final SimpleDateFormat _dateReceive[]= new SimpleDateFormat[__dateReceiveFmt.length];
+
+    private long parse(final String dateVal)
+    {
+        for (int i = 0; i < _dateReceive.length; i++)
+        {
+            if (_dateReceive[i] == null)
+            {
+                _dateReceive[i] = new SimpleDateFormat(__dateReceiveFmt[i], Locale.US);
+                _dateReceive[i].setTimeZone(__GMT);
+            }
+
+            try
+            {
+                Date date = (Date) _dateReceive[i].parseObject(dateVal);
+                return date.getTime();
+            }
+            catch (java.lang.Exception e)
+            {
+                // LOG.ignore(e);
+            }
+        }
+
+        if (dateVal.endsWith(" GMT"))
+        {
+            final String val = dateVal.substring(0, dateVal.length() - 4);
+
+            for (SimpleDateFormat element : _dateReceive)
+            {
+                try
+                {
+                    Date date = (Date) element.parseObject(val);
+                    return date.getTime();
+                }
+                catch (java.lang.Exception e)
+                {
+                    // LOG.ignore(e);
+                }
+            }
+        }
+        return -1;
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/http/HttpContent.java b/lib/jetty/org/eclipse/jetty/http/HttpContent.java
new file mode 100644 (file)
index 0000000..ebae56d
--- /dev/null
@@ -0,0 +1,172 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.http;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.ByteBuffer;
+import java.nio.channels.ReadableByteChannel;
+
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.resource.Resource;
+
+/* ------------------------------------------------------------ */
+/** HttpContent.
+ * 
+ *
+ */
+public interface HttpContent
+{
+    String getContentType();
+    String getLastModified();
+    ByteBuffer getIndirectBuffer();
+    ByteBuffer getDirectBuffer();
+    String getETag();
+    Resource getResource();
+    long getContentLength();
+    InputStream getInputStream() throws IOException;
+    ReadableByteChannel getReadableByteChannel() throws IOException;
+    void release();
+
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    public class ResourceAsHttpContent implements HttpContent
+    {
+        final Resource _resource;
+        final String _mimeType;
+        final int _maxBuffer;
+        final String _etag;
+
+        /* ------------------------------------------------------------ */
+        public ResourceAsHttpContent(final Resource resource, final String mimeType)
+        {
+            this(resource,mimeType,-1,false);
+        }
+
+        /* ------------------------------------------------------------ */
+        public ResourceAsHttpContent(final Resource resource, final String mimeType, int maxBuffer)
+        {
+            this(resource,mimeType,maxBuffer,false);
+        }
+
+        /* ------------------------------------------------------------ */
+        public ResourceAsHttpContent(final Resource resource, final String mimeType, boolean etag)
+        {
+            this(resource,mimeType,-1,etag);
+        }
+
+        /* ------------------------------------------------------------ */
+        public ResourceAsHttpContent(final Resource resource, final String mimeType, int maxBuffer, boolean etag)
+        {
+            _resource=resource;
+            _mimeType=mimeType;
+            _maxBuffer=maxBuffer;
+            _etag=etag?resource.getWeakETag():null;
+        }
+
+        /* ------------------------------------------------------------ */
+        @Override
+        public String getContentType()
+        {
+            return _mimeType;
+        }
+
+        /* ------------------------------------------------------------ */
+        @Override
+        public String getLastModified()
+        {
+            return null;
+        }
+
+        /* ------------------------------------------------------------ */
+        @Override
+        public ByteBuffer getDirectBuffer()
+        {
+            if (_resource.length()<=0 || _maxBuffer<_resource.length())
+                return null;
+            try
+            {
+                return BufferUtil.toBuffer(_resource,true);
+            }
+            catch(IOException e)
+            {
+                throw new RuntimeException(e);
+            }
+        }
+        
+        /* ------------------------------------------------------------ */
+        @Override
+        public String getETag()
+        {
+            return _etag;
+        }
+
+        /* ------------------------------------------------------------ */
+        @Override
+        public ByteBuffer getIndirectBuffer()
+        {
+            if (_resource.length()<=0 || _maxBuffer<_resource.length())
+                return null;
+            try
+            {
+                return BufferUtil.toBuffer(_resource,false);
+            }
+            catch(IOException e)
+            {
+                throw new RuntimeException(e);
+            }
+        }
+
+        /* ------------------------------------------------------------ */
+        @Override
+        public long getContentLength()
+        {
+            return _resource.length();
+        }
+
+        /* ------------------------------------------------------------ */
+        @Override
+        public InputStream getInputStream() throws IOException
+        {
+            return _resource.getInputStream();
+        }
+        
+        /* ------------------------------------------------------------ */
+        @Override
+        public ReadableByteChannel getReadableByteChannel() throws IOException
+        {
+            return _resource.getReadableByteChannel();
+        }
+
+        /* ------------------------------------------------------------ */
+        @Override
+        public Resource getResource()
+        {
+            return _resource;
+        }
+
+        /* ------------------------------------------------------------ */
+        @Override
+        public void release()
+        {
+            _resource.close();
+        }
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/http/HttpCookie.java b/lib/jetty/org/eclipse/jetty/http/HttpCookie.java
new file mode 100644 (file)
index 0000000..1a2426b
--- /dev/null
@@ -0,0 +1,164 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.http;
+
+import java.util.concurrent.TimeUnit;
+
+public class HttpCookie
+{
+    private final String _name;
+    private final String _value;
+    private final String _comment;
+    private final String _domain;
+    private final long _maxAge;
+    private final String _path;
+    private final boolean _secure;
+    private final int _version;
+    private final boolean _httpOnly;
+    private final long _expiration;
+
+    public HttpCookie(String name, String value)
+    {
+        this(name, value, -1);
+    }
+
+    public HttpCookie(String name, String value, String domain, String path)
+    {
+        this(name, value, domain, path, -1, false, false);
+    }
+
+    public HttpCookie(String name, String value, long maxAge)
+    {
+        this(name, value, null, null, maxAge, false, false);
+    }
+
+    public HttpCookie(String name, String value, String domain, String path, long maxAge, boolean httpOnly, boolean secure)
+    {
+        this(name, value, domain, path, maxAge, httpOnly, secure, null, 0);
+    }
+
+    public HttpCookie(String name, String value, String domain, String path, long maxAge, boolean httpOnly, boolean secure, String comment, int version)
+    {
+        _name = name;
+        _value = value;
+        _domain = domain;
+        _path = path;
+        _maxAge = maxAge;
+        _httpOnly = httpOnly;
+        _secure = secure;
+        _comment = comment;
+        _version = version;
+        _expiration = maxAge < 0 ? -1 : System.nanoTime() + TimeUnit.SECONDS.toNanos(maxAge);
+    }
+
+    /**
+     * @return the cookie name
+     */
+    public String getName()
+    {
+        return _name;
+    }
+
+    /**
+     * @return the cookie value
+     */
+    public String getValue()
+    {
+        return _value;
+    }
+
+    /**
+     * @return the cookie comment
+     */
+    public String getComment()
+    {
+        return _comment;
+    }
+
+    /**
+     * @return the cookie domain
+     */
+    public String getDomain()
+    {
+        return _domain;
+    }
+
+    /**
+     * @return the cookie max age in seconds
+     */
+    public long getMaxAge()
+    {
+        return _maxAge;
+    }
+
+    /**
+     * @return the cookie path
+     */
+    public String getPath()
+    {
+        return _path;
+    }
+
+    /**
+     * @return whether the cookie is valid for secure domains
+     */
+    public boolean isSecure()
+    {
+        return _secure;
+    }
+
+    /**
+     * @return the cookie version
+     */
+    public int getVersion()
+    {
+        return _version;
+    }
+
+    /**
+     * @return whether the cookie is valid for the http protocol only
+     */
+    public boolean isHttpOnly()
+    {
+        return _httpOnly;
+    }
+
+    /**
+     * @param timeNanos the time to check for cookie expiration, in nanoseconds
+     * @return whether the cookie is expired by the given time
+     */
+    public boolean isExpired(long timeNanos)
+    {
+        return _expiration >= 0 && timeNanos >= _expiration;
+    }
+
+    /**
+     * @return a string representation of this cookie
+     */
+    public String asString()
+    {
+        StringBuilder builder = new StringBuilder();
+        builder.append(getName()).append("=").append(getValue());
+        if (getDomain() != null)
+            builder.append(";$Domain=").append(getDomain());
+        if (getPath() != null)
+            builder.append(";$Path=").append(getPath());
+        return builder.toString();
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/http/HttpField.java b/lib/jetty/org/eclipse/jetty/http/HttpField.java
new file mode 100644 (file)
index 0000000..50f29b1
--- /dev/null
@@ -0,0 +1,89 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.http;
+
+
+/* ------------------------------------------------------------ */
+/** A HTTP Field
+ */
+public class HttpField
+{
+    private final HttpHeader _header;
+    private final String _name;
+    private final String _value;
+        
+    public HttpField(HttpHeader header, String name, String value)
+    {
+        _header = header;
+        _name = name;
+        _value = value;
+    }  
+    
+    public HttpField(HttpHeader header, String value)
+    {
+        this(header,header.asString(),value);
+    }
+    
+    public HttpField(HttpHeader header, HttpHeaderValue value)
+    {
+        this(header,header.asString(),value.asString());
+    }
+    
+    public HttpField(String name, String value)
+    {
+        this(HttpHeader.CACHE.get(name),name,value);
+    }
+
+    public HttpHeader getHeader()
+    {
+        return _header;
+    }
+
+    public String getName()
+    {
+        return _name;
+    }
+
+    public String getValue()
+    {
+        return _value;
+    }
+    
+    @Override
+    public String toString()
+    {
+        String v=getValue();
+        return getName() + ": " + (v==null?"":v);
+    }
+
+    public boolean isSame(HttpField field)
+    {
+        if (field==null)
+            return false;
+        if (field==this)
+            return true;
+        if (_header!=null && _header==field.getHeader())
+            return true;
+        if (_name.equalsIgnoreCase(field.getName()))
+            return true;
+        return false;
+    }
+    
+    
+}
diff --git a/lib/jetty/org/eclipse/jetty/http/HttpFields.java b/lib/jetty/org/eclipse/jetty/http/HttpFields.java
new file mode 100644 (file)
index 0000000..d474227
--- /dev/null
@@ -0,0 +1,784 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.http;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.NoSuchElementException;
+import java.util.Set;
+import java.util.StringTokenizer;
+import java.util.regex.Pattern;
+
+import org.eclipse.jetty.util.ArrayTernaryTrie;
+import org.eclipse.jetty.util.LazyList;
+import org.eclipse.jetty.util.QuotedStringTokenizer;
+import org.eclipse.jetty.util.StringUtil;
+import org.eclipse.jetty.util.Trie;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+
+/**
+ * HTTP Fields. A collection of HTTP header and or Trailer fields.
+ *
+ * <p>This class is not synchronized as it is expected that modifications will only be performed by a
+ * single thread.
+ * 
+ * <p>The cookie handling provided by this class is guided by the Servlet specification and RFC6265.
+ *
+ */
+public class HttpFields implements Iterable<HttpField>
+{
+    private static final Logger LOG = Log.getLogger(HttpFields.class);
+    private final static Pattern __splitter = Pattern.compile("\\s*,\\s*");     
+    public final static String __separators = ", \t";
+
+    private final ArrayList<HttpField> _fields = new ArrayList<>(20);
+
+    /**
+     * Constructor.
+     */
+    public HttpFields()
+    {
+    }
+
+    /**
+     * Get Collection of header names.
+     */
+    public Collection<String> getFieldNamesCollection()
+    {
+        final Set<String> list = new HashSet<>(_fields.size());
+        for (HttpField f : _fields)
+        {
+            if (f!=null)
+                list.add(f.getName());
+        }
+        return list;
+    }
+
+    /**
+     * Get enumeration of header _names. Returns an enumeration of strings representing the header
+     * _names for this request.
+     */
+    public Enumeration<String> getFieldNames()
+    {
+        return Collections.enumeration(getFieldNamesCollection());
+    }
+
+    public int size()
+    {
+        return _fields.size();
+    }
+
+    /**
+     * Get a Field by index.
+     * @return A Field value or null if the Field value has not been set
+     *
+     */
+    public HttpField getField(int i)
+    {
+        return _fields.get(i);
+    }
+
+    @Override
+    public Iterator<HttpField> iterator()
+    {
+        return _fields.iterator();
+    }
+
+    public HttpField getField(HttpHeader header)
+    {
+        for (int i=0;i<_fields.size();i++)
+        {
+            HttpField f=_fields.get(i);
+            if (f.getHeader()==header)
+                return f;
+        }
+        return null;
+    }
+
+    public HttpField getField(String name)
+    {
+        for (int i=0;i<_fields.size();i++)
+        {
+            HttpField f=_fields.get(i);
+            if (f.getName().equalsIgnoreCase(name))
+                return f;
+        }
+        return null;
+    }
+    
+    public boolean contains(HttpHeader header, String value)
+    {
+        for (int i=0;i<_fields.size();i++)
+        {
+            HttpField f=_fields.get(i);
+            if (f.getHeader()==header && contains(f,value))
+                return true;
+        }
+        return false;
+    }
+    
+    public boolean contains(String name, String value)
+    {
+        for (int i=0;i<_fields.size();i++)
+        {
+            HttpField f=_fields.get(i);
+            if (f.getName().equalsIgnoreCase(name) && contains(f,value))
+                return true;
+        }
+        return false;
+    }
+    
+    private boolean contains(HttpField field,String value)
+    {
+        String v = field.getValue();
+        if (v==null)
+            return false;
+
+        if (value.equalsIgnoreCase(v))
+            return true;
+
+        String[] split = __splitter.split(v);
+        for (int i = 0; split!=null && i < split.length; i++) 
+        {
+            if (value.equals(split[i]))
+                return true;
+        }
+
+        return false;
+    }
+    
+    public boolean containsKey(String name)
+    {
+        for (int i=0;i<_fields.size();i++)
+        {
+            HttpField f=_fields.get(i);
+            if (f.getName().equalsIgnoreCase(name))
+                return true;
+        }
+        return false;
+    }
+
+    public String getStringField(HttpHeader header)
+    {
+        return getStringField(header.asString());
+    }
+
+    public String get(HttpHeader header)
+    {
+        return getStringField(header.asString());
+    }
+
+    public String get(String header)
+    {
+        return getStringField(header);
+    }
+
+    /**
+     * @return the value of a field, or null if not found. For multiple fields of the same name,
+     *         only the first is returned.
+     * @param name the case-insensitive field name
+     */
+    public String getStringField(String name)
+    {
+        HttpField field = getField(name);
+        return field==null?null:field.getValue();
+    }
+
+    /**
+     * Get multi headers
+     *
+     * @return List the values
+     * @param name the case-insensitive field name
+     */
+    public List<String> getValuesList(String name)
+    {
+        final List<String> list = new ArrayList<>();
+        for (HttpField f : _fields)
+            if (f.getName().equalsIgnoreCase(name))
+                list.add(f.getValue());
+        return list;
+    }
+
+    /**
+     * Get multi headers
+     *
+     * @return Enumeration of the values
+     * @param name the case-insensitive field name
+     */
+    public Enumeration<String> getValues(final String name)
+    {
+        for (int i=0;i<_fields.size();i++)
+        {
+            final HttpField f = _fields.get(i);
+            
+            if (f.getName().equalsIgnoreCase(name) && f.getValue()!=null)
+            {
+                final int first=i;
+                return new Enumeration<String>()
+                {
+                    HttpField field=f;
+                    int i = first+1;
+
+                    @Override
+                    public boolean hasMoreElements()
+                    {
+                        if (field==null)
+                        {
+                            while (i<_fields.size()) 
+                            {
+                                field=_fields.get(i++);
+                                if (field.getName().equalsIgnoreCase(name) && field.getValue()!=null)
+                                    return true;
+                            }
+                            field=null;
+                            return false;
+                        }
+                        return true;
+                    }
+
+                    @Override
+                    public String nextElement() throws NoSuchElementException
+                    {
+                        if (hasMoreElements())
+                        {
+                            String value=field.getValue();
+                            field=null;
+                            return value;
+                        }
+                        throw new NoSuchElementException();
+                    }
+
+                };
+            }
+        }
+
+        List<String> empty=Collections.emptyList();
+        return Collections.enumeration(empty);
+    }
+
+    /**
+     * Get multi field values with separator. The multiple values can be represented as separate
+     * headers of the same name, or by a single header using the separator(s), or a combination of
+     * both. Separators may be quoted.
+     *
+     * @param name the case-insensitive field name
+     * @param separators String of separators.
+     * @return Enumeration of the values, or null if no such header.
+     */
+    public Enumeration<String> getValues(String name, final String separators)
+    {
+        final Enumeration<String> e = getValues(name);
+        if (e == null)
+            return null;
+        return new Enumeration<String>()
+        {
+            QuotedStringTokenizer tok = null;
+
+            @Override
+            public boolean hasMoreElements()
+            {
+                if (tok != null && tok.hasMoreElements()) return true;
+                while (e.hasMoreElements())
+                {
+                    String value = e.nextElement();
+                    if (value!=null)
+                    {
+                        tok = new QuotedStringTokenizer(value, separators, false, false);
+                        if (tok.hasMoreElements()) return true;
+                    }
+                }
+                tok = null;
+                return false;
+            }
+
+            @Override
+            public String nextElement() throws NoSuchElementException
+            {
+                if (!hasMoreElements()) throw new NoSuchElementException();
+                String next = (String) tok.nextElement();
+                if (next != null) next = next.trim();
+                return next;
+            }
+        };
+    }
+
+    public void put(HttpField field)
+    {
+        boolean put=false;
+        for (int i=_fields.size();i-->0;)
+        {
+            HttpField f=_fields.get(i);
+            if (f.isSame(field))
+            {
+                if (put)
+                    _fields.remove(i);
+                else
+                {
+                    _fields.set(i,field);
+                    put=true;
+                }
+            }
+        }
+        if (!put)
+            _fields.add(field);
+    }
+    
+    /**
+     * Set a field.
+     *
+     * @param name the name of the field
+     * @param value the value of the field. If null the field is cleared.
+     */
+    public void put(String name, String value)
+    {
+        if (value == null)
+            remove(name);
+        else
+            put(new HttpField(name, value));
+    }
+
+    public void put(HttpHeader header, HttpHeaderValue value)
+    {
+        put(header,value.toString());
+    }
+
+    /**
+     * Set a field.
+     *
+     * @param header the header name of the field
+     * @param value the value of the field. If null the field is cleared.
+     */
+    public void put(HttpHeader header, String value)
+    {
+        if (value == null)
+            remove(header);
+        else
+            put(new HttpField(header, value));
+    }
+
+    /**
+     * Set a field.
+     *
+     * @param name the name of the field
+     * @param list the List value of the field. If null the field is cleared.
+     */
+    public void put(String name, List<String> list)
+    {
+        remove(name);
+        for (String v : list)
+            if (v!=null)
+                add(name,v);
+    }
+
+    /**
+     * Add to or set a field. If the field is allowed to have multiple values, add will add multiple
+     * headers of the same name.
+     *
+     * @param name the name of the field
+     * @param value the value of the field.
+     * @exception IllegalArgumentException If the name is a single valued field and already has a
+     *                value.
+     */
+    public void add(String name, String value) throws IllegalArgumentException
+    {
+        if (value == null)
+            return;
+
+        HttpField field = new HttpField(name, value);
+        _fields.add(field);
+    }
+
+    public void add(HttpHeader header, HttpHeaderValue value) throws IllegalArgumentException
+    {
+        add(header,value.toString());
+    }
+
+    /**
+     * Add to or set a field. If the field is allowed to have multiple values, add will add multiple
+     * headers of the same name.
+     *
+     * @param header the header
+     * @param value the value of the field.
+     * @exception IllegalArgumentException 
+     */
+    public void add(HttpHeader header, String value) throws IllegalArgumentException
+    {
+        if (value == null) throw new IllegalArgumentException("null value");
+
+        HttpField field = new HttpField(header, value);
+        _fields.add(field);
+    }
+
+    /**
+     * Remove a field.
+     *
+     * @param name the field to remove
+     */
+    public HttpField remove(HttpHeader name)
+    {
+        for (int i=_fields.size();i-->0;)
+        {
+            HttpField f=_fields.get(i);
+            if (f.getHeader()==name)
+                return _fields.remove(i);
+        }
+        return null;
+    }
+
+    /**
+     * Remove a field.
+     *
+     * @param name the field to remove
+     */
+    public HttpField remove(String name)
+    {
+        for (int i=_fields.size();i-->0;)
+        {
+            HttpField f=_fields.get(i);
+            if (f.getName().equalsIgnoreCase(name))
+                return _fields.remove(i);
+        }
+        return null;
+    }
+
+    /**
+     * Get a header as an long value. Returns the value of an integer field or -1 if not found. The
+     * case of the field name is ignored.
+     *
+     * @param name the case-insensitive field name
+     * @exception NumberFormatException If bad long found
+     */
+    public long getLongField(String name) throws NumberFormatException
+    {
+        HttpField field = getField(name);
+        return field==null?-1L:StringUtil.toLong(field.getValue());
+    }
+
+    /**
+     * Get a header as a date value. Returns the value of a date field, or -1 if not found. The case
+     * of the field name is ignored.
+     *
+     * @param name the case-insensitive field name
+     */
+    public long getDateField(String name)
+    {
+        HttpField field = getField(name);
+        if (field == null)
+            return -1;
+
+        String val = valueParameters(field.getValue(), null);
+        if (val == null)
+            return -1;
+
+        final long date = DateParser.parseDate(val);
+        if (date==-1)
+            throw new IllegalArgumentException("Cannot convert date: " + val);
+        return date;
+    }
+
+
+    /**
+     * Sets the value of an long field.
+     *
+     * @param name the field name
+     * @param value the field long value
+     */
+    public void putLongField(HttpHeader name, long value)
+    {
+        String v = Long.toString(value);
+        put(name, v);
+    }
+
+    /**
+     * Sets the value of an long field.
+     *
+     * @param name the field name
+     * @param value the field long value
+     */
+    public void putLongField(String name, long value)
+    {
+        String v = Long.toString(value);
+        put(name, v);
+    }
+
+
+    /**
+     * Sets the value of a date field.
+     *
+     * @param name the field name
+     * @param date the field date value
+     */
+    public void putDateField(HttpHeader name, long date)
+    {
+        String d=DateGenerator.formatDate(date);
+        put(name, d);
+    }
+
+    /**
+     * Sets the value of a date field.
+     *
+     * @param name the field name
+     * @param date the field date value
+     */
+    public void putDateField(String name, long date)
+    {
+        String d=DateGenerator.formatDate(date);
+        put(name, d);
+    }
+
+    /**
+     * Sets the value of a date field.
+     *
+     * @param name the field name
+     * @param date the field date value
+     */
+    public void addDateField(String name, long date)
+    {
+        String d=DateGenerator.formatDate(date);
+        add(name,d);
+    }
+
+    @Override
+    public String
+    toString()
+    {
+        try
+        {
+            StringBuilder buffer = new StringBuilder();
+            for (HttpField field : _fields)
+            {
+                if (field != null)
+                {
+                    String tmp = field.getName();
+                    if (tmp != null) buffer.append(tmp);
+                    buffer.append(": ");
+                    tmp = field.getValue();
+                    if (tmp != null) buffer.append(tmp);
+                    buffer.append("\r\n");
+                }
+            }
+            buffer.append("\r\n");
+            return buffer.toString();
+        }
+        catch (Exception e)
+        {
+            LOG.warn(e);
+            return e.toString();
+        }
+    }
+
+    /**
+     * Clear the header.
+     */
+    public void clear()
+    {
+        _fields.clear();
+    }
+
+    public void add(HttpField field)
+    {
+        _fields.add(field);
+    }
+
+    
+    
+    /**
+     * Add fields from another HttpFields instance. Single valued fields are replaced, while all
+     * others are added.
+     *
+     * @param fields the fields to add
+     */
+    public void add(HttpFields fields)
+    {
+        if (fields == null) return;
+
+        Enumeration<String> e = fields.getFieldNames();
+        while (e.hasMoreElements())
+        {
+            String name = e.nextElement();
+            Enumeration<String> values = fields.getValues(name);
+            while (values.hasMoreElements())
+                add(name, values.nextElement());
+        }
+    }
+
+    /**
+     * Get field value parameters. Some field values can have parameters. This method separates the
+     * value from the parameters and optionally populates a map with the parameters. For example:
+     *
+     * <PRE>
+     *
+     * FieldName : Value ; param1=val1 ; param2=val2
+     *
+     * </PRE>
+     *
+     * @param value The Field value, possibly with parameteres.
+     * @param parameters A map to populate with the parameters, or null
+     * @return The value.
+     */
+    public static String valueParameters(String value, Map<String,String> parameters)
+    {
+        if (value == null) return null;
+
+        int i = value.indexOf(';');
+        if (i < 0) return value;
+        if (parameters == null) return value.substring(0, i).trim();
+
+        StringTokenizer tok1 = new QuotedStringTokenizer(value.substring(i), ";", false, true);
+        while (tok1.hasMoreTokens())
+        {
+            String token = tok1.nextToken();
+            StringTokenizer tok2 = new QuotedStringTokenizer(token, "= ");
+            if (tok2.hasMoreTokens())
+            {
+                String paramName = tok2.nextToken();
+                String paramVal = null;
+                if (tok2.hasMoreTokens()) paramVal = tok2.nextToken();
+                parameters.put(paramName, paramVal);
+            }
+        }
+
+        return value.substring(0, i).trim();
+    }
+
+    private static final Float __one = new Float("1.0");
+    private static final Float __zero = new Float("0.0");
+    private static final Trie<Float> __qualities = new ArrayTernaryTrie<>();
+    static
+    {
+        __qualities.put("*", __one);
+        __qualities.put("1.0", __one);
+        __qualities.put("1", __one);
+        __qualities.put("0.9", new Float("0.9"));
+        __qualities.put("0.8", new Float("0.8"));
+        __qualities.put("0.7", new Float("0.7"));
+        __qualities.put("0.66", new Float("0.66"));
+        __qualities.put("0.6", new Float("0.6"));
+        __qualities.put("0.5", new Float("0.5"));
+        __qualities.put("0.4", new Float("0.4"));
+        __qualities.put("0.33", new Float("0.33"));
+        __qualities.put("0.3", new Float("0.3"));
+        __qualities.put("0.2", new Float("0.2"));
+        __qualities.put("0.1", new Float("0.1"));
+        __qualities.put("0", __zero);
+        __qualities.put("0.0", __zero);
+    }
+
+    public static Float getQuality(String value)
+    {
+        if (value == null) return __zero;
+
+        int qe = value.indexOf(";");
+        if (qe++ < 0 || qe == value.length()) return __one;
+
+        if (value.charAt(qe++) == 'q')
+        {
+            qe++;
+            Float q = __qualities.get(value, qe, value.length() - qe);
+            if (q != null)
+                return q;
+        }
+
+        Map<String,String> params = new HashMap<>(4);
+        valueParameters(value, params);
+        String qs = params.get("q");
+        if (qs==null)
+            qs="*";
+        Float q = __qualities.get(qs);
+        if (q == null)
+        {
+            try
+            {
+                q = new Float(qs);
+            }
+            catch (Exception e)
+            {
+                q = __one;
+            }
+        }
+        return q;
+    }
+
+    /**
+     * List values in quality order.
+     *
+     * @param e Enumeration of values with quality parameters
+     * @return values in quality order.
+     */
+    public static List<String> qualityList(Enumeration<String> e)
+    {
+        if (e == null || !e.hasMoreElements())
+            return Collections.emptyList();
+
+        Object list = null;
+        Object qual = null;
+
+        // Assume list will be well ordered and just add nonzero
+        while (e.hasMoreElements())
+        {
+            String v = e.nextElement();
+            Float q = getQuality(v);
+
+            if (q >= 0.001)
+            {
+                list = LazyList.add(list, v);
+                qual = LazyList.add(qual, q);
+            }
+        }
+
+        List<String> vl = LazyList.getList(list, false);
+        if (vl.size() < 2) 
+            return vl;
+
+        List<Float> ql = LazyList.getList(qual, false);
+
+        // sort list with swaps
+        Float last = __zero;
+        for (int i = vl.size(); i-- > 0;)
+        {
+            Float q = ql.get(i);
+            if (last.compareTo(q) > 0)
+            {
+                String tmp = vl.get(i);
+                vl.set(i, vl.get(i + 1));
+                vl.set(i + 1, tmp);
+                ql.set(i, ql.get(i + 1));
+                ql.set(i + 1, q);
+                last = __zero;
+                i = vl.size();
+                continue;
+            }
+            last = q;
+        }
+        ql.clear();
+        return vl;
+    }
+
+
+
+}
diff --git a/lib/jetty/org/eclipse/jetty/http/HttpGenerator.java b/lib/jetty/org/eclipse/jetty/http/HttpGenerator.java
new file mode 100644 (file)
index 0000000..a51e4ba
--- /dev/null
@@ -0,0 +1,1104 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.http;
+
+import java.io.IOException;
+import java.nio.BufferOverflowException;
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+
+import org.eclipse.jetty.http.HttpTokens.EndOfContent;
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.StringUtil;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/* ------------------------------------------------------------ */
+/**
+ * HttpGenerator. Builds HTTP Messages.
+ *
+ * If the system property "org.eclipse.jetty.http.HttpGenerator.STRICT" is set to true,
+ * then the generator will strictly pass on the exact strings received from methods and header
+ * fields.  Otherwise a fast case insensitive string lookup is used that may alter the
+ * case and white space of some methods/headers
+ * </p>
+ */
+public class HttpGenerator
+{
+    private final static Logger LOG = Log.getLogger(HttpGenerator.class);
+
+    public final static boolean __STRICT=Boolean.getBoolean("org.eclipse.jetty.http.HttpGenerator.STRICT"); 
+
+    private final static byte[] __colon_space = new byte[] {':',' '};
+    private final static HttpHeaderValue[] CLOSE = {HttpHeaderValue.CLOSE};
+    public static final ResponseInfo CONTINUE_100_INFO = new ResponseInfo(HttpVersion.HTTP_1_1,null,-1,100,null,false);
+    public static final ResponseInfo PROGRESS_102_INFO = new ResponseInfo(HttpVersion.HTTP_1_1,null,-1,102,null,false);
+    public final static ResponseInfo RESPONSE_500_INFO =
+        new ResponseInfo(HttpVersion.HTTP_1_1,new HttpFields(){{put(HttpHeader.CONNECTION,HttpHeaderValue.CLOSE);}},0,HttpStatus.INTERNAL_SERVER_ERROR_500,null,false);
+
+    // states
+    public enum State { START, COMMITTED, COMPLETING, COMPLETING_1XX, END }
+    public enum Result { NEED_CHUNK,NEED_INFO,NEED_HEADER,FLUSH,CONTINUE,SHUTDOWN_OUT,DONE}
+
+    // other statics
+    public static final int CHUNK_SIZE = 12;
+
+    private State _state = State.START;
+    private EndOfContent _endOfContent = EndOfContent.UNKNOWN_CONTENT;
+
+    private long _contentPrepared = 0;
+    private boolean _noContent = false;
+    private Boolean _persistent = null;
+
+    private final int _send;
+    private final static int SEND_SERVER = 0x01;
+    private final static int SEND_XPOWEREDBY = 0x02;
+
+
+    /* ------------------------------------------------------------------------------- */
+    public static void setJettyVersion(String serverVersion)
+    {
+        SEND[SEND_SERVER] = StringUtil.getBytes("Server: " + serverVersion + "\015\012");
+        SEND[SEND_XPOWEREDBY] = StringUtil.getBytes("X-Powered-By: " + serverVersion + "\015\012");
+        SEND[SEND_SERVER | SEND_XPOWEREDBY] = StringUtil.getBytes("Server: " + serverVersion + "\015\012X-Powered-By: " +
+                serverVersion + "\015\012");
+    }
+
+    /* ------------------------------------------------------------------------------- */
+    // data
+    private boolean _needCRLF = false;
+
+    /* ------------------------------------------------------------------------------- */
+    public HttpGenerator()
+    {
+        this(false,false);
+    }
+    
+    /* ------------------------------------------------------------------------------- */
+    public HttpGenerator(boolean sendServerVersion,boolean sendXPoweredBy)
+    {
+        _send=(sendServerVersion?SEND_SERVER:0) | (sendXPoweredBy?SEND_XPOWEREDBY:0);
+    }
+
+    /* ------------------------------------------------------------------------------- */
+    public void reset()
+    {
+        _state = State.START;
+        _endOfContent = EndOfContent.UNKNOWN_CONTENT;
+        _noContent=false;
+        _persistent = null;
+        _contentPrepared = 0;
+        _needCRLF = false;
+    }
+
+    /* ------------------------------------------------------------ */
+    @Deprecated
+    public boolean getSendServerVersion ()
+    {
+        return (_send&SEND_SERVER)!=0;
+    }
+
+    /* ------------------------------------------------------------ */
+    @Deprecated
+    public void setSendServerVersion (boolean sendServerVersion)
+    {
+        throw new UnsupportedOperationException();
+    }
+
+    /* ------------------------------------------------------------ */
+    public State getState()
+    {
+        return _state;
+    }
+
+    /* ------------------------------------------------------------ */
+    public boolean isState(State state)
+    {
+        return _state == state;
+    }
+
+    /* ------------------------------------------------------------ */
+    public boolean isIdle()
+    {
+        return _state == State.START;
+    }
+
+    /* ------------------------------------------------------------ */
+    public boolean isEnd()
+    {
+        return _state == State.END;
+    }
+
+    /* ------------------------------------------------------------ */
+    public boolean isCommitted()
+    {
+        return _state.ordinal() >= State.COMMITTED.ordinal();
+    }
+
+    /* ------------------------------------------------------------ */
+    public boolean isChunking()
+    {
+        return _endOfContent==EndOfContent.CHUNKED_CONTENT;
+    }
+
+    /* ------------------------------------------------------------ */
+    public void setPersistent(boolean persistent)
+    {
+        _persistent=persistent;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return true if known to be persistent
+     */
+    public boolean isPersistent()
+    {
+        return Boolean.TRUE.equals(_persistent);
+    }
+
+    /* ------------------------------------------------------------ */
+    public boolean isWritten()
+    {
+        return _contentPrepared>0;
+    }
+
+    /* ------------------------------------------------------------ */
+    public long getContentPrepared()
+    {
+        return _contentPrepared;
+    }
+
+    /* ------------------------------------------------------------ */
+    public void abort()
+    {
+        _persistent=false;
+        _state=State.END;
+        _endOfContent=null;
+    }
+
+    /* ------------------------------------------------------------ */
+    public Result generateRequest(RequestInfo info, ByteBuffer header, ByteBuffer chunk, ByteBuffer content, boolean last) throws IOException
+    {
+        switch(_state)
+        {
+            case START:
+            {
+                if (info==null)
+                    return Result.NEED_INFO;
+
+                // Do we need a request header
+                if (header==null)
+                    return Result.NEED_HEADER;
+
+                // If we have not been told our persistence, set the default
+                if (_persistent==null)
+                    _persistent=(info.getHttpVersion().ordinal() > HttpVersion.HTTP_1_0.ordinal());
+
+                // prepare the header
+                int pos=BufferUtil.flipToFill(header);
+                try
+                {
+                    // generate ResponseLine
+                    generateRequestLine(info,header);
+
+                    if (info.getHttpVersion()==HttpVersion.HTTP_0_9)
+                        _noContent=true;
+                    else
+                        generateHeaders(info,header,content,last);
+
+                    boolean expect100 = info.getHttpFields().contains(HttpHeader.EXPECT, HttpHeaderValue.CONTINUE.asString());
+
+                    if (expect100)
+                    {
+                        _state = State.COMMITTED;
+                    }
+                    else
+                    {
+                        // handle the content.
+                        int len = BufferUtil.length(content);
+                        if (len>0)
+                        {
+                            _contentPrepared+=len;
+                            if (isChunking())
+                                prepareChunk(header,len);
+                        }
+                        _state = last?State.COMPLETING:State.COMMITTED;
+                    }
+
+                    return Result.FLUSH;
+                }
+                catch(Exception e)
+                {
+                    String message= (e instanceof BufferOverflowException)?"Response header too large":e.getMessage();
+                    throw new IOException(message,e);
+                }
+                finally
+                {
+                    BufferUtil.flipToFlush(header,pos);
+                }
+            }
+
+            case COMMITTED:
+            {
+                int len = BufferUtil.length(content);
+
+                if (len>0)
+                {
+                    // Do we need a chunk buffer?
+                    if (isChunking())
+                    {
+                        // Do we need a chunk buffer?
+                        if (chunk==null)
+                            return Result.NEED_CHUNK;
+                        BufferUtil.clearToFill(chunk);
+                        prepareChunk(chunk,len);
+                        BufferUtil.flipToFlush(chunk,0);
+                    }
+                    _contentPrepared+=len;
+                }
+
+                if (last)
+                {
+                    _state=State.COMPLETING;
+                    return len>0?Result.FLUSH:Result.CONTINUE;
+                }
+
+                return Result.FLUSH;
+            }
+
+            case COMPLETING:
+            {
+                if (BufferUtil.hasContent(content))
+                {
+                    LOG.debug("discarding content in COMPLETING");
+                    BufferUtil.clear(content);
+                }
+
+                if (isChunking())
+                {
+                    // Do we need a chunk buffer?
+                    if (chunk==null)
+                        return Result.NEED_CHUNK;
+                    BufferUtil.clearToFill(chunk);
+                    prepareChunk(chunk,0);
+                    BufferUtil.flipToFlush(chunk,0);
+                    _endOfContent=EndOfContent.UNKNOWN_CONTENT;
+                    return Result.FLUSH;
+                }
+
+                _state=State.END;
+               return Boolean.TRUE.equals(_persistent)?Result.DONE:Result.SHUTDOWN_OUT;
+            }
+
+            case END:
+                if (BufferUtil.hasContent(content))
+                {
+                    LOG.debug("discarding content in COMPLETING");
+                    BufferUtil.clear(content);
+                }
+                return Result.DONE;
+
+            default:
+                throw new IllegalStateException();
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    public Result generateResponse(ResponseInfo info, ByteBuffer header, ByteBuffer chunk, ByteBuffer content, boolean last) throws IOException
+    {
+        switch(_state)
+        {
+            case START:
+            {
+                if (info==null)
+                    return Result.NEED_INFO;
+
+                // Handle 0.9
+                if (info.getHttpVersion() == HttpVersion.HTTP_0_9)
+                {
+                    _persistent = false;
+                    _endOfContent=EndOfContent.EOF_CONTENT;
+                    if (BufferUtil.hasContent(content))
+                        _contentPrepared+=content.remaining();
+                    _state = last?State.COMPLETING:State.COMMITTED;
+                    return Result.FLUSH;
+                }
+
+                // Do we need a response header
+                if (header==null)
+                    return Result.NEED_HEADER;
+
+                // If we have not been told our persistence, set the default
+                if (_persistent==null)
+                    _persistent=(info.getHttpVersion().ordinal() > HttpVersion.HTTP_1_0.ordinal());
+
+                // prepare the header
+                int pos=BufferUtil.flipToFill(header);
+                try
+                {
+                    // generate ResponseLine
+                    generateResponseLine(info,header);
+
+                    // Handle 1xx and no content responses
+                    int status=info.getStatus();
+                    if (status>=100 && status<200 )
+                    {
+                        _noContent=true;
+
+                        if (status!=HttpStatus.SWITCHING_PROTOCOLS_101 )
+                        {
+                            header.put(HttpTokens.CRLF);
+                            _state=State.COMPLETING_1XX;
+                            return Result.FLUSH;
+                        }
+                    }
+                    else if (status==HttpStatus.NO_CONTENT_204 || status==HttpStatus.NOT_MODIFIED_304)
+                    {
+                        _noContent=true;
+                    }
+
+                    generateHeaders(info,header,content,last);
+
+                    // handle the content.
+                    int len = BufferUtil.length(content);
+                    if (len>0)
+                    {
+                        _contentPrepared+=len;
+                        if (isChunking() && !info.isHead())
+                            prepareChunk(header,len);
+                    }
+                    _state = last?State.COMPLETING:State.COMMITTED;
+                }
+                catch(Exception e)
+                {
+                    String message= (e instanceof BufferOverflowException)?"Response header too large":e.getMessage();
+                    throw new IOException(message,e);
+                }
+                finally
+                {
+                    BufferUtil.flipToFlush(header,pos);
+                }
+
+                return Result.FLUSH;
+            }
+
+            case COMMITTED:
+            {
+                int len = BufferUtil.length(content);
+
+                // handle the content.
+                if (len>0)
+                {
+                    if (isChunking())
+                    {
+                        if (chunk==null)
+                            return Result.NEED_CHUNK;
+                        BufferUtil.clearToFill(chunk);
+                        prepareChunk(chunk,len);
+                        BufferUtil.flipToFlush(chunk,0);
+                    }
+                    _contentPrepared+=len;
+                }
+
+                if (last)
+                {
+                    _state=State.COMPLETING;
+                    return len>0?Result.FLUSH:Result.CONTINUE;
+                }
+                return len>0?Result.FLUSH:Result.DONE;
+
+            }
+
+            case COMPLETING_1XX:
+            {
+                reset();
+                return Result.DONE;
+            }
+
+            case COMPLETING:
+            {
+                if (BufferUtil.hasContent(content))
+                {
+                    LOG.debug("discarding content in COMPLETING");
+                    BufferUtil.clear(content);
+                }
+
+                if (isChunking())
+                {
+                    // Do we need a chunk buffer?
+                    if (chunk==null)
+                        return Result.NEED_CHUNK;
+
+                    // Write the last chunk
+                    BufferUtil.clearToFill(chunk);
+                    prepareChunk(chunk,0);
+                    BufferUtil.flipToFlush(chunk,0);
+                    _endOfContent=EndOfContent.UNKNOWN_CONTENT;
+                    return Result.FLUSH;
+                }
+
+                _state=State.END;
+
+               return Boolean.TRUE.equals(_persistent)?Result.DONE:Result.SHUTDOWN_OUT;
+            }
+
+            case END:
+                if (BufferUtil.hasContent(content))
+                {
+                    LOG.debug("discarding content in COMPLETING");
+                    BufferUtil.clear(content);
+                }
+                return Result.DONE;
+
+            default:
+                throw new IllegalStateException();
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    private void prepareChunk(ByteBuffer chunk, int remaining)
+    {
+        // if we need CRLF add this to header
+        if (_needCRLF)
+            BufferUtil.putCRLF(chunk);
+
+        // Add the chunk size to the header
+        if (remaining>0)
+        {
+            BufferUtil.putHexInt(chunk, remaining);
+            BufferUtil.putCRLF(chunk);
+            _needCRLF=true;
+        }
+        else
+        {
+            chunk.put(LAST_CHUNK);
+            _needCRLF=false;
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    private void generateRequestLine(RequestInfo request,ByteBuffer header)
+    {
+        header.put(StringUtil.getBytes(request.getMethod()));
+        header.put((byte)' ');
+        header.put(StringUtil.getBytes(request.getUri()));
+        switch(request.getHttpVersion())
+        {
+            case HTTP_1_0:
+            case HTTP_1_1:
+                header.put((byte)' ');
+                header.put(request.getHttpVersion().toBytes());
+                break;
+            default:
+                throw new IllegalStateException();
+        }
+        header.put(HttpTokens.CRLF);
+    }
+
+    /* ------------------------------------------------------------ */
+    private void generateResponseLine(ResponseInfo response, ByteBuffer header)
+    {
+        // Look for prepared response line
+        int status=response.getStatus();
+        PreparedResponse preprepared = status<__preprepared.length?__preprepared[status]:null;
+        String reason=response.getReason();
+        if (preprepared!=null)
+        {
+            if (reason==null)
+                header.put(preprepared._responseLine);
+            else
+            {
+                header.put(preprepared._schemeCode);
+                header.put(getReasonBytes(reason));
+                header.put(HttpTokens.CRLF);
+            }
+        }
+        else // generate response line
+        {
+            header.put(HTTP_1_1_SPACE);
+            header.put((byte) ('0' + status / 100));
+            header.put((byte) ('0' + (status % 100) / 10));
+            header.put((byte) ('0' + (status % 10)));
+            header.put((byte) ' ');
+            if (reason==null)
+            {
+                header.put((byte) ('0' + status / 100));
+                header.put((byte) ('0' + (status % 100) / 10));
+                header.put((byte) ('0' + (status % 10)));
+            }
+            else
+                header.put(getReasonBytes(reason));
+            header.put(HttpTokens.CRLF);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    private byte[] getReasonBytes(String reason)
+    {
+        if (reason.length()>1024)
+            reason=reason.substring(0,1024);
+        byte[] _bytes = StringUtil.getBytes(reason);
+
+        for (int i=_bytes.length;i-->0;)
+            if (_bytes[i]=='\r' || _bytes[i]=='\n')
+                _bytes[i]='?';
+        return _bytes;
+    }
+
+    /* ------------------------------------------------------------ */
+    private void generateHeaders(Info _info,ByteBuffer header,ByteBuffer content,boolean last)
+    {
+        final RequestInfo request=(_info instanceof RequestInfo)?(RequestInfo)_info:null;
+        final ResponseInfo response=(_info instanceof ResponseInfo)?(ResponseInfo)_info:null;
+
+        // default field values
+        int send=_send;
+        HttpField transfer_encoding=null;
+        boolean keep_alive=false;
+        boolean close=false;
+        boolean content_type=false;
+        StringBuilder connection = null;
+
+        // Generate fields
+        if (_info.getHttpFields() != null)
+        {
+            for (HttpField field : _info.getHttpFields())
+            {
+                HttpHeader h = field.getHeader();
+
+                switch (h==null?HttpHeader.UNKNOWN:h)
+                {
+                    case CONTENT_LENGTH:
+                        // handle specially below
+                        if (_info.getContentLength()>=0)
+                            _endOfContent=EndOfContent.CONTENT_LENGTH;
+                        break;
+
+                    case CONTENT_TYPE:
+                    {
+                        if (field.getValue().startsWith(MimeTypes.Type.MULTIPART_BYTERANGES.toString()))
+                            _endOfContent=EndOfContent.SELF_DEFINING_CONTENT;
+
+                        // write the field to the header
+                        content_type=true;
+                        putTo(field,header);
+                        break;
+                    }
+
+                    case TRANSFER_ENCODING:
+                    {
+                        if (_info.getHttpVersion() == HttpVersion.HTTP_1_1)
+                            transfer_encoding = field;
+                        // Do NOT add yet!
+                        break;
+                    }
+
+                    case CONNECTION:
+                    {
+                        if (request!=null)
+                            putTo(field,header);
+
+                        // Lookup and/or split connection value field
+                        HttpHeaderValue[] values = HttpHeaderValue.CLOSE.is(field.getValue())?CLOSE:new HttpHeaderValue[]{HttpHeaderValue.CACHE.get(field.getValue())};
+                        String[] split = null;
+
+                        if (values[0]==null)
+                        {
+                            split = field.getValue().split("\\s*,\\s*");
+                            if (split.length>0)
+                            {
+                                values=new HttpHeaderValue[split.length];
+                                for (int i=0;i<split.length;i++)
+                                    values[i]=HttpHeaderValue.CACHE.get(split[i]);
+                            }
+                        }
+
+                        // Handle connection values
+                        for (int i=0;i<values.length;i++)
+                        {
+                            HttpHeaderValue value=values[i];
+                            switch (value==null?HttpHeaderValue.UNKNOWN:value)
+                            {
+                                case UPGRADE:
+                                {
+                                    // special case for websocket connection ordering
+                                    header.put(HttpHeader.CONNECTION.getBytesColonSpace()).put(HttpHeader.UPGRADE.getBytes());
+                                    header.put(CRLF);
+                                    break;
+                                }
+
+                                case CLOSE:
+                                {
+                                    close=true;
+                                    if (response!=null)
+                                    {
+                                        _persistent=false;
+                                        if (_endOfContent == EndOfContent.UNKNOWN_CONTENT)
+                                            _endOfContent=EndOfContent.EOF_CONTENT;
+                                    }
+                                    break;
+                                }
+
+                                case KEEP_ALIVE:
+                                {
+                                    if (_info.getHttpVersion() == HttpVersion.HTTP_1_0)
+                                    {
+                                        keep_alive = true;
+                                        if (response!=null)
+                                            _persistent=true;
+                                    }
+                                    break;
+                                }
+
+                                default:
+                                {
+                                    if (connection==null)
+                                        connection=new StringBuilder();
+                                    else
+                                        connection.append(',');
+                                    connection.append(split==null?field.getValue():split[i]);
+                                }
+                            }
+                        }
+
+                        // Do NOT add yet!
+                        break;
+                    }
+
+                    case SERVER:
+                    {
+                        send=send&~SEND_SERVER;
+                        putTo(field,header);
+                        break;
+                    }
+
+                    default:
+                        putTo(field,header);
+                }
+            }
+        }
+
+
+        // Calculate how to end _content and connection, _content length and transfer encoding
+        // settings.
+        // From RFC 2616 4.4:
+        // 1. No body for 1xx, 204, 304 & HEAD response
+        // 2. Force _content-length?
+        // 3. If Transfer-Encoding!=identity && HTTP/1.1 && !HttpConnection==close then chunk
+        // 4. Content-Length
+        // 5. multipart/byteranges
+        // 6. close
+        int status=response!=null?response.getStatus():-1;
+        switch (_endOfContent)
+        {
+            case UNKNOWN_CONTENT:
+                // It may be that we have no _content, or perhaps _content just has not been
+                // written yet?
+
+                // Response known not to have a body
+                if (_contentPrepared == 0 && response!=null && (status < 200 || status == 204 || status == 304))
+                    _endOfContent=EndOfContent.NO_CONTENT;
+                else if (_info.getContentLength()>0)
+                {
+                    // we have been given a content length
+                    _endOfContent=EndOfContent.CONTENT_LENGTH;
+                    long content_length = _info.getContentLength();
+                    if ((response!=null || content_length>0 || content_type ) && !_noContent)
+                    {
+                        // known length but not actually set.
+                        header.put(HttpHeader.CONTENT_LENGTH.getBytesColonSpace());
+                        BufferUtil.putDecLong(header, content_length);
+                        header.put(HttpTokens.CRLF);
+                    }
+                }
+                else if (last)
+                {
+                    // we have seen all the _content there is, so we can be content-length limited.
+                    _endOfContent=EndOfContent.CONTENT_LENGTH;
+                    long content_length = _contentPrepared+BufferUtil.length(content);
+
+                    // Do we need to tell the headers about it
+                    if ((response!=null || content_length>0 || content_type ) && !_noContent)
+                    {
+                        header.put(HttpHeader.CONTENT_LENGTH.getBytesColonSpace());
+                        BufferUtil.putDecLong(header, content_length);
+                        header.put(HttpTokens.CRLF);
+                    }
+                }
+                else
+                {
+                    // No idea, so we must assume that a body is coming.
+                    _endOfContent = EndOfContent.CHUNKED_CONTENT;
+                    // HTTP 1.0 does not understand chunked content, so we must use EOF content.
+                    // For a request with HTTP 1.0 & Connection: keep-alive
+                    // we *must* close the connection, otherwise the client
+                    // has no way to detect the end of the content.
+                    if (!isPersistent() || _info.getHttpVersion().ordinal() < HttpVersion.HTTP_1_1.ordinal())
+                        _endOfContent = EndOfContent.EOF_CONTENT;
+                }
+                break;
+
+            case CONTENT_LENGTH:
+                long content_length = _info.getContentLength();
+                if ((response!=null || content_length>0 || content_type ) && !_noContent)
+                {
+                    // known length but not actually set.
+                    header.put(HttpHeader.CONTENT_LENGTH.getBytesColonSpace());
+                    BufferUtil.putDecLong(header, content_length);
+                    header.put(HttpTokens.CRLF);
+                }
+                break;
+
+            case NO_CONTENT:
+                if (response!=null && status >= 200 && status != 204 && status != 304)
+                    header.put(CONTENT_LENGTH_0);
+                break;
+
+            case EOF_CONTENT:
+                _persistent = request!=null;
+                break;
+
+            case CHUNKED_CONTENT:
+                break;
+
+            default:
+                break;
+        }
+
+        // Add transfer_encoding if needed
+        if (isChunking())
+        {
+            // try to use user supplied encoding as it may have other values.
+            if (transfer_encoding != null && !HttpHeaderValue.CHUNKED.toString().equalsIgnoreCase(transfer_encoding.getValue()))
+            {
+                String c = transfer_encoding.getValue();
+                if (c.endsWith(HttpHeaderValue.CHUNKED.toString()))
+                    putTo(transfer_encoding,header);
+                else
+                    throw new IllegalArgumentException("BAD TE");
+            }
+            else
+                header.put(TRANSFER_ENCODING_CHUNKED);
+        }
+
+        // Handle connection if need be
+        if (_endOfContent==EndOfContent.EOF_CONTENT)
+        {
+            keep_alive=false;
+            _persistent=false;
+        }
+
+        // If this is a response, work out persistence
+        if (response!=null)
+        {
+            if (!isPersistent() && (close || _info.getHttpVersion().ordinal() > HttpVersion.HTTP_1_0.ordinal()))
+            {
+                if (connection==null)
+                    header.put(CONNECTION_CLOSE);
+                else
+                {
+                    header.put(CONNECTION_CLOSE,0,CONNECTION_CLOSE.length-2);
+                    header.put((byte)',');
+                    header.put(StringUtil.getBytes(connection.toString()));
+                    header.put(CRLF);
+                }
+            }
+            else if (keep_alive)
+            {
+                if (connection==null)
+                    header.put(CONNECTION_KEEP_ALIVE);
+                else
+                {
+                    header.put(CONNECTION_KEEP_ALIVE,0,CONNECTION_KEEP_ALIVE.length-2);
+                    header.put((byte)',');
+                    header.put(StringUtil.getBytes(connection.toString()));
+                    header.put(CRLF);
+                }
+            }
+            else if (connection!=null)
+            {
+                header.put(HttpHeader.CONNECTION.getBytesColonSpace());
+                header.put(StringUtil.getBytes(connection.toString()));
+                header.put(CRLF);
+            }
+        }
+
+        if (status>199)
+            header.put(SEND[send]);
+
+        // end the header.
+        header.put(HttpTokens.CRLF);
+    }
+
+    /* ------------------------------------------------------------------------------- */
+    public static byte[] getReasonBuffer(int code)
+    {
+        PreparedResponse status = code<__preprepared.length?__preprepared[code]:null;
+        if (status!=null)
+            return status._reason;
+        return null;
+    }
+
+    /* ------------------------------------------------------------------------------- */
+    @Override
+    public String toString()
+    {
+        return String.format("%s{s=%s}",
+                getClass().getSimpleName(),
+                _state);
+    }
+
+    /* ------------------------------------------------------------------------------- */
+    /* ------------------------------------------------------------------------------- */
+    /* ------------------------------------------------------------------------------- */
+    // common _content
+    private static final byte[] LAST_CHUNK =    { (byte) '0', (byte) '\015', (byte) '\012', (byte) '\015', (byte) '\012'};
+    private static final byte[] CONTENT_LENGTH_0 = StringUtil.getBytes("Content-Length: 0\015\012");
+    private static final byte[] CONNECTION_KEEP_ALIVE = StringUtil.getBytes("Connection: keep-alive\015\012");
+    private static final byte[] CONNECTION_CLOSE = StringUtil.getBytes("Connection: close\015\012");
+    private static final byte[] HTTP_1_1_SPACE = StringUtil.getBytes(HttpVersion.HTTP_1_1+" ");
+    private static final byte[] CRLF = StringUtil.getBytes("\015\012");
+    private static final byte[] TRANSFER_ENCODING_CHUNKED = StringUtil.getBytes("Transfer-Encoding: chunked\015\012");
+    private static final byte[][] SEND = new byte[][]{
+            new byte[0],
+            StringUtil.getBytes("Server: Jetty(9.x.x)\015\012"),
+        StringUtil.getBytes("X-Powered-By: Jetty(9.x.x)\015\012"),
+        StringUtil.getBytes("Server: Jetty(9.x.x)\015\012X-Powered-By: Jetty(9.x.x)\015\012")
+    };
+
+    /* ------------------------------------------------------------------------------- */
+    /* ------------------------------------------------------------------------------- */
+    /* ------------------------------------------------------------------------------- */
+    // Build cache of response lines for status
+    private static class PreparedResponse
+    {
+        byte[] _reason;
+        byte[] _schemeCode;
+        byte[] _responseLine;
+    }
+    private static final PreparedResponse[] __preprepared = new PreparedResponse[HttpStatus.MAX_CODE+1];
+    static
+    {
+        int versionLength=HttpVersion.HTTP_1_1.toString().length();
+
+        for (int i=0;i<__preprepared.length;i++)
+        {
+            HttpStatus.Code code = HttpStatus.getCode(i);
+            if (code==null)
+                continue;
+            String reason=code.getMessage();
+            byte[] line=new byte[versionLength+5+reason.length()+2];
+            HttpVersion.HTTP_1_1.toBuffer().get(line,0,versionLength);
+            line[versionLength+0]=' ';
+            line[versionLength+1]=(byte)('0'+i/100);
+            line[versionLength+2]=(byte)('0'+(i%100)/10);
+            line[versionLength+3]=(byte)('0'+(i%10));
+            line[versionLength+4]=' ';
+            for (int j=0;j<reason.length();j++)
+                line[versionLength+5+j]=(byte)reason.charAt(j);
+            line[versionLength+5+reason.length()]=HttpTokens.CARRIAGE_RETURN;
+            line[versionLength+6+reason.length()]=HttpTokens.LINE_FEED;
+
+            __preprepared[i] = new PreparedResponse();
+            __preprepared[i]._schemeCode = Arrays.copyOfRange(line, 0,versionLength+5);
+            __preprepared[i]._reason = Arrays.copyOfRange(line, versionLength+5, line.length-2);
+            __preprepared[i]._responseLine=line;
+        }
+    }
+
+    public static class Info
+    {
+        final HttpVersion _httpVersion;
+        final HttpFields _httpFields;
+        final long _contentLength;
+
+        private Info(HttpVersion httpVersion, HttpFields httpFields, long contentLength)
+        {
+            _httpVersion = httpVersion;
+            _httpFields = httpFields;
+            _contentLength = contentLength;
+        }
+
+        public HttpVersion getHttpVersion()
+        {
+            return _httpVersion;
+        }
+        public HttpFields getHttpFields()
+        {
+            return _httpFields;
+        }
+        public long getContentLength()
+        {
+            return _contentLength;
+        }
+    }
+
+    public static class RequestInfo extends Info
+    {
+        private final String _method;
+        private final String _uri;
+
+        public RequestInfo(HttpVersion httpVersion, HttpFields httpFields, long contentLength, String method, String uri)
+        {
+            super(httpVersion,httpFields,contentLength);
+            _method = method;
+            _uri = uri;
+        }
+
+        public String getMethod()
+        {
+            return _method;
+        }
+
+        public String getUri()
+        {
+            return _uri;
+        }
+
+        @Override
+        public String toString()
+        {
+            return String.format("RequestInfo{%s %s %s,%d}",_method,_uri,_httpVersion,_contentLength);
+        }
+    }
+
+    public static class ResponseInfo extends Info
+    {
+        private final int _status;
+        private final String _reason;
+        private final boolean _head;
+
+        public ResponseInfo(HttpVersion httpVersion, HttpFields httpFields, long contentLength, int status, String reason, boolean head)
+        {
+            super(httpVersion,httpFields,contentLength);
+            _status = status;
+            _reason = reason;
+            _head = head;
+        }
+
+        public boolean isInformational()
+        {
+            return _status>=100 && _status<200;
+        }
+
+        public int getStatus()
+        {
+            return _status;
+        }
+
+        public String getReason()
+        {
+            return _reason;
+        }
+
+        public boolean isHead()
+        {
+            return _head;
+        }
+
+        @Override
+        public String toString()
+        {
+            return String.format("ResponseInfo{%s %s %s,%d,%b}",_httpVersion,_status,_reason,_contentLength,_head);
+        }
+    } 
+
+    private static void putSanitisedName(String s,ByteBuffer buffer)
+    {
+        int l=s.length();
+        for (int i=0;i<l;i++)
+        {
+            char c=s.charAt(i);
+            
+            if (c<0 || c>0xff || c=='\r' || c=='\n'|| c==':')
+                buffer.put((byte)'?');
+            else
+                buffer.put((byte)(0xff&c));
+        }
+    }
+
+    private static void putSanitisedValue(String s,ByteBuffer buffer)
+    {
+        int l=s.length();
+        for (int i=0;i<l;i++)
+        {
+            char c=s.charAt(i);
+            
+            if (c<0 || c>0xff || c=='\r' || c=='\n')
+                buffer.put((byte)'?');
+            else
+                buffer.put((byte)(0xff&c));
+        }
+    }
+
+    public static void putTo(HttpField field, ByteBuffer bufferInFillMode)
+    {
+        if (field instanceof CachedHttpField)
+        {
+            ((CachedHttpField)field).putTo(bufferInFillMode);
+        }
+        else
+        {
+            HttpHeader header=field.getHeader();
+            if (header!=null)
+            {
+                bufferInFillMode.put(header.getBytesColonSpace());
+                putSanitisedValue(field.getValue(),bufferInFillMode);
+            }
+            else
+            {
+                putSanitisedName(field.getName(),bufferInFillMode);
+                bufferInFillMode.put(__colon_space);
+                putSanitisedValue(field.getValue(),bufferInFillMode);
+            }
+
+            BufferUtil.putCRLF(bufferInFillMode);
+        }
+    }
+
+    public static void putTo(HttpFields fields, ByteBuffer bufferInFillMode) 
+    {
+        for (HttpField field : fields)
+        {
+            if (field != null)
+                putTo(field,bufferInFillMode);
+        }
+        BufferUtil.putCRLF(bufferInFillMode);
+    }
+    
+    public static class CachedHttpField extends HttpField
+    {
+        private final byte[] _bytes;
+        public CachedHttpField(HttpHeader header,String value)
+        {
+            super(header,value);
+            int cbl=header.getBytesColonSpace().length;
+            _bytes=Arrays.copyOf(header.getBytesColonSpace(), cbl+value.length()+2);
+            System.arraycopy(value.getBytes(StandardCharsets.ISO_8859_1),0,_bytes,cbl,value.length());
+            _bytes[_bytes.length-2]=(byte)'\r';
+            _bytes[_bytes.length-1]=(byte)'\n';
+        }
+        
+        public void putTo(ByteBuffer bufferInFillMode)
+        {
+            bufferInFillMode.put(_bytes);
+        }
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/http/HttpHeader.java b/lib/jetty/org/eclipse/jetty/http/HttpHeader.java
new file mode 100644 (file)
index 0000000..ab0ddcf
--- /dev/null
@@ -0,0 +1,178 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.http;
+
+import java.nio.ByteBuffer;
+
+import org.eclipse.jetty.util.ArrayTrie;
+import org.eclipse.jetty.util.StringUtil;
+import org.eclipse.jetty.util.Trie;
+
+
+public enum HttpHeader
+{
+    /* ------------------------------------------------------------ */
+    /** General Fields.
+     */
+    CONNECTION("Connection"),
+    CACHE_CONTROL("Cache-Control"),
+    DATE("Date"),
+    PRAGMA("Pragma"),
+    PROXY_CONNECTION ("Proxy-Connection"),
+    TRAILER("Trailer"),
+    TRANSFER_ENCODING("Transfer-Encoding"),
+    UPGRADE("Upgrade"),
+    VIA("Via"),
+    WARNING("Warning"),
+    NEGOTIATE("Negotiate"),
+
+    /* ------------------------------------------------------------ */
+    /** Entity Fields.
+     */
+    ALLOW("Allow"),
+    CONTENT_ENCODING("Content-Encoding"),
+    CONTENT_LANGUAGE("Content-Language"),
+    CONTENT_LENGTH("Content-Length"),
+    CONTENT_LOCATION("Content-Location"),
+    CONTENT_MD5("Content-MD5"),
+    CONTENT_RANGE("Content-Range"),
+    CONTENT_TYPE("Content-Type"),
+    EXPIRES("Expires"),
+    LAST_MODIFIED("Last-Modified"),
+
+    /* ------------------------------------------------------------ */
+    /** Request Fields.
+     */
+    ACCEPT("Accept"),
+    ACCEPT_CHARSET("Accept-Charset"),
+    ACCEPT_ENCODING("Accept-Encoding"),
+    ACCEPT_LANGUAGE("Accept-Language"),
+    AUTHORIZATION("Authorization"),
+    EXPECT("Expect"),
+    FORWARDED("Forwarded"),
+    FROM("From"),
+    HOST("Host"),
+    IF_MATCH("If-Match"),
+    IF_MODIFIED_SINCE("If-Modified-Since"),
+    IF_NONE_MATCH("If-None-Match"),
+    IF_RANGE("If-Range"),
+    IF_UNMODIFIED_SINCE("If-Unmodified-Since"),
+    KEEP_ALIVE("Keep-Alive"),
+    MAX_FORWARDS("Max-Forwards"),
+    PROXY_AUTHORIZATION("Proxy-Authorization"),
+    RANGE("Range"),
+    REQUEST_RANGE("Request-Range"),
+    REFERER("Referer"),
+    TE("TE"),
+    USER_AGENT("User-Agent"),
+    X_FORWARDED_FOR("X-Forwarded-For"),
+    X_FORWARDED_PROTO("X-Forwarded-Proto"),
+    X_FORWARDED_SERVER("X-Forwarded-Server"),
+    X_FORWARDED_HOST("X-Forwarded-Host"),
+
+    /* ------------------------------------------------------------ */
+    /** Response Fields.
+     */
+    ACCEPT_RANGES("Accept-Ranges"),
+    AGE("Age"),
+    ETAG("ETag"),
+    LOCATION("Location"),
+    PROXY_AUTHENTICATE("Proxy-Authenticate"),
+    RETRY_AFTER("Retry-After"),
+    SERVER("Server"),
+    SERVLET_ENGINE("Servlet-Engine"),
+    VARY("Vary"),
+    WWW_AUTHENTICATE("WWW-Authenticate"),
+
+    /* ------------------------------------------------------------ */
+    /** Other Fields.
+     */
+    COOKIE("Cookie"),
+    SET_COOKIE("Set-Cookie"),
+    SET_COOKIE2("Set-Cookie2"),
+    MIME_VERSION("MIME-Version"),
+    IDENTITY("identity"),
+    
+    X_POWERED_BY("X-Powered-By"),
+
+    UNKNOWN("::UNKNOWN::");
+
+
+    /* ------------------------------------------------------------ */
+    public final static Trie<HttpHeader> CACHE= new ArrayTrie<>(512);
+    static
+    {
+        for (HttpHeader header : HttpHeader.values())
+            if (header!=UNKNOWN)
+                CACHE.put(header.toString(),header);
+    }
+    
+    private final String _string;
+    private final byte[] _bytes;
+    private final byte[] _bytesColonSpace;
+    private final ByteBuffer _buffer;
+
+    /* ------------------------------------------------------------ */
+    HttpHeader(String s)
+    {
+        _string=s;
+        _bytes=StringUtil.getBytes(s);
+        _bytesColonSpace=StringUtil.getBytes(s+": ");
+        _buffer=ByteBuffer.wrap(_bytes);
+    }
+
+    /* ------------------------------------------------------------ */
+    public ByteBuffer toBuffer()
+    {
+        return _buffer.asReadOnlyBuffer();
+    }
+
+    /* ------------------------------------------------------------ */
+    public byte[] getBytes()
+    {
+        return _bytes;
+    }
+
+    /* ------------------------------------------------------------ */
+    public byte[] getBytesColonSpace()
+    {
+        return _bytesColonSpace;
+    }
+
+    /* ------------------------------------------------------------ */
+    public boolean is(String s)
+    {
+        return _string.equalsIgnoreCase(s);    
+    }
+
+    /* ------------------------------------------------------------ */
+    public String asString()
+    {
+        return _string;
+    }
+    
+    /* ------------------------------------------------------------ */
+    @Override
+    public String toString()
+    {
+        return _string;
+    }
+    
+}
+
diff --git a/lib/jetty/org/eclipse/jetty/http/HttpHeaderValue.java b/lib/jetty/org/eclipse/jetty/http/HttpHeaderValue.java
new file mode 100644 (file)
index 0000000..8338a32
--- /dev/null
@@ -0,0 +1,104 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.http;
+
+import java.nio.ByteBuffer;
+import java.util.EnumSet;
+
+import org.eclipse.jetty.util.ArrayTrie;
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.Trie;
+
+
+/**
+ * 
+ */
+public enum HttpHeaderValue
+{
+    CLOSE("close"),
+    CHUNKED("chunked"),
+    GZIP("gzip"),
+    IDENTITY("identity"),
+    KEEP_ALIVE("keep-alive"),
+    CONTINUE("100-continue"),
+    PROCESSING("102-processing"),
+    TE("TE"),
+    BYTES("bytes"),
+    NO_CACHE("no-cache"),
+    UPGRADE("Upgrade"),
+    UNKNOWN("::UNKNOWN::");
+
+    /* ------------------------------------------------------------ */
+    public final static Trie<HttpHeaderValue> CACHE= new ArrayTrie<HttpHeaderValue>();
+    static
+    {
+        for (HttpHeaderValue value : HttpHeaderValue.values())
+            if (value!=UNKNOWN)
+                CACHE.put(value.toString(),value);
+    }
+
+    private final String _string;
+    private final ByteBuffer _buffer;
+
+    /* ------------------------------------------------------------ */
+    HttpHeaderValue(String s)
+    {
+        _string=s;
+        _buffer=BufferUtil.toBuffer(s);
+    }
+
+    /* ------------------------------------------------------------ */
+    public ByteBuffer toBuffer()
+    {
+        return _buffer.asReadOnlyBuffer();
+    }
+
+    /* ------------------------------------------------------------ */
+    public boolean is(String s)
+    {
+        return _string.equalsIgnoreCase(s);
+    }
+    
+    /* ------------------------------------------------------------ */
+    public String asString()
+    {
+        return _string;
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public String toString()
+    {
+        return _string;
+    }
+
+    /* ------------------------------------------------------------ */
+    private static EnumSet<HttpHeader> __known =
+            EnumSet.of(HttpHeader.CONNECTION,
+                    HttpHeader.TRANSFER_ENCODING,
+                    HttpHeader.CONTENT_ENCODING);
+
+    /* ------------------------------------------------------------ */
+    public static boolean hasKnownValues(HttpHeader header)
+    {
+        if (header==null)
+            return false;
+        return __known.contains(header);
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/http/HttpMethod.java b/lib/jetty/org/eclipse/jetty/http/HttpMethod.java
new file mode 100644 (file)
index 0000000..8a26268
--- /dev/null
@@ -0,0 +1,174 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.http;
+
+import java.nio.ByteBuffer;
+
+import org.eclipse.jetty.util.ArrayTrie;
+import org.eclipse.jetty.util.StringUtil;
+import org.eclipse.jetty.util.Trie;
+
+
+/* ------------------------------------------------------------------------------- */
+/**
+ */
+public enum HttpMethod
+{
+    GET,
+    POST,
+    HEAD,
+    PUT,
+    OPTIONS,
+    DELETE,
+    TRACE,
+    CONNECT,
+    MOVE,
+    PROXY;
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Optimised lookup to find a method name and trailing space in a byte array.
+     * @param bytes Array containing ISO-8859-1 characters
+     * @param position The first valid index
+     * @param limit The first non valid index
+     * @return A HttpMethod if a match or null if no easy match.
+     */
+    public static HttpMethod lookAheadGet(byte[] bytes, final int position, int limit)
+    {
+        int length=limit-position;
+        if (length<4)
+            return null;
+        switch(bytes[position])
+        {
+            case 'G':
+                if (bytes[position+1]=='E' && bytes[position+2]=='T' && bytes[position+3]==' ')
+                    return GET;
+                break;
+            case 'P':
+                if (bytes[position+1]=='O' && bytes[position+2]=='S' && bytes[position+3]=='T' && length>=5 && bytes[position+4]==' ')
+                    return POST;
+                if (bytes[position+1]=='R' && bytes[position+2]=='O' && bytes[position+3]=='X' && length>=6 && bytes[position+4]=='Y' && bytes[position+5]==' ')
+                    return PROXY;
+                if (bytes[position+1]=='U' && bytes[position+2]=='T' && bytes[position+3]==' ')
+                    return PUT;
+                break;
+            case 'H':
+                if (bytes[position+1]=='E' && bytes[position+2]=='A' && bytes[position+3]=='D' && length>=5 && bytes[position+4]==' ')
+                    return HEAD;
+                break;
+            case 'O':
+                if (bytes[position+1]=='O' && bytes[position+2]=='T' && bytes[position+3]=='I' && length>=8 &&
+                bytes[position+4]=='O' && bytes[position+5]=='N' && bytes[position+6]=='S' && bytes[position+7]==' ' )
+                    return OPTIONS;
+                break;
+            case 'D':
+                if (bytes[position+1]=='E' && bytes[position+2]=='L' && bytes[position+3]=='E' && length>=7 &&
+                bytes[position+4]=='T' && bytes[position+5]=='E' && bytes[position+6]==' ' )
+                    return DELETE;
+                break;
+            case 'T':
+                if (bytes[position+1]=='R' && bytes[position+2]=='A' && bytes[position+3]=='C' && length>=6 &&
+                bytes[position+4]=='E' && bytes[position+5]==' ' )
+                    return TRACE;
+                break;
+            case 'C':
+                if (bytes[position+1]=='O' && bytes[position+2]=='N' && bytes[position+3]=='N' && length>=8 &&
+                bytes[position+4]=='E' && bytes[position+5]=='C' && bytes[position+6]=='T' && bytes[position+7]==' ' )
+                    return CONNECT;
+                break;
+            case 'M':
+                if (bytes[position+1]=='O' && bytes[position+2]=='V' && bytes[position+3]=='E' &&  bytes[position+4]==' ')
+                    return MOVE;
+                break;
+
+            default:
+                break;
+        }
+        return null;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Optimised lookup to find a method name and trailing space in a byte array.
+     * @param buffer buffer containing ISO-8859-1 characters
+     * @return A HttpMethod if a match or null if no easy match.
+     */
+    public static HttpMethod lookAheadGet(ByteBuffer buffer)
+    {
+        if (buffer.hasArray())
+            return lookAheadGet(buffer.array(),buffer.arrayOffset()+buffer.position(),buffer.arrayOffset()+buffer.limit());
+
+        // TODO use cache and check for space
+        // return CACHE.getBest(buffer,0,buffer.remaining());
+        return null;
+    }
+
+    /* ------------------------------------------------------------ */
+    public final static Trie<HttpMethod> CACHE= new ArrayTrie<>();
+    static
+    {
+        for (HttpMethod method : HttpMethod.values())
+            CACHE.put(method.toString(),method);
+    }
+
+    /* ------------------------------------------------------------ */
+    private final ByteBuffer _buffer;
+    private final byte[] _bytes;
+
+    /* ------------------------------------------------------------ */
+    HttpMethod()
+    {
+        _bytes=StringUtil.getBytes(toString());
+        _buffer=ByteBuffer.wrap(_bytes);
+    }
+
+    /* ------------------------------------------------------------ */
+    public byte[] getBytes()
+    {
+        return _bytes;
+    }
+
+    /* ------------------------------------------------------------ */
+    public boolean is(String s)
+    {
+        return toString().equalsIgnoreCase(s);
+    }
+
+    /* ------------------------------------------------------------ */
+    public ByteBuffer asBuffer()
+    {
+        return _buffer.asReadOnlyBuffer();
+    }
+
+    /* ------------------------------------------------------------ */
+    public String asString()
+    {
+        return toString();
+    }
+
+    /**
+     * Converts the given String parameter to an HttpMethod
+     * @param method the String to get the equivalent HttpMethod from
+     * @return the HttpMethod or null if the parameter method is unknown
+     */
+    public static HttpMethod fromString(String method)
+    {
+        return CACHE.get(method);
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/http/HttpParser.java b/lib/jetty/org/eclipse/jetty/http/HttpParser.java
new file mode 100644 (file)
index 0000000..79f1c28
--- /dev/null
@@ -0,0 +1,1688 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.http;
+
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+
+import org.eclipse.jetty.http.HttpTokens.EndOfContent;
+import org.eclipse.jetty.util.ArrayTernaryTrie;
+import org.eclipse.jetty.util.ArrayTrie;
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.StringUtil;
+import org.eclipse.jetty.util.Trie;
+import org.eclipse.jetty.util.TypeUtil;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+
+/* ------------------------------------------------------------ */
+/** A Parser for HTTP 0.9, 1.0 and 1.1
+ * <p>
+ * The is parser parses HTTP client and server messages from buffers
+ * passed in the {@link #parseNext(ByteBuffer)} method.  The parsed
+ * elements of the HTTP message are passed as event calls to the 
+ * {@link HttpHandler} instance the parser is constructed with.
+ * If the passed handler is a {@link RequestHandler} then server side
+ * parsing is performed and if it is a {@link ResponseHandler}, then 
+ * client side parsing is done.
+ * </p>
+ * <p>
+ * The contract of the {@link HttpHandler} API is that if a call returns 
+ * true then the call to {@link #parseNext(ByteBuffer)} will return as 
+ * soon as possible also with a true response.  Typically this indicates
+ * that the parsing has reached a stage where the caller should process 
+ * the events accumulated by the handler.    It is the preferred calling
+ * style that handling such as calling a servlet to process a request, 
+ * should be done after a true return from {@link #parseNext(ByteBuffer)}
+ * rather than from within the scope of a call like 
+ * {@link RequestHandler#messageComplete()}
+ * </p>
+ * <p>
+ * For performance, the parse is heavily dependent on the 
+ * {@link Trie#getBest(ByteBuffer, int, int)} method to look ahead in a
+ * single pass for both the structure ( : and CRLF ) and semantic (which
+ * header and value) of a header.  Specifically the static {@link HttpHeader#CACHE}
+ * is used to lookup common combinations of headers and values 
+ * (eg. "Connection: close"), or just header names (eg. "Connection:" ).
+ * For headers who's value is not known statically (eg. Host, COOKIE) then a
+ * per parser dynamic Trie of {@link HttpFields} from previous parsed messages
+ * is used to help the parsing of subsequent messages.
+ * </p>
+ * <p>
+ * If the system property "org.eclipse.jetty.http.HttpParser.STRICT" is set to true,
+ * then the parser will strictly pass on the exact strings received for methods and header
+ * fields.  Otherwise a fast case insensitive string lookup is used that may alter the
+ * case of the method and/or headers
+ * </p>
+ */
+public class HttpParser
+{
+    public static final Logger LOG = Log.getLogger(HttpParser.class);
+    public final static boolean __STRICT=Boolean.getBoolean("org.eclipse.jetty.http.HttpParser.STRICT"); 
+    public final static int INITIAL_URI_LENGTH=256;
+
+    /**
+     * Cache of common {@link HttpField}s including: <UL>
+     * <LI>Common static combinations such as:<UL>
+     *   <li>Connection: close
+     *   <li>Accept-Encoding: gzip
+     *   <li>Content-Length: 0
+     * </ul>
+     * <li>Combinations of Content-Type header for common mime types by common charsets
+     * <li>Most common headers with null values so that a lookup will at least
+     * determine the header name even if the name:value combination is not cached
+     * </ul>
+     */
+    public final static Trie<HttpField> CACHE = new ArrayTrie<>(2048);
+    
+    // States
+    public enum State
+    {
+        START,
+        METHOD,
+        RESPONSE_VERSION,
+        SPACE1,
+        STATUS,
+        URI,
+        SPACE2,
+        REQUEST_VERSION,
+        REASON,
+        PROXY,
+        HEADER,
+        HEADER_IN_NAME,
+        HEADER_VALUE,
+        HEADER_IN_VALUE,
+        CONTENT,
+        EOF_CONTENT,
+        CHUNKED_CONTENT,
+        CHUNK_SIZE,
+        CHUNK_PARAMS,
+        CHUNK,
+        END,
+        CLOSED
+    }
+
+    private final boolean DEBUG=LOG.isDebugEnabled(); // Cache debug to help branch prediction
+    private final HttpHandler<ByteBuffer> _handler;
+    private final RequestHandler<ByteBuffer> _requestHandler;
+    private final ResponseHandler<ByteBuffer> _responseHandler;
+    private final int _maxHeaderBytes;
+    private final boolean _strict;
+    private HttpField _field;
+    private HttpHeader _header;
+    private String _headerString;
+    private HttpHeaderValue _value;
+    private String _valueString;
+    private int _responseStatus;
+    private int _headerBytes;
+    private boolean _host;
+
+    /* ------------------------------------------------------------------------------- */
+    private volatile State _state=State.START;
+    private volatile boolean _eof;
+    private volatile boolean _closed;
+    private HttpMethod _method;
+    private String _methodString;
+    private HttpVersion _version;
+    private ByteBuffer _uri=ByteBuffer.allocate(INITIAL_URI_LENGTH); // Tune?
+    private EndOfContent _endOfContent;
+    private long _contentLength;
+    private long _contentPosition;
+    private int _chunkLength;
+    private int _chunkPosition;
+    private boolean _headResponse;
+    private boolean _cr;
+    private ByteBuffer _contentChunk;
+    private Trie<HttpField> _connectionFields;
+
+    private int _length;
+    private final StringBuilder _string=new StringBuilder();
+
+    static
+    {
+        CACHE.put(new HttpField(HttpHeader.CONNECTION,HttpHeaderValue.CLOSE));
+        CACHE.put(new HttpField(HttpHeader.CONNECTION,HttpHeaderValue.KEEP_ALIVE));
+        CACHE.put(new HttpField(HttpHeader.CONNECTION,HttpHeaderValue.UPGRADE));
+        CACHE.put(new HttpField(HttpHeader.ACCEPT_ENCODING,"gzip"));
+        CACHE.put(new HttpField(HttpHeader.ACCEPT_ENCODING,"gzip, deflate"));
+        CACHE.put(new HttpField(HttpHeader.ACCEPT_ENCODING,"gzip,deflate,sdch"));
+        CACHE.put(new HttpField(HttpHeader.ACCEPT_LANGUAGE,"en-US,en;q=0.5"));
+        CACHE.put(new HttpField(HttpHeader.ACCEPT_LANGUAGE,"en-GB,en-US;q=0.8,en;q=0.6"));
+        CACHE.put(new HttpField(HttpHeader.ACCEPT_CHARSET,"ISO-8859-1,utf-8;q=0.7,*;q=0.3"));
+        CACHE.put(new HttpField(HttpHeader.ACCEPT,"*/*"));
+        CACHE.put(new HttpField(HttpHeader.ACCEPT,"image/png,image/*;q=0.8,*/*;q=0.5"));
+        CACHE.put(new HttpField(HttpHeader.ACCEPT,"text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"));
+        CACHE.put(new HttpField(HttpHeader.PRAGMA,"no-cache"));
+        CACHE.put(new HttpField(HttpHeader.CACHE_CONTROL,"private, no-cache, no-cache=Set-Cookie, proxy-revalidate"));
+        CACHE.put(new HttpField(HttpHeader.CACHE_CONTROL,"no-cache"));
+        CACHE.put(new HttpField(HttpHeader.CONTENT_LENGTH,"0"));
+        CACHE.put(new HttpField(HttpHeader.CONTENT_ENCODING,"gzip"));
+        CACHE.put(new HttpField(HttpHeader.CONTENT_ENCODING,"deflate"));
+        CACHE.put(new HttpField(HttpHeader.TRANSFER_ENCODING,"chunked"));
+        CACHE.put(new HttpField(HttpHeader.EXPIRES,"Fri, 01 Jan 1990 00:00:00 GMT"));
+        
+        // Add common Content types as fields
+        for (String type : new String[]{"text/plain","text/html","text/xml","text/json","application/json","application/x-www-form-urlencoded"})
+        {
+            HttpField field=new HttpGenerator.CachedHttpField(HttpHeader.CONTENT_TYPE,type);
+            CACHE.put(field);
+            
+            for (String charset : new String[]{"UTF-8","ISO-8859-1"})
+            {
+                CACHE.put(new HttpGenerator.CachedHttpField(HttpHeader.CONTENT_TYPE,type+";charset="+charset));
+                CACHE.put(new HttpGenerator.CachedHttpField(HttpHeader.CONTENT_TYPE,type+"; charset="+charset));
+            }
+        }
+    
+        // Add headers with null values so HttpParser can avoid looking up name again for unknown values
+        for (HttpHeader h:HttpHeader.values())
+            if (!CACHE.put(new HttpField(h,(String)null)))
+                throw new IllegalStateException("CACHE FULL");
+        // Add some more common headers
+        CACHE.put(new HttpField(HttpHeader.REFERER,(String)null));
+        CACHE.put(new HttpField(HttpHeader.IF_MODIFIED_SINCE,(String)null));
+        CACHE.put(new HttpField(HttpHeader.IF_NONE_MATCH,(String)null));
+        CACHE.put(new HttpField(HttpHeader.AUTHORIZATION,(String)null));
+        CACHE.put(new HttpField(HttpHeader.COOKIE,(String)null));
+    }
+
+    /* ------------------------------------------------------------------------------- */
+    public HttpParser(RequestHandler<ByteBuffer> handler)
+    {
+        this(handler,-1,__STRICT);
+    }
+
+    /* ------------------------------------------------------------------------------- */
+    public HttpParser(ResponseHandler<ByteBuffer> handler)
+    {
+        this(handler,-1,__STRICT);
+    }
+
+    /* ------------------------------------------------------------------------------- */
+    public HttpParser(RequestHandler<ByteBuffer> handler,int maxHeaderBytes)
+    {
+        this(handler,maxHeaderBytes,__STRICT);
+    }
+
+    /* ------------------------------------------------------------------------------- */
+    public HttpParser(ResponseHandler<ByteBuffer> handler,int maxHeaderBytes)
+    {
+        this(handler,maxHeaderBytes,__STRICT);
+    }
+    
+    /* ------------------------------------------------------------------------------- */
+    public HttpParser(RequestHandler<ByteBuffer> handler,int maxHeaderBytes,boolean strict)
+    {
+        _handler=handler;
+        _requestHandler=handler;
+        _responseHandler=null;
+        _maxHeaderBytes=maxHeaderBytes;
+        _strict=strict;
+    }
+
+    /* ------------------------------------------------------------------------------- */
+    public HttpParser(ResponseHandler<ByteBuffer> handler,int maxHeaderBytes,boolean strict)
+    {
+        _handler=handler;
+        _requestHandler=null;
+        _responseHandler=handler;
+        _maxHeaderBytes=maxHeaderBytes;
+        _strict=strict;
+    }
+
+    /* ------------------------------------------------------------------------------- */
+    public long getContentLength()
+    {
+        return _contentLength;
+    }
+
+    /* ------------------------------------------------------------ */
+    public long getContentRead()
+    {
+        return _contentPosition;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Set if a HEAD response is expected
+     * @param head
+     */
+    public void setHeadResponse(boolean head)
+    {
+        _headResponse=head;
+    }
+
+    /* ------------------------------------------------------------------------------- */
+    protected void setResponseStatus(int status)
+    {
+        _responseStatus=status;
+    }
+
+    /* ------------------------------------------------------------------------------- */
+    public State getState()
+    {
+        return _state;
+    }
+
+    /* ------------------------------------------------------------------------------- */
+    public boolean inContentState()
+    {
+        return _state.ordinal()>=State.CONTENT.ordinal() && _state.ordinal()<State.END.ordinal();
+    }
+
+    /* ------------------------------------------------------------------------------- */
+    public boolean inHeaderState()
+    {
+        return _state.ordinal() < State.CONTENT.ordinal();
+    }
+
+    /* ------------------------------------------------------------------------------- */
+    public boolean isChunking()
+    {
+        return _endOfContent==EndOfContent.CHUNKED_CONTENT;
+    }
+
+    /* ------------------------------------------------------------ */
+    public boolean isStart()
+    {
+        return isState(State.START);
+    }
+
+    /* ------------------------------------------------------------ */
+    public boolean isClosed()
+    {
+        return isState(State.CLOSED);
+    }
+
+    /* ------------------------------------------------------------ */
+    public boolean isIdle()
+    {
+        return isState(State.START)||isState(State.END)||isState(State.CLOSED);
+    }
+
+    /* ------------------------------------------------------------ */
+    public boolean isComplete()
+    {
+        return isState(State.END)||isState(State.CLOSED);
+    }
+
+    /* ------------------------------------------------------------------------------- */
+    public boolean isState(State state)
+    {
+        return _state == state;
+    }
+
+    /* ------------------------------------------------------------------------------- */
+    private static class BadMessage extends Error
+    {
+        private static final long serialVersionUID = 1L;
+        private final int _code;
+        private final String _message;
+
+        BadMessage()
+        {
+            this(400,null);
+        }
+        
+        BadMessage(int code)
+        {
+            this(code,null);
+        }
+        
+        BadMessage(String message)
+        {
+            this(400,message);
+        }
+        
+        BadMessage(int code,String message)
+        {
+            _code=code;
+            _message=message;
+        }
+        
+    }
+    
+    /* ------------------------------------------------------------------------------- */
+    private byte next(ByteBuffer buffer)
+    {
+        byte ch = buffer.get();
+        
+        if (_cr)
+        {
+            if (ch!=HttpTokens.LINE_FEED)
+                throw new BadMessage("Bad EOL");
+            _cr=false;
+            return ch;
+        }
+
+        if (ch>=0 && ch<HttpTokens.SPACE)
+        {
+            if (ch==HttpTokens.CARRIAGE_RETURN)
+            {
+                if (buffer.hasRemaining())
+                {
+                    if(_maxHeaderBytes>0 && _state.ordinal()<State.END.ordinal())
+                        _headerBytes++;
+                    ch=buffer.get();
+                    if (ch!=HttpTokens.LINE_FEED)
+                        throw new BadMessage("Bad EOL");
+                }
+                else
+                {
+                    _cr=true;
+                    // Can return 0 here to indicate the need for more characters, 
+                    // because a real 0 in the buffer would cause a BadMessage below 
+                    return 0;
+                }
+            }
+            // Only LF or TAB acceptable special characters
+            else if (!(ch==HttpTokens.LINE_FEED || ch==HttpTokens.TAB))
+                throw new BadMessage("Illegal character");
+        }
+        
+        return ch;
+    }
+    
+    /* ------------------------------------------------------------------------------- */
+    /* Quick lookahead for the start state looking for a request method or a HTTP version,
+     * otherwise skip white space until something else to parse.
+     */
+    private boolean quickStart(ByteBuffer buffer)
+    {          
+        if (_requestHandler!=null)
+        {
+            _method = HttpMethod.lookAheadGet(buffer);
+            if (_method!=null)
+            {
+                _methodString = _method.asString();
+                buffer.position(buffer.position()+_methodString.length()+1);
+                
+                setState(State.SPACE1);
+                return false;
+            }
+        }
+        else if (_responseHandler!=null)
+        {
+            _version = HttpVersion.lookAheadGet(buffer);
+            if (_version!=null)
+            {
+                buffer.position(buffer.position()+_version.asString().length()+1);
+                setState(State.SPACE1);
+                return false;
+            }
+        }
+        
+        // Quick start look
+        while (_state==State.START && buffer.hasRemaining())
+        {
+            int ch=next(buffer);
+
+            if (ch > HttpTokens.SPACE)
+            {
+                _string.setLength(0);
+                _string.append((char)ch);
+                setState(_requestHandler!=null?State.METHOD:State.RESPONSE_VERSION);
+                return false;
+            }
+            else if (ch==0)
+                break;
+            else if (ch<0)
+                throw new BadMessage();
+            
+            // count this white space as a header byte to avoid DOS
+            if (_maxHeaderBytes>0 && ++_headerBytes>_maxHeaderBytes)
+            {
+                LOG.warn("padding is too large >"+_maxHeaderBytes);
+                throw new BadMessage(HttpStatus.BAD_REQUEST_400);
+            }
+        }
+        return false;
+    }
+
+    /* ------------------------------------------------------------------------------- */
+    private void setString(String s)
+    {
+        _string.setLength(0);
+        _string.append(s);
+        _length=s.length();
+    }
+    
+    /* ------------------------------------------------------------------------------- */
+    private String takeString()
+    {
+        _string.setLength(_length);
+        String s =_string.toString();
+        _string.setLength(0);
+        _length=-1;
+        return s;
+    }
+
+    /* ------------------------------------------------------------------------------- */
+    /* Parse a request or response line
+     */
+    private boolean parseLine(ByteBuffer buffer)
+    {
+        boolean handle=false;
+
+        // Process headers
+        while (_state.ordinal()<State.HEADER.ordinal() && buffer.hasRemaining() && !handle)
+        {
+            // process each character
+            byte ch=next(buffer);
+            if (ch==0)
+                break;
+
+            if (_maxHeaderBytes>0 && ++_headerBytes>_maxHeaderBytes)
+            {
+                if (_state==State.URI)
+                {
+                    LOG.warn("URI is too large >"+_maxHeaderBytes);
+                    throw new BadMessage(HttpStatus.REQUEST_URI_TOO_LONG_414);
+                }
+                else
+                {
+                    if (_requestHandler!=null)
+                        LOG.warn("request is too large >"+_maxHeaderBytes);
+                    else
+                        LOG.warn("response is too large >"+_maxHeaderBytes);
+                    throw new BadMessage(HttpStatus.REQUEST_ENTITY_TOO_LARGE_413);
+                }
+            }
+
+            switch (_state)
+            {
+                case METHOD:
+                    if (ch == HttpTokens.SPACE)
+                    {
+                        _length=_string.length();
+                        _methodString=takeString();
+                        HttpMethod method=HttpMethod.CACHE.get(_methodString);
+                        if (method!=null && !_strict)
+                            _methodString=method.asString();
+                        setState(State.SPACE1);
+                    }
+                    else if (ch < HttpTokens.SPACE)
+                        throw new BadMessage(ch<0?"Illegal character":"No URI");
+                    else
+                        _string.append((char)ch);
+                    break;
+
+                case RESPONSE_VERSION:
+                    if (ch == HttpTokens.SPACE)
+                    {
+                        _length=_string.length();
+                        String version=takeString();
+                        _version=HttpVersion.CACHE.get(version);
+                        if (_version==null)
+                            throw new BadMessage(HttpStatus.BAD_REQUEST_400,"Unknown Version");
+                        setState(State.SPACE1);
+                    }
+                    else if (ch < HttpTokens.SPACE)
+                        throw new BadMessage(ch<0?"Illegal character":"No Status");
+                    else
+                        _string.append((char)ch);
+                    break;
+
+                case SPACE1:
+                    if (ch > HttpTokens.SPACE || ch<0)
+                    {
+                        if (_responseHandler!=null)
+                        {
+                            setState(State.STATUS);
+                            setResponseStatus(ch-'0');
+                        }
+                        else
+                        {
+                            _uri.clear();
+                            setState(State.URI);
+                            // quick scan for space or EoBuffer
+                            if (buffer.hasArray())
+                            {
+                                byte[] array=buffer.array();
+                                int p=buffer.arrayOffset()+buffer.position();
+                                int l=buffer.arrayOffset()+buffer.limit();
+                                int i=p;
+                                while (i<l && array[i]>HttpTokens.SPACE)
+                                    i++;
+
+                                int len=i-p;
+                                _headerBytes+=len;
+                                
+                                if (_maxHeaderBytes>0 && ++_headerBytes>_maxHeaderBytes)
+                                {
+                                    LOG.warn("URI is too large >"+_maxHeaderBytes);
+                                    throw new BadMessage(HttpStatus.REQUEST_URI_TOO_LONG_414);
+                                }
+                                if (_uri.remaining()<=len)
+                                {
+                                    ByteBuffer uri = ByteBuffer.allocate(_uri.capacity()+2*len);
+                                    _uri.flip();
+                                    uri.put(_uri);
+                                    _uri=uri;
+                                }
+                                _uri.put(array,p-1,len+1);
+                                buffer.position(i-buffer.arrayOffset());
+                            }
+                            else
+                                _uri.put(ch);
+                        }
+                    }
+                    else if (ch < HttpTokens.SPACE)
+                    {
+                        throw new BadMessage(HttpStatus.BAD_REQUEST_400,_requestHandler!=null?"No URI":"No Status");
+                    }
+                    break;
+
+                case STATUS:
+                    if (ch == HttpTokens.SPACE)
+                    {
+                        setState(State.SPACE2);
+                    }
+                    else if (ch>='0' && ch<='9')
+                    {
+                        _responseStatus=_responseStatus*10+(ch-'0');
+                    }
+                    else if (ch < HttpTokens.SPACE && ch>=0)
+                    {
+                        handle=_responseHandler.startResponse(_version, _responseStatus, null)||handle;
+                        setState(State.HEADER);
+                    }
+                    else
+                    {
+                        throw new BadMessage();
+                    }
+                    break;
+
+                case URI:
+                    if (ch == HttpTokens.SPACE)
+                    {
+                        setState(State.SPACE2);
+                    }
+                    else if (ch < HttpTokens.SPACE && ch>=0)
+                    {
+                        // HTTP/0.9
+                        _uri.flip();
+                        handle=_requestHandler.startRequest(_method,_methodString,_uri,null)||handle;
+                        setState(State.END);
+                        BufferUtil.clear(buffer);
+                        handle=_handler.headerComplete()||handle;
+                        handle=_handler.messageComplete()||handle;
+                    }
+                    else
+                    {
+                        if (!_uri.hasRemaining())
+                        {
+                            ByteBuffer uri = ByteBuffer.allocate(_uri.capacity()*2);
+                            _uri.flip();
+                            uri.put(_uri);
+                            _uri=uri;
+                        }
+                        _uri.put(ch);
+                    }
+                    break;
+
+                case SPACE2:
+                    if (ch > HttpTokens.SPACE)
+                    {
+                        _string.setLength(0);
+                        _string.append((char)ch);
+                        if (_responseHandler!=null)
+                        {
+                            _length=1;
+                            setState(State.REASON);
+                        }
+                        else
+                        {
+                            setState(State.REQUEST_VERSION);
+
+                            // try quick look ahead for HTTP Version
+                            HttpVersion version;
+                            if (buffer.position()>0 && buffer.hasArray())
+                                version=HttpVersion.lookAheadGet(buffer.array(),buffer.arrayOffset()+buffer.position()-1,buffer.arrayOffset()+buffer.limit());
+                            else
+                                version=HttpVersion.CACHE.getBest(buffer,0,buffer.remaining());
+                            if (version==null)
+                            {
+                                if (_method==HttpMethod.PROXY)
+                                {
+                                    if (!(_requestHandler instanceof ProxyHandler))
+                                        throw new BadMessage();
+                                    
+                                    _uri.flip();
+                                    String protocol=BufferUtil.toString(_uri);
+                                    // This is the proxy protocol, so we can assume entire first line is in buffer else 400
+                                    buffer.position(buffer.position()-1);
+                                    String sAddr = getProxyField(buffer);
+                                    String dAddr = getProxyField(buffer);
+                                    int sPort = BufferUtil.takeInt(buffer);
+                                    next(buffer);
+                                    int dPort = BufferUtil.takeInt(buffer);
+                                    next(buffer);
+                                    _state=State.START;
+                                    ((ProxyHandler)_requestHandler).proxied(protocol,sAddr,dAddr,sPort,dPort);
+                                    return false;
+                                }
+                            }
+                            else
+                            {
+                                int pos = buffer.position()+version.asString().length()-1;
+                                if (pos<buffer.limit())
+                                {
+                                    byte n=buffer.get(pos);
+                                    if (n==HttpTokens.CARRIAGE_RETURN)
+                                    {
+                                        _cr=true;
+                                        _version=version;
+                                        _string.setLength(0);
+                                        buffer.position(pos+1);
+                                    }
+                                    else if (n==HttpTokens.LINE_FEED)
+                                    {
+                                        _version=version;
+                                        _string.setLength(0);
+                                        buffer.position(pos);
+                                    }
+                                }
+                            }
+                        }
+                    }
+                    else if (ch == HttpTokens.LINE_FEED)
+                    {
+                        if (_responseHandler!=null)
+                        {
+                            handle=_responseHandler.startResponse(_version, _responseStatus, null)||handle;
+                            setState(State.HEADER);
+                        }
+                        else
+                        {
+                            // HTTP/0.9
+                            _uri.flip();
+                            handle=_requestHandler.startRequest(_method,_methodString,_uri, null)||handle;
+                            setState(State.END);
+                            BufferUtil.clear(buffer);
+                            handle=_handler.headerComplete()||handle;
+                            handle=_handler.messageComplete()||handle;
+                        }
+                    }
+                    else if (ch<0)
+                        throw new BadMessage();
+                    break;
+
+                case REQUEST_VERSION:
+                    if (ch == HttpTokens.LINE_FEED)
+                    {
+                        if (_version==null)
+                        {
+                            _length=_string.length();
+                            _version=HttpVersion.CACHE.get(takeString());
+                        }
+                        if (_version==null)
+                            throw new BadMessage(HttpStatus.BAD_REQUEST_400,"Unknown Version");
+                        
+                        // Should we try to cache header fields?
+                        if (_connectionFields==null && _version.getVersion()>=HttpVersion.HTTP_1_1.getVersion())
+                        {
+                            int header_cache = _handler.getHeaderCacheSize();
+                            _connectionFields=new ArrayTernaryTrie<>(header_cache);                            
+                        }
+
+                        setState(State.HEADER);
+                        _uri.flip();
+                        handle=_requestHandler.startRequest(_method,_methodString,_uri, _version)||handle;
+                        continue;
+                    }
+                    else if (ch>=HttpTokens.SPACE)
+                        _string.append((char)ch);
+                    else
+                        throw new BadMessage();
+
+                    break;
+
+                case REASON:
+                    if (ch == HttpTokens.LINE_FEED)
+                    {
+                        String reason=takeString();
+
+                        setState(State.HEADER);
+                        handle=_responseHandler.startResponse(_version, _responseStatus, reason)||handle;
+                        continue;
+                    }
+                    else if (ch>=HttpTokens.SPACE)
+                    {
+                        _string.append((char)ch);
+                        if (ch!=' '&&ch!='\t')
+                            _length=_string.length();
+                    } 
+                    else
+                        throw new BadMessage();
+                    break;
+
+                default:
+                    throw new IllegalStateException(_state.toString());
+
+            }
+        }
+
+        return handle;
+    }
+
+    private boolean handleKnownHeaders(ByteBuffer buffer)
+    {
+        boolean add_to_connection_trie=false;
+        switch (_header)
+        {
+            case CONTENT_LENGTH:
+                if (_endOfContent != EndOfContent.CHUNKED_CONTENT)
+                {
+                    try
+                    {
+                        _contentLength=Long.parseLong(_valueString);
+                    }
+                    catch(NumberFormatException e)
+                    {
+                        LOG.ignore(e);
+                        throw new BadMessage(HttpStatus.BAD_REQUEST_400,"Bad Content-Length");
+                    }
+                    if (_contentLength <= 0)
+                        _endOfContent=EndOfContent.NO_CONTENT;
+                    else
+                        _endOfContent=EndOfContent.CONTENT_LENGTH;
+                }
+                break;
+
+            case TRANSFER_ENCODING:
+                if (_value==HttpHeaderValue.CHUNKED)
+                    _endOfContent=EndOfContent.CHUNKED_CONTENT;
+                else
+                {
+                    if (_valueString.endsWith(HttpHeaderValue.CHUNKED.toString()))
+                        _endOfContent=EndOfContent.CHUNKED_CONTENT;
+                    else if (_valueString.contains(HttpHeaderValue.CHUNKED.toString()))
+                    {
+                        throw new BadMessage(HttpStatus.BAD_REQUEST_400,"Bad chunking");
+                    }
+                }
+                break;
+
+            case HOST:
+                add_to_connection_trie=_connectionFields!=null && _field==null;
+                _host=true;
+                String host=_valueString;
+                int port=0;
+                if (host==null || host.length()==0)
+                {
+                    throw new BadMessage(HttpStatus.BAD_REQUEST_400,"Bad Host header");
+                }
+
+                int len=host.length();
+                loop: for (int i = len; i-- > 0;)
+                {
+                    char c2 = (char)(0xff & host.charAt(i));
+                    switch (c2)
+                    {
+                        case ']':
+                            break loop;
+
+                        case ':':
+                            try
+                            {
+                                len=i;
+                                port = StringUtil.toInt(host.substring(i+1));
+                            }
+                            catch (NumberFormatException e)
+                            {
+                                if (DEBUG)
+                                    LOG.debug(e);
+                                throw new BadMessage(HttpStatus.BAD_REQUEST_400,"Bad Host header");
+                            }
+                            break loop;
+                    }
+                }
+                if (host.charAt(0)=='[')
+                {
+                    if (host.charAt(len-1)!=']') 
+                    {
+                        throw new BadMessage(HttpStatus.BAD_REQUEST_400,"Bad IPv6 Host header");
+                    }
+                    host = host.substring(1,len-1);
+                }
+                else if (len!=host.length())
+                    host = host.substring(0,len);
+                
+                if (_requestHandler!=null)
+                    _requestHandler.parsedHostHeader(host,port);
+                
+              break;
+              
+            case CONNECTION:
+                // Don't cache if not persistent
+                if (_valueString!=null && _valueString.contains("close"))
+                {
+                    _closed=true;
+                    _connectionFields=null;
+                }
+                break;
+
+            case AUTHORIZATION:
+            case ACCEPT:
+            case ACCEPT_CHARSET:
+            case ACCEPT_ENCODING:
+            case ACCEPT_LANGUAGE:
+            case COOKIE:
+            case CACHE_CONTROL:
+            case USER_AGENT:
+                add_to_connection_trie=_connectionFields!=null && _field==null;
+                break;
+                
+            default: break;
+        }
+    
+        if (add_to_connection_trie && !_connectionFields.isFull() && _header!=null && _valueString!=null)
+        {
+            _field=new HttpField(_header,_valueString);
+            _connectionFields.put(_field);
+        }
+        
+        return false;
+    }
+    
+    
+    /* ------------------------------------------------------------------------------- */
+    /*
+     * Parse the message headers and return true if the handler has signaled for a return
+     */
+    protected boolean parseHeaders(ByteBuffer buffer)
+    {
+        boolean handle=false;
+
+        // Process headers
+        while (_state.ordinal()<State.CONTENT.ordinal() && buffer.hasRemaining() && !handle)
+        {
+            // process each character
+            byte ch=next(buffer);
+            if (ch==0)
+                break;
+            
+            if (_maxHeaderBytes>0 && ++_headerBytes>_maxHeaderBytes)
+            {
+                LOG.warn("Header is too large >"+_maxHeaderBytes);
+                throw new BadMessage(HttpStatus.REQUEST_ENTITY_TOO_LARGE_413);
+            }
+
+            switch (_state)
+            {
+                case HEADER:
+                    switch(ch)
+                    {
+                        case HttpTokens.COLON:
+                        case HttpTokens.SPACE:
+                        case HttpTokens.TAB:
+                        {
+                            // header value without name - continuation?
+                            if (_valueString==null)
+                            {
+                                _string.setLength(0);
+                                _length=0;
+                            }
+                            else
+                            {
+                                setString(_valueString);
+                                _string.append(' ');
+                                _length++;
+                                _valueString=null;
+                            }
+                            setState(State.HEADER_VALUE);
+                            break;
+                        }
+
+                        default:
+                        {
+                            // handler last header if any.  Delayed to here just in case there was a continuation line (above)
+                            if (_headerString!=null || _valueString!=null)
+                            {
+                                // Handle known headers
+                                if (_header!=null && handleKnownHeaders(buffer))
+                                {
+                                    _headerString=_valueString=null;
+                                    _header=null;
+                                    _value=null;
+                                    _field=null;
+                                    return true;
+                                }
+                                handle=_handler.parsedHeader(_field!=null?_field:new HttpField(_header,_headerString,_valueString))||handle;
+                            }
+                            _headerString=_valueString=null;
+                            _header=null;
+                            _value=null;
+                            _field=null;
+
+                            // now handle the ch
+                            if (ch == HttpTokens.LINE_FEED)
+                            {
+                                _contentPosition=0;
+
+                                // End of headers!
+
+                                // Was there a required host header?
+                                if (!_host && _version!=HttpVersion.HTTP_1_0 && _requestHandler!=null)
+                                {
+                                    throw new BadMessage(HttpStatus.BAD_REQUEST_400,"No Host");
+                                }
+
+                                // is it a response that cannot have a body?
+                                if (_responseHandler !=null  && // response  
+                                    (_responseStatus == 304  || // not-modified response
+                                    _responseStatus == 204 || // no-content response
+                                    _responseStatus < 200)) // 1xx response
+                                    _endOfContent=EndOfContent.NO_CONTENT; // ignore any other headers set
+                                
+                                // else if we don't know framing
+                                else if (_endOfContent == EndOfContent.UNKNOWN_CONTENT)
+                                {
+                                    if (_responseStatus == 0  // request
+                                            || _responseStatus == 304 // not-modified response
+                                            || _responseStatus == 204 // no-content response
+                                            || _responseStatus < 200) // 1xx response
+                                        _endOfContent=EndOfContent.NO_CONTENT;
+                                    else
+                                        _endOfContent=EndOfContent.EOF_CONTENT;
+                                }
+
+                                // How is the message ended?
+                                switch (_endOfContent)
+                                {
+                                    case EOF_CONTENT:
+                                        setState(State.EOF_CONTENT);
+                                        handle=_handler.headerComplete()||handle;
+                                        break;
+
+                                    case CHUNKED_CONTENT:
+                                        setState(State.CHUNKED_CONTENT);
+                                        handle=_handler.headerComplete()||handle;
+                                        break;
+
+                                    case NO_CONTENT:
+                                        handle=_handler.headerComplete()||handle;
+                                        setState(State.END);
+                                        handle=_handler.messageComplete()||handle;
+                                        break;
+
+                                    default:
+                                        setState(State.CONTENT);
+                                        handle=_handler.headerComplete()||handle;
+                                        break;
+                                }
+                            }
+                            else if (ch<=HttpTokens.SPACE)
+                                throw new BadMessage();
+                            else
+                            {
+                                if (buffer.hasRemaining())
+                                {
+                                    // Try a look ahead for the known header name and value.
+                                    HttpField field=_connectionFields==null?null:_connectionFields.getBest(buffer,-1,buffer.remaining());
+                                    if (field==null)
+                                        field=CACHE.getBest(buffer,-1,buffer.remaining());
+                                        
+                                    if (field!=null)
+                                    {
+                                        final String n;
+                                        final String v;
+
+                                        if (_strict)
+                                        {
+                                            // Have to get the fields exactly from the buffer to match case
+                                            String fn=field.getName();
+                                            String fv=field.getValue();
+                                            n=BufferUtil.toString(buffer,buffer.position()-1,fn.length(),StandardCharsets.US_ASCII);
+                                            if (fv==null)
+                                                v=null;
+                                            else
+                                            {
+                                                v=BufferUtil.toString(buffer,buffer.position()+fn.length()+1,fv.length(),StandardCharsets.ISO_8859_1);
+                                                field=new HttpField(field.getHeader(),n,v);
+                                            }
+                                        }
+                                        else
+                                        {
+                                            n=field.getName();
+                                            v=field.getValue(); 
+                                        }
+                                        
+                                        _header=field.getHeader();
+                                        _headerString=n;
+         
+                                        if (v==null)
+                                        {
+                                            // Header only
+                                            setState(State.HEADER_VALUE);
+                                            _string.setLength(0);
+                                            _length=0;
+                                            buffer.position(buffer.position()+n.length()+1);
+                                            break;
+                                        }
+                                        else
+                                        {
+                                            // Header and value
+                                            int pos=buffer.position()+n.length()+v.length()+1;
+                                            byte b=buffer.get(pos);
+
+                                            if (b==HttpTokens.CARRIAGE_RETURN || b==HttpTokens.LINE_FEED)
+                                            {                     
+                                                _field=field;
+                                                _valueString=v;
+                                                setState(State.HEADER_IN_VALUE);
+
+                                                if (b==HttpTokens.CARRIAGE_RETURN)
+                                                {
+                                                    _cr=true;
+                                                    buffer.position(pos+1);
+                                                }
+                                                else
+                                                    buffer.position(pos);
+                                                break;
+                                            }
+                                            else
+                                            {
+                                                setState(State.HEADER_IN_VALUE);
+                                                setString(v);
+                                                buffer.position(pos);
+                                                break;
+                                            }
+                                        }
+                                    }
+                                }
+
+                                // New header
+                                setState(State.HEADER_IN_NAME);
+                                _string.setLength(0);
+                                _string.append((char)ch);
+                                _length=1;
+                            }
+                        }
+                    }
+                    break;
+
+                case HEADER_IN_NAME:
+                    if (ch==HttpTokens.COLON || ch==HttpTokens.LINE_FEED)
+                    {
+                        if (_headerString==null)
+                        {
+                            _headerString=takeString();
+                            _header=HttpHeader.CACHE.get(_headerString);
+                        }
+                        _length=-1;
+
+                        setState(ch==HttpTokens.LINE_FEED?State.HEADER:State.HEADER_VALUE);
+                        break;
+                    }
+                    
+                    if (ch>=HttpTokens.SPACE || ch==HttpTokens.TAB)
+                    {
+                        if (_header!=null)
+                        {
+                            setString(_header.asString());
+                            _header=null;
+                            _headerString=null;
+                        }
+
+                        _string.append((char)ch);
+                        if (ch>HttpTokens.SPACE)
+                            _length=_string.length();
+                        break;
+                    }
+                     
+                    throw new BadMessage("Illegal character");
+
+                case HEADER_VALUE:
+                    if (ch>HttpTokens.SPACE || ch<0)
+                    {
+                        _string.append((char)(0xff&ch));
+                        _length=_string.length();
+                        setState(State.HEADER_IN_VALUE);
+                        break;
+                    }
+                    
+                    if (ch==HttpTokens.SPACE || ch==HttpTokens.TAB)
+                        break;
+                    
+                    if (ch==HttpTokens.LINE_FEED)
+                    {
+                        if (_length > 0)
+                        {
+                            _value=null;
+                            _valueString=(_valueString==null)?takeString():(_valueString+" "+takeString());
+                        }
+                        setState(State.HEADER);
+                        break; 
+                    }
+
+                    throw new BadMessage("Illegal character");
+
+                case HEADER_IN_VALUE:
+                    if (ch>=HttpTokens.SPACE || ch<0 || ch==HttpTokens.TAB)
+                    {
+                        if (_valueString!=null)
+                        {
+                            setString(_valueString);
+                            _valueString=null;
+                            _field=null;
+                        }
+                        _string.append((char)(0xff&ch));
+                        if (ch>HttpTokens.SPACE || ch<0)
+                            _length=_string.length();
+                        break;
+                    }
+                    
+                    if (ch==HttpTokens.LINE_FEED)
+                    {
+                        if (_length > 0)
+                        {
+                            _value=null;
+                            _valueString=takeString();
+                            _length=-1;
+                        }
+                        setState(State.HEADER);
+                        break;
+                    }
+                    throw new BadMessage("Illegal character");
+                    
+                default:
+                    throw new IllegalStateException(_state.toString());
+
+            }
+        }
+
+        return handle;
+    }
+
+    /* ------------------------------------------------------------------------------- */
+    /**
+     * Parse until next Event.
+     * @return True if an {@link RequestHandler} method was called and it returned true;
+     */
+    public boolean parseNext(ByteBuffer buffer)
+    {
+        if (DEBUG)
+            LOG.debug("parseNext s={} {}",_state,BufferUtil.toDetailString(buffer));
+        try
+        {
+            // Start a request/response
+            if (_state==State.START)
+            {
+                _version=null;
+                _method=null;
+                _methodString=null;
+                _endOfContent=EndOfContent.UNKNOWN_CONTENT;
+                _header=null;
+                if (quickStart(buffer))
+                    return true;
+            }
+            
+            // Request/response line
+            if (_state.ordinal()>= State.START.ordinal() && _state.ordinal()<State.HEADER.ordinal())
+            {
+                if (parseLine(buffer))
+                    return true;
+            }
+
+            // parse headers
+            if (_state.ordinal()>= State.HEADER.ordinal() && _state.ordinal()<State.CONTENT.ordinal())
+            {
+                if (parseHeaders(buffer))
+                    return true;
+            }
+            
+            // parse content
+            if (_state.ordinal()>= State.CONTENT.ordinal() && _state.ordinal()<State.END.ordinal())
+            {
+                // Handle HEAD response
+                if (_responseStatus>0 && _headResponse)
+                {
+                    setState(State.END);
+                    if (_handler.messageComplete())
+                        return true;
+                }
+                else
+                {
+                    if (parseContent(buffer))
+                        return true;
+                }
+            }
+            
+            // handle end states
+            if (_state==State.END)
+            {
+                // eat white space
+                while (buffer.remaining()>0 && buffer.get(buffer.position())<=HttpTokens.SPACE)
+                    buffer.get();
+            }
+            else if (_state==State.CLOSED)
+            {
+                if (BufferUtil.hasContent(buffer))
+                {
+                    // Just ignore data when closed
+                    _headerBytes+=buffer.remaining();
+                    BufferUtil.clear(buffer);
+                    if (_headerBytes>_maxHeaderBytes)
+                    {
+                        // Don't want to waste time reading data of a closed request
+                        throw new IllegalStateException("too much data after closed");
+                    }
+                }
+            }
+            
+            // Handle EOF
+            if (_eof && !buffer.hasRemaining())
+            {
+                switch(_state)
+                {
+                    case CLOSED:
+                        break;
+                        
+                    case START:
+                        setState(State.CLOSED);
+                        _handler.earlyEOF();
+                        break;
+                        
+                    case END:
+                        setState(State.CLOSED);
+                        break;
+                        
+                    case EOF_CONTENT:
+                        setState(State.CLOSED);
+                        return _handler.messageComplete();
+
+                    case  CONTENT:
+                    case  CHUNKED_CONTENT:
+                    case  CHUNK_SIZE:
+                    case  CHUNK_PARAMS:
+                    case  CHUNK:
+                        setState(State.CLOSED);
+                        _handler.earlyEOF();
+                        break;
+
+                    default:
+                        if (DEBUG)
+                            LOG.debug("{} EOF in {}",this,_state);
+                        setState(State.CLOSED);
+                        _handler.badMessage(400,null);
+                        break;
+                }
+            }
+            
+            return false;
+        }
+        catch(BadMessage e)
+        {
+            BufferUtil.clear(buffer);
+
+            LOG.warn("badMessage: "+e._code+(e._message!=null?" "+e._message:"")+" for "+_handler);
+            if (DEBUG)
+                LOG.debug(e);
+            setState(State.CLOSED);
+            _handler.badMessage(e._code, e._message);
+            return false;
+        }
+        catch(Exception e)
+        {
+            BufferUtil.clear(buffer);
+
+            LOG.warn("badMessage: "+e.toString()+" for "+_handler);
+            if (DEBUG)
+                LOG.debug(e);
+            
+            if (_state.ordinal()<=State.END.ordinal())
+            {
+                setState(State.CLOSED);
+                _handler.badMessage(400,null);
+            }
+            else
+            {
+                _handler.earlyEOF();
+                setState(State.CLOSED);
+            }
+
+            return false;
+        }
+    }
+
+    protected boolean parseContent(ByteBuffer buffer)
+    {
+        int remaining=buffer.remaining();
+        if (remaining==0 && _state==State.CONTENT)
+        {
+            long content=_contentLength - _contentPosition;
+            if (content == 0)
+            {
+                setState(State.END);
+                if (_handler.messageComplete())
+                    return true;
+            }
+        }
+        
+        // Handle _content
+        byte ch;
+        while (_state.ordinal() < State.END.ordinal() && remaining>0)
+        {
+            switch (_state)
+            {
+                case EOF_CONTENT:
+                    _contentChunk=buffer.asReadOnlyBuffer();
+                    _contentPosition += remaining;
+                    buffer.position(buffer.position()+remaining);
+                    if (_handler.content(_contentChunk))
+                        return true;
+                    break;
+
+                case CONTENT:
+                {
+                    long content=_contentLength - _contentPosition;
+                    if (content == 0)
+                    {
+                        setState(State.END);
+                        if (_handler.messageComplete())
+                            return true;
+                    }
+                    else
+                    {
+                        _contentChunk=buffer.asReadOnlyBuffer();
+
+                        // limit content by expected size
+                        if (remaining > content)
+                        {
+                            // We can cast remaining to an int as we know that it is smaller than
+                            // or equal to length which is already an int.
+                            _contentChunk.limit(_contentChunk.position()+(int)content);
+                        }
+
+                        _contentPosition += _contentChunk.remaining();
+                        buffer.position(buffer.position()+_contentChunk.remaining());
+
+                        if (_handler.content(_contentChunk))
+                            return true;
+
+                        if(_contentPosition == _contentLength)
+                        {
+                            setState(State.END);
+                            if (_handler.messageComplete())
+                                return true;
+                        }
+                    }
+                    break;
+                }
+
+                case CHUNKED_CONTENT:
+                {
+                    ch=next(buffer);
+                    if (ch>HttpTokens.SPACE)
+                    {
+                        _chunkLength=TypeUtil.convertHexDigit(ch);
+                        _chunkPosition=0;
+                        setState(State.CHUNK_SIZE);
+                    }
+
+                    break;
+                }
+
+                case CHUNK_SIZE:
+                {
+                    ch=next(buffer);
+                    if (ch==0)
+                        break;
+                    if (ch == HttpTokens.LINE_FEED)
+                    {
+                        if (_chunkLength == 0)
+                        {
+                            setState(State.END);
+                            if (_handler.messageComplete())
+                                return true;
+                        }
+                        else
+                            setState(State.CHUNK);
+                    }
+                    else if (ch <= HttpTokens.SPACE || ch == HttpTokens.SEMI_COLON)
+                        setState(State.CHUNK_PARAMS);
+                    else
+                        _chunkLength=_chunkLength * 16 + TypeUtil.convertHexDigit(ch);
+                    break;
+                }
+
+                case CHUNK_PARAMS:
+                {
+                    ch=next(buffer);
+                    if (ch == HttpTokens.LINE_FEED)
+                    {
+                        if (_chunkLength == 0)
+                        {
+                            setState(State.END);
+                            if (_handler.messageComplete())
+                                return true;
+                        }
+                        else
+                            setState(State.CHUNK);
+                    }
+                    break;
+                }
+
+                case CHUNK:
+                {
+                    int chunk=_chunkLength - _chunkPosition;
+                    if (chunk == 0)
+                    {
+                        setState(State.CHUNKED_CONTENT);
+                    }
+                    else
+                    {
+                        _contentChunk=buffer.asReadOnlyBuffer();
+
+                        if (remaining > chunk)
+                            _contentChunk.limit(_contentChunk.position()+chunk);
+                        chunk=_contentChunk.remaining();
+
+                        _contentPosition += chunk;
+                        _chunkPosition += chunk;
+                        buffer.position(buffer.position()+chunk);
+                        if (_handler.content(_contentChunk))
+                            return true;
+                    }
+                    break;
+                }
+                
+                case CLOSED:
+                {
+                    BufferUtil.clear(buffer);
+                    return false;
+                }
+
+                default: 
+                    break;
+                    
+            }
+            
+            remaining=buffer.remaining();
+        }
+        return false;
+    }
+
+    /* ------------------------------------------------------------------------------- */
+    public boolean isAtEOF()
+    {
+        return _eof;
+    }
+    
+    /* ------------------------------------------------------------------------------- */
+    public void atEOF()
+
+    {        
+        if (DEBUG)
+            LOG.debug("atEOF {}", this);
+        _eof=true;
+    }
+
+    /* ------------------------------------------------------------------------------- */
+    public void close()
+    {
+        if (DEBUG)
+            LOG.debug("close {}", this);
+        setState(State.CLOSED);
+    }
+    
+    /* ------------------------------------------------------------------------------- */
+    public void reset()
+    {
+        if (DEBUG)
+            LOG.debug("reset {}", this);
+        // reset state
+        if (_state==State.CLOSED)
+            return;
+        if (_closed)
+        {
+            setState(State.CLOSED);
+            return;
+        }
+        
+        setState(State.START);
+        _endOfContent=EndOfContent.UNKNOWN_CONTENT;
+        _contentLength=-1;
+        _contentPosition=0;
+        _responseStatus=0;
+        _contentChunk=null;
+        _headerBytes=0;
+        _host=false;
+    }
+
+    /* ------------------------------------------------------------------------------- */
+    protected void setState(State state)
+    {
+        if (DEBUG)
+            LOG.debug("{} --> {}",_state,state);
+        _state=state;
+    }
+
+    /* ------------------------------------------------------------------------------- */
+    @Override
+    public String toString()
+    {
+        return String.format("%s{s=%s,%d of %d}",
+                getClass().getSimpleName(),
+                _state,
+                _contentPosition,
+                _contentLength);
+    }
+
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    /* Event Handler interface
+     * These methods return true if the caller should process the events
+     * so far received (eg return from parseNext and call HttpChannel.handle).
+     * If multiple callbacks are called in sequence (eg 
+     * headerComplete then messageComplete) from the same point in the parsing
+     * then it is sufficient for the caller to process the events only once.
+     */
+    public interface HttpHandler<T>
+    {
+        public boolean content(T item);
+
+        public boolean headerComplete();
+
+        public boolean messageComplete();
+
+        /**
+         * This is the method called by parser when a HTTP Header name and value is found
+         * @param field The field parsed
+         * @return True if the parser should return to its caller
+         */
+        public boolean parsedHeader(HttpField field);
+
+        /* ------------------------------------------------------------ */
+        /** Called to signal that an EOF was received unexpectedly
+         * during the parsing of a HTTP message
+         */
+        public void earlyEOF();
+
+        /* ------------------------------------------------------------ */
+        /** Called to signal that a bad HTTP message has been received.
+         * @param status The bad status to send
+         * @param reason The textual reason for badness
+         */
+        public void badMessage(int status, String reason);
+        
+        /* ------------------------------------------------------------ */
+        /** @return the size in bytes of the per parser header cache
+         */
+        public int getHeaderCacheSize();
+    }
+
+    public interface ProxyHandler 
+    {
+        void proxied(String protocol, String sAddr, String dAddr, int sPort, int dPort);
+    }
+    
+    public interface RequestHandler<T> extends HttpHandler<T>
+    {
+        /**
+         * This is the method called by parser when the HTTP request line is parsed
+         * @param method The method as enum if of a known type
+         * @param methodString The method as a string
+         * @param uri The raw bytes of the URI.  These are copied into a ByteBuffer that will not be changed until this parser is reset and reused.
+         * @param version
+         * @return true if handling parsing should return.
+         */
+        public abstract boolean startRequest(HttpMethod method, String methodString, ByteBuffer uri, HttpVersion version);
+
+        /**
+         * This is the method called by the parser after it has parsed the host header (and checked it's format). This is
+         * called after the {@link HttpHandler#parsedHeader(HttpField)} methods and before
+         * HttpHandler#headerComplete();
+         */
+        public abstract boolean parsedHostHeader(String host,int port);
+    }
+
+    public interface ResponseHandler<T> extends HttpHandler<T>
+    {
+        /**
+         * This is the method called by parser when the HTTP request line is parsed
+         */
+        public abstract boolean startResponse(HttpVersion version, int status, String reason);
+    }
+
+    public Trie<HttpField> getFieldCache()
+    {
+        return _connectionFields;
+    }
+
+    private String getProxyField(ByteBuffer buffer)
+    {
+        _string.setLength(0);
+        _length=0;
+        
+        while (buffer.hasRemaining())
+        {
+            // process each character
+            byte ch=next(buffer);
+            if (ch<=' ')
+                return _string.toString();
+            _string.append((char)ch);    
+        }
+        throw new BadMessage();
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/http/HttpScheme.java b/lib/jetty/org/eclipse/jetty/http/HttpScheme.java
new file mode 100644 (file)
index 0000000..13f2a8d
--- /dev/null
@@ -0,0 +1,79 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.http;
+
+import java.nio.ByteBuffer;
+
+import org.eclipse.jetty.util.ArrayTrie;
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.Trie;
+
+/* ------------------------------------------------------------------------------- */
+/**
+ */
+public enum HttpScheme
+{
+    HTTP("http"),
+    HTTPS("https"),
+    WS("ws"),
+    WSS("wss");
+
+    /* ------------------------------------------------------------ */
+    public final static Trie<HttpScheme> CACHE= new ArrayTrie<HttpScheme>();
+    static
+    {
+        for (HttpScheme version : HttpScheme.values())
+            CACHE.put(version.asString(),version);
+    }
+
+    private final String _string;
+    private final ByteBuffer _buffer;
+
+    /* ------------------------------------------------------------ */
+    HttpScheme(String s)
+    {
+        _string=s;
+        _buffer=BufferUtil.toBuffer(s);
+    }
+
+    /* ------------------------------------------------------------ */
+    public ByteBuffer asByteBuffer()
+    {
+        return _buffer.asReadOnlyBuffer();
+    }
+
+    /* ------------------------------------------------------------ */
+    public boolean is(String s)
+    {
+        return _string.equalsIgnoreCase(s);
+    }
+
+    public String asString()
+    {
+        return _string;
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public String toString()
+    {
+        return _string;
+    }
+
+}
diff --git a/lib/jetty/org/eclipse/jetty/http/HttpStatus.java b/lib/jetty/org/eclipse/jetty/http/HttpStatus.java
new file mode 100644 (file)
index 0000000..e6ea1f7
--- /dev/null
@@ -0,0 +1,1037 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.http;
+
+/**
+ * <p>
+ * HttpStatusCode enum class, for status codes based on various HTTP RFCs. (see
+ * table below)
+ * </p>
+ *
+ * <table border="1" cellpadding="5">
+ * <tr>
+ * <th>Enum</th>
+ * <th>Code</th>
+ * <th>Message</th>
+ * <th>
+ * <a href="http://tools.ietf.org/html/rfc1945">RFC 1945 - HTTP/1.0</a></th>
+ * <th>
+ * <a href="http://tools.ietf.org/html/rfc2616">RFC 2616 - HTTP/1.1</a></th>
+ * <th>
+ * <a href="http://tools.ietf.org/html/rfc2518">RFC 2518 - WEBDAV</a></th>
+ * </tr>
+ *
+ * <tr>
+ * <td><strong><code>Informational - 1xx</code></strong></td>
+ * <td colspan="5">{@link #isInformational(int)}</td>
+ * </tr>
+ *
+ * <tr>
+ * <td>{@link #CONTINUE_100}</td>
+ * <td>100</td>
+ * <td>Continue</td>
+ * <td>&nbsp;</td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc2616#section-10.1.1">Sec. 10.1.1</a></td>
+ * <td>&nbsp;</td>
+ * </tr>
+ * <tr>
+ * <td>{@link #SWITCHING_PROTOCOLS_101}</td>
+ * <td>101</td>
+ * <td>Switching Protocols</td>
+ * <td>&nbsp;</td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc2616#section-10.1.2">Sec. 10.1.2</a></td>
+ * <td>&nbsp;</td>
+ * </tr>
+ * <tr>
+ * <td>{@link #PROCESSING_102}</td>
+ * <td>102</td>
+ * <td>Processing</td>
+ * <td>&nbsp;</td>
+ * <td>&nbsp;</td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc2518#section-10.1">Sec. 10.1</a></td>
+ * </tr>
+ *
+ * <tr>
+ * <td><strong><code>Success - 2xx</code></strong></td>
+ * <td colspan="5">{@link #isSuccess(int)}</td>
+ * </tr>
+ *
+ * <tr>
+ * <td>{@link #OK_200}</td>
+ * <td>200</td>
+ * <td>OK</td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc1945#section-9.2">Sec. 9.2</a></td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc2616#section-10.2.1">Sec. 10.2.1</a></td>
+ * <td>&nbsp;</td>
+ * </tr>
+ * <tr>
+ * <td>{@link #CREATED_201}</td>
+ * <td>201</td>
+ * <td>Created</td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc1945#section-9.2">Sec. 9.2</a></td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc2616#section-10.2.2">Sec. 10.2.2</a></td>
+ * <td>&nbsp;</td>
+ * </tr>
+ * <tr>
+ * <td>{@link #ACCEPTED_202}</td>
+ * <td>202</td>
+ * <td>Accepted</td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc1945#section-9.2">Sec. 9.2</a></td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc2616#section-10.2.3">Sec. 10.2.3</a></td>
+ * <td>&nbsp;</td>
+ * </tr>
+ * <tr>
+ * <td>{@link #NON_AUTHORITATIVE_INFORMATION_203}</td>
+ * <td>203</td>
+ * <td>Non Authoritative Information</td>
+ * <td>&nbsp;</td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc2616#section-10.2.4">Sec. 10.2.4</a></td>
+ * <td>&nbsp;</td>
+ * </tr>
+ * <tr>
+ * <td>{@link #NO_CONTENT_204}</td>
+ * <td>204</td>
+ * <td>No Content</td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc1945#section-9.2">Sec. 9.2</a></td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc2616#section-10.2.5">Sec. 10.2.5</a></td>
+ * <td>&nbsp;</td>
+ * </tr>
+ * <tr>
+ * <td>{@link #RESET_CONTENT_205}</td>
+ * <td>205</td>
+ * <td>Reset Content</td>
+ * <td>&nbsp;</td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc2616#section-10.2.6">Sec. 10.2.6</a></td>
+ * <td>&nbsp;</td>
+ * </tr>
+ * <tr>
+ * <td>{@link #PARTIAL_CONTENT_206}</td>
+ * <td>206</td>
+ * <td>Partial Content</td>
+ * <td>&nbsp;</td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc2616#section-10.2.7">Sec. 10.2.7</a></td>
+ * <td>&nbsp;</td>
+ * </tr>
+ * <tr>
+ * <td>{@link #MULTI_STATUS_207}</td>
+ * <td>207</td>
+ * <td>Multi-Status</td>
+ * <td>&nbsp;</td>
+ * <td>&nbsp;</td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc2518#section-10.2">Sec. 10.2</a></td>
+ * </tr>
+ * <tr>
+ * <td>&nbsp;</td>
+ * <td><strike>207</strike></td>
+ * <td><strike>Partial Update OK</strike></td>
+ * <td>&nbsp;</td>
+ * <td>
+ * <a href=
+ * "http://www.w3.org/Protocols/HTTP/1.1/draft-ietf-http-v11-spec-rev-01.txt"
+ * >draft/01</a></td>
+ * <td>&nbsp;</td>
+ * </tr>
+ *
+ * <tr>
+ * <td><strong><code>Redirection - 3xx</code></strong></td>
+ * <td colspan="5">{@link #isRedirection(int)}</td>
+ * </tr>
+ *
+ * <tr>
+ * <td>{@link #MULTIPLE_CHOICES_300}</td>
+ * <td>300</td>
+ * <td>Multiple Choices</td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc1945#section-9.3">Sec. 9.3</a></td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc2616#section-10.3.1">Sec. 10.3.1</a></td>
+ * <td>&nbsp;</td>
+ * </tr>
+ * <tr>
+ * <td>{@link #MOVED_PERMANENTLY_301}</td>
+ * <td>301</td>
+ * <td>Moved Permanently</td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc1945#section-9.3">Sec. 9.3</a></td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc2616#section-10.3.2">Sec. 10.3.2</a></td>
+ * <td>&nbsp;</td>
+ * </tr>
+ * <tr>
+ * <td>{@link #MOVED_TEMPORARILY_302}</td>
+ * <td>302</td>
+ * <td>Moved Temporarily</td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc1945#section-9.3">Sec. 9.3</a></td>
+ * <td>(now "<code>302 Found</code>")</td>
+ * <td>&nbsp;</td>
+ * </tr>
+ * <tr>
+ * <td>{@link #FOUND_302}</td>
+ * <td>302</td>
+ * <td>Found</td>
+ * <td>(was "<code>302 Moved Temporarily</code>")</td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc2616#section-10.3.3">Sec. 10.3.3</a></td>
+ * <td>&nbsp;</td>
+ * </tr>
+ * <tr>
+ * <td>{@link #SEE_OTHER_303}</td>
+ * <td>303</td>
+ * <td>See Other</td>
+ * <td>&nbsp;</td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc2616#section-10.3.4">Sec. 10.3.4</a></td>
+ * <td>&nbsp;</td>
+ * </tr>
+ * <tr>
+ * <td>{@link #NOT_MODIFIED_304}</td>
+ * <td>304</td>
+ * <td>Not Modified</td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc1945#section-9.3">Sec. 9.3</a></td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc2616#section-10.3.5">Sec. 10.3.5</a></td>
+ * <td>&nbsp;</td>
+ * </tr>
+ * <tr>
+ * <td>{@link #USE_PROXY_305}</td>
+ * <td>305</td>
+ * <td>Use Proxy</td>
+ * <td>&nbsp;</td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc2616#section-10.3.6">Sec. 10.3.6</a></td>
+ * <td>&nbsp;</td>
+ * </tr>
+ * <tr>
+ * <td>&nbsp;</td>
+ * <td>306</td>
+ * <td><em>(Unused)</em></td>
+ * <td>&nbsp;</td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc2616#section-10.3.7">Sec. 10.3.7</a></td>
+ * <td>&nbsp;</td>
+ * </tr>
+ * <tr>
+ * <td>{@link #TEMPORARY_REDIRECT_307}</td>
+ * <td>307</td>
+ * <td>Temporary Redirect</td>
+ * <td>&nbsp;</td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc2616#section-10.3.8">Sec. 10.3.8</a></td>
+ * <td>&nbsp;</td>
+ * </tr>
+ *
+ * <tr>
+ * <td><strong><code>Client Error - 4xx</code></strong></td>
+ * <td colspan="5">{@link #isClientError(int)}</td>
+ * </tr>
+ *
+ * <tr>
+ * <td>{@link #BAD_REQUEST_400}</td>
+ * <td>400</td>
+ * <td>Bad Request</td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc1945#section-9.4">Sec. 9.4</a></td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc2616#section-10.4.1">Sec. 10.4.1</a></td>
+ * <td>&nbsp;</td>
+ * </tr>
+ * <tr>
+ * <td>{@link #UNAUTHORIZED_401}</td>
+ * <td>401</td>
+ * <td>Unauthorized</td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc1945#section-9.4">Sec. 9.4</a></td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc2616#section-10.4.2">Sec. 10.4.2</a></td>
+ * <td>&nbsp;</td>
+ * </tr>
+ * <tr>
+ * <td>{@link #PAYMENT_REQUIRED_402}</td>
+ * <td>402</td>
+ * <td>Payment Required</td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc1945#section-9.4">Sec. 9.4</a></td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc2616#section-10.4.3">Sec. 10.4.3</a></td>
+ * <td>&nbsp;</td>
+ * </tr>
+ * <tr>
+ * <td>{@link #FORBIDDEN_403}</td>
+ * <td>403</td>
+ * <td>Forbidden</td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc1945#section-9.4">Sec. 9.4</a></td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc2616#section-10.4.4">Sec. 10.4.4</a></td>
+ * <td>&nbsp;</td>
+ * </tr>
+ * <tr>
+ * <td>{@link #NOT_FOUND_404}</td>
+ * <td>404</td>
+ * <td>Not Found</td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc1945#section-9.4">Sec. 9.4</a></td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc2616#section-10.4.5">Sec. 10.4.5</a></td>
+ * <td>&nbsp;</td>
+ * </tr>
+ * <tr>
+ * <td>{@link #METHOD_NOT_ALLOWED_405}</td>
+ * <td>405</td>
+ * <td>Method Not Allowed</td>
+ * <td>&nbsp;</td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc2616#section-10.4.6">Sec. 10.4.6</a></td>
+ * <td>&nbsp;</td>
+ * </tr>
+ * <tr>
+ * <td>{@link #NOT_ACCEPTABLE_406}</td>
+ * <td>406</td>
+ * <td>Not Acceptable</td>
+ * <td>&nbsp;</td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc2616#section-10.4.7">Sec. 10.4.7</a></td>
+ * <td>&nbsp;</td>
+ * </tr>
+ * <tr>
+ * <td>{@link #PROXY_AUTHENTICATION_REQUIRED_407}</td>
+ * <td>407</td>
+ * <td>Proxy Authentication Required</td>
+ * <td>&nbsp;</td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc2616#section-10.4.8">Sec. 10.4.8</a></td>
+ * <td>&nbsp;</td>
+ * </tr>
+ * <tr>
+ * <td>{@link #REQUEST_TIMEOUT_408}</td>
+ * <td>408</td>
+ * <td>Request Timeout</td>
+ * <td>&nbsp;</td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc2616#section-10.4.9">Sec. 10.4.9</a></td>
+ * <td>&nbsp;</td>
+ * </tr>
+ * <tr>
+ * <td>{@link #CONFLICT_409}</td>
+ * <td>409</td>
+ * <td>Conflict</td>
+ * <td>&nbsp;</td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc2616#section-10.4.10">Sec. 10.4.10</a>
+ * </td>
+ * <td>&nbsp;</td>
+ * </tr>
+ * <tr>
+ * <td>{@link #GONE_410}</td>
+ * <td>410</td>
+ * <td>Gone</td>
+ * <td>&nbsp;</td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc2616#section-10.4.11">Sec. 10.4.11</a>
+ * </td>
+ * <td>&nbsp;</td>
+ * </tr>
+ * <tr>
+ * <td>{@link #LENGTH_REQUIRED_411}</td>
+ * <td>411</td>
+ * <td>Length Required</td>
+ * <td>&nbsp;</td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc2616#section-10.4.12">Sec. 10.4.12</a>
+ * </td>
+ * <td>&nbsp;</td>
+ * </tr>
+ * <tr>
+ * <td>{@link #PRECONDITION_FAILED_412}</td>
+ * <td>412</td>
+ * <td>Precondition Failed</td>
+ * <td>&nbsp;</td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc2616#section-10.4.13">Sec. 10.4.13</a>
+ * </td>
+ * <td>&nbsp;</td>
+ * </tr>
+ * <tr>
+ * <td>{@link #REQUEST_ENTITY_TOO_LARGE_413}</td>
+ * <td>413</td>
+ * <td>Request Entity Too Large</td>
+ * <td>&nbsp;</td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc2616#section-10.4.14">Sec. 10.4.14</a>
+ * </td>
+ * <td>&nbsp;</td>
+ * </tr>
+ * <tr>
+ * <td>{@link #REQUEST_URI_TOO_LONG_414}</td>
+ * <td>414</td>
+ * <td>Request-URI Too Long</td>
+ * <td>&nbsp;</td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc2616#section-10.4.15">Sec. 10.4.15</a>
+ * </td>
+ * <td>&nbsp;</td>
+ * </tr>
+ * <tr>
+ * <td>{@link #UNSUPPORTED_MEDIA_TYPE_415}</td>
+ * <td>415</td>
+ * <td>Unsupported Media Type</td>
+ * <td>&nbsp;</td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc2616#section-10.4.16">Sec. 10.4.16</a>
+ * </td>
+ * <td>&nbsp;</td>
+ * </tr>
+ * <tr>
+ * <td>{@link #REQUESTED_RANGE_NOT_SATISFIABLE_416}</td>
+ * <td>416</td>
+ * <td>Requested Range Not Satisfiable</td>
+ * <td>&nbsp;</td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc2616#section-10.4.17">Sec. 10.4.17</a>
+ * </td>
+ * <td>&nbsp;</td>
+ * </tr>
+ * <tr>
+ * <td>{@link #EXPECTATION_FAILED_417}</td>
+ * <td>417</td>
+ * <td>Expectation Failed</td>
+ * <td>&nbsp;</td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc2616#section-10.4.18">Sec. 10.4.18</a>
+ * </td>
+ * <td>&nbsp;</td>
+ * </tr>
+ * <tr>
+ * <td>&nbsp;</td>
+ * <td><strike>418</strike></td>
+ * <td><strike>Reauthentication Required</strike></td>
+ * <td>&nbsp;</td>
+ * <td>
+ * <a href=
+ * "http://tools.ietf.org/html/draft-ietf-http-v11-spec-rev-01#section-10.4.19"
+ * >draft/01</a></td>
+ * <td>&nbsp;</td>
+ * </tr>
+ * <tr>
+ * <td>&nbsp;</td>
+ * <td><strike>418</strike></td>
+ * <td><strike>Unprocessable Entity</strike></td>
+ * <td>&nbsp;</td>
+ * <td>&nbsp;</td>
+ * <td>
+ * <a href=
+ * "http://tools.ietf.org/html/draft-ietf-webdav-protocol-05#section-10.3"
+ * >draft/05</a></td>
+ * </tr>
+ * <tr>
+ * <td>&nbsp;</td>
+ * <td><strike>419</strike></td>
+ * <td><strike>Proxy Reauthentication Required</stike></td>
+ * <td>&nbsp;</td>
+ * <td>
+ * <a href=
+ * "http://tools.ietf.org/html/draft-ietf-http-v11-spec-rev-01#section-10.4.20"
+ * >draft/01</a></td>
+ * <td>&nbsp;</td>
+ * </tr>
+ * <tr>
+ * <td>&nbsp;</td>
+ * <td><strike>419</strike></td>
+ * <td><strike>Insufficient Space on Resource</stike></td>
+ * <td>&nbsp;</td>
+ * <td>&nbsp;</td>
+ * <td>
+ * <a href=
+ * "http://tools.ietf.org/html/draft-ietf-webdav-protocol-05#section-10.4"
+ * >draft/05</a></td>
+ * </tr>
+ * <tr>
+ * <td>&nbsp;</td>
+ * <td><strike>420</strike></td>
+ * <td><strike>Method Failure</strike></td>
+ * <td>&nbsp;</td>
+ * <td>&nbsp;</td>
+ * <td>
+ * <a href=
+ * "http://tools.ietf.org/html/draft-ietf-webdav-protocol-05#section-10.5"
+ * >draft/05</a></td>
+ * </tr>
+ * <tr>
+ * <td>&nbsp;</td>
+ * <td>421</td>
+ * <td><em>(Unused)</em></td>
+ * <td>&nbsp;</td>
+ * <td>&nbsp;</td>
+ * <td>&nbsp;</td>
+ * </tr>
+ * <tr>
+ * <td>{@link #UNPROCESSABLE_ENTITY_422}</td>
+ * <td>422</td>
+ * <td>Unprocessable Entity</td>
+ * <td>&nbsp;</td>
+ * <td>&nbsp;</td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc2518#section-10.3">Sec. 10.3</a></td>
+ * </tr>
+ * <tr>
+ * <td>{@link #LOCKED_423}</td>
+ * <td>423</td>
+ * <td>Locked</td>
+ * <td>&nbsp;</td>
+ * <td>&nbsp;</td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc2518#section-10.4">Sec. 10.4</a></td>
+ * </tr>
+ * <tr>
+ * <td>{@link #FAILED_DEPENDENCY_424}</td>
+ * <td>424</td>
+ * <td>Failed Dependency</td>
+ * <td>&nbsp;</td>
+ * <td>&nbsp;</td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc2518#section-10.5">Sec. 10.5</a></td>
+ * </tr>
+ *
+ * <tr>
+ * <td><strong><code>Server Error - 5xx</code></strong></td>
+ * <td colspan="5">{@link #isServerError(int)}</td>
+ * </tr>
+ *
+ * <tr>
+ * <td>{@link #INTERNAL_SERVER_ERROR_500}</td>
+ * <td>500</td>
+ * <td>Internal Server Error</td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc1945#section-9.5">Sec. 9.5</a></td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc2616#section-10.5.1">Sec. 10.5.1</a></td>
+ * <td>&nbsp;</td>
+ * </tr>
+ * <tr>
+ * <td>{@link #NOT_IMPLEMENTED_501}</td>
+ * <td>501</td>
+ * <td>Not Implemented</td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc1945#section-9.5">Sec. 9.5</a></td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc2616#section-10.5.2">Sec. 10.5.2</a></td>
+ * <td>&nbsp;</td>
+ * </tr>
+ * <tr>
+ * <td>{@link #BAD_GATEWAY_502}</td>
+ * <td>502</td>
+ * <td>Bad Gateway</td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc1945#section-9.5">Sec. 9.5</a></td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc2616#section-10.5.3">Sec. 10.5.3</a></td>
+ * <td>&nbsp;</td>
+ * </tr>
+ * <tr>
+ * <td>{@link #SERVICE_UNAVAILABLE_503}</td>
+ * <td>503</td>
+ * <td>Service Unavailable</td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc1945#section-9.5">Sec. 9.5</a></td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc2616#section-10.5.4">Sec. 10.5.4</a></td>
+ * <td>&nbsp;</td>
+ * </tr>
+ * <tr>
+ * <td>{@link #GATEWAY_TIMEOUT_504}</td>
+ * <td>504</td>
+ * <td>Gateway Timeout</td>
+ * <td>&nbsp;</td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc2616#section-10.5.5">Sec. 10.5.5</a></td>
+ * <td>&nbsp;</td>
+ * </tr>
+ * <tr>
+ * <td>{@link #HTTP_VERSION_NOT_SUPPORTED_505}</td>
+ * <td>505</td>
+ * <td>HTTP Version Not Supported</td>
+ * <td>&nbsp;</td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc2616#section-10.5.6">Sec. 10.5.6</a></td>
+ * <td>&nbsp;</td>
+ * </tr>
+ * <tr>
+ * <td>&nbsp;</td>
+ * <td>506</td>
+ * <td><em>(Unused)</em></td>
+ * <td>&nbsp;</td>
+ * <td>&nbsp;</td>
+ * <td>&nbsp;</td>
+ * </tr>
+ * <tr>
+ * <td>{@link #INSUFFICIENT_STORAGE_507}</td>
+ * <td>507</td>
+ * <td>Insufficient Storage</td>
+ * <td>&nbsp;</td>
+ * <td>&nbsp;</td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc2518#section-10.6">Sec. 10.6</a></td>
+ * </tr>
+ *
+ * </table>
+ *
+ * @version $Id$
+ */
+public class HttpStatus
+{
+    public final static int NOT_SET_000 = 0;
+    public final static int CONTINUE_100 = 100;
+    public final static int SWITCHING_PROTOCOLS_101 = 101;
+    public final static int PROCESSING_102 = 102;
+
+    public final static int OK_200 = 200;
+    public final static int CREATED_201 = 201;
+    public final static int ACCEPTED_202 = 202;
+    public final static int NON_AUTHORITATIVE_INFORMATION_203 = 203;
+    public final static int NO_CONTENT_204 = 204;
+    public final static int RESET_CONTENT_205 = 205;
+    public final static int PARTIAL_CONTENT_206 = 206;
+    public final static int MULTI_STATUS_207 = 207;
+
+    public final static int MULTIPLE_CHOICES_300 = 300;
+    public final static int MOVED_PERMANENTLY_301 = 301;
+    public final static int MOVED_TEMPORARILY_302 = 302;
+    public final static int FOUND_302 = 302;
+    public final static int SEE_OTHER_303 = 303;
+    public final static int NOT_MODIFIED_304 = 304;
+    public final static int USE_PROXY_305 = 305;
+    public final static int TEMPORARY_REDIRECT_307 = 307;
+
+    public final static int BAD_REQUEST_400 = 400;
+    public final static int UNAUTHORIZED_401 = 401;
+    public final static int PAYMENT_REQUIRED_402 = 402;
+    public final static int FORBIDDEN_403 = 403;
+    public final static int NOT_FOUND_404 = 404;
+    public final static int METHOD_NOT_ALLOWED_405 = 405;
+    public final static int NOT_ACCEPTABLE_406 = 406;
+    public final static int PROXY_AUTHENTICATION_REQUIRED_407 = 407;
+    public final static int REQUEST_TIMEOUT_408 = 408;
+    public final static int CONFLICT_409 = 409;
+    public final static int GONE_410 = 410;
+    public final static int LENGTH_REQUIRED_411 = 411;
+    public final static int PRECONDITION_FAILED_412 = 412;
+    public final static int REQUEST_ENTITY_TOO_LARGE_413 = 413;
+    public final static int REQUEST_URI_TOO_LONG_414 = 414;
+    public final static int UNSUPPORTED_MEDIA_TYPE_415 = 415;
+    public final static int REQUESTED_RANGE_NOT_SATISFIABLE_416 = 416;
+    public final static int EXPECTATION_FAILED_417 = 417;
+    public final static int UNPROCESSABLE_ENTITY_422 = 422;
+    public final static int LOCKED_423 = 423;
+    public final static int FAILED_DEPENDENCY_424 = 424;
+
+    public final static int INTERNAL_SERVER_ERROR_500 = 500;
+    public final static int NOT_IMPLEMENTED_501 = 501;
+    public final static int BAD_GATEWAY_502 = 502;
+    public final static int SERVICE_UNAVAILABLE_503 = 503;
+    public final static int GATEWAY_TIMEOUT_504 = 504;
+    public final static int HTTP_VERSION_NOT_SUPPORTED_505 = 505;
+    public final static int INSUFFICIENT_STORAGE_507 = 507;
+
+    public static final int MAX_CODE = 507;
+
+
+    private static final Code[] codeMap = new Code[MAX_CODE+1];
+
+    static
+    {
+        for (Code code : Code.values())
+        {
+            codeMap[code._code] = code;
+        }
+    }
+
+
+    public enum Code
+    {
+        /*
+         * --------------------------------------------------------------------
+         * Informational messages in 1xx series. As defined by ... RFC 1945 -
+         * HTTP/1.0 RFC 2616 - HTTP/1.1 RFC 2518 - WebDAV
+         */
+
+        /** <code>100 Continue</code> */
+        CONTINUE(CONTINUE_100, "Continue"),
+        /** <code>101 Switching Protocols</code> */
+        SWITCHING_PROTOCOLS(SWITCHING_PROTOCOLS_101, "Switching Protocols"),
+        /** <code>102 Processing</code> */
+        PROCESSING(PROCESSING_102, "Processing"),
+
+        /*
+         * --------------------------------------------------------------------
+         * Success messages in 2xx series. As defined by ... RFC 1945 - HTTP/1.0
+         * RFC 2616 - HTTP/1.1 RFC 2518 - WebDAV
+         */
+
+        /** <code>200 OK</code> */
+        OK(OK_200, "OK"),
+        /** <code>201 Created</code> */
+        CREATED(CREATED_201, "Created"),
+        /** <code>202 Accepted</code> */
+        ACCEPTED(ACCEPTED_202, "Accepted"),
+        /** <code>203 Non Authoritative Information</code> */
+        NON_AUTHORITATIVE_INFORMATION(NON_AUTHORITATIVE_INFORMATION_203, "Non Authoritative Information"),
+        /** <code>204 No Content</code> */
+        NO_CONTENT(NO_CONTENT_204, "No Content"),
+        /** <code>205 Reset Content</code> */
+        RESET_CONTENT(RESET_CONTENT_205, "Reset Content"),
+        /** <code>206 Partial Content</code> */
+        PARTIAL_CONTENT(PARTIAL_CONTENT_206, "Partial Content"),
+        /** <code>207 Multi-Status</code> */
+        MULTI_STATUS(MULTI_STATUS_207, "Multi-Status"),
+
+        /*
+         * --------------------------------------------------------------------
+         * Redirection messages in 3xx series. As defined by ... RFC 1945 -
+         * HTTP/1.0 RFC 2616 - HTTP/1.1
+         */
+
+        /** <code>300 Mutliple Choices</code> */
+        MULTIPLE_CHOICES(MULTIPLE_CHOICES_300, "Multiple Choices"),
+        /** <code>301 Moved Permanently</code> */
+        MOVED_PERMANENTLY(MOVED_PERMANENTLY_301, "Moved Permanently"),
+        /** <code>302 Moved Temporarily</code> */
+        MOVED_TEMPORARILY(MOVED_TEMPORARILY_302, "Moved Temporarily"),
+        /** <code>302 Found</code> */
+        FOUND(FOUND_302, "Found"),
+        /** <code>303 See Other</code> */
+        SEE_OTHER(SEE_OTHER_303, "See Other"),
+        /** <code>304 Not Modified</code> */
+        NOT_MODIFIED(NOT_MODIFIED_304, "Not Modified"),
+        /** <code>305 Use Proxy</code> */
+        USE_PROXY(USE_PROXY_305, "Use Proxy"),
+        /** <code>307 Temporary Redirect</code> */
+        TEMPORARY_REDIRECT(TEMPORARY_REDIRECT_307, "Temporary Redirect"),
+
+        /*
+         * --------------------------------------------------------------------
+         * Client Error messages in 4xx series. As defined by ... RFC 1945 -
+         * HTTP/1.0 RFC 2616 - HTTP/1.1 RFC 2518 - WebDAV
+         */
+
+        /** <code>400 Bad Request</code> */
+        BAD_REQUEST(BAD_REQUEST_400, "Bad Request"),
+        /** <code>401 Unauthorized</code> */
+        UNAUTHORIZED(UNAUTHORIZED_401, "Unauthorized"),
+        /** <code>402 Payment Required</code> */
+        PAYMENT_REQUIRED(PAYMENT_REQUIRED_402, "Payment Required"),
+        /** <code>403 Forbidden</code> */
+        FORBIDDEN(FORBIDDEN_403, "Forbidden"),
+        /** <code>404 Not Found</code> */
+        NOT_FOUND(NOT_FOUND_404, "Not Found"),
+        /** <code>405 Method Not Allowed</code> */
+        METHOD_NOT_ALLOWED(METHOD_NOT_ALLOWED_405, "Method Not Allowed"),
+        /** <code>406 Not Acceptable</code> */
+        NOT_ACCEPTABLE(NOT_ACCEPTABLE_406, "Not Acceptable"),
+        /** <code>407 Proxy Authentication Required</code> */
+        PROXY_AUTHENTICATION_REQUIRED(PROXY_AUTHENTICATION_REQUIRED_407, "Proxy Authentication Required"),
+        /** <code>408 Request Timeout</code> */
+        REQUEST_TIMEOUT(REQUEST_TIMEOUT_408, "Request Timeout"),
+        /** <code>409 Conflict</code> */
+        CONFLICT(CONFLICT_409, "Conflict"),
+        /** <code>410 Gone</code> */
+        GONE(GONE_410, "Gone"),
+        /** <code>411 Length Required</code> */
+        LENGTH_REQUIRED(LENGTH_REQUIRED_411, "Length Required"),
+        /** <code>412 Precondition Failed</code> */
+        PRECONDITION_FAILED(PRECONDITION_FAILED_412, "Precondition Failed"),
+        /** <code>413 Request Entity Too Large</code> */
+        REQUEST_ENTITY_TOO_LARGE(REQUEST_ENTITY_TOO_LARGE_413, "Request Entity Too Large"),
+        /** <code>414 Request-URI Too Long</code> */
+        REQUEST_URI_TOO_LONG(REQUEST_URI_TOO_LONG_414, "Request-URI Too Long"),
+        /** <code>415 Unsupported Media Type</code> */
+        UNSUPPORTED_MEDIA_TYPE(UNSUPPORTED_MEDIA_TYPE_415, "Unsupported Media Type"),
+        /** <code>416 Requested Range Not Satisfiable</code> */
+        REQUESTED_RANGE_NOT_SATISFIABLE(REQUESTED_RANGE_NOT_SATISFIABLE_416, "Requested Range Not Satisfiable"),
+        /** <code>417 Expectation Failed</code> */
+        EXPECTATION_FAILED(EXPECTATION_FAILED_417, "Expectation Failed"),
+        /** <code>422 Unprocessable Entity</code> */
+        UNPROCESSABLE_ENTITY(UNPROCESSABLE_ENTITY_422, "Unprocessable Entity"),
+        /** <code>423 Locked</code> */
+        LOCKED(LOCKED_423, "Locked"),
+        /** <code>424 Failed Dependency</code> */
+        FAILED_DEPENDENCY(FAILED_DEPENDENCY_424, "Failed Dependency"),
+
+        /*
+         * --------------------------------------------------------------------
+         * Server Error messages in 5xx series. As defined by ... RFC 1945 -
+         * HTTP/1.0 RFC 2616 - HTTP/1.1 RFC 2518 - WebDAV
+         */
+
+        /** <code>500 Server Error</code> */
+        INTERNAL_SERVER_ERROR(INTERNAL_SERVER_ERROR_500, "Server Error"),
+        /** <code>501 Not Implemented</code> */
+        NOT_IMPLEMENTED(NOT_IMPLEMENTED_501, "Not Implemented"),
+        /** <code>502 Bad Gateway</code> */
+        BAD_GATEWAY(BAD_GATEWAY_502, "Bad Gateway"),
+        /** <code>503 Service Unavailable</code> */
+        SERVICE_UNAVAILABLE(SERVICE_UNAVAILABLE_503, "Service Unavailable"),
+        /** <code>504 Gateway Timeout</code> */
+        GATEWAY_TIMEOUT(GATEWAY_TIMEOUT_504, "Gateway Timeout"),
+        /** <code>505 HTTP Version Not Supported</code> */
+        HTTP_VERSION_NOT_SUPPORTED(HTTP_VERSION_NOT_SUPPORTED_505, "HTTP Version Not Supported"),
+        /** <code>507 Insufficient Storage</code> */
+        INSUFFICIENT_STORAGE(INSUFFICIENT_STORAGE_507, "Insufficient Storage");
+
+        private final int _code;
+        private final String _message;
+
+        private Code(int code, String message)
+        {
+            this._code = code;
+            _message=message;
+        }
+
+        public int getCode()
+        {
+            return _code;
+        }
+
+        public String getMessage()
+        {
+            return _message;
+        }
+
+
+        public boolean equals(int code)
+        {
+            return (this._code == code);
+        }
+
+        @Override
+        public String toString()
+        {
+            return String.format("[%03d %s]",this._code,this.getMessage());
+        }
+
+        /**
+         * Simple test against an code to determine if it falls into the
+         * <code>Informational</code> message category as defined in the <a
+         * href="http://tools.ietf.org/html/rfc1945">RFC 1945 - HTTP/1.0</a>,
+         * and <a href="http://tools.ietf.org/html/rfc2616">RFC 2616 -
+         * HTTP/1.1</a>.
+         *
+         * @return true if within range of codes that belongs to
+         *         <code>Informational</code> messages.
+         */
+        public boolean isInformational()
+        {
+            return HttpStatus.isInformational(this._code);
+        }
+
+        /**
+         * Simple test against an code to determine if it falls into the
+         * <code>Success</code> message category as defined in the <a
+         * href="http://tools.ietf.org/html/rfc1945">RFC 1945 - HTTP/1.0</a>,
+         * and <a href="http://tools.ietf.org/html/rfc2616">RFC 2616 -
+         * HTTP/1.1</a>.
+         *
+         * @return true if within range of codes that belongs to
+         *         <code>Success</code> messages.
+         */
+        public boolean isSuccess()
+        {
+            return HttpStatus.isSuccess(this._code);
+        }
+
+        /**
+         * Simple test against an code to determine if it falls into the
+         * <code>Redirection</code> message category as defined in the <a
+         * href="http://tools.ietf.org/html/rfc1945">RFC 1945 - HTTP/1.0</a>,
+         * and <a href="http://tools.ietf.org/html/rfc2616">RFC 2616 -
+         * HTTP/1.1</a>.
+         *
+         * @return true if within range of codes that belongs to
+         *         <code>Redirection</code> messages.
+         */
+        public boolean isRedirection()
+        {
+            return HttpStatus.isRedirection(this._code);
+        }
+
+        /**
+         * Simple test against an code to determine if it falls into the
+         * <code>Client Error</code> message category as defined in the <a
+         * href="http://tools.ietf.org/html/rfc1945">RFC 1945 - HTTP/1.0</a>,
+         * and <a href="http://tools.ietf.org/html/rfc2616">RFC 2616 -
+         * HTTP/1.1</a>.
+         *
+         * @return true if within range of codes that belongs to
+         *         <code>Client Error</code> messages.
+         */
+        public boolean isClientError()
+        {
+            return HttpStatus.isClientError(this._code);
+        }
+
+        /**
+         * Simple test against an code to determine if it falls into the
+         * <code>Server Error</code> message category as defined in the <a
+         * href="http://tools.ietf.org/html/rfc1945">RFC 1945 - HTTP/1.0</a>,
+         * and <a href="http://tools.ietf.org/html/rfc2616">RFC 2616 -
+         * HTTP/1.1</a>.
+         *
+         * @return true if within range of codes that belongs to
+         *         <code>Server Error</code> messages.
+         */
+        public boolean isServerError()
+        {
+            return HttpStatus.isServerError(this._code);
+        }
+    }
+
+
+    /**
+     * Get the HttpStatusCode for a specific code
+     *
+     * @param code
+     *            the code to lookup.
+     * @return the {@link HttpStatus} if found, or null if not found.
+     */
+    public static Code getCode(int code)
+    {
+        if (code <= MAX_CODE)
+        {
+            return codeMap[code];
+        }
+        return null;
+    }
+
+    /**
+     * Get the status message for a specific code.
+     *
+     * @param code
+     *            the code to look up
+     * @return the specific message, or the code number itself if code
+     *         does not match known list.
+     */
+    public static String getMessage(int code)
+    {
+        Code codeEnum = getCode(code);
+        if (codeEnum != null)
+        {
+            return codeEnum.getMessage();
+        }
+        else
+        {
+            return Integer.toString(code);
+        }
+    }
+
+    /**
+     * Simple test against an code to determine if it falls into the
+     * <code>Informational</code> message category as defined in the <a
+     * href="http://tools.ietf.org/html/rfc1945">RFC 1945 - HTTP/1.0</a>, and <a
+     * href="http://tools.ietf.org/html/rfc2616">RFC 2616 - HTTP/1.1</a>.
+     *
+     * @param code
+     *            the code to test.
+     * @return true if within range of codes that belongs to
+     *         <code>Informational</code> messages.
+     */
+    public static boolean isInformational(int code)
+    {
+        return ((100 <= code) && (code <= 199));
+    }
+
+    /**
+     * Simple test against an code to determine if it falls into the
+     * <code>Success</code> message category as defined in the <a
+     * href="http://tools.ietf.org/html/rfc1945">RFC 1945 - HTTP/1.0</a>, and <a
+     * href="http://tools.ietf.org/html/rfc2616">RFC 2616 - HTTP/1.1</a>.
+     *
+     * @param code
+     *            the code to test.
+     * @return true if within range of codes that belongs to
+     *         <code>Success</code> messages.
+     */
+    public static boolean isSuccess(int code)
+    {
+        return ((200 <= code) && (code <= 299));
+    }
+
+    /**
+     * Simple test against an code to determine if it falls into the
+     * <code>Redirection</code> message category as defined in the <a
+     * href="http://tools.ietf.org/html/rfc1945">RFC 1945 - HTTP/1.0</a>, and <a
+     * href="http://tools.ietf.org/html/rfc2616">RFC 2616 - HTTP/1.1</a>.
+     *
+     * @param code
+     *            the code to test.
+     * @return true if within range of codes that belongs to
+     *         <code>Redirection</code> messages.
+     */
+    public static boolean isRedirection(int code)
+    {
+        return ((300 <= code) && (code <= 399));
+    }
+
+    /**
+     * Simple test against an code to determine if it falls into the
+     * <code>Client Error</code> message category as defined in the <a
+     * href="http://tools.ietf.org/html/rfc1945">RFC 1945 - HTTP/1.0</a>, and <a
+     * href="http://tools.ietf.org/html/rfc2616">RFC 2616 - HTTP/1.1</a>.
+     *
+     * @param code
+     *            the code to test.
+     * @return true if within range of codes that belongs to
+     *         <code>Client Error</code> messages.
+     */
+    public static boolean isClientError(int code)
+    {
+        return ((400 <= code) && (code <= 499));
+    }
+
+    /**
+     * Simple test against an code to determine if it falls into the
+     * <code>Server Error</code> message category as defined in the <a
+     * href="http://tools.ietf.org/html/rfc1945">RFC 1945 - HTTP/1.0</a>, and <a
+     * href="http://tools.ietf.org/html/rfc2616">RFC 2616 - HTTP/1.1</a>.
+     *
+     * @param code
+     *            the code to test.
+     * @return true if within range of codes that belongs to
+     *         <code>Server Error</code> messages.
+     */
+    public static boolean isServerError(int code)
+    {
+        return ((500 <= code) && (code <= 599));
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/http/HttpTester.java b/lib/jetty/org/eclipse/jetty/http/HttpTester.java
new file mode 100644 (file)
index 0000000..537c457
--- /dev/null
@@ -0,0 +1,367 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.http;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+
+import org.eclipse.jetty.http.HttpGenerator.RequestInfo;
+import org.eclipse.jetty.http.HttpGenerator.ResponseInfo;
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.StringUtil;
+
+public class HttpTester
+{
+    private HttpTester()
+    {
+    }
+
+    public static Request newRequest()
+    {
+        return new Request();
+    }
+
+    public static Request parseRequest(String request)
+    {
+        Request r=new Request();
+        HttpParser parser =new HttpParser(r);
+        parser.parseNext(BufferUtil.toBuffer(request));
+        return r;
+    }
+
+    public static Request parseRequest(ByteBuffer request)
+    {
+        Request r=new Request();
+        HttpParser parser =new HttpParser(r);
+        parser.parseNext(request);
+        return r;
+    }
+
+    public static Response parseResponse(String response)
+    {
+        Response r=new Response();
+        HttpParser parser =new HttpParser(r);
+        parser.parseNext(BufferUtil.toBuffer(response));
+        return r;
+    }
+
+    public static Response parseResponse(ByteBuffer response)
+    {
+        Response r=new Response();
+        HttpParser parser =new HttpParser(r);
+        parser.parseNext(response);
+        return r;
+    }
+
+
+    public abstract static class Message extends HttpFields implements HttpParser.HttpHandler<ByteBuffer>
+    {
+        ByteArrayOutputStream _content;
+        HttpVersion _version=HttpVersion.HTTP_1_0;
+
+        public HttpVersion getVersion()
+        {
+            return _version;
+        }
+
+        public void setVersion(String version)
+        {
+            setVersion(HttpVersion.CACHE.get(version));
+        }
+
+        public void setVersion(HttpVersion version)
+        {
+            _version=version;
+        }
+
+        public void setContent(byte[] bytes)
+        {
+            try
+            {
+                _content=new ByteArrayOutputStream();
+                _content.write(bytes);
+            }
+            catch (IOException e)
+            {
+                throw new RuntimeException(e);
+            }
+        }
+
+        public void setContent(String content)
+        {
+            try
+            {
+                _content=new ByteArrayOutputStream();
+                _content.write(StringUtil.getBytes(content));
+            }
+            catch (IOException e)
+            {
+                throw new RuntimeException(e);
+            }
+        }
+
+        public void setContent(ByteBuffer content)
+        {
+            try
+            {
+                _content=new ByteArrayOutputStream();
+                _content.write(BufferUtil.toArray(content));
+            }
+            catch (IOException e)
+            {
+                throw new RuntimeException(e);
+            }
+        }
+        @Override
+        public boolean parsedHeader(HttpField field)
+        {
+            put(field.getName(),field.getValue());
+            return false;
+        }
+
+        @Override
+        public boolean messageComplete()
+        {
+            return true;
+        }
+
+        @Override
+        public boolean headerComplete()
+        {
+            _content=new ByteArrayOutputStream();
+            return false;
+        }
+
+        @Override
+        public void earlyEOF()
+        {
+        }
+
+        @Override
+        public boolean content(ByteBuffer ref)
+        {
+            try
+            {
+                _content.write(BufferUtil.toArray(ref));
+            }
+            catch (IOException e)
+            {
+                throw new RuntimeException(e);
+            }
+            return false;
+        }
+
+        @Override
+        public void badMessage(int status, String reason)
+        {
+            throw new RuntimeException(reason);
+        }
+
+        public ByteBuffer generate()
+        {
+            try
+            {
+                HttpGenerator generator = new HttpGenerator();
+                HttpGenerator.Info info = getInfo();
+                // System.err.println(info.getClass());
+                // System.err.println(info);
+
+                ByteArrayOutputStream out = new ByteArrayOutputStream();
+                ByteBuffer header=null;
+                ByteBuffer chunk=null;
+                ByteBuffer content=_content==null?null:ByteBuffer.wrap(_content.toByteArray());
+
+
+                loop: while(!generator.isEnd())
+                {
+                    HttpGenerator.Result result =  info instanceof RequestInfo
+                        ?generator.generateRequest((RequestInfo)info,header,chunk,content,true)
+                        :generator.generateResponse((ResponseInfo)info,header,chunk,content,true);
+                    switch(result)
+                    {
+                        case NEED_HEADER:
+                            header=BufferUtil.allocate(8192);
+                            continue;
+
+                        case NEED_CHUNK:
+                            chunk=BufferUtil.allocate(HttpGenerator.CHUNK_SIZE);
+                            continue;
+
+                        case NEED_INFO:
+                            throw new IllegalStateException();
+
+                        case FLUSH:
+                            if (BufferUtil.hasContent(header))
+                            {
+                                out.write(BufferUtil.toArray(header));
+                                BufferUtil.clear(header);
+                            }
+                            if (BufferUtil.hasContent(chunk))
+                            {
+                                out.write(BufferUtil.toArray(chunk));
+                                BufferUtil.clear(chunk);
+                            }
+                            if (BufferUtil.hasContent(content))
+                            {
+                                out.write(BufferUtil.toArray(content));
+                                BufferUtil.clear(content);
+                            }
+                            break;
+
+                        case SHUTDOWN_OUT:
+                            break loop;
+                    }
+                }
+
+                return ByteBuffer.wrap(out.toByteArray());
+            }
+            catch (IOException e)
+            {
+                throw new RuntimeException(e);
+            }
+
+        }
+        abstract public HttpGenerator.Info getInfo();
+
+        @Override
+        public int getHeaderCacheSize()
+        {
+            return 0;
+        }
+
+    }
+
+    public static class Request extends Message implements HttpParser.RequestHandler<ByteBuffer>
+    {
+        private String _method;
+        private String _uri;
+
+        @Override
+        public boolean startRequest(HttpMethod method, String methodString, ByteBuffer uri, HttpVersion version)
+        {
+            _method=methodString;
+            _uri=BufferUtil.toUTF8String(uri);
+            _version=version;
+            return false;
+        }
+
+        public String getMethod()
+        {
+            return _method;
+        }
+
+        public String getUri()
+        {
+            return _uri;
+        }
+
+        public void setMethod(String method)
+        {
+            _method=method;
+        }
+
+        public void setURI(String uri)
+        {
+            _uri=uri;
+        }
+
+        @Override
+        public HttpGenerator.RequestInfo getInfo()
+        {
+            return new HttpGenerator.RequestInfo(_version,this,_content==null?0:_content.size(),_method,_uri);
+        }
+
+        @Override
+        public String toString()
+        {
+            return String.format("%s %s %s\n%s\n",_method,_uri,_version,super.toString());
+        }
+
+        public void setHeader(String name, String value)
+        {
+            put(name,value);
+        }
+
+        @Override
+        public boolean parsedHostHeader(String host,int port)
+        {
+            return false;
+        }
+    }
+
+    public static class Response extends Message implements HttpParser.ResponseHandler<ByteBuffer>
+    {
+        private int _status;
+        private String _reason;
+
+        @Override
+        public boolean startResponse(HttpVersion version, int status, String reason)
+        {
+            _version=version;
+            _status=status;
+            _reason=reason;
+            return false;
+        }
+
+        public int getStatus()
+        {
+            return _status;
+        }
+
+        public String getReason()
+        {
+            return _reason;
+        }
+
+        public byte[] getContentBytes()
+        {
+            if (_content==null)
+                return null;
+            return _content.toByteArray();
+        }
+
+        public String getContent()
+        {
+            if (_content==null)
+                return null;
+            byte[] bytes=_content.toByteArray();
+
+            String content_type=get(HttpHeader.CONTENT_TYPE);
+            String encoding=MimeTypes.getCharsetFromContentType(content_type);
+            Charset charset=encoding==null?StandardCharsets.UTF_8:Charset.forName(encoding);
+
+            return new String(bytes,charset);
+        }
+
+        @Override
+        public HttpGenerator.ResponseInfo getInfo()
+        {
+            return new HttpGenerator.ResponseInfo(_version,this,_content==null?-1:_content.size(),_status,_reason,false);
+        }
+
+        @Override
+        public String toString()
+        {
+            return String.format("%s %s %s\n%s\n",_version,_status,_reason,super.toString());
+        }
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/http/HttpTokens.java b/lib/jetty/org/eclipse/jetty/http/HttpTokens.java
new file mode 100644 (file)
index 0000000..4138334
--- /dev/null
@@ -0,0 +1,38 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.http;
+
+/**
+ * HTTP constants
+ */
+public interface HttpTokens
+{
+    // Terminal symbols.
+    static final byte COLON= (byte)':';
+    static final byte TAB= 0x09;
+    static final byte LINE_FEED= 0x0A;
+    static final byte CARRIAGE_RETURN= 0x0D;
+    static final byte SPACE= 0x20;
+    static final byte[] CRLF = {CARRIAGE_RETURN,LINE_FEED};
+    static final byte SEMI_COLON= (byte)';';
+
+    public enum EndOfContent { UNKNOWN_CONTENT,NO_CONTENT,EOF_CONTENT,CONTENT_LENGTH,CHUNKED_CONTENT,SELF_DEFINING_CONTENT }
+
+}
+
diff --git a/lib/jetty/org/eclipse/jetty/http/HttpURI.java b/lib/jetty/org/eclipse/jetty/http/HttpURI.java
new file mode 100644 (file)
index 0000000..afe925e
--- /dev/null
@@ -0,0 +1,784 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.http;
+
+import java.io.UnsupportedEncodingException;
+import java.net.URI;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+
+import org.eclipse.jetty.util.MultiMap;
+import org.eclipse.jetty.util.StringUtil;
+import org.eclipse.jetty.util.TypeUtil;
+import org.eclipse.jetty.util.URIUtil;
+import org.eclipse.jetty.util.UrlEncoded;
+import org.eclipse.jetty.util.Utf8StringBuilder;
+
+
+/* ------------------------------------------------------------ */
+/** Http URI.
+ * Parse a HTTP URI from a string or byte array.  Given a URI
+ * <code>http://user@host:port/path/info;param?query#fragment</code>
+ * this class will split it into the following undecoded optional elements:<ul>
+ * <li>{@link #getScheme()} - http:</li>
+ * <li>{@link #getAuthority()} - //name@host:port</li>
+ * <li>{@link #getHost()} - host</li>
+ * <li>{@link #getPort()} - port</li>
+ * <li>{@link #getPath()} - /path/info</li>
+ * <li>{@link #getParam()} - param</li>
+ * <li>{@link #getQuery()} - query</li>
+ * <li>{@link #getFragment()} - fragment</li>
+ * </ul>
+ *
+ */
+public class HttpURI
+{
+    private static final byte[] __empty={};
+    private final static int
+    START=0,
+    AUTH_OR_PATH=1,
+    SCHEME_OR_PATH=2,
+    AUTH=4,
+    IPV6=5,
+    PORT=6,
+    PATH=7,
+    PARAM=8,
+    QUERY=9,
+    ASTERISK=10;
+
+    final Charset _charset;
+    boolean _partial=false;
+    byte[] _raw=__empty;
+    String _rawString;
+    int _scheme;
+    int _authority;
+    int _host;
+    int _port;
+    int _portValue;
+    int _path;
+    int _param;
+    int _query;
+    int _fragment;
+    int _end;
+    boolean _encoded=false;
+
+    public HttpURI()
+    {
+        _charset = URIUtil.__CHARSET;
+    }
+
+    public HttpURI(Charset charset)
+    {
+        _charset = charset;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param parsePartialAuth If True, parse auth without prior scheme, else treat all URIs starting with / as paths
+     */
+    public HttpURI(boolean parsePartialAuth)
+    {
+        _partial=parsePartialAuth;
+        _charset = URIUtil.__CHARSET;
+    }
+
+    public HttpURI(String raw)
+    {
+        _rawString=raw;
+        byte[] b = raw.getBytes(StandardCharsets.UTF_8);
+        parse(b,0,b.length);
+        _charset = URIUtil.__CHARSET;
+    }
+
+    public HttpURI(byte[] raw,int offset, int length)
+    {
+        parse2(raw,offset,length);
+        _charset = URIUtil.__CHARSET;
+    }
+
+    public HttpURI(URI uri)
+    {
+        parse(uri.toASCIIString());
+        _charset = URIUtil.__CHARSET;
+    }
+
+    public void parse(String raw)
+    {
+        byte[] b = StringUtil.getUtf8Bytes(raw);
+        parse2(b,0,b.length);
+        _rawString=raw;
+    }
+
+    public void parseConnect(String raw)
+    {
+        byte[] b = StringUtil.getBytes(raw);
+        parseConnect(b,0,b.length);
+        _rawString=raw;
+    }
+
+    public void parse(byte[] raw,int offset, int length)
+    {
+        _rawString=null;
+        parse2(raw,offset,length);
+    }
+
+
+    public void parseConnect(byte[] raw,int offset, int length)
+    {
+        _rawString=null;
+        _encoded=false;
+        _raw=raw;
+        int i=offset;
+        int e=offset+length;
+        int state=AUTH;
+        _end=offset+length;
+        _scheme=offset;
+        _authority=offset;
+        _host=offset;
+        _port=_end;
+        _portValue=-1;
+        _path=_end;
+        _param=_end;
+        _query=_end;
+        _fragment=_end;
+
+        loop: while (i<e)
+        {
+            char c=(char)(0xff&_raw[i]);
+            int s=i++;
+
+            switch (state)
+            {
+                case AUTH:
+                {
+                    switch (c)
+                    {
+                        case ':':
+                        {
+                            _port = s;
+                            break loop;
+                        }
+                        case '[':
+                        {
+                            state = IPV6;
+                            break;
+                        }
+                    }
+                    continue;
+                }
+
+                case IPV6:
+                {
+                    switch (c)
+                    {
+                        case '/':
+                        {
+                            throw new IllegalArgumentException("No closing ']' for " + new String(_raw,offset,length,_charset));
+                        }
+                        case ']':
+                        {
+                            state = AUTH;
+                            break;
+                        }
+                    }
+
+                    continue;
+                }
+            }
+        }
+
+        if (_port<_path)
+            _portValue=TypeUtil.parseInt(_raw, _port+1, _path-_port-1,10);
+        else
+            throw new IllegalArgumentException("No port");
+        _path=offset;
+    }
+
+
+    private void parse2(byte[] raw,int offset, int length)
+    {
+        _encoded=false;
+        _raw=raw;
+        int i=offset;
+        int e=offset+length;
+        int state=START;
+        int m=offset;
+        _end=offset+length;
+        _scheme=offset;
+        _authority=offset;
+        _host=offset;
+        _port=offset;
+        _portValue=-1;
+        _path=offset;
+        _param=_end;
+        _query=_end;
+        _fragment=_end;
+        while (i<e)
+        {
+            char c=(char)(0xff&_raw[i]);
+            int s=i++;
+
+            state: switch (state)
+            {
+                case START:
+                {
+                    m=s;
+                    switch(c)
+                    {
+                        case '/':
+                            state=AUTH_OR_PATH;
+                            break;
+                        case ';':
+                            _param=s;
+                            state=PARAM;
+                            break;
+                        case '?':
+                            _param=s;
+                            _query=s;
+                            state=QUERY;
+                            break;
+                        case '#':
+                            _param=s;
+                            _query=s;
+                            _fragment=s;
+                            break;
+                        case '*':
+                            _path=s;
+                            state=ASTERISK;
+                            break;
+
+                        default:
+                            state=SCHEME_OR_PATH;
+                    }
+
+                    continue;
+                }
+
+                case AUTH_OR_PATH:
+                {
+                    if ((_partial||_scheme!=_authority) && c=='/')
+                    {
+                        _host=i;
+                        _port=_end;
+                        _path=_end;
+                        state=AUTH;
+                    }
+                    else if (c==';' || c=='?' || c=='#')
+                    {
+                        i--;
+                        state=PATH;
+                    }
+                    else
+                    {
+                        _host=m;
+                        _port=m;
+                        state=PATH;
+                    }
+                    continue;
+                }
+
+                case SCHEME_OR_PATH:
+                {
+                    // short cut for http and https
+                    if (length>6 && c=='t')
+                    {
+                        if (_raw[offset+3]==':')
+                        {
+                            s=offset+3;
+                            i=offset+4;
+                            c=':';
+                        }
+                        else if (_raw[offset+4]==':')
+                        {
+                            s=offset+4;
+                            i=offset+5;
+                            c=':';
+                        }
+                        else if (_raw[offset+5]==':')
+                        {
+                            s=offset+5;
+                            i=offset+6;
+                            c=':';
+                        }
+                    }
+
+                    switch (c)
+                    {
+                        case ':':
+                        {
+                            m = i++;
+                            _authority = m;
+                            _path = m;
+                            c = (char)(0xff & _raw[i]);
+                            if (c == '/')
+                                state = AUTH_OR_PATH;
+                            else
+                            {
+                                _host = m;
+                                _port = m;
+                                state = PATH;
+                            }
+                            break;
+                        }
+
+                        case '/':
+                        {
+                            state = PATH;
+                            break;
+                        }
+
+                        case ';':
+                        {
+                            _param = s;
+                            state = PARAM;
+                            break;
+                        }
+
+                        case '?':
+                        {
+                            _param = s;
+                            _query = s;
+                            state = QUERY;
+                            break;
+                        }
+
+                        case '#':
+                        {
+                            _param = s;
+                            _query = s;
+                            _fragment = s;
+                            break;
+                        }
+                    }
+                    continue;
+                }
+
+                case AUTH:
+                {
+                    switch (c)
+                    {
+
+                        case '/':
+                        {
+                            m = s;
+                            _path = m;
+                            _port = _path;
+                            state = PATH;
+                            break;
+                        }
+                        case '@':
+                        {
+                            _host = i;
+                            break;
+                        }
+                        case ':':
+                        {
+                            _port = s;
+                            state = PORT;
+                            break;
+                        }
+                        case '[':
+                        {
+                            state = IPV6;
+                            break;
+                        }
+                    }
+                    continue;
+                }
+
+                case IPV6:
+                {
+                    switch (c)
+                    {
+                        case '/':
+                        {
+                            throw new IllegalArgumentException("No closing ']' for " + new String(_raw,offset,length,_charset));
+                        }
+                        case ']':
+                        {
+                            state = AUTH;
+                            break;
+                        }
+                    }
+
+                    continue;
+                }
+
+                case PORT:
+                {
+                    if (c=='/')
+                    {
+                        m=s;
+                        _path=m;
+                        if (_port<=_authority)
+                            _port=_path;
+                        state=PATH;
+                    }
+                    continue;
+                }
+
+                case PATH:
+                {
+                    switch (c)
+                    {
+                        case ';':
+                        {
+                            _param = s;
+                            state = PARAM;
+                            break;
+                        }
+                        case '?':
+                        {
+                            _param = s;
+                            _query = s;
+                            state = QUERY;
+                            break;
+                        }
+                        case '#':
+                        {
+                            _param = s;
+                            _query = s;
+                            _fragment = s;
+                            break state;
+                        }
+                        case '%':
+                        {
+                            _encoded=true;
+                        }
+                    }
+                    continue;
+                }
+
+                case PARAM:
+                {
+                    switch (c)
+                    {
+                        case '?':
+                        {
+                            _query = s;
+                            state = QUERY;
+                            break;
+                        }
+                        case '#':
+                        {
+                            _query = s;
+                            _fragment = s;
+                            break state;
+                        }
+                    }
+                    continue;
+                }
+
+                case QUERY:
+                {
+                    if (c=='#')
+                    {
+                        _fragment=s;
+                        break state;
+                    }
+                    continue;
+                }
+
+                case ASTERISK:
+                {
+                    throw new IllegalArgumentException("only '*'");
+                }
+            }
+        }
+
+        if (_port<_path)
+            _portValue=TypeUtil.parseInt(_raw, _port+1, _path-_port-1,10);
+    }
+
+    public String getScheme()
+    {
+        if (_scheme==_authority)
+            return null;
+        int l=_authority-_scheme;
+        if (l==5 &&
+                _raw[_scheme]=='h' &&
+                _raw[_scheme+1]=='t' &&
+                _raw[_scheme+2]=='t' &&
+                _raw[_scheme+3]=='p' )
+            return HttpScheme.HTTP.asString();
+        if (l==6 &&
+                _raw[_scheme]=='h' &&
+                _raw[_scheme+1]=='t' &&
+                _raw[_scheme+2]=='t' &&
+                _raw[_scheme+3]=='p' &&
+                _raw[_scheme+4]=='s' )
+            return HttpScheme.HTTPS.asString();
+
+        return new String(_raw,_scheme,_authority-_scheme-1,_charset);
+    }
+
+    public String getAuthority()
+    {
+        if (_authority==_path)
+            return null;
+        return new String(_raw,_authority,_path-_authority,_charset);
+    }
+
+    public String getHost()
+    {
+        if (_host==_port)
+            return null;
+        if (_raw[_host]=='[')
+            return new String(_raw,_host+1,_port-_host-2,_charset);
+        return new String(_raw,_host,_port-_host,_charset);
+    }
+
+    public int getPort()
+    {
+        return _portValue;
+    }
+
+    public String getPath()
+    {
+        if (_path==_param)
+            return null;
+        return new String(_raw,_path,_param-_path,_charset);
+    }
+
+    public String getDecodedPath()
+    {
+        if (_path==_param)
+            return null;
+
+        Utf8StringBuilder utf8b=null;
+
+        for (int i=_path;i<_param;i++)
+        {
+            byte b = _raw[i];
+
+            if (b=='%')
+            {
+                if (utf8b==null)
+                {
+                    utf8b=new Utf8StringBuilder();
+                    utf8b.append(_raw,_path,i-_path);
+                }
+                
+                if ((i+2)>=_param)
+                    throw new IllegalArgumentException("Bad % encoding: "+this);
+                if (_raw[i+1]=='u')
+                {
+                    if ((i+5)>=_param)
+                        throw new IllegalArgumentException("Bad %u encoding: "+this);
+                    try
+                    {
+                        String unicode = new String(Character.toChars(TypeUtil.parseInt(_raw,i+2,4,16)));
+                        utf8b.getStringBuilder().append(unicode);
+                        i+=5;
+                    }
+                    catch(Exception e)
+                    {
+                        throw new RuntimeException(e);
+                    }
+                }
+                else
+                {
+                    b=(byte)(0xff&TypeUtil.parseInt(_raw,i+1,2,16));
+                    utf8b.append(b);
+                    i+=2;
+                }
+                continue;
+            }
+            else if (utf8b!=null)
+            {
+                utf8b.append(b);
+            }
+        }
+
+        if (utf8b==null)
+            return StringUtil.toUTF8String(_raw, _path, _param-_path);
+        return utf8b.toString();
+    }
+
+    public String getDecodedPath(String encoding)
+    {
+        return getDecodedPath(Charset.forName(encoding));
+    }
+
+    public String getDecodedPath(Charset encoding)
+    {
+        if (_path==_param)
+            return null;
+
+        int length = _param-_path;
+        byte[] bytes=null;
+        int n=0;
+
+        for (int i=_path;i<_param;i++)
+        {
+            byte b = _raw[i];
+
+            if (b=='%')
+            {
+                if (bytes==null)
+                {
+                    bytes=new byte[length];
+                    System.arraycopy(_raw,_path,bytes,0,n);
+                }
+                
+                if ((i+2)>=_param)
+                    throw new IllegalArgumentException("Bad % encoding: "+this);
+                if (_raw[i+1]=='u')
+                {
+                    if ((i+5)>=_param)
+                        throw new IllegalArgumentException("Bad %u encoding: "+this);
+
+                    try
+                    {
+                        String unicode = new String(Character.toChars(TypeUtil.parseInt(_raw,i+2,4,16)));
+                        byte[] encoded = unicode.getBytes(encoding);
+                        System.arraycopy(encoded,0,bytes,n,encoded.length);
+                        n+=encoded.length;
+                        i+=5;
+                    }
+                    catch(Exception e)
+                    {
+                        throw new RuntimeException(e);
+                    }
+                }
+                else
+                {
+                    b=(byte)(0xff&TypeUtil.parseInt(_raw,i+1,2,16));
+                    bytes[n++]=b;
+                    i+=2;
+                }
+                continue;
+            }
+            else if (bytes==null)
+            {
+                n++;
+                continue;
+            }
+
+            bytes[n++]=b;
+        }
+
+
+        if (bytes==null)
+            return new String(_raw,_path,_param-_path,encoding);
+
+        return new String(bytes,0,n,encoding);
+    }
+
+    public String getPathAndParam()
+    {
+        if (_path==_query)
+            return null;
+        return new String(_raw,_path,_query-_path,_charset);
+    }
+
+    public String getCompletePath()
+    {
+        if (_path==_end)
+            return null;
+        return new String(_raw,_path,_end-_path,_charset);
+    }
+
+    public String getParam()
+    {
+        if (_param==_query)
+            return null;
+        return new String(_raw,_param+1,_query-_param-1,_charset);
+    }
+
+    public String getQuery()
+    {
+        if (_query==_fragment)
+            return null;
+        return new String(_raw,_query+1,_fragment-_query-1,_charset);
+    }
+
+    public String getQuery(String encoding)
+    {
+        if (_query==_fragment)
+            return null;
+        return StringUtil.toString(_raw,_query+1,_fragment-_query-1,encoding);
+    }
+
+    public boolean hasQuery()
+    {
+        return (_fragment>_query);
+    }
+
+    public String getFragment()
+    {
+        if (_fragment==_end)
+            return null;
+        return new String(_raw,_fragment+1,_end-_fragment-1,_charset);
+    }
+
+    public void decodeQueryTo(MultiMap<String> parameters)
+    {
+        if (_query==_fragment)
+            return;
+        if (_charset.equals(StandardCharsets.UTF_8))
+            UrlEncoded.decodeUtf8To(_raw,_query+1,_fragment-_query-1,parameters);
+        else
+            UrlEncoded.decodeTo(new String(_raw,_query+1,_fragment-_query-1,_charset),parameters,_charset,-1);
+    }
+
+    public void decodeQueryTo(MultiMap<String> parameters, String encoding) throws UnsupportedEncodingException
+    {
+        if (_query==_fragment)
+            return;
+
+        if (encoding==null || StringUtil.isUTF8(encoding))
+            UrlEncoded.decodeUtf8To(_raw,_query+1,_fragment-_query-1,parameters);
+        else
+            UrlEncoded.decodeTo(StringUtil.toString(_raw,_query+1,_fragment-_query-1,encoding),parameters,encoding,-1);
+    }
+
+    public void decodeQueryTo(MultiMap<String> parameters, Charset encoding) throws UnsupportedEncodingException
+    {
+        if (_query==_fragment)
+            return;
+
+        if (encoding==null || StandardCharsets.UTF_8.equals(encoding))
+            UrlEncoded.decodeUtf8To(_raw,_query+1,_fragment-_query-1,parameters);
+        else
+            UrlEncoded.decodeTo(new String(_raw,_query+1,_fragment-_query-1,encoding),parameters,encoding,-1);
+    }
+
+    public void clear()
+    {
+        _scheme=_authority=_host=_port=_path=_param=_query=_fragment=_end=0;
+        _raw=__empty;
+        _rawString="";
+        _encoded=false;
+    }
+
+    @Override
+    public String toString()
+    {
+        if (_rawString==null)
+            _rawString=new String(_raw,_scheme,_end-_scheme,_charset);
+        return _rawString;
+    }
+
+    public void writeTo(Utf8StringBuilder buf)
+    {
+        buf.append(_raw,_scheme,_end-_scheme);
+    }
+
+}
diff --git a/lib/jetty/org/eclipse/jetty/http/HttpVersion.java b/lib/jetty/org/eclipse/jetty/http/HttpVersion.java
new file mode 100644 (file)
index 0000000..eb889e5
--- /dev/null
@@ -0,0 +1,172 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.http;
+
+import java.nio.ByteBuffer;
+
+import org.eclipse.jetty.util.ArrayTrie;
+import org.eclipse.jetty.util.StringUtil;
+import org.eclipse.jetty.util.Trie;
+
+
+/* ------------------------------------------------------------------------------- */
+public enum HttpVersion
+{
+    HTTP_0_9("HTTP/0.9",9),
+    HTTP_1_0("HTTP/1.0",10),
+    HTTP_1_1("HTTP/1.1",11),
+    HTTP_2_0("HTTP/2.0",20);
+
+    /* ------------------------------------------------------------ */
+    public final static Trie<HttpVersion> CACHE= new ArrayTrie<HttpVersion>();
+    static
+    {
+        for (HttpVersion version : HttpVersion.values())
+            CACHE.put(version.toString(),version);
+    }
+
+    /* ------------------------------------------------------------ */
+    /** 
+     * Optimised lookup to find a Http Version and whitespace in a byte array.
+     * @param bytes Array containing ISO-8859-1 characters
+     * @param position The first valid index
+     * @param limit The first non valid index
+     * @return A HttpMethod if a match or null if no easy match.
+     */
+    public static HttpVersion lookAheadGet(byte[] bytes, int position, int limit)
+    {
+        int length=limit-position;
+        if (length<9)
+            return null;
+
+        if (bytes[position+4]=='/' && bytes[position+6]=='.' && Character.isWhitespace((char)bytes[position+8]) &&
+            ((bytes[position]=='H' &&  bytes[position+1]=='T' && bytes[position+2]=='T' && bytes[position+3]=='P') ||
+             (bytes[position]=='h' &&  bytes[position+1]=='t' && bytes[position+2]=='t' && bytes[position+3]=='p')))
+        {
+            switch(bytes[position+5])
+            {
+                case '1':
+                    switch(bytes[position+7])
+                    {
+                        case '0':
+                            return HTTP_1_0;
+                        case '1':
+                            return HTTP_1_1;
+                    }
+                    break;
+                case '2':
+                    switch(bytes[position+7])
+                    {
+                        case '0':
+                            return HTTP_2_0;
+                    }
+                    break;
+            }
+        }
+        
+        return null;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** 
+     * Optimised lookup to find a HTTP Version and trailing white space in a byte array.
+     * @param buffer buffer containing ISO-8859-1 characters
+     * @return A HttpVersion if a match or null if no easy match.
+     */
+    public static HttpVersion lookAheadGet(ByteBuffer buffer)
+    {
+        if (buffer.hasArray())
+            return lookAheadGet(buffer.array(),buffer.arrayOffset()+buffer.position(),buffer.arrayOffset()+buffer.limit());
+        return null;
+    }
+    
+    
+    private final String _string;
+    private final byte[] _bytes;
+    private final ByteBuffer _buffer;
+    private final int _version;
+
+    /* ------------------------------------------------------------ */
+    HttpVersion(String s,int version)
+    {
+        _string=s;
+        _bytes=StringUtil.getBytes(s);
+        _buffer=ByteBuffer.wrap(_bytes);
+        _version=version;
+    }
+
+    /* ------------------------------------------------------------ */
+    public byte[] toBytes()
+    {
+        return _bytes;
+    }
+
+    /* ------------------------------------------------------------ */
+    public ByteBuffer toBuffer()
+    {
+        return _buffer.asReadOnlyBuffer();
+    }
+
+    /* ------------------------------------------------------------ */
+    public int getVersion()
+    {
+        return _version;
+    }
+
+    /* ------------------------------------------------------------ */
+    public boolean is(String s)
+    {
+        return _string.equalsIgnoreCase(s);    
+    }
+    
+    /* ------------------------------------------------------------ */
+    public String asString()
+    {
+        return _string;
+    }
+    
+    /* ------------------------------------------------------------ */
+    @Override
+    public String toString()
+    {
+        return _string;
+    }
+
+    /**
+     * Case insensitive fromString() conversion
+     * @param version the String to convert to enum constant
+     * @return the enum constant or null if version unknown
+     */
+    public static HttpVersion fromString(String version)
+    {
+        return CACHE.get(version);
+    }
+
+    /* ------------------------------------------------------------ */
+    public static HttpVersion fromVersion(int version)
+    {
+        switch(version)
+        {
+            case 9: return HttpVersion.HTTP_0_9;
+            case 10: return HttpVersion.HTTP_1_0;
+            case 11: return HttpVersion.HTTP_1_1;
+            default: throw new IllegalArgumentException();
+        }
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/http/MimeTypes.java b/lib/jetty/org/eclipse/jetty/http/MimeTypes.java
new file mode 100644 (file)
index 0000000..9cac4ab
--- /dev/null
@@ -0,0 +1,485 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.http;
+
+import java.nio.ByteBuffer;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.MissingResourceException;
+import java.util.ResourceBundle;
+import java.util.Set;
+
+import org.eclipse.jetty.util.ArrayTrie;
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.StringUtil;
+import org.eclipse.jetty.util.Trie;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+
+/* ------------------------------------------------------------ */
+/**
+ * 
+ */
+public class MimeTypes
+{
+    public enum Type
+    {
+        FORM_ENCODED("application/x-www-form-urlencoded"),
+        MESSAGE_HTTP("message/http"),
+        MULTIPART_BYTERANGES("multipart/byteranges"),
+
+        TEXT_HTML("text/html"),
+        TEXT_PLAIN("text/plain"),
+        TEXT_XML("text/xml"),
+        TEXT_JSON("text/json",StandardCharsets.UTF_8),
+        APPLICATION_JSON("application/json",StandardCharsets.UTF_8),
+
+        TEXT_HTML_8859_1("text/html; charset=ISO-8859-1",TEXT_HTML),
+        TEXT_HTML_UTF_8("text/html; charset=UTF-8",TEXT_HTML),
+        
+        TEXT_PLAIN_8859_1("text/plain; charset=ISO-8859-1",TEXT_PLAIN),
+        TEXT_PLAIN_UTF_8("text/plain; charset=UTF-8",TEXT_PLAIN),
+        
+        TEXT_XML_8859_1("text/xml; charset=ISO-8859-1",TEXT_XML),
+        TEXT_XML_UTF_8("text/xml; charset=UTF-8",TEXT_XML),
+        
+        TEXT_JSON_8859_1("text/json; charset=ISO-8859-1",TEXT_JSON),
+        TEXT_JSON_UTF_8("text/json; charset=UTF-8",TEXT_JSON),
+        
+        APPLICATION_JSON_8859_1("text/json; charset=ISO-8859-1",APPLICATION_JSON),
+        APPLICATION_JSON_UTF_8("text/json; charset=UTF-8",APPLICATION_JSON);
+
+
+        /* ------------------------------------------------------------ */
+        private final String _string;
+        private final Type _base;
+        private final ByteBuffer _buffer;
+        private final Charset _charset;
+        private final boolean _assumedCharset;
+        private final HttpField _field;
+
+        /* ------------------------------------------------------------ */
+        Type(String s)
+        {
+            _string=s;
+            _buffer=BufferUtil.toBuffer(s);
+            _base=this;
+            _charset=null;
+            _assumedCharset=false;
+            _field=new HttpGenerator.CachedHttpField(HttpHeader.CONTENT_TYPE,_string);
+        } 
+
+        /* ------------------------------------------------------------ */
+        Type(String s,Type base)
+        {
+            _string=s;
+            _buffer=BufferUtil.toBuffer(s);
+            _base=base;
+            int i=s.indexOf("; charset=");
+            _charset=Charset.forName(s.substring(i+10));
+            _assumedCharset=false;
+            _field=new HttpGenerator.CachedHttpField(HttpHeader.CONTENT_TYPE,_string);
+        }
+
+        /* ------------------------------------------------------------ */
+        Type(String s,Charset cs)
+        {
+            _string=s;
+            _base=this;
+            _buffer=BufferUtil.toBuffer(s);
+            _charset=cs;
+            _assumedCharset=true;
+            _field=new HttpGenerator.CachedHttpField(HttpHeader.CONTENT_TYPE,_string);
+        }
+
+        /* ------------------------------------------------------------ */
+        public ByteBuffer asBuffer()
+        {
+            return _buffer.asReadOnlyBuffer();
+        }
+        
+        /* ------------------------------------------------------------ */
+        public Charset getCharset()
+        {
+            return _charset;
+        }
+        
+        /* ------------------------------------------------------------ */
+        public boolean is(String s)
+        {
+            return _string.equalsIgnoreCase(s);    
+        }
+
+        /* ------------------------------------------------------------ */
+        public String asString()
+        {
+            return _string;
+        }
+        
+        /* ------------------------------------------------------------ */
+        @Override
+        public String toString()
+        {
+            return _string;
+        }
+
+        /* ------------------------------------------------------------ */
+        public boolean isCharsetAssumed()
+        {
+            return _assumedCharset;
+        }
+
+        /* ------------------------------------------------------------ */
+        public HttpField getContentTypeField()
+        {
+            return _field;
+        }
+
+        /* ------------------------------------------------------------ */
+        public Type getBaseType()
+        {
+            return _base;
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    private static final Logger LOG = Log.getLogger(MimeTypes.class);
+    public  final static Trie<MimeTypes.Type> CACHE= new ArrayTrie<>(512);
+    private final static Trie<ByteBuffer> TYPES= new ArrayTrie<ByteBuffer>(512);
+    private final static Map<String,String> __dftMimeMap = new HashMap<String,String>();
+    private final static Map<String,String> __encodings = new HashMap<String,String>();
+
+    static
+    {
+        for (MimeTypes.Type type : MimeTypes.Type.values())
+        {
+            CACHE.put(type.toString(),type);
+            TYPES.put(type.toString(),type.asBuffer());
+
+            int charset=type.toString().indexOf(";charset=");
+            if (charset>0)
+            {
+                CACHE.put(type.toString().replace(";charset=","; charset="),type);
+                TYPES.put(type.toString().replace(";charset=","; charset="),type.asBuffer());
+            }
+        }
+
+        try
+        {
+            ResourceBundle mime = ResourceBundle.getBundle("org/eclipse/jetty/http/mime");
+            Enumeration<String> i = mime.getKeys();
+            while(i.hasMoreElements())
+            {
+                String ext = i.nextElement();
+                String m = mime.getString(ext);
+                __dftMimeMap.put(StringUtil.asciiToLowerCase(ext),normalizeMimeType(m));
+            }
+        }
+        catch(MissingResourceException e)
+        {
+            LOG.warn(e.toString());
+            LOG.debug(e);
+        }
+
+        try
+        {
+            ResourceBundle encoding = ResourceBundle.getBundle("org/eclipse/jetty/http/encoding");
+            Enumeration<String> i = encoding.getKeys();
+            while(i.hasMoreElements())
+            {
+                String type = i.nextElement();
+                __encodings.put(type,encoding.getString(type));
+            }
+        }
+        catch(MissingResourceException e)
+        {
+            LOG.warn(e.toString());
+            LOG.debug(e);
+        }
+    }
+
+
+    /* ------------------------------------------------------------ */
+    private final Map<String,String> _mimeMap=new HashMap<String,String>();
+
+    /* ------------------------------------------------------------ */
+    /** Constructor.
+     */
+    public MimeTypes()
+    {
+    }
+
+    /* ------------------------------------------------------------ */
+    public synchronized Map<String,String> getMimeMap()
+    {
+        return _mimeMap;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param mimeMap A Map of file extension to mime-type.
+     */
+    public void setMimeMap(Map<String,String> mimeMap)
+    {
+        _mimeMap.clear();
+        if (mimeMap!=null)
+        {
+            for (Entry<String, String> ext : mimeMap.entrySet())
+                _mimeMap.put(StringUtil.asciiToLowerCase(ext.getKey()),normalizeMimeType(ext.getValue()));
+        }
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** Get the MIME type by filename extension.
+     * @param filename A file name
+     * @return MIME type matching the longest dot extension of the
+     * file name.
+     */
+    public String getMimeByExtension(String filename)
+    {
+        String type=null;
+
+        if (filename!=null)
+        {
+            int i=-1;
+            while(type==null)
+            {
+                i=filename.indexOf(".",i+1);
+
+                if (i<0 || i>=filename.length())
+                    break;
+
+                String ext=StringUtil.asciiToLowerCase(filename.substring(i+1));
+                if (_mimeMap!=null)
+                    type=_mimeMap.get(ext);
+                if (type==null)
+                    type=__dftMimeMap.get(ext);
+            }
+        }
+
+        if (type==null)
+        {
+            if (_mimeMap!=null)
+                type=_mimeMap.get("*");
+            if (type==null)
+                type=__dftMimeMap.get("*");
+        }
+
+        return type;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Set a mime mapping
+     * @param extension
+     * @param type
+     */
+    public void addMimeMapping(String extension,String type)
+    {
+        _mimeMap.put(StringUtil.asciiToLowerCase(extension),normalizeMimeType(type));
+    }
+
+    /* ------------------------------------------------------------ */
+    public static Set<String> getKnownMimeTypes()
+    {
+        return new HashSet<>(__dftMimeMap.values());
+    }
+    
+    /* ------------------------------------------------------------ */
+    private static String normalizeMimeType(String type)
+    {
+        MimeTypes.Type t =CACHE.get(type);
+        if (t!=null)
+            return t.asString();
+
+        return StringUtil.asciiToLowerCase(type);
+    }
+
+    /* ------------------------------------------------------------ */
+    public static String getCharsetFromContentType(String value)
+    {
+        if (value==null)
+            return null;
+        int end=value.length();
+        int state=0;
+        int start=0;
+        boolean quote=false;
+        int i=0;
+        for (;i<end;i++)
+        {
+            char b = value.charAt(i);
+
+            if (quote && state!=10)
+            {
+                if ('"'==b)
+                    quote=false;
+                continue;
+            }
+
+            switch(state)
+            {
+                case 0:
+                    if ('"'==b)
+                    {
+                        quote=true;
+                        break;
+                    }
+                    if (';'==b)
+                        state=1;
+                    break;
+
+                case 1: if ('c'==b) state=2; else if (' '!=b) state=0; break;
+                case 2: if ('h'==b) state=3; else state=0;break;
+                case 3: if ('a'==b) state=4; else state=0;break;
+                case 4: if ('r'==b) state=5; else state=0;break;
+                case 5: if ('s'==b) state=6; else state=0;break;
+                case 6: if ('e'==b) state=7; else state=0;break;
+                case 7: if ('t'==b) state=8; else state=0;break;
+
+                case 8: if ('='==b) state=9; else if (' '!=b) state=0; break;
+
+                case 9:
+                    if (' '==b)
+                        break;
+                    if ('"'==b)
+                    {
+                        quote=true;
+                        start=i+1;
+                        state=10;
+                        break;
+                    }
+                    start=i;
+                    state=10;
+                    break;
+
+                case 10:
+                    if (!quote && (';'==b || ' '==b )||
+                            (quote && '"'==b ))
+                        return StringUtil.normalizeCharset(value,start,i-start);
+            }
+        }
+
+        if (state==10)
+            return StringUtil.normalizeCharset(value,start,i-start);
+
+        return null;
+    }
+
+    public static String inferCharsetFromContentType(String value)
+    {
+        return __encodings.get(value);
+    }
+    
+    public static String getContentTypeWithoutCharset(String value)
+    {
+        int end=value.length();
+        int state=0;
+        int start=0;
+        boolean quote=false;
+        int i=0;
+        StringBuilder builder=null;
+        for (;i<end;i++)
+        {
+            char b = value.charAt(i);
+
+            if ('"'==b)
+            {
+                if (quote)
+                {
+                    quote=false;
+                }
+                else
+                {
+                    quote=true;
+                }
+                
+                switch(state)
+                {
+                    case 11:
+                        builder.append(b);break;
+                    case 10:
+                        break;
+                    case 9:
+                        builder=new StringBuilder();
+                        builder.append(value,0,start+1);
+                        state=10;
+                        break;
+                    default:
+                        start=i;
+                        state=0;           
+                }
+                continue;
+            }
+            
+            if (quote)
+            {
+                if (builder!=null && state!=10)
+                    builder.append(b);
+                continue;
+            }
+
+            switch(state)
+            {
+                case 0:
+                    if (';'==b)
+                        state=1;
+                    else if (' '!=b)
+                        start=i;
+                    break;
+
+                case 1: if ('c'==b) state=2; else if (' '!=b) state=0; break;
+                case 2: if ('h'==b) state=3; else state=0;break;
+                case 3: if ('a'==b) state=4; else state=0;break;
+                case 4: if ('r'==b) state=5; else state=0;break;
+                case 5: if ('s'==b) state=6; else state=0;break;
+                case 6: if ('e'==b) state=7; else state=0;break;
+                case 7: if ('t'==b) state=8; else state=0;break;
+                case 8: if ('='==b) state=9; else if (' '!=b) state=0; break;
+
+                case 9:
+                    if (' '==b)
+                        break;
+                    builder=new StringBuilder();
+                    builder.append(value,0,start+1);
+                    state=10;
+                    break;
+
+                case 10:
+                    if (';'==b)
+                    {
+                        builder.append(b);
+                        state=11;
+                    }
+                    break;
+                case 11:
+                    if (' '!=b)
+                        builder.append(b);
+            }
+        }
+        if (builder==null)
+            return value;
+        return builder.toString();
+
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/http/PathMap.java b/lib/jetty/org/eclipse/jetty/http/PathMap.java
new file mode 100644 (file)
index 0000000..b78f572
--- /dev/null
@@ -0,0 +1,572 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.http;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.StringTokenizer;
+
+import org.eclipse.jetty.util.ArrayTernaryTrie;
+import org.eclipse.jetty.util.Trie;
+import org.eclipse.jetty.util.URIUtil;
+
+/* ------------------------------------------------------------ */
+/** URI path map to Object.
+ * This mapping implements the path specification recommended
+ * in the 2.2 Servlet API.
+ *
+ * Path specifications can be of the following forms:<PRE>
+ * /foo/bar           - an exact path specification.
+ * /foo/*             - a prefix path specification (must end '/*').
+ * *.ext              - a suffix path specification.
+ * /                  - the default path specification.
+ * ""                 - the / path specification
+ * </PRE>
+ * Matching is performed in the following order <NL>
+ * <LI>Exact match.
+ * <LI>Longest prefix match.
+ * <LI>Longest suffix match.
+ * <LI>default.
+ * </NL>
+ * Multiple path specifications can be mapped by providing a list of
+ * specifications. By default this class uses characters ":," as path
+ * separators, unless configured differently by calling the static
+ * method @see PathMap#setPathSpecSeparators(String)
+ * <P>
+ * Special characters within paths such as '?� and ';' are not treated specially
+ * as it is assumed they would have been either encoded in the original URL or
+ * stripped from the path.
+ * <P>
+ * This class is not synchronized.  If concurrent modifications are
+ * possible then it should be synchronized at a higher level.
+ *
+ *
+ */
+public class PathMap<O> extends HashMap<String,O>
+{
+    /* ------------------------------------------------------------ */
+    private static String __pathSpecSeparators = ":,";
+
+    /* ------------------------------------------------------------ */
+    /** Set the path spec separator.
+     * Multiple path specification may be included in a single string
+     * if they are separated by the characters set in this string.
+     * By default this class uses ":," characters as path separators.
+     * @param s separators
+     */
+    public static void setPathSpecSeparators(String s)
+    {
+        __pathSpecSeparators=s;
+    }
+
+    /* --------------------------------------------------------------- */
+    Trie<MappedEntry<O>> _prefixMap=new ArrayTernaryTrie<>(false);
+    Trie<MappedEntry<O>> _suffixMap=new ArrayTernaryTrie<>(false);
+    final Map<String,MappedEntry<O>> _exactMap=new HashMap<>();
+
+    List<MappedEntry<O>> _defaultSingletonList=null;
+    MappedEntry<O> _prefixDefault=null;
+    MappedEntry<O> _default=null;
+    boolean _nodefault=false;
+
+    /* --------------------------------------------------------------- */
+    public PathMap()
+    {
+        this(11);
+    }
+
+    /* --------------------------------------------------------------- */
+    public PathMap(boolean noDefault)
+    {
+        this(11, noDefault);
+    }
+
+    /* --------------------------------------------------------------- */
+    public PathMap(int capacity)
+    {
+        this(capacity, false);
+    }
+
+    /* --------------------------------------------------------------- */
+    private PathMap(int capacity, boolean noDefault)
+    {
+        super(capacity);
+        _nodefault=noDefault;
+    }
+
+    /* --------------------------------------------------------------- */
+    /** Construct from dictionary PathMap.
+     */
+    public PathMap(Map<String, ? extends O> m)
+    {
+        putAll(m);
+    }
+
+    /* --------------------------------------------------------------- */
+    /** Add a single path match to the PathMap.
+     * @param pathSpec The path specification, or comma separated list of
+     * path specifications.
+     * @param object The object the path maps to
+     */
+    @Override
+    public O put(String pathSpec, O object)
+    {
+        if ("".equals(pathSpec.trim()))
+        {
+            MappedEntry<O> entry = new MappedEntry<>("",object);
+            entry.setMapped("");
+            _exactMap.put("", entry);
+            return super.put("", object);
+        }
+
+        StringTokenizer tok = new StringTokenizer(pathSpec,__pathSpecSeparators);
+        O old =null;
+
+        while (tok.hasMoreTokens())
+        {
+            String spec=tok.nextToken();
+
+            if (!spec.startsWith("/") && !spec.startsWith("*."))
+                throw new IllegalArgumentException("PathSpec "+spec+". must start with '/' or '*.'");
+
+            old = super.put(spec,object);
+
+            // Make entry that was just created.
+            MappedEntry<O> entry = new MappedEntry<>(spec,object);
+
+            if (entry.getKey().equals(spec))
+            {
+                if (spec.equals("/*"))
+                    _prefixDefault=entry;
+                else if (spec.endsWith("/*"))
+                {
+                    String mapped=spec.substring(0,spec.length()-2);
+                    entry.setMapped(mapped);
+                    while (!_prefixMap.put(mapped,entry))
+                        _prefixMap=new ArrayTernaryTrie<>((ArrayTernaryTrie<MappedEntry<O>>)_prefixMap,1.5);
+                }
+                else if (spec.startsWith("*."))
+                {
+                    String suffix=spec.substring(2);
+                    while(!_suffixMap.put(suffix,entry))
+                        _suffixMap=new ArrayTernaryTrie<>((ArrayTernaryTrie<MappedEntry<O>>)_suffixMap,1.5);
+                }
+                else if (spec.equals(URIUtil.SLASH))
+                {
+                    if (_nodefault)
+                        _exactMap.put(spec,entry);
+                    else
+                    {
+                        _default=entry;
+                        _defaultSingletonList=Collections.singletonList(_default);
+                    }
+                }
+                else
+                {
+                    entry.setMapped(spec);
+                    _exactMap.put(spec,entry);
+                }
+            }
+        }
+
+        return old;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Get object matched by the path.
+     * @param path the path.
+     * @return Best matched object or null.
+     */
+    public O match(String path)
+    {
+        MappedEntry<O> entry = getMatch(path);
+        if (entry!=null)
+            return entry.getValue();
+        return null;
+    }
+
+
+    /* --------------------------------------------------------------- */
+    /** Get the entry mapped by the best specification.
+     * @param path the path.
+     * @return Map.Entry of the best matched  or null.
+     */
+    public MappedEntry<O> getMatch(String path)
+    {
+        if (path==null)
+            return null;
+
+        int l=path.length();
+
+        MappedEntry<O> entry=null;
+
+        //special case
+        if (l == 1 && path.charAt(0)=='/')
+        {
+            entry = _exactMap.get("");
+            if (entry != null)
+                return entry;
+        }
+
+        // try exact match
+        entry=_exactMap.get(path);
+        if (entry!=null)
+            return entry;
+
+        // prefix search
+        int i=l;
+        final Trie<PathMap.MappedEntry<O>> prefix_map=_prefixMap;
+        while(i>=0)
+        {
+            entry=prefix_map.getBest(path,0,i);
+            if (entry==null)
+                break;
+            String key = entry.getKey();
+            if (key.length()-2>=path.length() || path.charAt(key.length()-2)=='/')
+                return entry;
+            i=key.length()-3;
+        }
+
+        // Prefix Default
+        if (_prefixDefault!=null)
+            return _prefixDefault;
+
+        // Extension search
+        i=0;
+        final Trie<PathMap.MappedEntry<O>> suffix_map=_suffixMap;
+        while ((i=path.indexOf('.',i+1))>0)
+        {
+            entry=suffix_map.get(path,i+1,l-i-1);
+            if (entry!=null)
+                return entry;
+        }
+
+        // Default
+        return _default;
+    }
+
+    /* --------------------------------------------------------------- */
+    /** Get all entries matched by the path.
+     * Best match first.
+     * @param path Path to match
+     * @return List of Map.Entry instances key=pathSpec
+     */
+    public List<? extends Map.Entry<String,O>> getMatches(String path)
+    {
+        MappedEntry<O> entry;
+        List<MappedEntry<O>> entries=new ArrayList<>();
+
+        if (path==null)
+            return entries;
+        if (path.length()==0)
+            return _defaultSingletonList;
+
+        // try exact match
+        entry=_exactMap.get(path);
+        if (entry!=null)
+            entries.add(entry);
+
+        // prefix search
+        int l=path.length();
+        int i=l;
+        final Trie<PathMap.MappedEntry<O>> prefix_map=_prefixMap;
+        while(i>=0)
+        {
+            entry=prefix_map.getBest(path,0,i);
+            if (entry==null)
+                break;
+            String key = entry.getKey();
+            if (key.length()-2>=path.length() || path.charAt(key.length()-2)=='/')
+                entries.add(entry);
+
+            i=key.length()-3;
+        }
+
+        // Prefix Default
+        if (_prefixDefault!=null)
+            entries.add(_prefixDefault);
+
+        // Extension search
+        i=0;
+        final Trie<PathMap.MappedEntry<O>> suffix_map=_suffixMap;
+        while ((i=path.indexOf('.',i+1))>0)
+        {
+            entry=suffix_map.get(path,i+1,l-i-1);
+            if (entry!=null)
+                entries.add(entry);
+        }
+
+        // root match
+        if ("/".equals(path))
+        {
+            entry=_exactMap.get("");
+            if (entry!=null)
+                entries.add(entry);
+        }
+            
+        // Default
+        if (_default!=null)
+            entries.add(_default);
+
+        return entries;
+    }
+
+
+    /* --------------------------------------------------------------- */
+    /** Return whether the path matches any entries in the PathMap,
+     * excluding the default entry
+     * @param path Path to match
+     * @return Whether the PathMap contains any entries that match this
+     */
+    public boolean containsMatch(String path)
+    {
+        MappedEntry<?> match = getMatch(path);
+        return match!=null && !match.equals(_default);
+    }
+
+    /* --------------------------------------------------------------- */
+    @Override
+    public O remove(Object pathSpec)
+    {
+        if (pathSpec!=null)
+        {
+            String spec=(String) pathSpec;
+            if (spec.equals("/*"))
+                _prefixDefault=null;
+            else if (spec.endsWith("/*"))
+                _prefixMap.remove(spec.substring(0,spec.length()-2));
+            else if (spec.startsWith("*."))
+                _suffixMap.remove(spec.substring(2));
+            else if (spec.equals(URIUtil.SLASH))
+            {
+                _default=null;
+                _defaultSingletonList=null;
+            }
+            else
+                _exactMap.remove(spec);
+        }
+        return super.remove(pathSpec);
+    }
+
+    /* --------------------------------------------------------------- */
+    @Override
+    public void clear()
+    {
+        _exactMap.clear();
+        _prefixMap=new ArrayTernaryTrie<>(false);
+        _suffixMap=new ArrayTernaryTrie<>(false);
+        _default=null;
+        _defaultSingletonList=null;
+        _prefixDefault=null;
+        super.clear();
+    }
+
+    /* --------------------------------------------------------------- */
+    /**
+     * @return true if match.
+     */
+    public static boolean match(String pathSpec, String path)
+        throws IllegalArgumentException
+    {
+        return match(pathSpec, path, false);
+    }
+
+    /* --------------------------------------------------------------- */
+    /**
+     * @return true if match.
+     */
+    public static boolean match(String pathSpec, String path, boolean noDefault)
+        throws IllegalArgumentException
+    {
+        if (pathSpec.length()==0)
+            return "/".equals(path);
+            
+        char c = pathSpec.charAt(0);
+        if (c=='/')
+        {
+            if (!noDefault && pathSpec.length()==1 || pathSpec.equals(path))
+                return true;
+
+            if(isPathWildcardMatch(pathSpec, path))
+                return true;
+        }
+        else if (c=='*')
+            return path.regionMatches(path.length()-pathSpec.length()+1,
+                    pathSpec,1,pathSpec.length()-1);
+        return false;
+    }
+
+    /* --------------------------------------------------------------- */
+    private static boolean isPathWildcardMatch(String pathSpec, String path)
+    {
+        // For a spec of "/foo/*" match "/foo" , "/foo/..." but not "/foobar"
+        int cpl=pathSpec.length()-2;
+        if (pathSpec.endsWith("/*") && path.regionMatches(0,pathSpec,0,cpl))
+        {
+            if (path.length()==cpl || '/'==path.charAt(cpl))
+                return true;
+        }
+        return false;
+    }
+
+
+    /* --------------------------------------------------------------- */
+    /** Return the portion of a path that matches a path spec.
+     * @return null if no match at all.
+     */
+    public static String pathMatch(String pathSpec, String path)
+    {
+        char c = pathSpec.charAt(0);
+
+        if (c=='/')
+        {
+            if (pathSpec.length()==1)
+                return path;
+
+            if (pathSpec.equals(path))
+                return path;
+
+            if (isPathWildcardMatch(pathSpec, path))
+                return path.substring(0,pathSpec.length()-2);
+        }
+        else if (c=='*')
+        {
+            if (path.regionMatches(path.length()-(pathSpec.length()-1),
+                    pathSpec,1,pathSpec.length()-1))
+                return path;
+        }
+        return null;
+    }
+
+    /* --------------------------------------------------------------- */
+    /** Return the portion of a path that is after a path spec.
+     * @return The path info string
+     */
+    public static String pathInfo(String pathSpec, String path)
+    {
+        if ("".equals(pathSpec))
+            return path; //servlet 3 spec sec 12.2 will be '/'
+
+        char c = pathSpec.charAt(0);
+
+        if (c=='/')
+        {
+            if (pathSpec.length()==1)
+                return null;
+
+            boolean wildcard = isPathWildcardMatch(pathSpec, path);
+
+            // handle the case where pathSpec uses a wildcard and path info is "/*"
+            if (pathSpec.equals(path) && !wildcard)
+                return null;
+
+            if (wildcard)
+            {
+                if (path.length()==pathSpec.length()-2)
+                    return null;
+                return path.substring(pathSpec.length()-2);
+            }
+        }
+        return null;
+    }
+
+
+    /* ------------------------------------------------------------ */
+    /** Relative path.
+     * @param base The base the path is relative to.
+     * @param pathSpec The spec of the path segment to ignore.
+     * @param path the additional path
+     * @return base plus path with pathspec removed
+     */
+    public static String relativePath(String base,
+            String pathSpec,
+            String path )
+    {
+        String info=pathInfo(pathSpec,path);
+        if (info==null)
+            info=path;
+
+        if( info.startsWith( "./"))
+            info = info.substring( 2);
+        if( base.endsWith( URIUtil.SLASH))
+            if( info.startsWith( URIUtil.SLASH))
+                path = base + info.substring(1);
+            else
+                path = base + info;
+        else
+            if( info.startsWith( URIUtil.SLASH))
+                path = base + info;
+            else
+                path = base + URIUtil.SLASH + info;
+        return path;
+    }
+
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    public static class MappedEntry<O> implements Map.Entry<String,O>
+    {
+        private final String key;
+        private final O value;
+        private String mapped;
+
+        MappedEntry(String key, O value)
+        {
+            this.key=key;
+            this.value=value;
+        }
+
+        @Override
+        public String getKey()
+        {
+            return key;
+        }
+
+        @Override
+        public O getValue()
+        {
+            return value;
+        }
+
+        @Override
+        public O setValue(O o)
+        {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public String toString()
+        {
+            return key+"="+value;
+        }
+
+        public String getMapped()
+        {
+            return mapped;
+        }
+
+        void setMapped(String mapped)
+        {
+            this.mapped = mapped;
+        }
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/http/encoding.properties b/lib/jetty/org/eclipse/jetty/http/encoding.properties
new file mode 100644 (file)
index 0000000..311c802
--- /dev/null
@@ -0,0 +1,4 @@
+text/html      = ISO-8859-1
+text/plain     = ISO-8859-1
+text/xml       = UTF-8
+text/json   = UTF-8
diff --git a/lib/jetty/org/eclipse/jetty/http/mime.properties b/lib/jetty/org/eclipse/jetty/http/mime.properties
new file mode 100644 (file)
index 0000000..b270989
--- /dev/null
@@ -0,0 +1,183 @@
+ai=application/postscript
+aif=audio/x-aiff
+aifc=audio/x-aiff
+aiff=audio/x-aiff
+apk=application/vnd.android.package-archive
+asc=text/plain
+asf=video/x.ms.asf
+asx=video/x.ms.asx
+au=audio/basic
+avi=video/x-msvideo
+bcpio=application/x-bcpio
+bin=application/octet-stream
+cab=application/x-cabinet
+cdf=application/x-netcdf
+class=application/java-vm
+cpio=application/x-cpio
+cpt=application/mac-compactpro
+crt=application/x-x509-ca-cert
+csh=application/x-csh
+css=text/css
+csv=text/comma-separated-values
+dcr=application/x-director
+dir=application/x-director
+dll=application/x-msdownload
+dms=application/octet-stream
+doc=application/msword
+dtd=application/xml-dtd
+dvi=application/x-dvi
+dxr=application/x-director
+eps=application/postscript
+etx=text/x-setext
+exe=application/octet-stream
+ez=application/andrew-inset
+gif=image/gif
+gtar=application/x-gtar
+gz=application/gzip
+gzip=application/gzip
+hdf=application/x-hdf
+hqx=application/mac-binhex40
+htc=text/x-component
+htm=text/html
+html=text/html
+ice=x-conference/x-cooltalk
+ico=image/x-icon
+ief=image/ief
+iges=model/iges
+igs=model/iges
+jad=text/vnd.sun.j2me.app-descriptor
+jar=application/java-archive
+java=text/plain
+jnlp=application/x-java-jnlp-file
+jpe=image/jpeg
+jpeg=image/jpeg
+jpg=image/jpeg
+js=application/javascript
+json=application/json
+jsp=text/html
+kar=audio/midi
+latex=application/x-latex
+lha=application/octet-stream
+lzh=application/octet-stream
+man=application/x-troff-man
+mathml=application/mathml+xml
+me=application/x-troff-me
+mesh=model/mesh
+mid=audio/midi
+midi=audio/midi
+mif=application/vnd.mif
+mol=chemical/x-mdl-molfile
+mov=video/quicktime
+movie=video/x-sgi-movie
+mp2=audio/mpeg
+mp3=audio/mpeg
+mpe=video/mpeg
+mpeg=video/mpeg
+mpg=video/mpeg
+mpga=audio/mpeg
+ms=application/x-troff-ms
+msh=model/mesh
+msi=application/octet-stream
+nc=application/x-netcdf
+oda=application/oda
+odb=application/vnd.oasis.opendocument.database
+odc=application/vnd.oasis.opendocument.chart
+odf=application/vnd.oasis.opendocument.formula
+odg=application/vnd.oasis.opendocument.graphics
+odi=application/vnd.oasis.opendocument.image
+odm=application/vnd.oasis.opendocument.text-master
+odp=application/vnd.oasis.opendocument.presentation
+ods=application/vnd.oasis.opendocument.spreadsheet
+odt=application/vnd.oasis.opendocument.text
+ogg=application/ogg
+otc=application/vnd.oasis.opendocument.chart-template
+otf=application/vnd.oasis.opendocument.formula-template
+otg=application/vnd.oasis.opendocument.graphics-template
+oth=application/vnd.oasis.opendocument.text-web
+oti=application/vnd.oasis.opendocument.image-template
+otp=application/vnd.oasis.opendocument.presentation-template
+ots=application/vnd.oasis.opendocument.spreadsheet-template
+ott=application/vnd.oasis.opendocument.text-template
+pbm=image/x-portable-bitmap
+pdb=chemical/x-pdb
+pdf=application/pdf
+pgm=image/x-portable-graymap
+pgn=application/x-chess-pgn
+png=image/png
+pnm=image/x-portable-anymap
+ppm=image/x-portable-pixmap
+pps=application/vnd.ms-powerpoint
+ppt=application/vnd.ms-powerpoint
+ps=application/postscript
+qml=text/x-qml
+qt=video/quicktime
+ra=audio/x-pn-realaudio
+ram=audio/x-pn-realaudio
+ras=image/x-cmu-raster
+rdf=application/rdf+xml
+rgb=image/x-rgb
+rm=audio/x-pn-realaudio
+roff=application/x-troff
+rpm=application/x-rpm
+rtf=application/rtf
+rtx=text/richtext
+rv=video/vnd.rn-realvideo
+ser=application/java-serialized-object
+sgm=text/sgml
+sgml=text/sgml
+sh=application/x-sh
+shar=application/x-shar
+silo=model/mesh
+sit=application/x-stuffit
+skd=application/x-koan
+skm=application/x-koan
+skp=application/x-koan
+skt=application/x-koan
+smi=application/smil
+smil=application/smil
+snd=audio/basic
+spl=application/x-futuresplash
+src=application/x-wais-source
+sv4cpio=application/x-sv4cpio
+sv4crc=application/x-sv4crc
+svg=image/svg+xml
+swf=application/x-shockwave-flash
+t=application/x-troff
+tar=application/x-tar
+tar.gz=application/x-gtar
+tcl=application/x-tcl
+tex=application/x-tex
+texi=application/x-texinfo
+texinfo=application/x-texinfo
+tgz=application/x-gtar
+tif=image/tiff
+tiff=image/tiff
+tr=application/x-troff
+tsv=text/tab-separated-values
+txt=text/plain
+ustar=application/x-ustar
+vcd=application/x-cdlink
+vrml=model/vrml
+vxml=application/voicexml+xml
+wav=audio/x-wav
+wbmp=image/vnd.wap.wbmp
+wml=text/vnd.wap.wml
+wmlc=application/vnd.wap.wmlc
+wmls=text/vnd.wap.wmlscript
+wmlsc=application/vnd.wap.wmlscriptc
+wrl=model/vrml
+wtls-ca-certificate=application/vnd.wap.wtls-ca-certificate
+xbm=image/x-xbitmap
+xht=application/xhtml+xml
+xhtml=application/xhtml+xml
+xls=application/vnd.ms-excel
+xml=application/xml
+xpm=image/x-xpixmap
+xsd=application/xml
+xsl=application/xml
+xslt=application/xslt+xml
+xul=application/vnd.mozilla.xul+xml
+xwd=image/x-xwindowdump
+xyz=chemical/x-xyz
+z=application/compress
+zip=application/zip
diff --git a/lib/jetty/org/eclipse/jetty/http/package-info.java b/lib/jetty/org/eclipse/jetty/http/package-info.java
new file mode 100644 (file)
index 0000000..825422a
--- /dev/null
@@ -0,0 +1,23 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+/**
+ * Jetty Http : Tools for Http processing
+ */
+package org.eclipse.jetty.http;
+
diff --git a/lib/jetty/org/eclipse/jetty/http/useragents b/lib/jetty/org/eclipse/jetty/http/useragents
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/lib/jetty/org/eclipse/jetty/io/AbstractConnection.java b/lib/jetty/org/eclipse/jetty/io/AbstractConnection.java
new file mode 100644 (file)
index 0000000..7f8e9f8
--- /dev/null
@@ -0,0 +1,587 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.io;
+
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.Executor;
+import java.util.concurrent.RejectedExecutionException;
+import java.util.concurrent.TimeoutException;
+import java.util.concurrent.atomic.AtomicReference;
+
+import org.eclipse.jetty.util.Callback;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.thread.NonBlockingThread;
+
+/**
+ * <p>A convenience base implementation of {@link Connection}.</p>
+ * <p>This class uses the capabilities of the {@link EndPoint} API to provide a
+ * more traditional style of async reading.  A call to {@link #fillInterested()}
+ * will schedule a callback to {@link #onFillable()} or {@link #onFillInterestedFailed(Throwable)}
+ * as appropriate.</p>
+ */
+public abstract class AbstractConnection implements Connection
+{
+    private static final Logger LOG = Log.getLogger(AbstractConnection.class);
+    
+    public static final boolean EXECUTE_ONFILLABLE=true;
+
+    private final List<Listener> listeners = new CopyOnWriteArrayList<>();
+    private final AtomicReference<State> _state = new AtomicReference<>(IDLE);
+    private final long _created=System.currentTimeMillis();
+    private final EndPoint _endPoint;
+    private final Executor _executor;
+    private final Callback _readCallback;
+    private final boolean _executeOnfillable;
+    private int _inputBufferSize=2048;
+
+    protected AbstractConnection(EndPoint endp, Executor executor)
+    {
+        this(endp,executor,EXECUTE_ONFILLABLE);
+    }
+    
+    protected AbstractConnection(EndPoint endp, Executor executor, final boolean executeOnfillable)
+    {
+        if (executor == null)
+            throw new IllegalArgumentException("Executor must not be null!");
+        _endPoint = endp;
+        _executor = executor;
+        _readCallback = new ReadCallback();
+        _executeOnfillable=executeOnfillable;
+        _state.set(IDLE);
+    }
+
+    @Override
+    public void addListener(Listener listener)
+    {
+        listeners.add(listener);
+    }
+
+    public int getInputBufferSize()
+    {
+        return _inputBufferSize;
+    }
+
+    public void setInputBufferSize(int inputBufferSize)
+    {
+        _inputBufferSize = inputBufferSize;
+    }
+
+    protected Executor getExecutor()
+    {
+        return _executor;
+    }
+    
+    protected void failedCallback(final Callback callback, final Throwable x)
+    {
+        if (NonBlockingThread.isNonBlockingThread())
+        {
+            try
+            {
+                getExecutor().execute(new Runnable()
+                {
+                    @Override
+                    public void run()
+                    {
+                        callback.failed(x);
+                    }
+                });
+            }
+            catch(RejectedExecutionException e)
+            {
+                LOG.debug(e);
+                callback.failed(x);
+            }
+        }
+        else
+        {
+            callback.failed(x);
+        }
+    }
+    
+    /**
+     * <p>Utility method to be called to register read interest.</p>
+     * <p>After a call to this method, {@link #onFillable()} or {@link #onFillInterestedFailed(Throwable)}
+     * will be called back as appropriate.</p>
+     * @see #onFillable()
+     */
+    public void fillInterested()
+    {
+        LOG.debug("fillInterested {}",this);           
+        
+        while(true)
+        {
+            State state=_state.get();
+            if (next(state,state.fillInterested()))
+                break;
+        }
+    }
+    
+    public void fillInterested(Callback callback)
+    {
+        LOG.debug("fillInterested {}",this);
+
+        while(true)
+        {
+            State state=_state.get();
+            // TODO yuck
+            if (state instanceof FillingInterestedCallback && ((FillingInterestedCallback)state)._callback==callback)
+                break;
+            State next=new FillingInterestedCallback(callback,state);
+            if (next(state,next))
+                break;
+        }
+    }
+    
+    /**
+     * <p>Callback method invoked when the endpoint is ready to be read.</p>
+     * @see #fillInterested()
+     */
+    public abstract void onFillable();
+
+    /**
+     * <p>Callback method invoked when the endpoint failed to be ready to be read.</p>
+     * @param cause the exception that caused the failure
+     */
+    protected void onFillInterestedFailed(Throwable cause)
+    {
+        LOG.debug("{} onFillInterestedFailed {}", this, cause);
+        if (_endPoint.isOpen())
+        {
+            boolean close = true;
+            if (cause instanceof TimeoutException)
+                close = onReadTimeout();
+            if (close)
+            {
+                if (_endPoint.isOutputShutdown())
+                    _endPoint.close();
+                else
+                    _endPoint.shutdownOutput();
+            }
+        }
+
+        if (_endPoint.isOpen())
+            fillInterested();        
+    }
+
+    /**
+     * <p>Callback method invoked when the endpoint failed to be ready to be read after a timeout</p>
+     * @return true to signal that the endpoint must be closed, false to keep the endpoint open
+     */
+    protected boolean onReadTimeout()
+    {
+        return true;
+    }
+
+    @Override
+    public void onOpen()
+    {
+        LOG.debug("onOpen {}", this);
+
+        for (Listener listener : listeners)
+            listener.onOpened(this);
+    }
+
+    @Override
+    public void onClose()
+    {
+        LOG.debug("onClose {}",this);
+
+        for (Listener listener : listeners)
+            listener.onClosed(this);
+    }
+
+    @Override
+    public EndPoint getEndPoint()
+    {
+        return _endPoint;
+    }
+
+    @Override
+    public void close()
+    {
+        getEndPoint().close();
+    }
+
+    @Override
+    public int getMessagesIn()
+    {
+        return -1;
+    }
+
+    @Override
+    public int getMessagesOut()
+    {
+        return -1;
+    }
+
+    @Override
+    public long getBytesIn()
+    {
+        return -1;
+    }
+
+    @Override
+    public long getBytesOut()
+    {
+        return -1;
+    }
+
+    @Override
+    public long getCreatedTimeStamp()
+    {
+        return _created;
+    }
+
+    @Override
+    public String toString()
+    {
+        return String.format("%s@%x{%s}", getClass().getSimpleName(), hashCode(), _state.get());
+    }
+    
+    public boolean next(State state, State next)
+    {
+        if (next==null)
+            return true;
+        if(_state.compareAndSet(state,next))
+        {
+            LOG.debug("{}-->{} {}",state,next,this);
+            if (next!=state)
+                next.onEnter(AbstractConnection.this);
+            return true;
+        }
+        return false;
+    }
+    
+    private static final class IdleState extends State
+    {
+        private IdleState()
+        {
+            super("IDLE");
+        }
+
+        @Override
+        State fillInterested()
+        {
+            return FILL_INTERESTED;
+        }
+    }
+
+
+    private static final class FillInterestedState extends State
+    {
+        private FillInterestedState()
+        {
+            super("FILL_INTERESTED");
+        }
+
+        @Override
+        public void onEnter(AbstractConnection connection)
+        {
+            connection.getEndPoint().fillInterested(connection._readCallback);
+        }
+
+        @Override
+        State fillInterested()
+        {
+            return this;
+        }
+
+        @Override
+        public State onFillable()
+        {
+            return FILLING;
+        }
+
+        @Override
+        State onFailed()
+        {
+            return IDLE;
+        }
+    }
+
+
+    private static final class RefillingState extends State
+    {
+        private RefillingState()
+        {
+            super("REFILLING");
+        }
+
+        @Override
+        State fillInterested()
+        {
+            return FILLING_FILL_INTERESTED;
+        }
+
+        @Override
+        public State onFilled()
+        {
+            return IDLE;
+        }
+    }
+
+
+    private static final class FillingFillInterestedState extends State
+    {
+        private FillingFillInterestedState(String name)
+        {
+            super(name);
+        }
+
+        @Override
+        State fillInterested()
+        {
+            return this;
+        }
+
+        State onFilled()
+        {
+            return FILL_INTERESTED;
+        }
+    }
+
+
+    private static final class FillingState extends State
+    {
+        private FillingState()
+        {
+            super("FILLING");
+        }
+
+        @Override
+        public void onEnter(AbstractConnection connection)
+        {
+            if (connection._executeOnfillable)
+                connection.getExecutor().execute(connection._runOnFillable);
+            else
+                connection._runOnFillable.run();
+        }
+
+        @Override
+        State fillInterested()
+        {
+            return FILLING_FILL_INTERESTED;
+        }
+
+        @Override
+        public State onFilled()
+        {
+            return IDLE;
+        }
+    }
+
+
+    public static class State
+    {
+        private final String _name;
+        State(String name)
+        {
+            _name=name;
+        }
+
+        @Override
+        public String toString()
+        {
+            return _name;
+        }
+        
+        void onEnter(AbstractConnection connection)
+        {
+        }
+        
+        State fillInterested()
+        {
+            throw new IllegalStateException(this.toString());
+        }
+
+        State onFillable()
+        {
+            throw new IllegalStateException(this.toString());
+        }
+
+        State onFilled()
+        {
+            throw new IllegalStateException(this.toString());
+        }
+        
+        State onFailed()
+        {
+            throw new IllegalStateException(this.toString());
+        }
+    }
+    
+
+    public static final State IDLE=new IdleState();
+    
+    public static final State FILL_INTERESTED=new FillInterestedState();
+    
+    public static final State FILLING=new FillingState();
+    
+    public static final State REFILLING=new RefillingState();
+
+    public static final State FILLING_FILL_INTERESTED=new FillingFillInterestedState("FILLING_FILL_INTERESTED");
+    
+    public class NestedState extends State
+    {
+        private final State _nested;
+        
+        NestedState(State nested)
+        {
+            super("NESTED("+nested+")");
+            _nested=nested;
+        }
+        NestedState(String name,State nested)
+        {
+            super(name+"("+nested+")");
+            _nested=nested;
+        }
+
+        @Override
+        State fillInterested()
+        {
+            return new NestedState(_nested.fillInterested());
+        }
+
+        @Override
+        State onFillable()
+        {
+            return new NestedState(_nested.onFillable());
+        }
+        
+        @Override
+        State onFilled()
+        {
+            return new NestedState(_nested.onFilled());
+        }
+    }
+    
+    
+    public class FillingInterestedCallback extends NestedState
+    {
+        private final Callback _callback;
+        
+        FillingInterestedCallback(Callback callback,State nested)
+        {
+            super("FILLING_INTERESTED_CALLBACK",nested==FILLING?REFILLING:nested);
+            _callback=callback;
+        }
+
+        @Override
+        void onEnter(final AbstractConnection connection)
+        {
+            Callback callback=new Callback()
+            {
+                @Override
+                public void succeeded()
+                {
+                    while(true)
+                    {
+                        State state = connection._state.get();
+                        if (!(state instanceof NestedState))
+                            break;
+                        State nested=((NestedState)state)._nested;
+                        if (connection.next(state,nested))
+                            break;
+                    }
+                    _callback.succeeded();
+                }
+
+                @Override
+                public void failed(Throwable x)
+                {
+                    while(true)
+                    {
+                        State state = connection._state.get();
+                        if (!(state instanceof NestedState))
+                            break;
+                        State nested=((NestedState)state)._nested;
+                        if (connection.next(state,nested))
+                            break;
+                    }
+                    _callback.failed(x);
+                }  
+            };
+            
+            connection.getEndPoint().fillInterested(callback);
+        }
+    }
+    
+    private final Runnable _runOnFillable = new Runnable()
+    {
+        @Override
+        public void run()
+        {
+            try
+            {
+                onFillable();
+            }
+            finally
+            {
+                while(true)
+                {
+                    State state=_state.get();
+                    if (next(state,state.onFilled()))
+                        break;
+                }
+            }
+        }
+    };
+    
+    
+    private class ReadCallback implements Callback
+    {   
+        @Override
+        public void succeeded()
+        {
+            while(true)
+            {
+                State state=_state.get();
+                if (next(state,state.onFillable()))
+                    break;
+            }
+        }
+
+        @Override
+        public void failed(final Throwable x)
+        {
+            _executor.execute(new Runnable()
+            {
+                @Override
+                public void run()
+                {
+                    while(true)
+                    {
+                        State state=_state.get();
+                        if (next(state,state.onFailed()))
+                            break;
+                    }
+                    onFillInterestedFailed(x);
+                }
+            });
+        }
+        
+        @Override
+        public String toString()
+        {
+            return String.format("AC.ReadCB@%x{%s}", AbstractConnection.this.hashCode(),AbstractConnection.this);
+        }
+    };
+}
diff --git a/lib/jetty/org/eclipse/jetty/io/AbstractEndPoint.java b/lib/jetty/org/eclipse/jetty/io/AbstractEndPoint.java
new file mode 100644 (file)
index 0000000..8fa2cc8
--- /dev/null
@@ -0,0 +1,180 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.io;
+
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.nio.ByteBuffer;
+import java.util.concurrent.TimeoutException;
+
+import org.eclipse.jetty.util.Callback;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.thread.Scheduler;
+
+public abstract class AbstractEndPoint extends IdleTimeout implements EndPoint
+{
+    private static final Logger LOG = Log.getLogger(AbstractEndPoint.class);
+    private final long _created=System.currentTimeMillis();
+    private final InetSocketAddress _local;
+    private final InetSocketAddress _remote;
+    private volatile Connection _connection;
+
+    private final FillInterest _fillInterest = new FillInterest()
+    {
+        @Override
+        protected boolean needsFill() throws IOException
+        {
+            return AbstractEndPoint.this.needsFill();
+        }
+    };
+    
+    private final WriteFlusher _writeFlusher = new WriteFlusher(this)
+    {
+        @Override
+        protected void onIncompleteFlushed()
+        {
+            AbstractEndPoint.this.onIncompleteFlush();
+        }
+    };
+
+    protected AbstractEndPoint(Scheduler scheduler,InetSocketAddress local,InetSocketAddress remote)
+    {
+        super(scheduler);
+        _local=local;
+        _remote=remote;
+    }
+
+    @Override
+    public long getCreatedTimeStamp()
+    {
+        return _created;
+    }
+
+    @Override
+    public InetSocketAddress getLocalAddress()
+    {
+        return _local;
+    }
+
+    @Override
+    public InetSocketAddress getRemoteAddress()
+    {
+        return _remote;
+    }
+    
+    @Override
+    public Connection getConnection()
+    {
+        return _connection;
+    }
+
+    @Override
+    public void setConnection(Connection connection)
+    {
+        _connection = connection;
+    }
+
+    @Override
+    public void onOpen()
+    {
+        LOG.debug("onOpen {}",this);
+        super.onOpen();
+    }
+
+    @Override
+    public void onClose()
+    {
+        super.onClose();
+        LOG.debug("onClose {}",this);
+        _writeFlusher.onClose();
+        _fillInterest.onClose();
+    }
+    
+    @Override
+    public void close()
+    {
+        onClose();
+    }
+
+    @Override
+    public void fillInterested(Callback callback) throws IllegalStateException
+    {
+        notIdle();
+        _fillInterest.register(callback);
+    }
+
+    @Override
+    public void write(Callback callback, ByteBuffer... buffers) throws IllegalStateException
+    {
+        _writeFlusher.write(callback, buffers);
+    }
+
+    protected abstract void onIncompleteFlush();
+
+    protected abstract boolean needsFill() throws IOException;
+
+    protected FillInterest getFillInterest()
+    {
+        return _fillInterest;
+    }
+
+    protected WriteFlusher getWriteFlusher()
+    {
+        return _writeFlusher;
+    }
+
+    @Override
+    protected void onIdleExpired(TimeoutException timeout)
+    {
+        boolean output_shutdown=isOutputShutdown();
+        boolean input_shutdown=isInputShutdown();
+        boolean fillFailed = _fillInterest.onFail(timeout);
+        boolean writeFailed = _writeFlusher.onFail(timeout);
+        
+        // If the endpoint is half closed and there was no onFail handling, the close here
+        // This handles the situation where the connection has completed its close handling 
+        // and the endpoint is half closed, but the other party does not complete the close.
+        // This perhaps should not check for half closed, however the servlet spec case allows
+        // for a dispatched servlet or suspended request to extend beyond the connections idle 
+        // time.  So if this test would always close an idle endpoint that is not handled, then 
+        // we would need a mode to ignore timeouts for some HTTP states
+        if (isOpen() && (output_shutdown || input_shutdown) && !(fillFailed || writeFailed))
+            close();
+        else 
+            LOG.debug("Ignored idle endpoint {}",this);
+    }
+
+    @Override
+    public String toString()
+    {
+        return String.format("%s@%x{%s<->%d,%s,%s,%s,%s,%s,%d,%s}",
+                getClass().getSimpleName(),
+                hashCode(),
+                getRemoteAddress(),
+                getLocalAddress().getPort(),
+                isOpen()?"Open":"CLOSED",
+                isInputShutdown()?"ISHUT":"in",
+                isOutputShutdown()?"OSHUT":"out",
+                _fillInterest.isInterested()?"R":"-",
+                _writeFlusher.isInProgress()?"W":"-",
+                getIdleTimeout(),
+                getConnection()==null?null:getConnection().getClass().getSimpleName());
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/io/ArrayByteBufferPool.java b/lib/jetty/org/eclipse/jetty/io/ArrayByteBufferPool.java
new file mode 100644 (file)
index 0000000..fa3a01d
--- /dev/null
@@ -0,0 +1,133 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.io;
+
+import java.nio.ByteBuffer;
+import java.util.Queue;
+import java.util.concurrent.ConcurrentLinkedQueue;
+
+import org.eclipse.jetty.util.BufferUtil;
+
+public class ArrayByteBufferPool implements ByteBufferPool
+{
+    private final int _min;
+    private final Bucket[] _direct;
+    private final Bucket[] _indirect;
+    private final int _inc;
+
+    public ArrayByteBufferPool()
+    {
+        this(0,1024,64*1024);
+    }
+
+    public ArrayByteBufferPool(int minSize, int increment, int maxSize)
+    {
+        if (minSize>=increment)
+            throw new IllegalArgumentException("minSize >= increment");
+        if ((maxSize%increment)!=0 || increment>=maxSize)
+            throw new IllegalArgumentException("increment must be a divisor of maxSize");
+        _min=minSize;
+        _inc=increment;
+
+        _direct=new Bucket[maxSize/increment];
+        _indirect=new Bucket[maxSize/increment];
+
+        int size=0;
+        for (int i=0;i<_direct.length;i++)
+        {
+            size+=_inc;
+            _direct[i]=new Bucket(size);
+            _indirect[i]=new Bucket(size);
+        }
+    }
+
+    @Override
+    public ByteBuffer acquire(int size, boolean direct)
+    {
+        Bucket bucket = bucketFor(size,direct);
+        ByteBuffer buffer = bucket==null?null:bucket._queue.poll();
+
+        if (buffer == null)
+        {
+            int capacity = bucket==null?size:bucket._size;
+            buffer = direct ? BufferUtil.allocateDirect(capacity) : BufferUtil.allocate(capacity);
+        }
+
+        return buffer;
+    }
+
+    @Override
+    public void release(ByteBuffer buffer)
+    {
+        if (buffer!=null)
+        {    
+            Bucket bucket = bucketFor(buffer.capacity(),buffer.isDirect());
+            if (bucket!=null)
+            {
+                BufferUtil.clear(buffer);
+                bucket._queue.offer(buffer);
+            }
+        }
+    }
+
+    public void clear()
+    {
+        for (int i=0;i<_direct.length;i++)
+        {
+            _direct[i]._queue.clear();
+            _indirect[i]._queue.clear();
+        }
+    }
+
+    private Bucket bucketFor(int size,boolean direct)
+    {
+        if (size<=_min)
+            return null;
+        int b=(size-1)/_inc;
+        if (b>=_direct.length)
+            return null;
+        Bucket bucket = direct?_direct[b]:_indirect[b];
+                
+        return bucket;
+    }
+
+    public static class Bucket
+    {
+        public final int _size;
+        public final Queue<ByteBuffer> _queue= new ConcurrentLinkedQueue<>();
+
+        Bucket(int size)
+        {
+            _size=size;
+        }
+        
+        @Override
+        public String toString()
+        {
+            return String.format("Bucket@%x{%d,%d}",hashCode(),_size,_queue.size());
+        }
+    }
+    
+
+    // Package local for testing
+    Bucket[] bucketsFor(boolean direct)
+    {
+        return direct ? _direct : _indirect;
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/io/ByteArrayEndPoint.java b/lib/jetty/org/eclipse/jetty/io/ByteArrayEndPoint.java
new file mode 100644 (file)
index 0000000..4b8c527
--- /dev/null
@@ -0,0 +1,409 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.io;
+
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.nio.ByteBuffer;
+import java.nio.channels.ClosedChannelException;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.thread.Scheduler;
+
+
+/* ------------------------------------------------------------ */
+/** ByteArrayEndPoint.
+ *
+ */
+public class ByteArrayEndPoint extends AbstractEndPoint
+{
+    static final Logger LOG = Log.getLogger(ByteArrayEndPoint.class);
+    public final static InetSocketAddress NOIP=new InetSocketAddress(0);
+
+    protected volatile ByteBuffer _in;
+    protected volatile ByteBuffer _out;
+    protected volatile boolean _ishut;
+    protected volatile boolean _oshut;
+    protected volatile boolean _closed;
+    protected volatile boolean _growOutput;
+
+
+    /* ------------------------------------------------------------ */
+    /**
+     *
+     */
+    public ByteArrayEndPoint()
+    {
+        this(null,0,null,null);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     *
+     */
+    public ByteArrayEndPoint(byte[] input, int outputSize)
+    {
+        this(null,0,input!=null?BufferUtil.toBuffer(input):null,BufferUtil.allocate(outputSize));
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     *
+     */
+    public ByteArrayEndPoint(String input, int outputSize)
+    {
+        this(null,0,input!=null?BufferUtil.toBuffer(input):null,BufferUtil.allocate(outputSize));
+    }
+
+    /* ------------------------------------------------------------ */
+    public ByteArrayEndPoint(Scheduler scheduler, long idleTimeoutMs)
+    {
+        this(scheduler,idleTimeoutMs,null,null);
+    }
+
+    /* ------------------------------------------------------------ */
+    public ByteArrayEndPoint(Scheduler timer, long idleTimeoutMs, byte[] input, int outputSize)
+    {
+        this(timer,idleTimeoutMs,input!=null?BufferUtil.toBuffer(input):null,BufferUtil.allocate(outputSize));
+    }
+
+    /* ------------------------------------------------------------ */
+    public ByteArrayEndPoint(Scheduler timer, long idleTimeoutMs, String input, int outputSize)
+    {
+        this(timer,idleTimeoutMs,input!=null?BufferUtil.toBuffer(input):null,BufferUtil.allocate(outputSize));
+    }
+
+    /* ------------------------------------------------------------ */
+    public ByteArrayEndPoint(Scheduler timer, long idleTimeoutMs, ByteBuffer input, ByteBuffer output)
+    {
+        super(timer,NOIP,NOIP);
+        _in=input==null?BufferUtil.EMPTY_BUFFER:input;
+        _out=output==null?BufferUtil.allocate(1024):output;
+        setIdleTimeout(idleTimeoutMs);
+    }
+
+
+
+
+
+    /* ------------------------------------------------------------ */
+    @Override
+    protected void onIncompleteFlush()
+    {
+        // Don't need to do anything here as takeOutput does the signalling.
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    protected boolean needsFill() throws IOException
+    {
+        if (_closed)
+            throw new ClosedChannelException();
+        return _in == null || BufferUtil.hasContent(_in);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Returns the in.
+     */
+    public ByteBuffer getIn()
+    {
+        return _in;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     */
+    public void setInputEOF()
+    {
+        _in = null;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param in The in to set.
+     */
+    public void setInput(ByteBuffer in)
+    {
+        _in = in;
+        if (in == null || BufferUtil.hasContent(in))
+            getFillInterest().fillable();
+    }
+
+    /* ------------------------------------------------------------ */
+    public void setInput(String s)
+    {
+        setInput(BufferUtil.toBuffer(s,StandardCharsets.UTF_8));
+    }
+
+    /* ------------------------------------------------------------ */
+    public void setInput(String s,Charset charset)
+    {
+        setInput(BufferUtil.toBuffer(s,charset));
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Returns the out.
+     */
+    public ByteBuffer getOutput()
+    {
+        return _out;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Returns the out.
+     */
+    public String getOutputString()
+    {
+        return getOutputString(StandardCharsets.UTF_8);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Returns the out.
+     */
+    public String getOutputString(Charset charset)
+    {
+        return BufferUtil.toString(_out,charset);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Returns the out.
+     */
+    public ByteBuffer takeOutput()
+    {
+        ByteBuffer b=_out;
+        _out=BufferUtil.allocate(b.capacity());
+        getWriteFlusher().completeWrite();
+        return b;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Returns the out.
+     */
+    public String takeOutputString()
+    {
+        return takeOutputString(StandardCharsets.UTF_8);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Returns the out.
+     */
+    public String takeOutputString(Charset charset)
+    {
+        ByteBuffer buffer=takeOutput();
+        return BufferUtil.toString(buffer,charset);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param out The out to set.
+     */
+    public void setOutput(ByteBuffer out)
+    {
+        _out = out;
+        getWriteFlusher().completeWrite();
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see org.eclipse.io.EndPoint#isOpen()
+     */
+    @Override
+    public boolean isOpen()
+    {
+        return !_closed;
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     */
+    @Override
+    public boolean isInputShutdown()
+    {
+        return _ishut||_closed;
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     */
+    @Override
+    public boolean isOutputShutdown()
+    {
+        return _oshut||_closed;
+    }
+
+    /* ------------------------------------------------------------ */
+    private void shutdownInput()
+    {
+        _ishut=true;
+        if (_oshut)
+            close();
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see org.eclipse.io.EndPoint#shutdownOutput()
+     */
+    @Override
+    public void shutdownOutput()
+    {
+        _oshut=true;
+        if (_ishut)
+            close();
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see org.eclipse.io.EndPoint#close()
+     */
+    @Override
+    public void close()
+    {
+        super.close();
+        _closed=true;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return <code>true</code> if there are bytes remaining to be read from the encoded input
+     */
+    public boolean hasMore()
+    {
+        return getOutput().position()>0;
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see org.eclipse.io.EndPoint#fill(org.eclipse.io.Buffer)
+     */
+    @Override
+    public int fill(ByteBuffer buffer) throws IOException
+    {
+        if (_closed)
+            throw new EofException("CLOSED");
+        if (_in==null)
+            shutdownInput();
+        if (_ishut)
+            return -1;
+        int filled=BufferUtil.append(buffer,_in);
+        if (filled>0)
+            notIdle();
+        return filled;
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see org.eclipse.io.EndPoint#flush(org.eclipse.io.Buffer, org.eclipse.io.Buffer, org.eclipse.io.Buffer)
+     */
+    @Override
+    public boolean flush(ByteBuffer... buffers) throws IOException
+    {
+        if (_closed)
+            throw new IOException("CLOSED");
+        if (_oshut)
+            throw new IOException("OSHUT");
+
+        boolean flushed=true;
+        boolean idle=true;
+
+        for (ByteBuffer b : buffers)
+        {
+            if (BufferUtil.hasContent(b))
+            {
+                if (_growOutput && b.remaining()>BufferUtil.space(_out))
+                {
+                    BufferUtil.compact(_out);
+                    if (b.remaining()>BufferUtil.space(_out))
+                    {
+                        ByteBuffer n = BufferUtil.allocate(_out.capacity()+b.remaining()*2);
+                        BufferUtil.append(n,_out);
+                        _out=n;
+                    }
+                }
+
+                if (BufferUtil.append(_out,b)>0)
+                    idle=false;
+
+                if (BufferUtil.hasContent(b))
+                {
+                    flushed=false;
+                    break;
+                }
+            }
+        }
+        if (!idle)
+            notIdle();
+        return flushed;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     *
+     */
+    public void reset()
+    {
+        getFillInterest().onClose();
+        getWriteFlusher().onClose();
+        _ishut=false;
+        _oshut=false;
+        _closed=false;
+        _in=null;
+        BufferUtil.clear(_out);
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see org.eclipse.io.EndPoint#getConnection()
+     */
+    @Override
+    public Object getTransport()
+    {
+        return null;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return the growOutput
+     */
+    public boolean isGrowOutput()
+    {
+        return _growOutput;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param growOutput the growOutput to set
+     */
+    public void setGrowOutput(boolean growOutput)
+    {
+        _growOutput=growOutput;
+    }
+
+
+}
diff --git a/lib/jetty/org/eclipse/jetty/io/ByteBufferPool.java b/lib/jetty/org/eclipse/jetty/io/ByteBufferPool.java
new file mode 100644 (file)
index 0000000..302adde
--- /dev/null
@@ -0,0 +1,51 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.io;
+
+import java.nio.ByteBuffer;
+
+/**
+ * <p>A {@link ByteBuffer} pool.</p>
+ * <p>Acquired buffers may be {@link #release(ByteBuffer) released} but they do not need to;
+ * if they are released, they may be recycled and reused, otherwise they will be garbage
+ * collected as usual.</p>
+ */
+public interface ByteBufferPool
+{
+    /**
+     * <p>Requests a {@link ByteBuffer} of the given size.</p>
+     * <p>The returned buffer may have a bigger capacity than the size being
+     * requested but it will have the limit set to the given size.</p>
+     *
+     * @param size   the size of the buffer
+     * @param direct whether the buffer must be direct or not
+     * @return the requested buffer
+     * @see #release(ByteBuffer)
+     */
+    public ByteBuffer acquire(int size, boolean direct);
+
+    /**
+     * <p>Returns a {@link ByteBuffer}, usually obtained with {@link #acquire(int, boolean)}
+     * (but not necessarily), making it available for recycling and reuse.</p>
+     *
+     * @param buffer the buffer to return
+     * @see #acquire(int, boolean)
+     */
+    public void release(ByteBuffer buffer);
+}
diff --git a/lib/jetty/org/eclipse/jetty/io/ChannelEndPoint.java b/lib/jetty/org/eclipse/jetty/io/ChannelEndPoint.java
new file mode 100644 (file)
index 0000000..c65ca0c
--- /dev/null
@@ -0,0 +1,229 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.io;
+
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.net.Socket;
+import java.nio.ByteBuffer;
+import java.nio.channels.ByteChannel;
+import java.nio.channels.GatheringByteChannel;
+import java.nio.channels.SocketChannel;
+
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.thread.Scheduler;
+
+/**
+ * Channel End Point.
+ * <p>Holds the channel and socket for an NIO endpoint.
+ */
+public class ChannelEndPoint extends AbstractEndPoint
+{
+    private static final Logger LOG = Log.getLogger(ChannelEndPoint.class);
+
+    private final ByteChannel _channel;
+    private final Socket _socket;
+    private volatile boolean _ishut;
+    private volatile boolean _oshut;
+
+    public ChannelEndPoint(Scheduler scheduler,SocketChannel channel)
+    {
+        super(scheduler,
+            (InetSocketAddress)channel.socket().getLocalSocketAddress(),
+            (InetSocketAddress)channel.socket().getRemoteSocketAddress());
+        _channel = channel;
+        _socket=channel.socket();
+    }
+
+    @Override
+    public boolean isOpen()
+    {
+        return _channel.isOpen();
+    }
+
+    protected void shutdownInput()
+    {
+        LOG.debug("ishut {}", this);
+        _ishut=true;
+        if (_oshut)
+            close();
+    }
+
+    @Override
+    public void shutdownOutput()
+    {
+        LOG.debug("oshut {}", this);
+        _oshut = true;
+        if (_channel.isOpen())
+        {
+            try
+            {
+                if (!_socket.isOutputShutdown())
+                    _socket.shutdownOutput();
+            }
+            catch (IOException e)
+            {
+                LOG.debug(e);
+            }
+            finally
+            {
+                if (_ishut)
+                {
+                    close();
+                }
+            }
+        }
+    }
+
+    @Override
+    public boolean isOutputShutdown()
+    {
+        return _oshut || !_channel.isOpen() || _socket.isOutputShutdown();
+    }
+
+    @Override
+    public boolean isInputShutdown()
+    {
+        return _ishut || !_channel.isOpen() || _socket.isInputShutdown();
+    }
+
+    @Override
+    public void close()
+    {
+        super.close();
+        LOG.debug("close {}", this);
+        try
+        {
+            _channel.close();
+        }
+        catch (IOException e)
+        {
+            LOG.debug(e);
+        }
+        finally
+        {
+            _ishut=true;
+            _oshut=true;
+        }
+    }
+
+    @Override
+    public int fill(ByteBuffer buffer) throws IOException
+    {
+        if (_ishut)
+            return -1;
+
+        int pos=BufferUtil.flipToFill(buffer);
+        try
+        {
+            int filled = _channel.read(buffer);
+            if (LOG.isDebugEnabled()) // Avoid boxing of variable 'filled'
+                LOG.debug("filled {} {}", filled, this);
+
+            if (filled>0)
+                notIdle();
+            else if (filled==-1)
+                shutdownInput();
+
+            return filled;
+        }
+        catch(IOException e)
+        {
+            LOG.debug(e);
+            shutdownInput();
+            return -1;
+        }
+        finally
+        {
+            BufferUtil.flipToFlush(buffer,pos);
+        }
+    }
+
+    @Override
+    public boolean flush(ByteBuffer... buffers) throws IOException
+    {
+        int flushed=0;
+        try
+        {
+            if (buffers.length==1)
+                flushed=_channel.write(buffers[0]);
+            else if (buffers.length>1 && _channel instanceof GatheringByteChannel)
+                flushed= (int)((GatheringByteChannel)_channel).write(buffers,0,buffers.length);
+            else
+            {
+                for (ByteBuffer b : buffers)
+                {
+                    if (b.hasRemaining())
+                    {
+                        int l=_channel.write(b);
+                        if (l>0)
+                            flushed+=l;
+                        if (b.hasRemaining())
+                            break;
+                    }
+                }
+            }
+            if (LOG.isDebugEnabled())
+                LOG.debug("flushed {} {}", flushed, this);
+        }
+        catch (IOException e)
+        {
+            throw new EofException(e);
+        }
+
+        if (flushed>0)
+            notIdle();
+
+        for (ByteBuffer b : buffers)
+            if (!BufferUtil.isEmpty(b))
+                return false;
+
+        return true;
+    }
+
+    public ByteChannel getChannel()
+    {
+        return _channel;
+    }
+
+    @Override
+    public Object getTransport()
+    {
+        return _channel;
+    }
+
+    public Socket getSocket()
+    {
+        return _socket;
+    }
+
+    @Override
+    protected void onIncompleteFlush()
+    {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    protected boolean needsFill() throws IOException
+    {
+        throw new UnsupportedOperationException();
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/io/ClientConnectionFactory.java b/lib/jetty/org/eclipse/jetty/io/ClientConnectionFactory.java
new file mode 100644 (file)
index 0000000..7eb5931
--- /dev/null
@@ -0,0 +1,89 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.io;
+
+import java.io.IOException;
+import java.util.Map;
+
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/**
+ * Factory for client-side {@link Connection} instances.
+ */
+public interface ClientConnectionFactory
+{
+    /**
+     *
+     * @param endPoint the {@link org.eclipse.jetty.io.EndPoint} to link the newly created connection to
+     * @param context the context data to create the connection
+     * @return a new {@link Connection}
+     * @throws IOException if the connection cannot be created
+     */
+    public Connection newConnection(EndPoint endPoint, Map<String, Object> context) throws IOException;
+
+    public static class Helper
+    {
+        private static Logger LOG = Log.getLogger(Helper.class);
+
+        private Helper()
+        {
+        }
+
+        /**
+         * Replaces the given {@code oldConnection} with the given {@code newConnection} on the
+         * {@link EndPoint} associated with {@code oldConnection}, performing connection lifecycle management.
+         * <p />
+         * The {@code oldConnection} will be closed by invoking {@link org.eclipse.jetty.io.Connection#onClose()}
+         * and the {@code newConnection} will be opened by invoking {@link org.eclipse.jetty.io.Connection#onOpen()}.
+         * @param oldConnection the old connection to replace
+         * @param newConnection the new connection replacement
+         */
+        public static void replaceConnection(Connection oldConnection, Connection newConnection)
+        {
+            close(oldConnection);
+            oldConnection.getEndPoint().setConnection(newConnection);
+            open(newConnection);
+        }
+
+        private static void open(Connection connection)
+        {
+            try
+            {
+                connection.onOpen();
+            }
+            catch (Throwable x)
+            {
+                LOG.debug(x);
+            }
+        }
+
+        private static void close(Connection connection)
+        {
+            try
+            {
+                connection.onClose();
+            }
+            catch (Throwable x)
+            {
+                LOG.debug(x);
+            }
+        }
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/io/Connection.java b/lib/jetty/org/eclipse/jetty/io/Connection.java
new file mode 100644 (file)
index 0000000..96baa01
--- /dev/null
@@ -0,0 +1,89 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.io;
+
+import java.io.Closeable;
+
+import org.eclipse.jetty.util.Callback;
+
+/**
+ * <p>A {@link Connection} is associated to an {@link EndPoint} so that I/O events
+ * happening on the {@link EndPoint} can be processed by the {@link Connection}.</p>
+ * <p>A typical implementation of {@link Connection} overrides {@link #onOpen()} to
+ * {@link EndPoint#fillInterested(Callback) set read interest} on the {@link EndPoint},
+ * and when the {@link EndPoint} signals read readyness, this {@link Connection} can
+ * read bytes from the network and interpret them.</p>
+ */
+public interface Connection extends Closeable
+{
+    public void addListener(Listener listener);
+
+    /**
+     * <p>Callback method invoked when this {@link Connection} is opened.</p>
+     * <p>Creators of the connection implementation are responsible for calling this method.</p>
+     */
+    public void onOpen();
+
+    /**
+     * <p>Callback method invoked when this {@link Connection} is closed.</p>
+     * <p>Creators of the connection implementation are responsible for calling this method.</p>
+     */
+    public void onClose();
+
+    /**
+     * @return the {@link EndPoint} associated with this {@link Connection}
+     */
+    public EndPoint getEndPoint();
+
+    /**
+     * <p>Performs a logical close of this connection.</p>
+     * <p>For simple connections, this may just mean to delegate the close to the associated
+     * {@link EndPoint} but, for example, SSL connections should write the SSL close message
+     * before closing the associated {@link EndPoint}.</p>
+     */
+    @Override
+    public void close();
+
+    public int getMessagesIn();
+    public int getMessagesOut();
+    public long getBytesIn();
+    public long getBytesOut();
+    public long getCreatedTimeStamp();
+    
+    
+    public interface Listener
+    {
+        public void onOpened(Connection connection);
+
+        public void onClosed(Connection connection);
+
+        public static class Adapter implements Listener
+        {
+            @Override
+            public void onOpened(Connection connection)
+            {
+            }
+
+            @Override
+            public void onClosed(Connection connection)
+            {
+            }
+        }
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/io/EndPoint.java b/lib/jetty/org/eclipse/jetty/io/EndPoint.java
new file mode 100644 (file)
index 0000000..87adb40
--- /dev/null
@@ -0,0 +1,243 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.io;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.nio.ByteBuffer;
+import java.nio.channels.ReadPendingException;
+import java.nio.channels.WritePendingException;
+
+import org.eclipse.jetty.util.Callback;
+import org.eclipse.jetty.util.FutureCallback;
+
+
+/**
+ *
+ * A transport EndPoint
+ * 
+ * <h3>Asynchronous Methods</h3>
+ * <p>The asynchronous scheduling methods of {@link EndPoint} 
+ * has been influenced by NIO.2 Futures and Completion
+ * handlers, but does not use those actual interfaces because they have
+ * some inefficiencies.</p>
+ * <p>This class will frequently be used in conjunction with some of the utility
+ * implementations of {@link Callback}, such as {@link FutureCallback} and
+ * {@link ExecutorCallback}. Examples are:</p>
+ *
+ * <h3>Blocking Read</h3>
+ * <p>A FutureCallback can be used to block until an endpoint is ready to be filled
+ * from:
+ * <blockquote><pre>
+ * FutureCallback&lt;String&gt; future = new FutureCallback&lt;&gt;();
+ * endpoint.fillInterested("ContextObj",future);
+ * ...
+ * String context = future.get(); // This blocks
+ * int filled=endpoint.fill(mybuffer);
+ * </pre></blockquote></p>
+ *
+ * <h3>Dispatched Read</h3>
+ * <p>By using a different callback, the read can be done asynchronously in its own dispatched thread:
+ * <blockquote><pre>
+ * endpoint.fillInterested("ContextObj",new ExecutorCallback&lt;String&gt;(executor)
+ * {
+ *   public void onCompleted(String context)
+ *   {
+ *     int filled=endpoint.fill(mybuffer);
+ *     ...
+ *   }
+ *   public void onFailed(String context,Throwable cause) {...}
+ * });
+ * </pre></blockquote></p>
+ * <p>The executor callback can also be customized to not dispatch in some circumstances when
+ * it knows it can use the callback thread and does not need to dispatch.</p>
+ *
+ * <h3>Blocking Write</h3>
+ * <p>The write contract is that the callback complete is not called until all data has been
+ * written or there is a failure.  For blocking this looks like:
+ * <blockquote><pre>
+ * FutureCallback&lt;String&gt; future = new FutureCallback&lt;&gt;();
+ * endpoint.write("ContextObj",future,headerBuffer,contentBuffer);
+ * String context = future.get(); // This blocks
+ * </pre></blockquote></p>
+ *
+ * <h3>Dispatched Write</h3>
+ * <p>Note also that multiple buffers may be passed in write so that gather writes
+ * can be done:
+ * <blockquote><pre>
+ * endpoint.write("ContextObj",new ExecutorCallback&lt;String&gt;(executor)
+ * {
+ *   public void onCompleted(String context)
+ *   {
+ *     int filled=endpoint.fill(mybuffer);
+ *     ...
+ *   }
+ *   public void onFailed(String context,Throwable cause) {...}
+ * },headerBuffer,contentBuffer);
+ * </pre></blockquote></p>
+ */
+public interface EndPoint extends Closeable
+{
+    /* ------------------------------------------------------------ */
+    /**
+     * @return The local Inet address to which this <code>EndPoint</code> is bound, or <code>null</code>
+     * if this <code>EndPoint</code> does not represent a network connection.
+     */
+    InetSocketAddress getLocalAddress();
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return The remote Inet address to which this <code>EndPoint</code> is bound, or <code>null</code>
+     * if this <code>EndPoint</code> does not represent a network connection.
+     */
+    InetSocketAddress getRemoteAddress();
+
+    /* ------------------------------------------------------------ */
+    boolean isOpen();
+
+    /* ------------------------------------------------------------ */
+    long getCreatedTimeStamp();
+
+    /* ------------------------------------------------------------ */
+    /** Shutdown the output.
+     * <p>This call indicates that no more data will be sent on this endpoint that
+     * that the remote end should read an EOF once all previously sent data has been
+     * consumed. Shutdown may be done either at the TCP/IP level, as a protocol exchange (Eg
+     * TLS close handshake) or both.
+     * <p>
+     * If the endpoint has {@link #isInputShutdown()} true, then this call has the same effect
+     * as {@link #close()}.
+     */
+    void shutdownOutput();
+
+    /* ------------------------------------------------------------ */
+    /** Test if output is shutdown.
+     * The output is shutdown by a call to {@link #shutdownOutput()}
+     * or {@link #close()}.
+     * @return true if the output is shutdown or the endpoint is closed.
+     */
+    boolean isOutputShutdown();
+
+    /* ------------------------------------------------------------ */
+    /** Test if the input is shutdown.
+     * The input is shutdown if an EOF has been read while doing
+     * a {@link #fill(ByteBuffer)}.   Once the input is shutdown, all calls to
+     * {@link #fill(ByteBuffer)} will  return -1, until such time as the
+     * end point is close, when they will return {@link EofException}.
+     * @return True if the input is shutdown or the endpoint is closed.
+     */
+    boolean isInputShutdown();
+
+    /**
+     * Close any backing stream associated with the endpoint
+     */
+    @Override
+    void close();
+
+    /**
+     * Fill the passed buffer with data from this endpoint.  The bytes are appended to any
+     * data already in the buffer by writing from the buffers limit up to it's capacity.
+     * The limit is updated to include the filled bytes.
+     *
+     * @param buffer The buffer to fill. The position and limit are modified during the fill. After the
+     * operation, the position is unchanged and the limit is increased to reflect the new data filled.
+     * @return an <code>int</code> value indicating the number of bytes
+     * filled or -1 if EOF is read or the input is shutdown.
+     * @throws EofException If the endpoint is closed.
+     */
+    int fill(ByteBuffer buffer) throws IOException;
+
+
+    /**
+     * Flush data from the passed header/buffer to this endpoint.  As many bytes as can be consumed
+     * are taken from the header/buffer position up until the buffer limit.  The header/buffers position
+     * is updated to indicate how many bytes have been consumed.
+     * @return True IFF all the buffers have been consumed and the endpoint has flushed the data to its 
+     * destination (ie is not buffering any data).
+     *
+     * @throws EofException If the endpoint is closed or output is shutdown.
+     */
+    boolean flush(ByteBuffer... buffer) throws IOException;
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return The underlying transport object (socket, channel, etc.)
+     */
+    Object getTransport();
+
+    /* ------------------------------------------------------------ */
+    /** Get the max idle time in ms.
+     * <p>The max idle time is the time the endpoint can be idle before
+     * extraordinary handling takes place.
+     * @return the max idle time in ms or if ms <= 0 implies an infinite timeout
+     */
+    long getIdleTimeout();
+
+    /* ------------------------------------------------------------ */
+    /** Set the idle timeout.
+     * @param idleTimeout the idle timeout in MS. Timeout <= 0 implies an infinite timeout
+     */
+    void setIdleTimeout(long idleTimeout);
+
+
+    /**
+     * <p>Requests callback methods to be invoked when a call to {@link #fill(ByteBuffer)} would return data or EOF.</p>
+     *
+     * @param callback the callback to call when an error occurs or we are readable.
+     * @throws ReadPendingException if another read operation is concurrent.
+     */
+    void fillInterested(Callback callback) throws ReadPendingException;
+
+    /**
+     * <p>Writes the given buffers via {@link #flush(ByteBuffer...)} and invokes callback methods when either
+     * all the data has been flushed or an error occurs.</p>
+     *
+     * @param callback the callback to call when an error occurs or the write completed.
+     * @param buffers one or more {@link ByteBuffer}s that will be flushed.
+     * @throws WritePendingException if another write operation is concurrent.
+     */
+    void write(Callback callback, ByteBuffer... buffers) throws WritePendingException;
+
+    /**
+     * @return the {@link Connection} associated with this {@link EndPoint}
+     * @see #setConnection(Connection)
+     */
+    Connection getConnection();
+
+    /**
+     * @param connection the {@link Connection} associated with this {@link EndPoint}
+     * @see #getConnection()
+     */
+    void setConnection(Connection connection);
+
+    /**
+     * <p>Callback method invoked when this {@link EndPoint} is opened.</p>
+     * @see #onClose()
+     */
+    void onOpen();
+
+    /**
+     * <p>Callback method invoked when this {@link EndPoint} is close.</p>
+     * @see #onOpen()
+     */
+    void onClose();
+
+    
+}
diff --git a/lib/jetty/org/eclipse/jetty/io/EofException.java b/lib/jetty/org/eclipse/jetty/io/EofException.java
new file mode 100644 (file)
index 0000000..72042f4
--- /dev/null
@@ -0,0 +1,46 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.io;
+
+import java.io.EOFException;
+
+
+/* ------------------------------------------------------------ */
+/** A Jetty specialization of EOFException.
+ * <p> This is thrown by Jetty to distinguish between EOF received from 
+ * the connection, vs and EOF thrown by some application talking to some other file/socket etc.
+ * The only difference in handling is that Jetty EOFs are logged less verbosely.
+ */
+public class EofException extends EOFException
+{
+    public EofException()
+    {
+    }
+    
+    public EofException(String reason)
+    {
+        super(reason);
+    }
+    
+    public EofException(Throwable th)
+    {
+        if (th!=null)
+            initCause(th);
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/io/FillInterest.java b/lib/jetty/org/eclipse/jetty/io/FillInterest.java
new file mode 100644 (file)
index 0000000..b2c3f68
--- /dev/null
@@ -0,0 +1,135 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.io;
+
+import java.io.IOException;
+import java.nio.channels.ClosedChannelException;
+import java.nio.channels.ReadPendingException;
+import java.util.concurrent.atomic.AtomicReference;
+
+import org.eclipse.jetty.util.Callback;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+
+/* ------------------------------------------------------------ */
+/** 
+ * A Utility class to help implement {@link EndPoint#fillInterested(Callback)}
+ * by keeping state and calling the context and callback objects.
+ * 
+ */
+public abstract class FillInterest
+{
+    private final static Logger LOG = Log.getLogger(FillInterest.class);
+    private final AtomicReference<Callback> _interested = new AtomicReference<>(null);
+
+    /* ------------------------------------------------------------ */
+    protected FillInterest()
+    {
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Call to register interest in a callback when a read is possible.
+     * The callback will be called either immediately if {@link #needsFill()} 
+     * returns true or eventually once {@link #fillable()} is called.
+     * @param callback
+     * @throws ReadPendingException
+     */
+    public <C> void register(Callback callback) throws ReadPendingException
+    {
+        if (callback==null)
+            throw new IllegalArgumentException();
+        
+        if (!_interested.compareAndSet(null,callback))
+        {
+            LOG.warn("Read pending for "+_interested.get()+" pervented "+callback);
+            throw new ReadPendingException();
+        }
+        try
+        {
+            if (needsFill())
+                fillable();
+        }
+        catch(IOException e)
+        {
+            onFail(e);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Call to signal that a read is now possible.
+     */
+    public void fillable()
+    {
+        Callback callback=_interested.get();
+        if (callback!=null && _interested.compareAndSet(callback,null))
+            callback.succeeded();
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return True if a read callback has been registered
+     */
+    public boolean isInterested()
+    {
+        return _interested.get()!=null;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** Call to signal a failure to a registered interest
+     * @return true if the cause was passed to a {@link Callback} instance
+     */
+    public boolean onFail(Throwable cause)
+    {
+        Callback callback=_interested.get();
+        if (callback!=null && _interested.compareAndSet(callback,null))
+        {
+            callback.failed(cause);
+            return true;
+        }
+        return false;
+    }
+    
+    /* ------------------------------------------------------------ */
+    public void onClose()
+    {
+        Callback callback=_interested.get();
+        if (callback!=null && _interested.compareAndSet(callback,null))
+            callback.failed(new ClosedChannelException());
+    }
+    
+    /* ------------------------------------------------------------ */
+    @Override
+    public String toString()
+    {
+        return String.format("FillInterest@%x{%b,%s}",hashCode(),_interested.get(),_interested.get());
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** Register the read interest 
+     * Abstract method to be implemented by the Specific ReadInterest to
+     * enquire if a read is immediately possible and if not to schedule a future
+     * call to {@link #fillable()} or {@link #onFail(Throwable)}
+     * @return true if a read is possible
+     * @throws IOException
+     */
+    abstract protected boolean needsFill() throws IOException;
+    
+    
+}
diff --git a/lib/jetty/org/eclipse/jetty/io/IdleTimeout.java b/lib/jetty/org/eclipse/jetty/io/IdleTimeout.java
new file mode 100644 (file)
index 0000000..8b251ac
--- /dev/null
@@ -0,0 +1,182 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.io;
+
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import java.util.concurrent.atomic.AtomicReference;
+
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.thread.Scheduler;
+
+/**
+ * An Abstract implementation of an Idle Timeout.
+ * <p/>
+ * This implementation is optimised that timeout operations are not cancelled on
+ * every operation. Rather timeout are allowed to expire and a check is then made
+ * to see when the last operation took place.  If the idle timeout has not expired,
+ * the timeout is rescheduled for the earliest possible time a timeout could occur.
+ */
+public abstract class IdleTimeout
+{
+    private static final Logger LOG = Log.getLogger(IdleTimeout.class);
+    private final Scheduler _scheduler;
+    private final AtomicReference<Scheduler.Task> _timeout = new AtomicReference<>();
+    private volatile long _idleTimeout;
+    private volatile long _idleTimestamp = System.currentTimeMillis();
+
+    private final Runnable _idleTask = new Runnable()
+    {
+        @Override
+        public void run()
+        {
+            long idleLeft = checkIdleTimeout();
+            if (idleLeft >= 0)
+                scheduleIdleTimeout(idleLeft > 0 ? idleLeft : getIdleTimeout());
+        }
+    };
+
+    /**
+     * @param scheduler A scheduler used to schedule checks for the idle timeout.
+     */
+    public IdleTimeout(Scheduler scheduler)
+    {
+        _scheduler = scheduler;
+    }
+
+    public long getIdleTimestamp()
+    {
+        return _idleTimestamp;
+    }
+
+    public long getIdleTimeout()
+    {
+        return _idleTimeout;
+    }
+
+    public void setIdleTimeout(long idleTimeout)
+    {
+        long old = _idleTimeout;
+        _idleTimeout = idleTimeout;
+
+        // Do we have an old timeout
+        if (old > 0)
+        {
+            // if the old was less than or equal to the new timeout, then nothing more to do
+            if (old <= idleTimeout)
+                return;
+
+            // old timeout is too long, so cancel it.
+            deactivate();
+        }
+
+        // If we have a new timeout, then check and reschedule
+        if (isOpen())
+            activate();
+    }
+
+    /**
+     * This method should be called when non-idle activity has taken place.
+     */
+    public void notIdle()
+    {
+        _idleTimestamp = System.currentTimeMillis();
+    }
+
+    private void scheduleIdleTimeout(long delay)
+    {
+        Scheduler.Task newTimeout = null;
+        if (isOpen() && delay > 0 && _scheduler != null)
+            newTimeout = _scheduler.schedule(_idleTask, delay, TimeUnit.MILLISECONDS);
+        Scheduler.Task oldTimeout = _timeout.getAndSet(newTimeout);
+        if (oldTimeout != null)
+            oldTimeout.cancel();
+    }
+
+    public void onOpen()
+    {
+        activate();
+    }
+
+    private void activate()
+    {
+        if (_idleTimeout > 0)
+            _idleTask.run();
+    }
+
+    public void onClose()
+    {
+        deactivate();
+    }
+
+    private void deactivate()
+    {
+        Scheduler.Task oldTimeout = _timeout.getAndSet(null);
+        if (oldTimeout != null)
+            oldTimeout.cancel();
+    }
+
+    protected long checkIdleTimeout()
+    {
+        if (isOpen())
+        {
+            long idleTimestamp = getIdleTimestamp();
+            long idleTimeout = getIdleTimeout();
+            long idleElapsed = System.currentTimeMillis() - idleTimestamp;
+            long idleLeft = idleTimeout - idleElapsed;
+
+            LOG.debug("{} idle timeout check, elapsed: {} ms, remaining: {} ms", this, idleElapsed, idleLeft);
+
+            if (idleTimestamp != 0 && idleTimeout > 0)
+            {
+                if (idleLeft <= 0)
+                {
+                    LOG.debug("{} idle timeout expired", this);
+                    try
+                    {
+                        onIdleExpired(new TimeoutException("Idle timeout expired: " + idleElapsed + "/" + idleTimeout + " ms"));
+                    }
+                    finally
+                    {
+                        notIdle();
+                    }
+                }
+            }
+
+            return idleLeft >= 0 ? idleLeft : 0;
+        }
+        return -1;
+    }
+
+    /**
+     * This abstract method is called when the idle timeout has expired.
+     *
+     * @param timeout a TimeoutException
+     */
+    protected abstract void onIdleExpired(TimeoutException timeout);
+
+    /**
+     * This abstract method should be called to check if idle timeouts
+     * should still be checked.
+     *
+     * @return True if the entity monitored should still be checked for idle timeouts
+     */
+    public abstract boolean isOpen();
+}
diff --git a/lib/jetty/org/eclipse/jetty/io/LeakTrackingByteBufferPool.java b/lib/jetty/org/eclipse/jetty/io/LeakTrackingByteBufferPool.java
new file mode 100644 (file)
index 0000000..a6fc756
--- /dev/null
@@ -0,0 +1,73 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.io;
+
+import java.nio.ByteBuffer;
+
+import org.eclipse.jetty.util.LeakDetector;
+import org.eclipse.jetty.util.component.ContainerLifeCycle;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+public class LeakTrackingByteBufferPool extends ContainerLifeCycle implements ByteBufferPool
+{
+    private static final Logger LOG = Log.getLogger(LeakTrackingByteBufferPool.class);
+
+    private final LeakDetector<ByteBuffer> leakDetector = new LeakDetector<ByteBuffer>()
+    {
+        @Override
+        protected void leaked(LeakInfo leakInfo)
+        {
+            LeakTrackingByteBufferPool.this.leaked(leakInfo);
+        }
+    };
+    
+    private final ByteBufferPool delegate;
+
+    public LeakTrackingByteBufferPool(ByteBufferPool delegate)
+    {
+        this.delegate = delegate;
+        addBean(leakDetector);
+        addBean(delegate);
+    }
+
+    @Override
+    public ByteBuffer acquire(int size, boolean direct)
+    {
+        ByteBuffer buffer = delegate.acquire(size, direct);
+        if (!leakDetector.acquired(buffer))
+            LOG.warn("ByteBuffer {}@{} not tracked", buffer, System.identityHashCode(buffer));
+        return buffer;
+    }
+
+    @Override
+    public void release(ByteBuffer buffer)
+    {
+        if (buffer == null)
+            return;
+        if (!leakDetector.released(buffer))
+            LOG.warn("ByteBuffer {}@{} released but not acquired", buffer, System.identityHashCode(buffer));
+        delegate.release(buffer);
+    }
+
+    protected void leaked(LeakDetector<ByteBuffer>.LeakInfo leakInfo)
+    {
+        LOG.warn("ByteBuffer " + leakInfo.getResourceDescription() + " leaked at:", leakInfo.getStackFrames());
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/io/MappedByteBufferPool.java b/lib/jetty/org/eclipse/jetty/io/MappedByteBufferPool.java
new file mode 100644 (file)
index 0000000..b331904
--- /dev/null
@@ -0,0 +1,111 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.io;
+
+import java.nio.ByteBuffer;
+import java.util.Queue;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.ConcurrentMap;
+
+import org.eclipse.jetty.util.BufferUtil;
+
+public class MappedByteBufferPool implements ByteBufferPool
+{
+    private final ConcurrentMap<Integer, Queue<ByteBuffer>> directBuffers = new ConcurrentHashMap<>();
+    private final ConcurrentMap<Integer, Queue<ByteBuffer>> heapBuffers = new ConcurrentHashMap<>();
+    private final int factor;
+
+    public MappedByteBufferPool()
+    {
+        this(1024);
+    }
+
+    public MappedByteBufferPool(int factor)
+    {
+        this.factor = factor;
+    }
+
+    @Override
+    public ByteBuffer acquire(int size, boolean direct)
+    {
+        int bucket = bucketFor(size);
+        ConcurrentMap<Integer, Queue<ByteBuffer>> buffers = buffersFor(direct);
+
+        ByteBuffer result = null;
+        Queue<ByteBuffer> byteBuffers = buffers.get(bucket);
+        if (byteBuffers != null)
+            result = byteBuffers.poll();
+
+        if (result == null)
+        {
+            int capacity = bucket * factor;
+            result = direct ? BufferUtil.allocateDirect(capacity) : BufferUtil.allocate(capacity);
+        }
+
+        BufferUtil.clear(result);
+        return result;
+    }
+
+    @Override
+    public void release(ByteBuffer buffer)
+    {
+        if (buffer == null)
+            return; // nothing to do
+        
+        // validate that this buffer is from this pool
+        assert((buffer.capacity() % factor) == 0);
+        
+        int bucket = bucketFor(buffer.capacity());
+        ConcurrentMap<Integer, Queue<ByteBuffer>> buffers = buffersFor(buffer.isDirect());
+
+        // Avoid to create a new queue every time, just to be discarded immediately
+        Queue<ByteBuffer> byteBuffers = buffers.get(bucket);
+        if (byteBuffers == null)
+        {
+            byteBuffers = new ConcurrentLinkedQueue<>();
+            Queue<ByteBuffer> existing = buffers.putIfAbsent(bucket, byteBuffers);
+            if (existing != null)
+                byteBuffers = existing;
+        }
+
+        BufferUtil.clear(buffer);
+        byteBuffers.offer(buffer);
+    }
+
+    public void clear()
+    {
+        directBuffers.clear();
+        heapBuffers.clear();
+    }
+
+    private int bucketFor(int size)
+    {
+        int bucket = size / factor;
+        if (size % factor > 0)
+            ++bucket;
+        return bucket;
+    }
+
+    // Package local for testing
+    ConcurrentMap<Integer, Queue<ByteBuffer>> buffersFor(boolean direct)
+    {
+        return direct ? directBuffers : heapBuffers;
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/io/NegotiatingClientConnection.java b/lib/jetty/org/eclipse/jetty/io/NegotiatingClientConnection.java
new file mode 100644 (file)
index 0000000..cd05630
--- /dev/null
@@ -0,0 +1,128 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.io;
+
+import java.io.IOException;
+import java.util.Map;
+import java.util.concurrent.Executor;
+import javax.net.ssl.SSLEngine;
+
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+public abstract class NegotiatingClientConnection extends AbstractConnection
+{
+    private static final Logger LOG = Log.getLogger(NegotiatingClientConnection.class);
+
+    private final SSLEngine engine;
+    private final ClientConnectionFactory connectionFactory;
+    private final Map<String, Object> context;
+    private volatile boolean completed;
+
+    protected NegotiatingClientConnection(EndPoint endp, Executor executor, SSLEngine sslEngine, ClientConnectionFactory connectionFactory, Map<String, Object> context)
+    {
+        super(endp, executor);
+        this.engine = sslEngine;
+        this.connectionFactory = connectionFactory;
+        this.context = context;
+    }
+
+    protected SSLEngine getSSLEngine()
+    {
+        return engine;
+    }
+
+    protected void completed()
+    {
+        completed = true;
+    }
+
+    @Override
+    public void onOpen()
+    {
+        super.onOpen();
+        try
+        {
+            getEndPoint().flush(BufferUtil.EMPTY_BUFFER);
+            if (completed)
+                replaceConnection();
+            else
+                fillInterested();
+        }
+        catch (IOException x)
+        {
+            close();
+            throw new RuntimeIOException(x);
+        }
+    }
+
+    @Override
+    public void onFillable()
+    {
+        while (true)
+        {
+            int filled = fill();
+            if (filled == 0 && !completed)
+                fillInterested();
+            if (filled <= 0 || completed)
+                break;
+        }
+        if (completed)
+            replaceConnection();
+    }
+
+    private int fill()
+    {
+        try
+        {
+            return getEndPoint().fill(BufferUtil.EMPTY_BUFFER);
+        }
+        catch (IOException x)
+        {
+            LOG.debug(x);
+            close();
+            return -1;
+        }
+    }
+
+    private void replaceConnection()
+    {
+        EndPoint endPoint = getEndPoint();
+        try
+        {
+            Connection oldConnection = endPoint.getConnection();
+            Connection newConnection = connectionFactory.newConnection(endPoint, context);
+            ClientConnectionFactory.Helper.replaceConnection(oldConnection, newConnection);
+        }
+        catch (Throwable x)
+        {
+            LOG.debug(x);
+            close();
+        }
+    }
+
+    @Override
+    public void close()
+    {
+        // Gentler close for SSL.
+        getEndPoint().shutdownOutput();
+        super.close();
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/io/NegotiatingClientConnectionFactory.java b/lib/jetty/org/eclipse/jetty/io/NegotiatingClientConnectionFactory.java
new file mode 100644 (file)
index 0000000..ff38600
--- /dev/null
@@ -0,0 +1,35 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.io;
+
+
+public abstract class NegotiatingClientConnectionFactory implements ClientConnectionFactory
+{
+    private final ClientConnectionFactory connectionFactory;
+
+    protected NegotiatingClientConnectionFactory(ClientConnectionFactory connectionFactory)
+    {
+        this.connectionFactory = connectionFactory;
+    }
+
+    public ClientConnectionFactory getClientConnectionFactory()
+    {
+        return connectionFactory;
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/io/NetworkTrafficListener.java b/lib/jetty/org/eclipse/jetty/io/NetworkTrafficListener.java
new file mode 100644 (file)
index 0000000..6a4239f
--- /dev/null
@@ -0,0 +1,100 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.io;
+
+import java.net.Socket;
+import java.nio.ByteBuffer;
+
+/**
+ * <p>A listener for raw network traffic within Jetty.</p>
+ * <p>{@link NetworkTrafficListener}s can be installed in a
+ * <code>org.eclipse.jetty.server.nio.NetworkTrafficSelectChannelConnector</code>,
+ * and are notified of the following network traffic events:</p>
+ * <ul>
+ * <li>Connection opened, when the server has accepted the connection from a remote client</li>
+ * <li>Incoming bytes, when the server receives bytes sent from a remote client</li>
+ * <li>Outgoing bytes, when the server sends bytes to a remote client</li>
+ * <li>Connection closed, when the server has closed the connection to a remote client</li>
+ * </ul>
+ * <p>{@link NetworkTrafficListener}s can be used to log the network traffic viewed by
+ * a Jetty server (for example logging to filesystem) for activities such as debugging
+ * or request/response cycles or for replaying request/response cycles to other servers.</p>
+ */
+public interface NetworkTrafficListener
+{
+    /**
+     * <p>Callback method invoked when a connection from a remote client has been accepted.</p>
+     * <p>The {@code socket} parameter can be used to extract socket address information of
+     * the remote client.</p>
+     *
+     * @param socket the socket associated with the remote client
+     */
+    public void opened(Socket socket);
+
+    /**
+     * <p>Callback method invoked when bytes sent by a remote client arrived on the server.</p>
+     *
+     * @param socket the socket associated with the remote client
+     * @param bytes  the read-only buffer containing the incoming bytes
+     */
+    public void incoming(Socket socket, ByteBuffer bytes);
+
+    /**
+     * <p>Callback method invoked when bytes are sent to a remote client from the server.</p>
+     * <p>This method is invoked after the bytes have been actually written to the remote client.</p>
+     *
+     * @param socket the socket associated with the remote client
+     * @param bytes  the read-only buffer containing the outgoing bytes
+     */
+    public void outgoing(Socket socket, ByteBuffer bytes);
+
+    /**
+     * <p>Callback method invoked when a connection to a remote client has been closed.</p>
+     * <p>The {@code socket} parameter is already closed when this method is called, so it
+     * cannot be queried for socket address information of the remote client.<br />
+     * However, the {@code socket} parameter is the same object passed to {@link #opened(Socket)},
+     * so it is possible to map socket information in {@link #opened(Socket)} and retrieve it
+     * in this method.
+     *
+     * @param socket the (closed) socket associated with the remote client
+     */
+    public void closed(Socket socket);
+
+    /**
+     * <p>A commodity class that implements {@link NetworkTrafficListener} with empty methods.</p>
+     */
+    public static class Adapter implements NetworkTrafficListener
+    {
+        public void opened(Socket socket)
+        {
+        }
+
+        public void incoming(Socket socket, ByteBuffer bytes)
+        {
+        }
+
+        public void outgoing(Socket socket, ByteBuffer bytes)
+        {
+        }
+
+        public void closed(Socket socket)
+        {
+        }
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/io/NetworkTrafficSelectChannelEndPoint.java b/lib/jetty/org/eclipse/jetty/io/NetworkTrafficSelectChannelEndPoint.java
new file mode 100644 (file)
index 0000000..a4b6f7d
--- /dev/null
@@ -0,0 +1,155 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.io;
+
+import java.io.IOException;
+import java.net.Socket;
+import java.nio.ByteBuffer;
+import java.nio.channels.SelectionKey;
+import java.nio.channels.SocketChannel;
+import java.util.List;
+
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.thread.Scheduler;
+
+public class NetworkTrafficSelectChannelEndPoint extends SelectChannelEndPoint
+{
+    private static final Logger LOG = Log.getLogger(NetworkTrafficSelectChannelEndPoint.class);
+
+    private final List<NetworkTrafficListener> listeners;
+
+    public NetworkTrafficSelectChannelEndPoint(SocketChannel channel, SelectorManager.ManagedSelector selectSet, SelectionKey key, Scheduler scheduler, long idleTimeout, List<NetworkTrafficListener> listeners) throws IOException
+    {
+        super(channel, selectSet, key, scheduler, idleTimeout);
+        this.listeners = listeners;
+    }
+
+    @Override
+    public int fill(ByteBuffer buffer) throws IOException
+    {
+        int read = super.fill(buffer);
+        notifyIncoming(buffer, read);
+        return read;
+    }
+
+    @Override
+    public boolean flush(ByteBuffer... buffers) throws IOException
+    {
+        boolean flushed=true;
+        for (ByteBuffer b : buffers)
+        {
+            if (b.hasRemaining())
+            {
+                int position = b.position();
+                ByteBuffer view=b.slice();
+                flushed&=super.flush(b);
+                int l=b.position()-position;
+                view.limit(view.position()+l);
+                notifyOutgoing(view);
+                if (!flushed)
+                    break;
+            }
+        }
+        return flushed;
+    }
+
+    
+
+    @Override
+    public void onOpen()
+    {
+        super.onOpen();
+        if (listeners != null && !listeners.isEmpty())
+        {
+            for (NetworkTrafficListener listener : listeners)
+            {
+                try
+                {
+                    listener.opened(getSocket());
+                }
+                catch (Exception x)
+                {
+                    LOG.warn(x);
+                }
+            }
+        }
+    }
+
+    @Override
+    public void onClose()
+    {
+        super.onClose();
+        if (listeners != null && !listeners.isEmpty())
+        {
+            for (NetworkTrafficListener listener : listeners)
+            {
+                try
+                {
+                    listener.closed(getSocket());
+                }
+                catch (Exception x)
+                {
+                    LOG.warn(x);
+                }
+            }
+        }
+    }
+
+
+    public void notifyIncoming(ByteBuffer buffer, int read)
+    {
+        if (listeners != null && !listeners.isEmpty() && read > 0)
+        {
+            for (NetworkTrafficListener listener : listeners)
+            {
+                try
+                {
+                    ByteBuffer view = buffer.asReadOnlyBuffer();
+                    listener.incoming(getSocket(), view);
+                }
+                catch (Exception x)
+                {
+                    LOG.warn(x);
+                }
+            }
+        }
+    }
+
+    public void notifyOutgoing(ByteBuffer view)
+    {
+        if (listeners != null && !listeners.isEmpty() && view.hasRemaining())
+        {
+            Socket socket=getSocket();
+            for (NetworkTrafficListener listener : listeners)
+            {
+                try
+                {
+                    listener.outgoing(socket, view);   
+                }
+                catch (Exception x)
+                {
+                    LOG.warn(x);
+                }
+            }
+        }
+    }
+
+}
diff --git a/lib/jetty/org/eclipse/jetty/io/RuntimeIOException.java b/lib/jetty/org/eclipse/jetty/io/RuntimeIOException.java
new file mode 100644 (file)
index 0000000..88c33f7
--- /dev/null
@@ -0,0 +1,48 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+
+package org.eclipse.jetty.io;
+
+/* ------------------------------------------------------------ */
+/**
+ * Subclass of {@link java.lang.RuntimeException} used to signal that there
+ * was an {@link java.io.IOException} thrown by underlying {@link java.io.Writer}
+ */
+public class RuntimeIOException extends RuntimeException
+{
+    public RuntimeIOException()
+    {
+        super();
+    }
+
+    public RuntimeIOException(String message)
+    {
+        super(message);
+    }
+
+    public RuntimeIOException(Throwable cause)
+    {
+        super(cause);
+    }
+
+    public RuntimeIOException(String message, Throwable cause)
+    {
+        super(message,cause);
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/io/SelectChannelEndPoint.java b/lib/jetty/org/eclipse/jetty/io/SelectChannelEndPoint.java
new file mode 100644 (file)
index 0000000..3050402
--- /dev/null
@@ -0,0 +1,207 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.io;
+
+import java.nio.channels.CancelledKeyException;
+import java.nio.channels.SelectionKey;
+import java.nio.channels.SocketChannel;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.eclipse.jetty.io.SelectorManager.ManagedSelector;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.thread.Scheduler;
+
+/**
+ * An ChannelEndpoint that can be scheduled by {@link SelectorManager}.
+ */
+public class SelectChannelEndPoint extends ChannelEndPoint implements SelectorManager.SelectableEndPoint
+{
+    public static final Logger LOG = Log.getLogger(SelectChannelEndPoint.class);
+
+    private final Runnable _updateTask = new Runnable()
+    {
+        @Override
+        public void run()
+        {
+            try
+            {
+                if (getChannel().isOpen())
+                {
+                    int oldInterestOps = _key.interestOps();
+                    int newInterestOps = _interestOps.get();
+                    if (newInterestOps != oldInterestOps)
+                        setKeyInterests(oldInterestOps, newInterestOps);
+                }
+            }
+            catch (CancelledKeyException x)
+            {
+                LOG.debug("Ignoring key update for concurrently closed channel {}", this);
+                close();
+            }
+            catch (Exception x)
+            {
+                LOG.warn("Ignoring key update for " + this, x);
+                close();
+            }
+        }
+    };
+
+    /**
+     * true if {@link ManagedSelector#destroyEndPoint(EndPoint)} has not been called
+     */
+    private final AtomicBoolean _open = new AtomicBoolean();
+    private final SelectorManager.ManagedSelector _selector;
+    private final SelectionKey _key;
+    /**
+     * The desired value for {@link SelectionKey#interestOps()}
+     */
+    private final AtomicInteger _interestOps = new AtomicInteger();
+
+    public SelectChannelEndPoint(SocketChannel channel, ManagedSelector selector, SelectionKey key, Scheduler scheduler, long idleTimeout)
+    {
+        super(scheduler,channel);
+        _selector = selector;
+        _key = key;
+        setIdleTimeout(idleTimeout);
+    }
+
+    @Override
+    protected boolean needsFill()
+    {
+        updateLocalInterests(SelectionKey.OP_READ, true);
+        return false;
+    }
+
+    @Override
+    protected void onIncompleteFlush()
+    {
+        updateLocalInterests(SelectionKey.OP_WRITE, true);
+    }
+
+    @Override
+    public void onSelected()
+    {
+        assert _selector.isSelectorThread();
+        int oldInterestOps = _key.interestOps();
+        int readyOps = _key.readyOps();
+        int newInterestOps = oldInterestOps & ~readyOps;
+        setKeyInterests(oldInterestOps, newInterestOps);
+        updateLocalInterests(readyOps, false);
+        if (_key.isReadable())
+            getFillInterest().fillable();
+        if (_key.isWritable())
+            getWriteFlusher().completeWrite();
+    }
+
+
+    private void updateLocalInterests(int operation, boolean add)
+    {
+        while (true)
+        {
+            int oldInterestOps = _interestOps.get();
+            int newInterestOps;
+            if (add)
+                newInterestOps = oldInterestOps | operation;
+            else
+                newInterestOps = oldInterestOps & ~operation;
+
+            if (isInputShutdown())
+                newInterestOps &= ~SelectionKey.OP_READ;
+            if (isOutputShutdown())
+                newInterestOps &= ~SelectionKey.OP_WRITE;
+
+            if (newInterestOps != oldInterestOps)
+            {
+                if (_interestOps.compareAndSet(oldInterestOps, newInterestOps))
+                {
+                    LOG.debug("Local interests updated {} -> {} for {}", oldInterestOps, newInterestOps, this);
+                    _selector.updateKey(_updateTask);
+                }
+                else
+                {
+                    LOG.debug("Local interests update conflict: now {}, was {}, attempted {} for {}", _interestOps.get(), oldInterestOps, newInterestOps, this);
+                    continue;
+                }
+            }
+            else
+            {
+                LOG.debug("Ignoring local interests update {} -> {} for {}", oldInterestOps, newInterestOps, this);
+            }
+            break;
+        }
+    }
+
+
+    private void setKeyInterests(int oldInterestOps, int newInterestOps)
+    {
+        LOG.debug("Key interests updated {} -> {}", oldInterestOps, newInterestOps);
+        _key.interestOps(newInterestOps);
+    }
+
+    @Override
+    public void close()
+    {
+        if (_open.compareAndSet(true, false))
+        {
+            super.close();
+            _selector.destroyEndPoint(this);
+        }
+    }
+
+    @Override
+    public boolean isOpen()
+    {
+        // We cannot rely on super.isOpen(), because there is a race between calls to close() and isOpen():
+        // a thread may call close(), which flips the boolean but has not yet called super.close(), and
+        // another thread calls isOpen() which would return true - wrong - if based on super.isOpen().
+        return _open.get();
+    }
+
+    @Override
+    public void onOpen()
+    {
+        if (_open.compareAndSet(false, true))
+            super.onOpen();
+    }
+
+    @Override
+    public String toString()
+    {
+        // Do NOT use synchronized (this)
+        // because it's very easy to deadlock when debugging is enabled.
+        // We do a best effort to print the right toString() and that's it.
+        try
+        {
+            boolean valid = _key!=null && _key.isValid();
+            int keyInterests = valid ? _key.interestOps() : -1;
+            int keyReadiness = valid ? _key.readyOps() : -1;
+            return String.format("%s{io=%d,kio=%d,kro=%d}",
+                    super.toString(),
+                    _interestOps.get(),
+                    keyInterests,
+                    keyReadiness);
+        }
+        catch (CancelledKeyException x)
+        {
+            return String.format("%s{io=%s,kio=-2,kro=-2}", super.toString(), _interestOps.get());
+        }
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/io/SelectorManager.java b/lib/jetty/org/eclipse/jetty/io/SelectorManager.java
new file mode 100644 (file)
index 0000000..fd3c6c6
--- /dev/null
@@ -0,0 +1,984 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.io;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.net.ConnectException;
+import java.net.Socket;
+import java.net.SocketAddress;
+import java.net.SocketTimeoutException;
+import java.nio.channels.CancelledKeyException;
+import java.nio.channels.SelectionKey;
+import java.nio.channels.Selector;
+import java.nio.channels.ServerSocketChannel;
+import java.nio.channels.SocketChannel;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Queue;
+import java.util.Set;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executor;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicReference;
+
+import org.eclipse.jetty.util.ConcurrentArrayQueue;
+import org.eclipse.jetty.util.TypeUtil;
+import org.eclipse.jetty.util.component.AbstractLifeCycle;
+import org.eclipse.jetty.util.component.ContainerLifeCycle;
+import org.eclipse.jetty.util.component.Dumpable;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.thread.NonBlockingThread;
+import org.eclipse.jetty.util.thread.Scheduler;
+
+/**
+ * <p>{@link SelectorManager} manages a number of {@link ManagedSelector}s that
+ * simplify the non-blocking primitives provided by the JVM via the {@code java.nio} package.</p>
+ * <p>{@link SelectorManager} subclasses implement methods to return protocol-specific
+ * {@link EndPoint}s and {@link Connection}s.</p>
+ */
+public abstract class SelectorManager extends AbstractLifeCycle implements Dumpable
+{
+    public static final String SUBMIT_KEY_UPDATES = "org.eclipse.jetty.io.SelectorManager.submitKeyUpdates";
+    public static final int DEFAULT_CONNECT_TIMEOUT = 15000;
+    protected static final Logger LOG = Log.getLogger(SelectorManager.class);
+    private final static boolean __submitKeyUpdates = Boolean.valueOf(System.getProperty(SUBMIT_KEY_UPDATES, "false"));
+    
+    private final Executor executor;
+    private final Scheduler scheduler;
+    private final ManagedSelector[] _selectors;
+    private long _connectTimeout = DEFAULT_CONNECT_TIMEOUT;
+    private long _selectorIndex;
+    
+    protected SelectorManager(Executor executor, Scheduler scheduler)
+    {
+        this(executor, scheduler, (Runtime.getRuntime().availableProcessors() + 1) / 2);
+    }
+
+    protected SelectorManager(Executor executor, Scheduler scheduler, int selectors)
+    {
+        if (selectors<=0)
+            throw new IllegalArgumentException("No selectors");
+        this.executor = executor;
+        this.scheduler = scheduler;
+        _selectors = new ManagedSelector[selectors];
+    }
+
+    public Executor getExecutor()
+    {
+        return executor;
+    }
+
+    public Scheduler getScheduler()
+    {
+        return scheduler;
+    }
+
+    /**
+     * Get the connect timeout
+     *
+     * @return the connect timeout (in milliseconds)
+     */
+    public long getConnectTimeout()
+    {
+        return _connectTimeout;
+    }
+
+    /**
+     * Set the connect timeout (in milliseconds)
+     *
+     * @param milliseconds the number of milliseconds for the timeout
+     */
+    public void setConnectTimeout(long milliseconds)
+    {
+        _connectTimeout = milliseconds;
+    }
+
+    /**
+     * Executes the given task in a different thread.
+     *
+     * @param task the task to execute
+     */
+    protected void execute(Runnable task)
+    {
+        executor.execute(task);
+    }
+
+    /**
+     * @return the number of selectors in use
+     */
+    public int getSelectorCount()
+    {
+        return _selectors.length;
+    }
+
+    private ManagedSelector chooseSelector()
+    {
+        // The ++ increment here is not atomic, but it does not matter,
+        // so long as the value changes sometimes, then connections will
+        // be distributed over the available selectors.
+        long s = _selectorIndex++;
+        int index = (int)(s % getSelectorCount());
+        return _selectors[index];
+    }
+
+    /**
+     * <p>Registers a channel to perform a non-blocking connect.</p>
+     * <p>The channel must be set in non-blocking mode, and {@link SocketChannel#connect(SocketAddress)}
+     * must be called prior to calling this method.</p>
+     *
+     * @param channel    the channel to register
+     * @param attachment the attachment object
+     */
+    public void connect(SocketChannel channel, Object attachment)
+    {
+        ManagedSelector set = chooseSelector();
+        set.submit(set.new Connect(channel, attachment));
+    }
+
+    /**
+     * <p>Registers a channel to perform non-blocking read/write operations.</p>
+     * <p>This method is called just after a channel has been accepted by {@link ServerSocketChannel#accept()},
+     * or just after having performed a blocking connect via {@link Socket#connect(SocketAddress, int)}.</p>
+     *
+     * @param channel the channel to register
+     */
+    public void accept(final SocketChannel channel)
+    {
+        final ManagedSelector selector = chooseSelector();
+        selector.submit(selector.new Accept(channel));
+    }
+    
+    /**
+     * <p>Registers a server channel for accept operations.
+     * When a {@link SocketChannel} is accepted from the given {@link ServerSocketChannel}
+     * then the {@link #accepted(SocketChannel)} method is called, which must be
+     * overridden by a derivation of this class to handle the accepted channel
+     * 
+     * @param server the server channel to register
+     */
+    public void acceptor(final ServerSocketChannel server)
+    {
+        final ManagedSelector selector = chooseSelector();
+        selector.submit(selector.new Acceptor(server));
+    }
+    
+    /**
+     * Callback method when a channel is accepted from the {@link ServerSocketChannel}
+     * passed to {@link #acceptor(ServerSocketChannel)}.
+     * The default impl throws an {@link UnsupportedOperationException}, so it must
+     * be overridden by subclasses if a server channel is provided.
+     *
+     * @param channel the
+     * @throws IOException
+     */
+    protected void accepted(SocketChannel channel) throws IOException
+    {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    protected void doStart() throws Exception
+    {
+        super.doStart();
+        for (int i = 0; i < _selectors.length; i++)
+        {
+            ManagedSelector selector = newSelector(i);
+            _selectors[i] = selector;
+            selector.start();
+            execute(new NonBlockingThread(selector));
+        }
+    }
+
+    /**
+     * <p>Factory method for {@link ManagedSelector}.</p>
+     *
+     * @param id an identifier for the {@link ManagedSelector to create}
+     * @return a new {@link ManagedSelector}
+     */
+    protected ManagedSelector newSelector(int id)
+    {
+        return new ManagedSelector(id);
+    }
+
+    @Override
+    protected void doStop() throws Exception
+    {
+        for (ManagedSelector selector : _selectors)
+            selector.stop();
+        super.doStop();
+    }
+
+    /**
+     * <p>Callback method invoked when an endpoint is opened.</p>
+     *
+     * @param endpoint the endpoint being opened
+     */
+    protected void endPointOpened(EndPoint endpoint)
+    {
+        endpoint.onOpen();
+    }
+
+    /**
+     * <p>Callback method invoked when an endpoint is closed.</p>
+     *
+     * @param endpoint the endpoint being closed
+     */
+    protected void endPointClosed(EndPoint endpoint)
+    {
+        endpoint.onClose();
+    }
+
+    /**
+     * <p>Callback method invoked when a connection is opened.</p>
+     *
+     * @param connection the connection just opened
+     */
+    public void connectionOpened(Connection connection)
+    {
+        try
+        {
+            connection.onOpen();
+        }
+        catch (Throwable x)
+        {
+            if (isRunning())
+                LOG.warn("Exception while notifying connection " + connection, x);
+            else
+                LOG.debug("Exception while notifying connection {}",connection, x);
+        }
+    }
+
+    /**
+     * <p>Callback method invoked when a connection is closed.</p>
+     *
+     * @param connection the connection just closed
+     */
+    public void connectionClosed(Connection connection)
+    {
+        try
+        {
+            connection.onClose();
+        }
+        catch (Throwable x)
+        {
+            LOG.debug("Exception while notifying connection " + connection, x);
+        }
+    }
+
+    protected boolean finishConnect(SocketChannel channel) throws IOException
+    {
+        return channel.finishConnect();
+    }
+
+    /**
+     * <p>Callback method invoked when a non-blocking connect cannot be completed.</p>
+     * <p>By default it just logs with level warning.</p>
+     *
+     * @param channel the channel that attempted the connect
+     * @param ex the exception that caused the connect to fail
+     * @param attachment the attachment object associated at registration
+     */
+    protected void connectionFailed(SocketChannel channel, Throwable ex, Object attachment)
+    {
+        LOG.warn(String.format("%s - %s", channel, attachment), ex);
+    }
+
+    /**
+     * <p>Factory method to create {@link EndPoint}.</p>
+     * <p>This method is invoked as a result of the registration of a channel via {@link #connect(SocketChannel, Object)}
+     * or {@link #accept(SocketChannel)}.</p>
+     *
+     * @param channel   the channel associated to the endpoint
+     * @param selector the selector the channel is registered to
+     * @param selectionKey      the selection key
+     * @return a new endpoint
+     * @throws IOException if the endPoint cannot be created
+     * @see #newConnection(SocketChannel, EndPoint, Object)
+     */
+    protected abstract EndPoint newEndPoint(SocketChannel channel, SelectorManager.ManagedSelector selector, SelectionKey selectionKey) throws IOException;
+
+    /**
+     * <p>Factory method to create {@link Connection}.</p>
+     *
+     * @param channel    the channel associated to the connection
+     * @param endpoint   the endpoint
+     * @param attachment the attachment
+     * @return a new connection
+     * @throws IOException
+     * @see #newEndPoint(SocketChannel, ManagedSelector, SelectionKey)
+     */
+    public abstract Connection newConnection(SocketChannel channel, EndPoint endpoint, Object attachment) throws IOException;
+
+    @Override
+    public String dump()
+    {
+        return ContainerLifeCycle.dump(this);
+    }
+
+    @Override
+    public void dump(Appendable out, String indent) throws IOException
+    {
+        ContainerLifeCycle.dumpObject(out, this);
+        ContainerLifeCycle.dump(out, indent, TypeUtil.asList(_selectors));
+    }
+
+    private enum State
+    {
+        CHANGES, MORE_CHANGES, SELECT, WAKEUP, PROCESS
+    }
+
+    /**
+     * <p>{@link ManagedSelector} wraps a {@link Selector} simplifying non-blocking operations on channels.</p>
+     * <p>{@link ManagedSelector} runs the select loop, which waits on {@link Selector#select()} until events
+     * happen for registered channels. When events happen, it notifies the {@link EndPoint} associated
+     * with the channel.</p>
+     */
+    public class ManagedSelector extends AbstractLifeCycle implements Runnable, Dumpable
+    {
+        private final AtomicReference<State> _state= new AtomicReference<>(State.PROCESS);
+        private final Queue<Runnable> _changes = new ConcurrentArrayQueue<>();
+        private final int _id;
+        private Selector _selector;
+        private volatile Thread _thread;
+
+        public ManagedSelector(int id)
+        {
+            _id = id;
+            setStopTimeout(5000);
+        }
+
+        @Override
+        protected void doStart() throws Exception
+        {
+            super.doStart();
+            _selector = Selector.open();
+            _state.set(State.PROCESS);
+        }
+
+        @Override
+        protected void doStop() throws Exception
+        {
+            LOG.debug("Stopping {}", this);
+            Stop stop = new Stop();
+            submit(stop);
+            stop.await(getStopTimeout());
+            LOG.debug("Stopped {}", this);
+        }
+
+        /**
+         * Submit a task to update a selector key.  If the System property {@link SelectorManager#SUBMIT_KEY_UPDATES}
+         * is set true (default is false), the task is passed to {@link #submit(Runnable)}.   Otherwise it is run immediately and the selector 
+         * woken up if need be.   
+         * @param update the update to a key
+         */
+        public void updateKey(Runnable update)
+        {
+            if (__submitKeyUpdates)
+            {
+                submit(update);
+            }
+            else
+            {
+                runChange(update);
+                if (_state.compareAndSet(State.SELECT, State.WAKEUP))
+                   wakeup();
+            }
+        }
+        
+        /**
+         * <p>Submits a change to be executed in the selector thread.</p>
+         * <p>Changes may be submitted from any thread, and the selector thread woken up
+         * (if necessary) to execute the change.</p>
+         *
+         * @param change the change to submit
+         */
+        public void submit(Runnable change)
+        {
+            // This method may be called from the selector thread, and therefore
+            // we could directly run the change without queueing, but this may
+            // lead to stack overflows on a busy server, so we always offer the
+            // change to the queue and process the state.
+
+            _changes.offer(change);
+            LOG.debug("Queued change {}", change);
+
+            out: while (true)
+            {
+                switch (_state.get())
+                {
+                    case SELECT:
+                        // Avoid multiple wakeup() calls if we the CAS fails
+                        if (!_state.compareAndSet(State.SELECT, State.WAKEUP))
+                            continue;
+                        wakeup();
+                        break out;
+                    case CHANGES:
+                        // Tell the selector thread that we have more changes.
+                        // If we fail to CAS, we possibly need to wakeup(), so loop.
+                        if (_state.compareAndSet(State.CHANGES, State.MORE_CHANGES))
+                            break out;
+                        continue;
+                    case WAKEUP:
+                        // Do nothing, we have already a wakeup scheduled
+                        break out;
+                    case MORE_CHANGES:
+                        // Do nothing, we already notified the selector thread of more changes
+                        break out;
+                    case PROCESS:
+                        // Do nothing, the changes will be run after the processing
+                        break out;
+                    default:
+                        throw new IllegalStateException();
+                }
+            }
+        }
+
+        private void runChanges()
+        {
+            Runnable change;
+            while ((change = _changes.poll()) != null)
+                runChange(change);
+        }
+
+        protected void runChange(Runnable change)
+        {
+            try
+            {
+                LOG.debug("Running change {}", change);
+                change.run();
+            }
+            catch (Throwable x)
+            {
+                LOG.debug("Could not run change " + change, x);
+            }
+        }
+
+        @Override
+        public void run()
+        {
+            _thread = Thread.currentThread();
+            String name = _thread.getName();
+            try
+            {
+                _thread.setName(name + "-selector-" + SelectorManager.this.getClass().getSimpleName()+"@"+Integer.toHexString(SelectorManager.this.hashCode())+"/"+_id);
+                LOG.debug("Starting {} on {}", _thread, this);
+                while (isRunning())
+                    select();
+                runChanges();
+            }
+            finally
+            {
+                LOG.debug("Stopped {} on {}", _thread, this);
+                _thread.setName(name);
+            }
+        }
+
+        /**
+         * <p>Process changes and waits on {@link Selector#select()}.</p>
+         *
+         * @see #submit(Runnable)
+         */
+        public void select()
+        {
+            boolean debug = LOG.isDebugEnabled();
+            try
+            {
+                _state.set(State.CHANGES);
+
+                // Run the changes, and only exit if we ran all changes
+                out: while(true)
+                {
+                    switch (_state.get())
+                    {
+                        case CHANGES:
+                            runChanges();
+                            if (_state.compareAndSet(State.CHANGES, State.SELECT))
+                                break out;
+                            continue;
+                        case MORE_CHANGES:
+                            runChanges();
+                            _state.set(State.CHANGES);
+                            continue;
+                        default:
+                            throw new IllegalStateException();    
+                    }
+                }
+                // Must check first for SELECT and *then* for WAKEUP
+                // because we read the state twice in the assert, and
+                // it could change from SELECT to WAKEUP in between.
+                assert _state.get() == State.SELECT || _state.get() == State.WAKEUP;
+
+                if (debug)
+                    LOG.debug("Selector loop waiting on select");
+                int selected = _selector.select();
+                if (debug)
+                    LOG.debug("Selector loop woken up from select, {}/{} selected", selected, _selector.keys().size());
+
+                _state.set(State.PROCESS);
+
+                Set<SelectionKey> selectedKeys = _selector.selectedKeys();
+                for (SelectionKey key : selectedKeys)
+                {
+                    if (key.isValid())
+                    {
+                        processKey(key);
+                    }
+                    else
+                    {
+                        if (debug)
+                            LOG.debug("Selector loop ignoring invalid key for channel {}", key.channel());
+                        Object attachment = key.attachment();
+                        if (attachment instanceof EndPoint)
+                            ((EndPoint)attachment).close();
+                    }
+                }
+                selectedKeys.clear();
+            }
+            catch (Throwable x)
+            {
+                if (isRunning())
+                    LOG.warn(x);
+                else
+                    LOG.ignore(x);
+            }
+        }
+
+        private void processKey(SelectionKey key)
+        {
+            Object attachment = key.attachment();
+            try
+            {
+                if (attachment instanceof SelectableEndPoint)
+                {
+                    ((SelectableEndPoint)attachment).onSelected();
+                }
+                else if (key.isConnectable())
+                {
+                    processConnect(key, (Connect)attachment);
+                }
+                else if (key.isAcceptable())
+                {
+                    processAccept(key);
+                }
+                else
+                {
+                    throw new IllegalStateException();
+                }
+            }
+            catch (CancelledKeyException x)
+            {
+                LOG.debug("Ignoring cancelled key for channel {}", key.channel());
+                if (attachment instanceof EndPoint)
+                    closeNoExceptions((EndPoint)attachment);
+            }
+            catch (Throwable x)
+            {
+                LOG.warn("Could not process key for channel " + key.channel(), x);
+                if (attachment instanceof EndPoint)
+                    closeNoExceptions((EndPoint)attachment);
+            }
+        }
+
+        private void processConnect(SelectionKey key, Connect connect)
+        {
+            SocketChannel channel = (SocketChannel)key.channel();
+            try
+            {
+                key.attach(connect.attachment);
+                boolean connected = finishConnect(channel);
+                if (connected)
+                {
+                    connect.timeout.cancel();
+                    key.interestOps(0);
+                    EndPoint endpoint = createEndPoint(channel, key);
+                    key.attach(endpoint);
+                }
+                else
+                {
+                    throw new ConnectException();
+                }
+            }
+            catch (Throwable x)
+            {
+                connect.failed(x);
+            }
+        }
+        
+        private void processAccept(SelectionKey key)
+        {
+            ServerSocketChannel server = (ServerSocketChannel)key.channel();
+            SocketChannel channel = null;
+            try
+            {
+                while ((channel = server.accept()) != null)
+                {
+                    accepted(channel);
+                }
+            }
+            catch (Throwable x)
+            {
+                closeNoExceptions(channel);
+                LOG.warn("Accept failed for channel " + channel, x);
+            }
+        }
+
+        private void closeNoExceptions(Closeable closeable)
+        {
+            try
+            {
+                if (closeable != null)
+                    closeable.close();
+            }
+            catch (Throwable x)
+            {
+                LOG.ignore(x);
+            }
+        }
+
+        public void wakeup()
+        {
+            _selector.wakeup();
+        }
+
+        public boolean isSelectorThread()
+        {
+            return Thread.currentThread() == _thread;
+        }
+
+        private EndPoint createEndPoint(SocketChannel channel, SelectionKey selectionKey) throws IOException
+        {
+            EndPoint endPoint = newEndPoint(channel, this, selectionKey);
+            endPointOpened(endPoint);
+            Connection connection = newConnection(channel, endPoint, selectionKey.attachment());
+            endPoint.setConnection(connection);
+            connectionOpened(connection);
+            LOG.debug("Created {}", endPoint);
+            return endPoint;
+        }
+
+        public void destroyEndPoint(EndPoint endPoint)
+        {
+            LOG.debug("Destroyed {}", endPoint);
+            Connection connection = endPoint.getConnection();
+            if (connection != null)
+                connectionClosed(connection);
+            endPointClosed(endPoint);
+        }
+
+        @Override
+        public String dump()
+        {
+            return ContainerLifeCycle.dump(this);
+        }
+
+        @Override
+        public void dump(Appendable out, String indent) throws IOException
+        {
+            out.append(String.valueOf(this)).append(" id=").append(String.valueOf(_id)).append("\n");
+
+            Thread selecting = _thread;
+
+            Object where = "not selecting";
+            StackTraceElement[] trace = selecting == null ? null : selecting.getStackTrace();
+            if (trace != null)
+            {
+                for (StackTraceElement t : trace)
+                    if (t.getClassName().startsWith("org.eclipse.jetty."))
+                    {
+                        where = t;
+                        break;
+                    }
+            }
+
+            Selector selector = _selector;
+            if (selector != null && selector.isOpen())
+            {
+                final ArrayList<Object> dump = new ArrayList<>(selector.keys().size() * 2);
+                dump.add(where);
+
+                DumpKeys dumpKeys = new DumpKeys(dump);
+                submit(dumpKeys);
+                dumpKeys.await(5, TimeUnit.SECONDS);
+
+                ContainerLifeCycle.dump(out, indent, dump);
+            }
+        }
+
+        public void dumpKeysState(List<Object> dumps)
+        {
+            Selector selector = _selector;
+            Set<SelectionKey> keys = selector.keys();
+            dumps.add(selector + " keys=" + keys.size());
+            for (SelectionKey key : keys)
+            {
+                if (key.isValid())
+                    dumps.add(key.attachment() + " iOps=" + key.interestOps() + " rOps=" + key.readyOps());
+                else
+                    dumps.add(key.attachment() + " iOps=-1 rOps=-1");
+            }
+        }
+
+        @Override
+        public String toString()
+        {
+            Selector selector = _selector;
+            return String.format("%s keys=%d selected=%d",
+                    super.toString(),
+                    selector != null && selector.isOpen() ? selector.keys().size() : -1,
+                    selector != null && selector.isOpen() ? selector.selectedKeys().size() : -1);
+        }
+
+        private class DumpKeys implements Runnable
+        {
+            private final CountDownLatch latch = new CountDownLatch(1);
+            private final List<Object> _dumps;
+
+            private DumpKeys(List<Object> dumps)
+            {
+                this._dumps = dumps;
+            }
+
+            @Override
+            public void run()
+            {
+                dumpKeysState(_dumps);
+                latch.countDown();
+            }
+
+            public boolean await(long timeout, TimeUnit unit)
+            {
+                try
+                {
+                    return latch.await(timeout, unit);
+                }
+                catch (InterruptedException x)
+                {
+                    return false;
+                }
+            }
+        }
+
+        private class Acceptor implements Runnable
+        {
+            private final ServerSocketChannel _channel;
+
+            public Acceptor(ServerSocketChannel channel)
+            {
+                this._channel = channel;
+            }
+
+            @Override
+            public void run()
+            {
+                try
+                {
+                    SelectionKey key = _channel.register(_selector, SelectionKey.OP_ACCEPT, null);
+                    LOG.debug("{} acceptor={}", this, key);
+                }
+                catch (Throwable x)
+                {
+                    closeNoExceptions(_channel);
+                    LOG.warn(x);
+                }
+            }
+        }
+
+        private class Accept implements Runnable
+        {
+            private final SocketChannel _channel;
+
+            public Accept(SocketChannel channel)
+            {
+                this._channel = channel;
+            }
+
+            @Override
+            public void run()
+            {
+                try
+                {
+                    SelectionKey key = _channel.register(_selector, 0, null);
+                    EndPoint endpoint = createEndPoint(_channel, key);
+                    key.attach(endpoint);
+                }
+                catch (Throwable x)
+                {
+                    closeNoExceptions(_channel);
+                    LOG.debug(x);
+                }
+            }
+        }
+
+        private class Connect implements Runnable
+        {
+            private final AtomicBoolean failed = new AtomicBoolean();
+            private final SocketChannel channel;
+            private final Object attachment;
+            private final Scheduler.Task timeout;
+
+            public Connect(SocketChannel channel, Object attachment)
+            {
+                this.channel = channel;
+                this.attachment = attachment;
+                this.timeout = scheduler.schedule(new ConnectTimeout(this), getConnectTimeout(), TimeUnit.MILLISECONDS);
+            }
+
+            @Override
+            public void run()
+            {
+                try
+                {
+                    channel.register(_selector, SelectionKey.OP_CONNECT, this);
+                }
+                catch (Throwable x)
+                {
+                    failed(x);
+                }
+            }
+
+            protected void failed(Throwable failure)
+            {
+                if (failed.compareAndSet(false, true))
+                {
+                    timeout.cancel();
+                    closeNoExceptions(channel);
+                    connectionFailed(channel, failure, attachment);
+                }
+            }
+        }
+
+        private class ConnectTimeout implements Runnable
+        {
+            private final Connect connect;
+
+            private ConnectTimeout(Connect connect)
+            {
+                this.connect = connect;
+            }
+
+            @Override
+            public void run()
+            {
+                SocketChannel channel = connect.channel;
+                if (channel.isConnectionPending())
+                {
+                    LOG.debug("Channel {} timed out while connecting, closing it", channel);
+                    connect.failed(new SocketTimeoutException());
+                }
+            }
+        }
+
+        private class Stop implements Runnable
+        {
+            private final CountDownLatch latch = new CountDownLatch(1);
+
+            @Override
+            public void run()
+            {
+                try
+                {
+                    for (SelectionKey key : _selector.keys())
+                    {
+                        Object attachment = key.attachment();
+                        if (attachment instanceof EndPoint)
+                        {
+                            EndPointCloser closer = new EndPointCloser((EndPoint)attachment);
+                            execute(closer);
+                            // We are closing the SelectorManager, so we want to block the
+                            // selector thread here until we have closed all EndPoints.
+                            // This is different than calling close() directly, because close()
+                            // can wait forever, while here we are limited by the stop timeout.
+                            closer.await(getStopTimeout());
+                        }
+                    }
+
+                    closeNoExceptions(_selector);
+                }
+                finally
+                {
+                    latch.countDown();
+                }
+            }
+
+            public boolean await(long timeout)
+            {
+                try
+                {
+                    return latch.await(timeout, TimeUnit.MILLISECONDS);
+                }
+                catch (InterruptedException x)
+                {
+                    return false;
+                }
+            }
+        }
+
+        private class EndPointCloser implements Runnable
+        {
+            private final CountDownLatch latch = new CountDownLatch(1);
+            private final EndPoint endPoint;
+
+            private EndPointCloser(EndPoint endPoint)
+            {
+                this.endPoint = endPoint;
+            }
+
+            @Override
+            public void run()
+            {
+                try
+                {
+                    closeNoExceptions(endPoint.getConnection());
+                }
+                finally
+                {
+                    latch.countDown();
+                }
+            }
+
+            private boolean await(long timeout)
+            {
+                try
+                {
+                    return latch.await(timeout, TimeUnit.MILLISECONDS);
+                }
+                catch (InterruptedException x)
+                {
+                    return false;
+                }
+            }
+        }
+    }
+
+    /**
+     * A {@link SelectableEndPoint} is an {@link EndPoint} that wish to be notified of
+     * non-blocking events by the {@link ManagedSelector}.
+     */
+    public interface SelectableEndPoint extends EndPoint
+    {
+        /**
+         * <p>Callback method invoked when a read or write events has been detected by the {@link ManagedSelector}
+         * for this endpoint.</p>
+         */
+        void onSelected();
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/io/UncheckedPrintWriter.java b/lib/jetty/org/eclipse/jetty/io/UncheckedPrintWriter.java
new file mode 100644 (file)
index 0000000..6898ddf
--- /dev/null
@@ -0,0 +1,682 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.io;
+
+import java.io.BufferedWriter;
+import java.io.IOException;
+import java.io.InterruptedIOException;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.PrintWriter;
+import java.io.Writer;
+
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/* ------------------------------------------------------------ */
+/**
+ * A wrapper for the {@link java.io.PrintWriter} that re-throws the instances of
+ * {@link java.io.IOException} thrown by the underlying implementation of
+ * {@link java.io.Writer} as {@link RuntimeIOException} instances.
+ */
+public class UncheckedPrintWriter extends PrintWriter
+{
+    private static final Logger LOG = Log.getLogger(UncheckedPrintWriter.class);
+
+    private boolean _autoFlush = false;
+    private IOException _ioException;
+    private boolean _isClosed = false;
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Line separator string. This is the value of the line.separator property
+     * at the moment that the stream was created.
+     */
+    private String _lineSeparator;
+
+    public UncheckedPrintWriter(Writer out)
+    {
+        this(out,false);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Create a new PrintWriter.
+     * 
+     * @param out
+     *            A character-output stream
+     * @param autoFlush
+     *            A boolean; if true, the println() methods will flush the
+     *            output buffer
+     */
+    public UncheckedPrintWriter(Writer out, boolean autoFlush)
+    {
+        super(out,autoFlush);
+        this._autoFlush = autoFlush;
+        this._lineSeparator = System.getProperty("line.separator");
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Create a new PrintWriter, without automatic line flushing, from an
+     * existing OutputStream. This convenience constructor creates the necessary
+     * intermediate OutputStreamWriter, which will convert characters into bytes
+     * using the default character encoding.
+     * 
+     * @param out
+     *            An output stream
+     * 
+     * @see java.io.OutputStreamWriter#OutputStreamWriter(java.io.OutputStream)
+     */
+    public UncheckedPrintWriter(OutputStream out)
+    {
+        this(out,false);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Create a new PrintWriter from an existing OutputStream. This convenience
+     * constructor creates the necessary intermediate OutputStreamWriter, which
+     * will convert characters into bytes using the default character encoding.
+     * 
+     * @param out
+     *            An output stream
+     * @param autoFlush
+     *            A boolean; if true, the println() methods will flush the
+     *            output buffer
+     * 
+     * @see java.io.OutputStreamWriter#OutputStreamWriter(java.io.OutputStream)
+     */
+    public UncheckedPrintWriter(OutputStream out, boolean autoFlush)
+    {
+        this(new BufferedWriter(new OutputStreamWriter(out)),autoFlush);
+    }
+    
+    
+    /* ------------------------------------------------------------ */
+    public boolean checkError()
+    {
+        return _ioException!=null || super.checkError();
+    }
+    
+    /* ------------------------------------------------------------ */
+    private void setError(Throwable th)
+    {
+      
+        super.setError();
+
+        if (th instanceof IOException)
+            _ioException=(IOException)th;
+        else
+        {
+            _ioException=new IOException(String.valueOf(th));
+            _ioException.initCause(th);
+        }
+
+        LOG.debug(th);
+    }
+
+
+    @Override
+    protected void setError()
+    {
+        setError(new IOException());
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Check to make sure that the stream has not been closed */
+    private void isOpen() throws IOException
+    {       
+        if (_ioException!=null)
+            throw new RuntimeIOException(_ioException); 
+        
+        if (_isClosed)
+            throw new IOException("Stream closed");
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Flush the stream.
+     */
+    @Override
+    public void flush()
+    {
+        try
+        {
+            synchronized (lock)
+            {
+                isOpen();
+                out.flush();
+            }
+        }
+        catch (IOException ex)
+        {
+            setError(ex);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Close the stream.
+     */
+    @Override
+    public void close()
+    {
+        try
+        {
+            synchronized (lock)
+            {
+                out.close();
+                _isClosed = true;
+            }
+        }
+        catch (IOException ex)
+        {
+            setError(ex);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Write a single character.
+     * 
+     * @param c
+     *            int specifying a character to be written.
+     */
+    @Override
+    public void write(int c)
+    {
+        try
+        {
+            synchronized (lock)
+            {
+                isOpen();
+                out.write(c);
+            }
+        }
+        catch (InterruptedIOException x)
+        {
+            Thread.currentThread().interrupt();
+        }
+        catch (IOException ex)
+        {
+            setError(ex);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Write a portion of an array of characters.
+     * 
+     * @param buf
+     *            Array of characters
+     * @param off
+     *            Offset from which to start writing characters
+     * @param len
+     *            Number of characters to write
+     */
+    @Override
+    public void write(char buf[], int off, int len)
+    {
+        try
+        {
+            synchronized (lock)
+            {
+                isOpen();
+                out.write(buf,off,len);
+            }
+        }
+        catch (InterruptedIOException x)
+        {
+            Thread.currentThread().interrupt();
+        }
+        catch (IOException ex)
+        {
+            setError(ex);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Write an array of characters. This method cannot be inherited from the
+     * Writer class because it must suppress I/O exceptions.
+     * 
+     * @param buf
+     *            Array of characters to be written
+     */
+    @Override
+    public void write(char buf[])
+    { 
+        this.write(buf,0,buf.length);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Write a portion of a string.
+     * 
+     * @param s
+     *            A String
+     * @param off
+     *            Offset from which to start writing characters
+     * @param len
+     *            Number of characters to write
+     */
+    @Override
+    public void write(String s, int off, int len)
+    {
+        try
+        {
+            synchronized (lock)
+            {
+                isOpen();
+                out.write(s,off,len);
+            }
+        }
+        catch (InterruptedIOException x)
+        {
+            Thread.currentThread().interrupt();
+        }
+        catch (IOException ex)
+        {
+            setError(ex);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Write a string. This method cannot be inherited from the Writer class
+     * because it must suppress I/O exceptions.
+     * 
+     * @param s
+     *            String to be written
+     */
+    @Override
+    public void write(String s)
+    {
+        this.write(s,0,s.length());
+    }
+
+    private void newLine()
+    {
+        try
+        {
+            synchronized (lock)
+            {
+                isOpen();
+                out.write(_lineSeparator);
+                if (_autoFlush)
+                    out.flush();
+            }
+        }
+        catch (InterruptedIOException x)
+        {
+            Thread.currentThread().interrupt();
+        }
+        catch (IOException ex)
+        {
+            setError(ex);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Print a boolean value. The string produced by <code>{@link
+     * java.lang.String#valueOf(boolean)}</code> is translated into bytes
+     * according to the platform's default character encoding, and these bytes
+     * are written in exactly the manner of the <code>{@link
+     * #write(int)}</code> method.
+     * 
+     * @param b
+     *            The <code>boolean</code> to be printed
+     */
+    @Override
+    public void print(boolean b)
+    {
+        this.write(b?"true":"false");
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Print a character. The character is translated into one or more bytes
+     * according to the platform's default character encoding, and these bytes
+     * are written in exactly the manner of the <code>{@link
+     * #write(int)}</code> method.
+     * 
+     * @param c
+     *            The <code>char</code> to be printed
+     */
+    @Override
+    public void print(char c)
+    {
+        this.write(c);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Print an integer. The string produced by <code>{@link
+     * java.lang.String#valueOf(int)}</code> is translated into bytes according
+     * to the platform's default character encoding, and these bytes are written
+     * in exactly the manner of the <code>{@link #write(int)}</code> method.
+     * 
+     * @param i
+     *            The <code>int</code> to be printed
+     * @see java.lang.Integer#toString(int)
+     */
+    @Override
+    public void print(int i)
+    {
+        this.write(String.valueOf(i));
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Print a long integer. The string produced by <code>{@link
+     * java.lang.String#valueOf(long)}</code> is translated into bytes according
+     * to the platform's default character encoding, and these bytes are written
+     * in exactly the manner of the <code>{@link #write(int)}</code> method.
+     * 
+     * @param l
+     *            The <code>long</code> to be printed
+     * @see java.lang.Long#toString(long)
+     */
+    @Override
+    public void print(long l)
+    {
+        this.write(String.valueOf(l));
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Print a floating-point number. The string produced by <code>{@link
+     * java.lang.String#valueOf(float)}</code> is translated into bytes
+     * according to the platform's default character encoding, and these bytes
+     * are written in exactly the manner of the <code>{@link #write(int)}</code>
+     * method.
+     * 
+     * @param f
+     *            The <code>float</code> to be printed
+     * @see java.lang.Float#toString(float)
+     */
+    @Override
+    public void print(float f)
+    {
+        this.write(String.valueOf(f));
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Print a double-precision floating-point number. The string produced by
+     * <code>{@link java.lang.String#valueOf(double)}</code> is translated into
+     * bytes according to the platform's default character encoding, and these
+     * bytes are written in exactly the manner of the <code>{@link
+     * #write(int)}</code> method.
+     * 
+     * @param d
+     *            The <code>double</code> to be printed
+     * @see java.lang.Double#toString(double)
+     */
+    @Override
+    public void print(double d)
+    {
+        this.write(String.valueOf(d));
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Print an array of characters. The characters are converted into bytes
+     * according to the platform's default character encoding, and these bytes
+     * are written in exactly the manner of the <code>{@link #write(int)}</code>
+     * method.
+     * 
+     * @param s
+     *            The array of chars to be printed
+     * 
+     * @throws NullPointerException
+     *             If <code>s</code> is <code>null</code>
+     */
+    @Override
+    public void print(char s[])
+    {
+        this.write(s);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Print a string. If the argument is <code>null</code> then the string
+     * <code>"null"</code> is printed. Otherwise, the string's characters are
+     * converted into bytes according to the platform's default character
+     * encoding, and these bytes are written in exactly the manner of the
+     * <code>{@link #write(int)}</code> method.
+     * 
+     * @param s
+     *            The <code>String</code> to be printed
+     */
+    @Override
+    public void print(String s)
+    {
+        if (s == null)
+        {
+            s = "null";
+        }
+        this.write(s);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Print an object. The string produced by the <code>{@link
+     * java.lang.String#valueOf(Object)}</code> method is translated into bytes
+     * according to the platform's default character encoding, and these bytes
+     * are written in exactly the manner of the <code>{@link #write(int)}</code>
+     * method.
+     * 
+     * @param obj
+     *            The <code>Object</code> to be printed
+     * @see java.lang.Object#toString()
+     */
+    @Override
+    public void print(Object obj)
+    {
+        this.write(String.valueOf(obj));
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Terminate the current line by writing the line separator string. The line
+     * separator string is defined by the system property
+     * <code>line.separator</code>, and is not necessarily a single newline
+     * character (<code>'\n'</code>).
+     */
+    @Override
+    public void println()
+    {
+        this.newLine();
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Print a boolean value and then terminate the line. This method behaves as
+     * though it invokes <code>{@link #print(boolean)}</code> and then
+     * <code>{@link #println()}</code>.
+     * 
+     * @param x
+     *            the <code>boolean</code> value to be printed
+     */
+    @Override
+    public void println(boolean x)
+    {
+        synchronized (lock)
+        {
+            this.print(x);
+            this.println();
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Print a character and then terminate the line. This method behaves as
+     * though it invokes <code>{@link #print(char)}</code> and then <code>{@link
+     * #println()}</code>.
+     * 
+     * @param x
+     *            the <code>char</code> value to be printed
+     */
+    @Override
+    public void println(char x)
+    {
+        synchronized (lock)
+        {
+            this.print(x);
+            this.println();
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Print an integer and then terminate the line. This method behaves as
+     * though it invokes <code>{@link #print(int)}</code> and then <code>{@link
+     * #println()}</code>.
+     * 
+     * @param x
+     *            the <code>int</code> value to be printed
+     */
+    @Override
+    public void println(int x)
+    {
+        synchronized (lock)
+        {
+            this.print(x);
+            this.println();
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Print a long integer and then terminate the line. This method behaves as
+     * though it invokes <code>{@link #print(long)}</code> and then
+     * <code>{@link #println()}</code>.
+     * 
+     * @param x
+     *            the <code>long</code> value to be printed
+     */
+    @Override
+    public void println(long x)
+    {
+        synchronized (lock)
+        {
+            this.print(x);
+            this.println();
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Print a floating-point number and then terminate the line. This method
+     * behaves as though it invokes <code>{@link #print(float)}</code> and then
+     * <code>{@link #println()}</code>.
+     * 
+     * @param x
+     *            the <code>float</code> value to be printed
+     */
+    @Override
+    public void println(float x)
+    {
+        synchronized (lock)
+        {
+            this.print(x);
+            this.println();
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Print a double-precision floating-point number and then terminate the
+     * line. This method behaves as though it invokes <code>{@link
+     * #print(double)}</code> and then <code>{@link #println()}</code>.
+     * 
+     * @param x
+     *            the <code>double</code> value to be printed
+     */
+    /* ------------------------------------------------------------ */
+    @Override
+    public void println(double x)
+    {
+        synchronized (lock)
+        {
+            this.print(x);
+            this.println();
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Print an array of characters and then terminate the line. This method
+     * behaves as though it invokes <code>{@link #print(char[])}</code> and then
+     * <code>{@link #println()}</code>.
+     * 
+     * @param x
+     *            the array of <code>char</code> values to be printed
+     */
+    @Override
+    public void println(char x[])
+    {
+        synchronized (lock)
+        {
+            this.print(x);
+            this.println();
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Print a String and then terminate the line. This method behaves as though
+     * it invokes <code>{@link #print(String)}</code> and then
+     * <code>{@link #println()}</code>.
+     * 
+     * @param x
+     *            the <code>String</code> value to be printed
+     */
+    @Override
+    public void println(String x)
+    {
+        synchronized (lock)
+        {
+            this.print(x);
+            this.println();
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Print an Object and then terminate the line. This method behaves as
+     * though it invokes <code>{@link #print(Object)}</code> and then
+     * <code>{@link #println()}</code>.
+     * 
+     * @param x
+     *            the <code>Object</code> value to be printed
+     */
+    @Override
+    public void println(Object x)
+    {
+        synchronized (lock)
+        {
+            this.print(x);
+            this.println();
+        }
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/io/WriteFlusher.java b/lib/jetty/org/eclipse/jetty/io/WriteFlusher.java
new file mode 100644 (file)
index 0000000..fccc622
--- /dev/null
@@ -0,0 +1,504 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.io;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.channels.ClosedChannelException;
+import java.nio.channels.WritePendingException;
+import java.util.Arrays;
+import java.util.EnumMap;
+import java.util.EnumSet;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicReference;
+
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.Callback;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+
+/**
+ * A Utility class to help implement {@link EndPoint#write(Callback, ByteBuffer...)} by calling
+ * {@link EndPoint#flush(ByteBuffer...)} until all content is written.
+ * The abstract method {@link #onIncompleteFlushed()} is called when not all content has been written after a call to
+ * flush and should organise for the {@link #completeWrite()} method to be called when a subsequent call to flush
+ * should  be able to make more progress.
+ * <p>
+ */
+abstract public class WriteFlusher
+{
+    private static final Logger LOG = Log.getLogger(WriteFlusher.class);
+    private static final boolean DEBUG = LOG.isDebugEnabled(); // Easy for the compiler to remove the code if DEBUG==false
+    private static final ByteBuffer[] EMPTY_BUFFERS = new ByteBuffer[0];
+    private static final EnumMap<StateType, Set<StateType>> __stateTransitions = new EnumMap<>(StateType.class);
+    private static final State __IDLE = new IdleState();
+    private static final State __WRITING = new WritingState();
+    private static final State __COMPLETING = new CompletingState();
+    private final EndPoint _endPoint;
+    private final AtomicReference<State> _state = new AtomicReference<>();
+
+    static
+    {
+        // fill the state machine
+        __stateTransitions.put(StateType.IDLE, EnumSet.of(StateType.WRITING));
+        __stateTransitions.put(StateType.WRITING, EnumSet.of(StateType.IDLE, StateType.PENDING, StateType.FAILED));
+        __stateTransitions.put(StateType.PENDING, EnumSet.of(StateType.COMPLETING,StateType.IDLE));
+        __stateTransitions.put(StateType.COMPLETING, EnumSet.of(StateType.IDLE, StateType.PENDING, StateType.FAILED));
+        __stateTransitions.put(StateType.FAILED, EnumSet.of(StateType.IDLE));
+    }
+
+    // A write operation may either complete immediately:
+    //     IDLE-->WRITING-->IDLE
+    // Or it may not completely flush and go via the PENDING state
+    //     IDLE-->WRITING-->PENDING-->COMPLETING-->IDLE
+    // Or it may take several cycles to complete
+    //     IDLE-->WRITING-->PENDING-->COMPLETING-->PENDING-->COMPLETING-->IDLE
+    //
+    // If a failure happens while in IDLE, it is a noop since there is no operation to tell of the failure.
+    // If a failure happens while in WRITING, but the the write has finished successfully or with an IOExceptions,
+    // the callback's complete or respectively failed methods will be called.
+    // If a failure happens in PENDING state, then the fail method calls the pending callback and moves to IDLE state
+    //
+    //   IDLE--(fail)-->IDLE
+    //   IDLE-->WRITING--(fail)-->FAILED-->IDLE
+    //   IDLE-->WRITING-->PENDING--(fail)-->IDLE
+    //   IDLE-->WRITING-->PENDING-->COMPLETING--(fail)-->FAILED-->IDLE
+    //
+    // So a call to fail in the PENDING state will be directly handled and the state changed to IDLE
+    // A call to fail in the WRITING or COMPLETING states will just set the state to FAILED and the failure will be
+    // handled with the write or completeWrite methods try to move the state from what they thought it was.
+    //
+
+    protected WriteFlusher(EndPoint endPoint)
+    {
+        _state.set(__IDLE);
+        _endPoint = endPoint;
+    }
+
+    private enum StateType
+    {
+        IDLE,
+        WRITING,
+        PENDING,
+        COMPLETING,
+        FAILED
+    }
+
+    /**
+     * Tries to update the current state to the given new state.
+     * @param previous the expected current state
+     * @param next the desired new state
+     * @return the previous state or null if the state transition failed
+     * @throws WritePendingException if currentState is WRITING and new state is WRITING (api usage error)
+     */
+    private boolean updateState(State previous,State next)
+    {
+        if (!isTransitionAllowed(previous,next))
+            throw new IllegalStateException();
+
+        boolean updated = _state.compareAndSet(previous, next);
+        if (DEBUG)
+            LOG.debug("update {}:{}{}{}", this, previous, updated?"-->":"!->",next);
+        return updated;
+    }
+
+    private void fail(PendingState pending)
+    {
+        State current = _state.get();
+        if (current.getType()==StateType.FAILED)
+        {
+            FailedState failed=(FailedState)current;
+            if (updateState(failed,__IDLE))
+            {
+                pending.fail(failed.getCause());
+                return;
+            }
+        }
+        throw new IllegalStateException();
+    }
+
+    private void ignoreFail()
+    {
+        State current = _state.get();
+        while (current.getType()==StateType.FAILED)
+        {
+            if (updateState(current,__IDLE))
+                return;
+            current = _state.get();
+        }
+    }
+
+    private boolean isTransitionAllowed(State currentState, State newState)
+    {
+        Set<StateType> allowedNewStateTypes = __stateTransitions.get(currentState.getType());
+        if (!allowedNewStateTypes.contains(newState.getType()))
+        {
+            LOG.warn("{}: {} -> {} not allowed", this, currentState, newState);
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * State represents a State of WriteFlusher.
+     */
+    private static class State
+    {
+        private final StateType _type;
+
+        private State(StateType stateType)
+        {
+            _type = stateType;
+        }
+
+        public StateType getType()
+        {
+            return _type;
+        }
+
+        @Override
+        public String toString()
+        {
+            return String.format("%s", _type);
+        }
+    }
+
+    /**
+     * In IdleState WriteFlusher is idle and accepts new writes
+     */
+    private static class IdleState extends State
+    {
+        private IdleState()
+        {
+            super(StateType.IDLE);
+        }
+    }
+
+    /**
+     * In WritingState WriteFlusher is currently writing.
+     */
+    private static class WritingState extends State
+    {
+        private WritingState()
+        {
+            super(StateType.WRITING);
+        }
+    }
+
+    /**
+     * In FailedState no more operations are allowed. The current implementation will never recover from this state.
+     */
+    private static class FailedState extends State
+    {
+        private final Throwable _cause;
+        private FailedState(Throwable cause)
+        {
+            super(StateType.FAILED);
+            _cause=cause;
+        }
+
+        public Throwable getCause()
+        {
+            return _cause;
+        }
+    }
+
+    /**
+     * In CompletingState WriteFlusher is flushing buffers that have not been fully written in write(). If write()
+     * didn't flush all buffers in one go, it'll switch the State to PendingState. completeWrite() will then switch to
+     * this state and try to flush the remaining buffers.
+     */
+    private static class CompletingState extends State
+    {
+        private CompletingState()
+        {
+            super(StateType.COMPLETING);
+        }
+    }
+
+    /**
+     * In PendingState not all buffers could be written in one go. Then write() will switch to PendingState() and
+     * preserve the state by creating a new PendingState object with the given parameters.
+     */
+    private class PendingState extends State
+    {
+        private final Callback _callback;
+        private final ByteBuffer[] _buffers;
+
+        private PendingState(ByteBuffer[] buffers, Callback callback)
+        {
+            super(StateType.PENDING);
+            _buffers = compact(buffers);
+            _callback = callback;
+        }
+
+        public ByteBuffer[] getBuffers()
+        {
+            return _buffers;
+        }
+
+        protected boolean fail(Throwable cause)
+        {
+            if (_callback!=null)
+            {
+                _callback.failed(cause);
+                return true;
+            }
+            return false;
+        }
+
+        protected void complete()
+        {
+            if (_callback!=null)
+                _callback.succeeded();
+        }
+
+        /**
+         * Compacting the buffers is needed because the semantic of WriteFlusher is
+         * to write the buffers and if the caller sees that the buffer is consumed,
+         * then it can recycle it.
+         * If we do not compact, then it is possible that we store a consumed buffer,
+         * which is then recycled and refilled; when the WriteFlusher is invoked to
+         * complete the write, it will write the refilled bytes, garbling the content.
+         *
+         * @param buffers the buffers to compact
+         * @return the compacted buffers
+         */
+        private ByteBuffer[] compact(ByteBuffer[] buffers)
+        {
+            int length = buffers.length;
+
+            // Just one element, no need to compact
+            if (length < 2)
+                return buffers;
+
+            // How many still have content ?
+            int consumed = 0;
+            while (consumed < length && BufferUtil.isEmpty(buffers[consumed]))
+                ++consumed;
+
+            // All of them still have content, no need to compact
+            if (consumed == 0)
+                return buffers;
+
+            // None has content, return empty
+            if (consumed == length)
+                return EMPTY_BUFFERS;
+
+            return Arrays.copyOfRange(buffers,consumed,length);
+        }
+    }
+
+    /**
+     * Abstract call to be implemented by specific WriteFlushers. It should schedule a call to {@link #completeWrite()}
+     * or {@link #onFail(Throwable)} when appropriate.
+     */
+    abstract protected void onIncompleteFlushed();
+
+    /**
+     * Tries to switch state to WRITING. If successful it writes the given buffers to the EndPoint. If state transition
+     * fails it'll fail the callback.
+     *
+     * If not all buffers can be written in one go it creates a new <code>PendingState</code> object to preserve the state
+     * and then calls {@link #onIncompleteFlushed()}. The remaining buffers will be written in {@link #completeWrite()}.
+     *
+     * If all buffers have been written it calls callback.complete().
+     *
+     * @param callback the callback to call on either failed or complete
+     * @param buffers the buffers to flush to the endpoint
+     */
+    public void write(Callback callback, ByteBuffer... buffers) throws WritePendingException
+    {
+        if (DEBUG)
+            LOG.debug("write: {} {}", this, BufferUtil.toDetailString(buffers));
+
+        if (!updateState(__IDLE,__WRITING))
+            throw new WritePendingException();
+
+        try
+        {
+            boolean flushed=_endPoint.flush(buffers);
+            if (DEBUG)
+                LOG.debug("flushed {}", flushed);
+
+            // Are we complete?
+            for (ByteBuffer b : buffers)
+            {
+                if (!flushed||BufferUtil.hasContent(b))
+                {
+                    PendingState pending=new PendingState(buffers, callback);
+                    if (updateState(__WRITING,pending))
+                        onIncompleteFlushed();
+                    else
+                        fail(pending);
+                    return;
+                }
+            }
+
+            // If updateState didn't succeed, we don't care as our buffers have been written
+            if (!updateState(__WRITING,__IDLE))
+                ignoreFail();
+            if (callback!=null)
+                callback.succeeded();
+        }
+        catch (IOException e)
+        {
+            if (DEBUG)
+                LOG.debug("write exception", e);
+            if (updateState(__WRITING,__IDLE))
+            {
+                if (callback!=null)
+                    callback.failed(e);
+            }
+            else
+                fail(new PendingState(buffers, callback));
+        }
+    }
+
+
+    /**
+     * Complete a write that has not completed and that called {@link #onIncompleteFlushed()} to request a call to this
+     * method when a call to {@link EndPoint#flush(ByteBuffer...)} is likely to be able to progress.
+     *
+     * It tries to switch from PENDING to COMPLETING. If state transition fails, then it does nothing as the callback
+     * should have been already failed. That's because the only way to switch from PENDING outside this method is
+     * {@link #onFail(Throwable)} or {@link #onClose()}
+     */
+    public void completeWrite()
+    {
+        if (DEBUG)
+            LOG.debug("completeWrite: {}", this);
+
+        State previous = _state.get();
+
+        if (previous.getType()!=StateType.PENDING)
+            return; // failure already handled.
+
+        PendingState pending = (PendingState)previous;
+        if (!updateState(pending,__COMPLETING))
+            return; // failure already handled.
+
+        try
+        {
+            ByteBuffer[] buffers = pending.getBuffers();
+
+            boolean flushed=_endPoint.flush(buffers);
+            if (DEBUG)
+                LOG.debug("flushed {}", flushed);
+
+            // Are we complete?
+            for (ByteBuffer b : buffers)
+            {
+                if (!flushed || BufferUtil.hasContent(b))
+                {
+                    if (updateState(__COMPLETING,pending))
+                        onIncompleteFlushed();
+                    else
+                        fail(pending);
+                    return;
+                }
+            }
+
+            // If updateState didn't succeed, we don't care as our buffers have been written
+            if (!updateState(__COMPLETING,__IDLE))
+                ignoreFail();
+            pending.complete();
+        }
+        catch (IOException e)
+        {
+            if (DEBUG)
+                LOG.debug("completeWrite exception", e);
+            if(updateState(__COMPLETING,__IDLE))
+                pending.fail(e);
+            else
+                fail(pending);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Notify the flusher of a failure
+     * @param cause The cause of the failure
+     * @return true if the flusher passed the failure to a {@link Callback} instance
+     */
+    public boolean onFail(Throwable cause)
+    {
+        // Keep trying to handle the failure until we get to IDLE or FAILED state
+        while(true)
+        {
+            State current=_state.get();
+            switch(current.getType())
+            {
+                case IDLE:
+                case FAILED:
+                    if (DEBUG)
+                        LOG.debug("ignored: {} {}", this, cause);
+                    return false;
+
+                case PENDING:
+                    if (DEBUG)
+                        LOG.debug("failed: {} {}", this, cause);
+
+                    PendingState pending = (PendingState)current;
+                    if (updateState(pending,__IDLE))
+                        return pending.fail(cause);
+                    break;
+
+                default:
+                    if (DEBUG)
+                        LOG.debug("failed: {} {}", this, cause);
+
+                    if (updateState(current,new FailedState(cause)))
+                        return false;
+                    break;
+            }
+        }
+    }
+
+    public void onClose()
+    {
+        if (_state.get()==__IDLE)
+            return;
+        onFail(new ClosedChannelException());
+    }
+
+    boolean isIdle()
+    {
+        return _state.get().getType() == StateType.IDLE;
+    }
+
+    public boolean isInProgress()
+    {
+        switch(_state.get().getType())
+        {
+            case WRITING:
+            case PENDING:
+            case COMPLETING:
+                return true;
+            default:
+                return false;
+        }
+    }
+
+    @Override
+    public String toString()
+    {
+        return String.format("WriteFlusher@%x{%s}", hashCode(), _state.get());
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/io/WriterOutputStream.java b/lib/jetty/org/eclipse/jetty/io/WriterOutputStream.java
new file mode 100644 (file)
index 0000000..d08b473
--- /dev/null
@@ -0,0 +1,101 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.io;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.Writer;
+import java.nio.charset.Charset;
+
+
+/* ------------------------------------------------------------ */
+/** Wrap a Writer as an OutputStream.
+ * When all you have is a Writer and only an OutputStream will do.
+ * Try not to use this as it indicates that your design is a dogs
+ * breakfast (JSP made me write it).
+ * 
+ */
+public class WriterOutputStream extends OutputStream
+{
+    protected final Writer _writer;
+    protected final Charset _encoding;
+    private final byte[] _buf=new byte[1];
+    
+    /* ------------------------------------------------------------ */
+    public WriterOutputStream(Writer writer, String encoding)
+    {
+        _writer=writer;
+        _encoding=encoding==null?null:Charset.forName(encoding);
+    }
+    
+    /* ------------------------------------------------------------ */
+    public WriterOutputStream(Writer writer)
+    {
+        _writer=writer;
+        _encoding=null;
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public void close()
+        throws IOException
+    {
+        _writer.close();
+    }
+    
+    /* ------------------------------------------------------------ */
+    @Override
+    public void flush()
+        throws IOException
+    {
+        _writer.flush();
+    }
+    
+    /* ------------------------------------------------------------ */
+    @Override
+    public void write(byte[] b) 
+        throws IOException
+    {
+        if (_encoding==null)
+            _writer.write(new String(b));
+        else
+            _writer.write(new String(b,_encoding));
+    }
+    
+    /* ------------------------------------------------------------ */
+    @Override
+    public void write(byte[] b, int off, int len)
+        throws IOException
+    {
+        if (_encoding==null)
+            _writer.write(new String(b,off,len));
+        else
+            _writer.write(new String(b,off,len,_encoding));
+    }
+    
+    /* ------------------------------------------------------------ */
+    @Override
+    public synchronized void write(int b)
+        throws IOException
+    {
+        _buf[0]=(byte)b;
+        write(_buf);
+    }
+}
+
diff --git a/lib/jetty/org/eclipse/jetty/io/package-info.java b/lib/jetty/org/eclipse/jetty/io/package-info.java
new file mode 100644 (file)
index 0000000..6316823
--- /dev/null
@@ -0,0 +1,23 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+/**
+ * Jetty IO : Core classes for Jetty IO subsystem
+ */
+package org.eclipse.jetty.io;
+
diff --git a/lib/jetty/org/eclipse/jetty/io/ssl/SslClientConnectionFactory.java b/lib/jetty/org/eclipse/jetty/io/ssl/SslClientConnectionFactory.java
new file mode 100644 (file)
index 0000000..ce3be14
--- /dev/null
@@ -0,0 +1,73 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.io.ssl;
+
+import java.io.IOException;
+import java.util.Map;
+import java.util.concurrent.Executor;
+
+import javax.net.ssl.SSLEngine;
+
+import org.eclipse.jetty.io.ByteBufferPool;
+import org.eclipse.jetty.io.ClientConnectionFactory;
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.util.ssl.SslContextFactory;
+
+public class SslClientConnectionFactory implements ClientConnectionFactory
+{
+    public static final String SSL_PEER_HOST_CONTEXT_KEY = "ssl.peer.host";
+    public static final String SSL_PEER_PORT_CONTEXT_KEY = "ssl.peer.port";
+    public static final String SSL_ENGINE_CONTEXT_KEY = "ssl.engine";
+
+    private final SslContextFactory sslContextFactory;
+    private final ByteBufferPool byteBufferPool;
+    private final Executor executor;
+    private final ClientConnectionFactory connectionFactory;
+
+    public SslClientConnectionFactory(SslContextFactory sslContextFactory, ByteBufferPool byteBufferPool, Executor executor, ClientConnectionFactory connectionFactory)
+    {
+        this.sslContextFactory = sslContextFactory;
+        this.byteBufferPool = byteBufferPool;
+        this.executor = executor;
+        this.connectionFactory = connectionFactory;
+    }
+
+    @Override
+    public org.eclipse.jetty.io.Connection newConnection(EndPoint endPoint, Map<String, Object> context) throws IOException
+    {
+        String host = (String)context.get(SSL_PEER_HOST_CONTEXT_KEY);
+        int port = (Integer)context.get(SSL_PEER_PORT_CONTEXT_KEY);
+        SSLEngine engine = sslContextFactory.newSSLEngine(host, port);
+        engine.setUseClientMode(true);
+        context.put(SSL_ENGINE_CONTEXT_KEY, engine);
+
+        SslConnection sslConnection = newSslConnection(byteBufferPool, executor, endPoint, engine);
+        sslConnection.setRenegotiationAllowed(sslContextFactory.isRenegotiationAllowed());
+        endPoint.setConnection(sslConnection);
+        EndPoint appEndPoint = sslConnection.getDecryptedEndPoint();
+        appEndPoint.setConnection(connectionFactory.newConnection(appEndPoint, context));
+
+        return sslConnection;
+    }
+
+    protected SslConnection newSslConnection(ByteBufferPool byteBufferPool, Executor executor, EndPoint endPoint, SSLEngine engine)
+    {
+        return new SslConnection(byteBufferPool, executor, endPoint, engine);
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/io/ssl/SslConnection.java b/lib/jetty/org/eclipse/jetty/io/ssl/SslConnection.java
new file mode 100644 (file)
index 0000000..37e8111
--- /dev/null
@@ -0,0 +1,929 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.io.ssl;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.channels.ClosedChannelException;
+import java.util.Arrays;
+import java.util.concurrent.Executor;
+
+import javax.net.ssl.SSLEngine;
+import javax.net.ssl.SSLEngineResult;
+import javax.net.ssl.SSLEngineResult.HandshakeStatus;
+import javax.net.ssl.SSLEngineResult.Status;
+import javax.net.ssl.SSLException;
+
+import org.eclipse.jetty.io.AbstractConnection;
+import org.eclipse.jetty.io.AbstractEndPoint;
+import org.eclipse.jetty.io.ByteBufferPool;
+import org.eclipse.jetty.io.Connection;
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.io.EofException;
+import org.eclipse.jetty.io.FillInterest;
+import org.eclipse.jetty.io.RuntimeIOException;
+import org.eclipse.jetty.io.SelectChannelEndPoint;
+import org.eclipse.jetty.io.WriteFlusher;
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.Callback;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/**
+ * A Connection that acts as an interceptor between an EndPoint providing SSL encrypted data
+ * and another consumer of an EndPoint (typically an {@link Connection} like HttpConnection) that
+ * wants unencrypted data.
+ * <p>
+ * The connector uses an {@link EndPoint} (typically {@link SelectChannelEndPoint}) as
+ * it's source/sink of encrypted data.   It then provides an endpoint via {@link #getDecryptedEndPoint()} to
+ * expose a source/sink of unencrypted data to another connection (eg HttpConnection).
+ * <p>
+ * The design of this class is based on a clear separation between the passive methods, which do not block nor schedule any
+ * asynchronous callbacks, and active methods that do schedule asynchronous callbacks.
+ * <p>
+ * The passive methods are {@link DecryptedEndPoint#fill(ByteBuffer)} and {@link DecryptedEndPoint#flush(ByteBuffer...)}. They make best
+ * effort attempts to progress the connection using only calls to the encrypted {@link EndPoint#fill(ByteBuffer)} and {@link EndPoint#flush(ByteBuffer...)}
+ * methods.  They will never block nor schedule any readInterest or write callbacks.   If a fill/flush cannot progress either because
+ * of network congestion or waiting for an SSL handshake message, then the fill/flush will simply return with zero bytes filled/flushed.
+ * Specifically, if a flush cannot proceed because it needs to receive a handshake message, then the flush will attempt to fill bytes from the
+ * encrypted endpoint, but if insufficient bytes are read it will NOT call {@link EndPoint#fillInterested(Callback)}.
+ * <p>
+ * It is only the active methods : {@link DecryptedEndPoint#fillInterested(Callback)} and
+ * {@link DecryptedEndPoint#write(Callback, ByteBuffer...)} that may schedule callbacks by calling the encrypted
+ * {@link EndPoint#fillInterested(Callback)} and {@link EndPoint#write(Callback, ByteBuffer...)}
+ * methods.  For normal data handling, the decrypted fillInterest method will result in an encrypted fillInterest and a decrypted
+ * write will result in an encrypted write. However, due to SSL handshaking requirements, it is also possible for a decrypted fill
+ * to call the encrypted write and for the decrypted flush to call the encrypted fillInterested methods.
+ * <p>
+ * MOST IMPORTANTLY, the encrypted callbacks from the active methods (#onFillable() and WriteFlusher#completeWrite()) do no filling or flushing
+ * themselves.  Instead they simple make the callbacks to the decrypted callbacks, so that the passive encrypted fill/flush will
+ * be called again and make another best effort attempt to progress the connection.
+ *
+ */
+public class SslConnection extends AbstractConnection
+{
+    private static final Logger LOG = Log.getLogger(SslConnection.class);
+    private static final boolean DEBUG = LOG.isDebugEnabled(); // Easy for the compiler to remove the code if DEBUG==false
+    private static final ByteBuffer __FILL_CALLED_FLUSH= BufferUtil.allocate(0);
+    private static final ByteBuffer __FLUSH_CALLED_FILL= BufferUtil.allocate(0);
+    private final ByteBufferPool _bufferPool;
+    private SSLEngine _sslEngine;
+    private final SslReconfigurator _sslFactory;
+    private final DecryptedEndPoint _decryptedEndPoint;
+    private ByteBuffer _decryptedInput;
+    private ByteBuffer _encryptedInput;
+    private ByteBuffer _encryptedOutput;
+    private final boolean _encryptedDirectBuffers = false;
+    private final boolean _decryptedDirectBuffers = false;
+    private final Runnable _runCompletWrite = new Runnable()
+    {
+        @Override
+        public void run()
+        {
+            _decryptedEndPoint.getWriteFlusher().completeWrite();
+        }
+    };
+    private boolean _renegotiationAllowed;
+
+    public SslConnection(ByteBufferPool byteBufferPool, Executor executor, EndPoint endPoint, SSLEngine sslEngine)
+    {
+       this(byteBufferPool, executor, endPoint, sslEngine, null);
+    }
+    public SslConnection(ByteBufferPool byteBufferPool, Executor executor, EndPoint endPoint, SSLEngine sslEngine, SslReconfigurator fact)
+    {
+        // This connection does not execute calls to onfillable, so they will be called by the selector thread.
+        // onfillable does not block and will only wakeup another thread to do the actual reading and handling.
+        super(endPoint, executor, !EXECUTE_ONFILLABLE);
+        this._bufferPool = byteBufferPool;
+        this._sslEngine = sslEngine;
+        this._sslFactory = fact;
+        this._decryptedEndPoint = newDecryptedEndPoint();
+    }
+
+    protected DecryptedEndPoint newDecryptedEndPoint()
+    {
+        return new DecryptedEndPoint();
+    }
+
+    public SSLEngine getSSLEngine()
+    {
+        return _sslEngine;
+    }
+
+    public DecryptedEndPoint getDecryptedEndPoint()
+    {
+        return _decryptedEndPoint;
+    }
+
+    public boolean isRenegotiationAllowed()
+    {
+        return _renegotiationAllowed;
+    }
+
+    public void setRenegotiationAllowed(boolean renegotiationAllowed)
+    {
+        this._renegotiationAllowed = renegotiationAllowed;
+    }
+
+    @Override
+    public void onOpen()
+    {
+        try
+        {
+            // Begin the handshake
+            _sslEngine.beginHandshake();
+            super.onOpen();
+            getDecryptedEndPoint().getConnection().onOpen();
+        }
+        catch (SSLException x)
+        {
+            getEndPoint().close();
+            throw new RuntimeIOException(x);
+        }
+    }
+
+    @Override
+    public void onClose()
+    {
+        _decryptedEndPoint.getConnection().onClose();
+        super.onClose();
+    }
+
+    @Override
+    public void close()
+    {
+        getDecryptedEndPoint().getConnection().close();
+    }
+
+    @Override
+    public void onFillable()
+    {
+        // onFillable means that there are encrypted bytes ready to be filled.
+        // however we do not fill them here on this callback, but instead wakeup
+        // the decrypted readInterest and/or writeFlusher so that they will attempt
+        // to do the fill and/or flush again and these calls will do the actually
+        // filling.
+
+        if (DEBUG)
+            LOG.debug("onFillable enter {}", _decryptedEndPoint);
+
+        // We have received a close handshake, close the end point to send FIN.
+        if (_decryptedEndPoint.isInputShutdown())
+            _decryptedEndPoint.close();
+
+        // wake up whoever is doing the fill or the flush so they can
+        // do all the filling, unwrapping, wrapping and flushing
+        _decryptedEndPoint.getFillInterest().fillable();
+
+        // If we are handshaking, then wake up any waiting write as well as it may have been blocked on the read
+        synchronized(_decryptedEndPoint)
+        {
+            if (_decryptedEndPoint._flushRequiresFillToProgress)
+            {
+                _decryptedEndPoint._flushRequiresFillToProgress = false;
+                getExecutor().execute(_runCompletWrite);
+            }
+        }
+
+        if (DEBUG)
+            LOG.debug("onFillable exit {}", _decryptedEndPoint);
+    }
+
+    @Override
+    public void onFillInterestedFailed(Throwable cause)
+    {
+        // this means that the fill interest in encrypted bytes has failed.
+        // However we do not handle that here on this callback, but instead wakeup
+        // the decrypted readInterest and/or writeFlusher so that they will attempt
+        // to do the fill and/or flush again and these calls will do the actually
+        // handle the cause.
+        _decryptedEndPoint.getFillInterest().onFail(cause);
+
+        boolean failFlusher = false;
+        synchronized(_decryptedEndPoint)
+        {
+            if (_decryptedEndPoint._flushRequiresFillToProgress)
+            {
+                _decryptedEndPoint._flushRequiresFillToProgress = false;
+                failFlusher = true;
+            }
+        }
+        if (failFlusher)
+            _decryptedEndPoint.getWriteFlusher().onFail(cause);
+    }
+
+    @Override
+    public String toString()
+    {
+        ByteBuffer b = _encryptedInput;
+        int ei=b==null?-1:b.remaining();
+        b = _encryptedOutput;
+        int eo=b==null?-1:b.remaining();
+        b = _decryptedInput;
+        int di=b==null?-1:b.remaining();
+
+        return String.format("SslConnection@%x{%s,eio=%d/%d,di=%d} -> %s",
+                hashCode(),
+                _sslEngine.getHandshakeStatus(),
+                ei,eo,di,
+                _decryptedEndPoint.getConnection());
+    }
+
+    public class DecryptedEndPoint extends AbstractEndPoint
+    {
+        private boolean _fillRequiresFlushToProgress;
+        private boolean _flushRequiresFillToProgress;
+        private boolean _cannotAcceptMoreAppDataToFlush;
+        private boolean _handshaken;
+        private boolean _underFlown;
+        private boolean _peeking = _sslFactory != null;
+
+        private final Callback _writeCallback = new Callback()
+        {
+            @Override
+            public void succeeded()
+            {
+                // This means that a write of encrypted data has completed.  Writes are done
+                // only if there is a pending writeflusher or a read needed to write
+                // data.  In either case the appropriate callback is passed on.
+                boolean fillable = false;
+                synchronized (DecryptedEndPoint.this)
+                {
+                    if (DEBUG)
+                        LOG.debug("write.complete {}", SslConnection.this.getEndPoint());
+
+                    releaseEncryptedOutputBuffer();
+
+                    _cannotAcceptMoreAppDataToFlush = false;
+
+                    if (_fillRequiresFlushToProgress)
+                    {
+                        _fillRequiresFlushToProgress = false;
+                        fillable = true;
+                    }
+                }
+                if (fillable)
+                    getFillInterest().fillable();
+                getExecutor().execute(_runCompletWrite);
+            }
+
+            @Override
+            public void failed(final Throwable x)
+            {
+                // This means that a write of data has failed.  Writes are done
+                // only if there is an active writeflusher or a read needed to write
+                // data.  In either case the appropriate callback is passed on.
+                boolean fail_filler = false;
+                synchronized (DecryptedEndPoint.this)
+                {
+                    if (DEBUG)
+                        LOG.debug("{} write.failed", SslConnection.this, x);
+                    BufferUtil.clear(_encryptedOutput);
+                    releaseEncryptedOutputBuffer();
+
+                    _cannotAcceptMoreAppDataToFlush = false;
+
+                    if (_fillRequiresFlushToProgress)
+                    {
+                        _fillRequiresFlushToProgress = false;
+                        fail_filler = true;
+                    }
+                }
+
+                final boolean filler_failed=fail_filler;
+
+                failedCallback(new Callback()
+                {
+                    @Override
+                    public void succeeded()
+                    {                        
+                    }
+
+                    @Override
+                    public void failed(Throwable x)
+                    {
+                        if (filler_failed)
+                            getFillInterest().onFail(x);
+                        getWriteFlusher().onFail(x);
+                    }
+                    
+                },x);
+            }
+        };
+
+        public DecryptedEndPoint()
+        {
+            super(null,getEndPoint().getLocalAddress(), getEndPoint().getRemoteAddress());
+            setIdleTimeout(getEndPoint().getIdleTimeout());
+        }
+
+        @Override
+        protected FillInterest getFillInterest()
+        {
+            return super.getFillInterest();
+        }
+
+        @Override
+        public void setIdleTimeout(long idleTimeout)
+        {
+            super.setIdleTimeout(idleTimeout);
+            getEndPoint().setIdleTimeout(idleTimeout);
+        }
+
+        @Override
+        protected WriteFlusher getWriteFlusher()
+        {
+            return super.getWriteFlusher();
+        }
+
+        @Override
+        protected void onIncompleteFlush()
+        {
+            // This means that the decrypted endpoint write method was called and not
+            // all data could be wrapped. So either we need to write some encrypted data,
+            // OR if we are handshaking we need to read some encrypted data OR
+            // if neither then we should just try the flush again.
+            boolean flush = false;
+            synchronized (DecryptedEndPoint.this)
+            {
+                if (DEBUG)
+                    LOG.debug("onIncompleteFlush {}", getEndPoint());
+                // If we have pending output data,
+                if (BufferUtil.hasContent(_encryptedOutput))
+                {
+                    // write it
+                    _cannotAcceptMoreAppDataToFlush = true;
+                    getEndPoint().write(_writeCallback, _encryptedOutput);
+                }
+                // If we are handshaking and need to read,
+                else if (_sslEngine.getHandshakeStatus() == HandshakeStatus.NEED_UNWRAP)
+                {
+                    // check if we are actually read blocked in order to write
+                    _flushRequiresFillToProgress = true;
+                    SslConnection.this.fillInterested();
+                }
+                else
+                {
+                    flush = true;
+                }
+            }
+            if (flush)
+            {
+                // If the output is closed,
+                if (isOutputShutdown())
+                {
+                    // don't bother writing, just notify of close
+                    getWriteFlusher().onClose();
+                }
+                // Else,
+                else
+                {
+                    // try to flush what is pending
+                    getWriteFlusher().completeWrite();
+                }
+            }
+        }
+
+        @Override
+        protected boolean needsFill() throws IOException
+        {
+            // This means that the decrypted data consumer has called the fillInterested
+            // method on the DecryptedEndPoint, so we have to work out if there is
+            // decrypted data to be filled or what callbacks to setup to be told when there
+            // might be more encrypted data available to attempt another call to fill
+
+            synchronized (DecryptedEndPoint.this)
+            {
+                // Do we already have some app data, then app can fill now so return true
+                if (BufferUtil.hasContent(_decryptedInput))
+                    return true;
+
+                // If we have no encrypted data to decrypt OR we have some, but it is not enough
+                if (BufferUtil.isEmpty(_encryptedInput) || _underFlown)
+                {
+                    // We are not ready to read data
+
+                    // Are we actually write blocked?
+                    if (_fillRequiresFlushToProgress)
+                    {
+                        // we must be blocked trying to write before we can read
+
+                        // Do we have data to write
+                        if (BufferUtil.hasContent(_encryptedOutput))
+                        {
+                            // write it
+                            _cannotAcceptMoreAppDataToFlush = true;
+                            getEndPoint().write(_writeCallback, _encryptedOutput);
+                        }
+                        else
+                        {
+                            // we have already written the net data
+                            // pretend we are readable so the wrap is done by next readable callback
+                            _fillRequiresFlushToProgress = false;
+                            return true;
+                        }
+                    }
+                    else
+                    {
+                        // Normal readable callback
+                        // Get called back on onfillable when then is more data to fill
+                        SslConnection.this.fillInterested();
+                    }
+
+                    return false;
+                }
+                else
+                {
+                    // We are ready to read data
+                    return true;
+                }
+            }
+        }
+
+        @Override
+        public void setConnection(Connection connection)
+        {
+            if (connection instanceof AbstractConnection)
+            {
+                AbstractConnection a = (AbstractConnection)connection;
+                if (a.getInputBufferSize()<_sslEngine.getSession().getApplicationBufferSize())
+                    a.setInputBufferSize(_sslEngine.getSession().getApplicationBufferSize());
+            }
+            super.setConnection(connection);
+        }
+
+        public SslConnection getSslConnection()
+        {
+            return SslConnection.this;
+        }
+
+        @Override
+        public synchronized int fill(ByteBuffer buffer) throws IOException
+        {
+            if (DEBUG)
+                LOG.debug("{} fill enter", SslConnection.this);
+            try
+            {
+                // Do we already have some decrypted data?
+                if (BufferUtil.hasContent(_decryptedInput))
+                    return BufferUtil.append(buffer,_decryptedInput);
+
+                // We will need a network buffer
+                if (_encryptedInput == null)
+                    _encryptedInput = _bufferPool.acquire(_sslEngine.getSession().getPacketBufferSize(), _encryptedDirectBuffers);
+                else if(!_peeking)
+                    BufferUtil.compact(_encryptedInput);
+
+                // We also need an app buffer, but can use the passed buffer if it is big enough
+                ByteBuffer app_in;
+                if (BufferUtil.space(buffer) > _sslEngine.getSession().getApplicationBufferSize())
+                    app_in = buffer;
+                else if (_decryptedInput == null)
+                    app_in = _decryptedInput = _bufferPool.acquire(_sslEngine.getSession().getApplicationBufferSize(), _decryptedDirectBuffers);
+                else
+                    app_in = _decryptedInput;
+
+                // loop filling and unwrapping until we have something
+                while (true)
+                {
+                    // Let's try reading some encrypted data... even if we have some already.
+                    int net_filled = getEndPoint().fill(_encryptedInput);
+                    if (DEBUG)
+                        LOG.debug("{} filled {} encrypted bytes", SslConnection.this, net_filled);
+
+                    decryption: while (true)
+                    {
+                        // Let's unwrap even if we have no net data because in that
+                        // case we want to fall through to the handshake handling
+                        int pos = BufferUtil.flipToFill(app_in);
+                        SSLEngineResult unwrapResult = _sslEngine.unwrap(_encryptedInput, app_in);
+                        BufferUtil.flipToFlush(app_in, pos);
+                        if (DEBUG)
+                            LOG.debug("{} unwrap {}", SslConnection.this, unwrapResult);
+
+                        HandshakeStatus handshakeStatus = _sslEngine.getHandshakeStatus();
+                        HandshakeStatus unwrapHandshakeStatus = unwrapResult.getHandshakeStatus();
+                        Status unwrapResultStatus = unwrapResult.getStatus();
+
+                        _underFlown = unwrapResultStatus == Status.BUFFER_UNDERFLOW;
+
+                        if (_underFlown)
+                        {
+                            if (net_filled < 0)
+                                closeInbound();
+                            if (net_filled <= 0)
+                                return net_filled;
+                        }
+
+                        switch (unwrapResultStatus)
+                        {
+                            case CLOSED:
+                            {
+                                switch (handshakeStatus)
+                                {
+                                    case NOT_HANDSHAKING:
+                                    {
+                                        // We were not handshaking, so just tell the app we are closed
+                                        return -1;
+                                    }
+                                    case NEED_TASK:
+                                    {
+                                        _sslEngine.getDelegatedTask().run();
+                                        continue;
+                                    }
+                                    case NEED_WRAP:
+                                    {
+                                        // We need to send some handshake data (probably the close handshake).
+                                        // We return -1 so that the application can drive the close by flushing
+                                        // or shutting down the output.
+                                        return -1;
+                                    }
+                                    default:
+                                    {
+                                        throw new IllegalStateException();
+                                    }
+                                }
+                            }
+                            case BUFFER_UNDERFLOW:
+                            case OK:
+                            {
+                                if (unwrapHandshakeStatus == HandshakeStatus.FINISHED && !_handshaken)
+                                {
+                                    _handshaken = true;
+                                    if (DEBUG)
+                                        LOG.debug("{} {} handshake completed", SslConnection.this,
+                                                _sslEngine.getUseClientMode() ? "client-side" : "resumed session server-side");
+                                }
+
+                                // Check whether renegotiation is allowed
+                                if (_handshaken && handshakeStatus != HandshakeStatus.NOT_HANDSHAKING && !isRenegotiationAllowed())
+                                {
+                                    if (DEBUG)
+                                        LOG.debug("{} renegotiation denied", SslConnection.this);
+                                    closeInbound();
+                                    return -1;
+                                }
+
+                                // If bytes were produced, don't bother with the handshake status;
+                                // pass the decrypted data to the application, which will perform
+                                // another call to fill() or flush().
+                                if (unwrapResult.bytesProduced() > 0)
+                                {
+                                    if (app_in == buffer)
+                                        return unwrapResult.bytesProduced();
+                                    return BufferUtil.append(buffer,_decryptedInput);
+                                }
+
+                                switch (handshakeStatus)
+                                {
+                                    case NOT_HANDSHAKING:
+                                    {
+                                        if (_underFlown)
+                                            break decryption;
+                                        continue;
+                                    }
+                                    case NEED_TASK:
+                                    {
+                                        _sslEngine.getDelegatedTask().run();
+                                        if(_peeking)
+                                        {
+                                               _sslEngine = _sslFactory.restartSSL(_sslEngine.getHandshakeSession());
+                                               _encryptedInput.position(0);
+                                               _peeking = false;
+                                               continue decryption;
+                                        }
+                                        continue;
+                                    }
+                                    case NEED_WRAP:
+                                    {
+                                        // If we are called from flush()
+                                        // return to let it do the wrapping.
+                                        if (buffer == __FLUSH_CALLED_FILL)
+                                            return 0;
+
+                                        _fillRequiresFlushToProgress = true;
+                                        flush(__FILL_CALLED_FLUSH);
+                                        if (BufferUtil.isEmpty(_encryptedOutput))
+                                        {
+                                            // The flush wrote all the encrypted bytes so continue to fill
+                                            _fillRequiresFlushToProgress = false;
+                                            continue;
+                                        }
+                                        else
+                                        {
+                                            // The flush did not complete, return from fill()
+                                            // and let the write completion mechanism to kick in.
+                                            return 0;
+                                        }
+                                    }
+                                    case NEED_UNWRAP:
+                                    {
+                                        if (_underFlown)
+                                            break decryption;
+                                        continue;
+                                    }
+                                    default:
+                                    {
+                                        throw new IllegalStateException();
+                                    }
+                                }
+                            }
+                            default:
+                            {
+                                throw new IllegalStateException();
+                            }
+                        }
+                    }
+                }
+            }
+            catch (Exception e)
+            {
+                getEndPoint().close();
+                throw e;
+            }
+            finally
+            {
+                // If we are handshaking, then wake up any waiting write as well as it may have been blocked on the read
+                if (_flushRequiresFillToProgress)
+                {
+                    _flushRequiresFillToProgress = false;
+                    getExecutor().execute(_runCompletWrite);
+                }
+
+                if (_encryptedInput != null && !_encryptedInput.hasRemaining())
+                {
+                    _bufferPool.release(_encryptedInput);
+                    _encryptedInput = null;
+                }
+                if (_decryptedInput != null && !_decryptedInput.hasRemaining())
+                {
+                    _bufferPool.release(_decryptedInput);
+                    _decryptedInput = null;
+                }
+                if (DEBUG)
+                    LOG.debug("{} fill exit", SslConnection.this);
+            }
+        }
+
+        private void closeInbound()
+        {
+            try
+            {
+                _sslEngine.closeInbound();
+            }
+            catch (SSLException x)
+            {
+                LOG.ignore(x);
+            }
+        }
+
+        @Override
+        public synchronized boolean flush(ByteBuffer... appOuts) throws IOException
+        {
+            // The contract for flush does not require that all appOuts bytes are written
+            // or even that any appOut bytes are written!  If the connection is write block
+            // or busy handshaking, then zero bytes may be taken from appOuts and this method
+            // will return 0 (even if some handshake bytes were flushed and filled).
+            // it is the applications responsibility to call flush again - either in a busy loop
+            // or better yet by using EndPoint#write to do the flushing.
+
+            if (DEBUG)
+                LOG.debug("{} flush enter {}", SslConnection.this, Arrays.toString(appOuts));
+            int consumed=0;
+            try
+            {
+                if (_cannotAcceptMoreAppDataToFlush)
+                {
+                    if (_sslEngine.isOutboundDone())
+                        throw new EofException(new ClosedChannelException());
+                    return false;
+                }
+
+                // We will need a network buffer
+                if (_encryptedOutput == null)
+                    _encryptedOutput = _bufferPool.acquire(_sslEngine.getSession().getPacketBufferSize(), _encryptedDirectBuffers);
+
+                while (true)
+                {
+                    // We call sslEngine.wrap to try to take bytes from appOut buffers and encrypt them into the _netOut buffer
+                    BufferUtil.compact(_encryptedOutput);
+                    int pos = BufferUtil.flipToFill(_encryptedOutput);
+                    SSLEngineResult wrapResult = _sslEngine.wrap(appOuts, _encryptedOutput);
+                    if (DEBUG)
+                        LOG.debug("{} wrap {}", SslConnection.this, wrapResult);
+                    BufferUtil.flipToFlush(_encryptedOutput, pos);
+                    if (wrapResult.bytesConsumed()>0)
+                        consumed+=wrapResult.bytesConsumed();
+
+                    boolean allConsumed=true;
+                    // clear empty buffers to prevent position creeping up the buffer
+                    for (ByteBuffer b : appOuts)
+                    {
+                        if (BufferUtil.isEmpty(b))
+                            BufferUtil.clear(b);
+                        else
+                            allConsumed=false;
+                    }
+
+                    Status wrapResultStatus = wrapResult.getStatus();
+
+                    // and deal with the results returned from the sslEngineWrap
+                    switch (wrapResultStatus)
+                    {
+                        case CLOSED:
+                            // The SSL engine has close, but there may be close handshake that needs to be written
+                            if (BufferUtil.hasContent(_encryptedOutput))
+                            {
+                                _cannotAcceptMoreAppDataToFlush = true;
+                                getEndPoint().flush(_encryptedOutput);
+                                getEndPoint().shutdownOutput();
+                                // If we failed to flush the close handshake then we will just pretend that
+                                // the write has progressed normally and let a subsequent call to flush
+                                // (or WriteFlusher#onIncompleteFlushed) to finish writing the close handshake.
+                                // The caller will find out about the close on a subsequent flush or fill.
+                                if (BufferUtil.hasContent(_encryptedOutput))
+                                    return false;
+                            }
+                            // otherwise we have written, and the caller will close the underlying connection
+                            else
+                            {
+                                getEndPoint().shutdownOutput();
+                            }
+                            return allConsumed;
+
+                        case BUFFER_UNDERFLOW:
+                            throw new IllegalStateException();
+
+                        default:
+                            if (DEBUG)
+                                LOG.debug("{} {} {}", this, wrapResultStatus, BufferUtil.toDetailString(_encryptedOutput));
+
+                            if (wrapResult.getHandshakeStatus() == HandshakeStatus.FINISHED && !_handshaken)
+                            {
+                                _handshaken = true;
+                                if (DEBUG)
+                                    LOG.debug("{} {} handshake completed", SslConnection.this, "server-side");
+                            }
+
+                            HandshakeStatus handshakeStatus = _sslEngine.getHandshakeStatus();
+
+                            // Check whether renegotiation is allowed
+                            if (_handshaken && handshakeStatus != HandshakeStatus.NOT_HANDSHAKING && !isRenegotiationAllowed())
+                            {
+                                if (DEBUG)
+                                    LOG.debug("{} renegotiation denied", SslConnection.this);
+                                shutdownOutput();
+                                return allConsumed;
+                            }
+
+                            // if we have net bytes, let's try to flush them
+                            if (BufferUtil.hasContent(_encryptedOutput))
+                                getEndPoint().flush(_encryptedOutput);
+
+                            // But we also might have more to do for the handshaking state.
+                            switch (handshakeStatus)
+                            {
+                                case NOT_HANDSHAKING:
+                                    // Return with the number of bytes consumed (which may be 0)
+                                    return allConsumed && BufferUtil.isEmpty(_encryptedOutput);
+
+                                case NEED_TASK:
+                                    // run the task and continue
+                                    _sslEngine.getDelegatedTask().run();
+                                    continue;
+
+                                case NEED_WRAP:
+                                    // Hey we just wrapped! Oh well who knows what the sslEngine is thinking, so continue and we will wrap again
+                                    continue;
+
+                                case NEED_UNWRAP:
+                                    // Ah we need to fill some data so we can write.
+                                    // So if we were not called from fill and the app is not reading anyway
+                                    if (appOuts[0]!=__FILL_CALLED_FLUSH && !getFillInterest().isInterested())
+                                    {
+                                        // Tell the onFillable method that there might be a write to complete
+                                        _flushRequiresFillToProgress = true;
+                                        fill(__FLUSH_CALLED_FILL);
+                                        // Check if after the fill() we need to wrap again
+                                        if (handshakeStatus == HandshakeStatus.NEED_WRAP)
+                                            continue;
+                                    }
+                                    return allConsumed && BufferUtil.isEmpty(_encryptedOutput);
+
+                                case FINISHED:
+                                    throw new IllegalStateException();
+                            }
+                    }
+                }
+            }
+            catch (Exception e)
+            {
+                getEndPoint().close();
+                throw e;
+            }
+            finally
+            {
+                if (DEBUG)
+                    LOG.debug("{} flush exit, consumed {}", SslConnection.this, consumed);
+                releaseEncryptedOutputBuffer();
+            }
+        }
+
+        private void releaseEncryptedOutputBuffer()
+        {
+            if (!Thread.holdsLock(DecryptedEndPoint.this))
+                throw new IllegalStateException();
+            if (_encryptedOutput != null && !_encryptedOutput.hasRemaining())
+            {
+                _bufferPool.release(_encryptedOutput);
+                _encryptedOutput = null;
+            }
+        }
+
+        @Override
+        public void shutdownOutput()
+        {
+            boolean ishut = isInputShutdown();
+            boolean oshut = isOutputShutdown();
+            if (DEBUG)
+                LOG.debug("{} shutdownOutput: oshut={}, ishut={}", SslConnection.this, oshut, ishut);
+            if (ishut)
+            {
+                // Aggressively close, since inbound close alert has already been processed
+                // and the TLS specification allows to close the connection directly, which
+                // is what most other implementations expect: a FIN rather than a TLS close
+                // reply. If a TLS close reply is sent, most implementations send a RST.
+                getEndPoint().close();
+            }
+            else if (!oshut)
+            {
+                try
+                {
+                    _sslEngine.closeOutbound();
+                    flush(BufferUtil.EMPTY_BUFFER); // Send close handshake
+                    SslConnection.this.fillInterested(); // seek reply FIN or RST or close handshake
+                }
+                catch (Exception e)
+                {
+                    LOG.ignore(e);
+                    getEndPoint().close();
+                }
+            }
+        }
+
+        @Override
+        public boolean isOutputShutdown()
+        {
+            return _sslEngine.isOutboundDone() || getEndPoint().isOutputShutdown();
+        }
+
+        @Override
+        public void close()
+        {
+            super.close();
+            // First send the TLS Close Alert, then the FIN
+            shutdownOutput();
+            getEndPoint().close();
+        }
+
+        @Override
+        public boolean isOpen()
+        {
+            return getEndPoint().isOpen();
+        }
+
+        @Override
+        public Object getTransport()
+        {
+            return getEndPoint();
+        }
+
+        @Override
+        public boolean isInputShutdown()
+        {
+            return _sslEngine.isInboundDone();
+        }
+
+        @Override
+        public String toString()
+        {
+            return super.toString()+"->"+getEndPoint().toString();
+        }
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/io/ssl/SslReconfigurator.java b/lib/jetty/org/eclipse/jetty/io/ssl/SslReconfigurator.java
new file mode 100644 (file)
index 0000000..b393d88
--- /dev/null
@@ -0,0 +1,29 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.io.ssl;
+
+import javax.net.ssl.SSLEngine;
+import javax.net.ssl.SSLSession;
+
+public interface SslReconfigurator {
+    public boolean shouldRestartSSL();
+
+    public SSLEngine restartSSL(SSLSession sslSession);
+
+}
diff --git a/lib/jetty/org/eclipse/jetty/io/ssl/package-info.java b/lib/jetty/org/eclipse/jetty/io/ssl/package-info.java
new file mode 100644 (file)
index 0000000..f8676c3
--- /dev/null
@@ -0,0 +1,23 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+/**
+ * Jetty IO : Core SSL Support
+ */
+package org.eclipse.jetty.io.ssl;
+
diff --git a/lib/jetty/org/eclipse/jetty/security/AbstractUserAuthentication.java b/lib/jetty/org/eclipse/jetty/security/AbstractUserAuthentication.java
new file mode 100644 (file)
index 0000000..bc049fd
--- /dev/null
@@ -0,0 +1,98 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.security;
+
+import java.io.Serializable;
+import java.util.Set;
+
+import org.eclipse.jetty.server.Authentication.User;
+import org.eclipse.jetty.server.UserIdentity;
+import org.eclipse.jetty.server.UserIdentity.Scope;
+
+/**
+ * AbstractUserAuthentication
+ *
+ *
+ * Base class for representing an authenticated user.
+ */
+public abstract class AbstractUserAuthentication implements User, Serializable
+{
+    private static final long serialVersionUID = -6290411814232723403L;
+    protected String _method;
+    protected transient UserIdentity _userIdentity;
+    
+    
+    
+    public AbstractUserAuthentication(String method, UserIdentity userIdentity)
+    {
+        _method = method;
+        _userIdentity = userIdentity;
+    }
+    
+
+    @Override
+    public String getAuthMethod()
+    {
+        return _method;
+    }
+
+    @Override
+    public UserIdentity getUserIdentity()
+    {
+        return _userIdentity;
+    }
+
+    @Override
+    public boolean isUserInRole(Scope scope, String role)
+    {
+        String roleToTest = null;
+        if (scope!=null && scope.getRoleRefMap()!=null)
+            roleToTest=scope.getRoleRefMap().get(role);
+        if (roleToTest==null)
+            roleToTest=role;
+        //Servlet Spec 3.1 pg 125 if testing special role **
+        if ("**".equals(roleToTest.trim()))
+        {
+            //if ** is NOT a declared role name, the we return true 
+            //as the user is authenticated. If ** HAS been declared as a
+            //role name, then we have to check if the user has that role
+            if (!declaredRolesContains("**"))
+                return true;
+            else
+                return _userIdentity.isUserInRole(role, scope);
+        }
+      
+        return _userIdentity.isUserInRole(role, scope);
+    }
+
+    public boolean declaredRolesContains(String roleName)
+    {
+        SecurityHandler security=SecurityHandler.getCurrentSecurityHandler();
+        if (security==null)
+            return false;
+        
+        if (security instanceof ConstraintAware)
+        {
+            Set<String> declaredRoles = ((ConstraintAware)security).getRoles();
+            return (declaredRoles != null) && declaredRoles.contains(roleName);
+        }
+        
+        return false;
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/security/Authenticator.java b/lib/jetty/org/eclipse/jetty/security/Authenticator.java
new file mode 100644 (file)
index 0000000..e36a3b3
--- /dev/null
@@ -0,0 +1,138 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.security;
+
+import java.util.Set;
+
+import javax.servlet.ServletContext;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+
+import org.eclipse.jetty.server.Authentication;
+import org.eclipse.jetty.server.Authentication.User;
+import org.eclipse.jetty.server.Server;
+
+/**
+ * Authenticator Interface
+ * <p>
+ * An Authenticator is responsible for checking requests and sending
+ * response challenges in order to authenticate a request.
+ * Various types of {@link Authentication} are returned in order to
+ * signal the next step in authentication.
+ *
+ * @version $Rev: 4793 $ $Date: 2009-03-19 00:00:01 +0100 (Thu, 19 Mar 2009) $
+ */
+public interface Authenticator
+{
+    /* ------------------------------------------------------------ */
+    /**
+     * Configure the Authenticator
+     * @param configuration
+     */
+    void setConfiguration(AuthConfiguration configuration);
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return The name of the authentication method
+     */
+    String getAuthMethod();
+    
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * Called prior to validateRequest. The authenticator can
+     * manipulate the request to update it with information that
+     * can be inspected prior to validateRequest being called.
+     * The primary purpose of this method is to satisfy the Servlet
+     * Spec 3.1 section 13.6.3 on handling Form authentication
+     * where the http method of the original request causing authentication
+     * is not the same as the http method resulting from the redirect
+     * after authentication.
+     * @param request
+     */
+    void prepareRequest(ServletRequest request);
+    
+
+    /* ------------------------------------------------------------ */
+    /** Validate a request
+     * @param request The request
+     * @param response The response
+     * @param mandatory True if authentication is mandatory.
+     * @return An Authentication.  If Authentication is successful, this will be a {@link org.eclipse.jetty.server.Authentication.User}. If a response has
+     * been sent by the Authenticator (which can be done for both successful and unsuccessful authentications), then the result will
+     * implement {@link org.eclipse.jetty.server.Authentication.ResponseSent}.  If Authentication is not manditory, then a
+     * {@link org.eclipse.jetty.server.Authentication.Deferred} may be returned.
+     *
+     * @throws ServerAuthException
+     */
+    Authentication validateRequest(ServletRequest request, ServletResponse response, boolean mandatory) throws ServerAuthException;
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param request
+     * @param response
+     * @param mandatory
+     * @param validatedUser
+     * @return true if response is secure
+     * @throws ServerAuthException
+     */
+    boolean secureResponse(ServletRequest request, ServletResponse response, boolean mandatory, User validatedUser) throws ServerAuthException;
+
+
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    /**
+     * Authenticator Configuration
+     */
+    interface AuthConfiguration
+    {
+        String getAuthMethod();
+        String getRealmName();
+
+        /** Get a SecurityHandler init parameter
+         * @see SecurityHandler#getInitParameter(String)
+         * @param param parameter name
+         * @return Parameter value or null
+         */
+        String getInitParameter(String param);
+
+        /* ------------------------------------------------------------ */
+        /** Get a SecurityHandler init parameter names
+         * @see SecurityHandler#getInitParameterNames()
+         * @return Set of parameter names
+         */
+        Set<String> getInitParameterNames();
+
+        LoginService getLoginService();
+        IdentityService getIdentityService();
+        boolean isSessionRenewedOnAuthentication();
+    }
+
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    /**
+     * Authenticator Factory
+     */
+    interface Factory
+    {
+        Authenticator getAuthenticator(Server server, ServletContext context, AuthConfiguration configuration, IdentityService identityService, LoginService loginService);
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/security/ConstraintAware.java b/lib/jetty/org/eclipse/jetty/security/ConstraintAware.java
new file mode 100644 (file)
index 0000000..5cf1348
--- /dev/null
@@ -0,0 +1,70 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.security;
+
+import java.util.List;
+import java.util.Set;
+
+/**
+ * @version $Rev: 4466 $ $Date: 2009-02-10 23:42:54 +0100 (Tue, 10 Feb 2009) $
+ */
+public interface ConstraintAware
+{
+    List<ConstraintMapping> getConstraintMappings();
+    Set<String> getRoles();
+    
+    /* ------------------------------------------------------------ */
+    /** Set Constraint Mappings and roles.
+     * Can only be called during initialization.
+     * @param constraintMappings
+     * @param roles
+     */
+    void setConstraintMappings(List<ConstraintMapping> constraintMappings, Set<String> roles);
+    
+    /* ------------------------------------------------------------ */
+    /** Add a Constraint Mapping.
+     * May be called for running webapplication as an annotated servlet is instantiated.
+     * @param mapping
+     */
+    void addConstraintMapping(ConstraintMapping mapping);
+    
+    
+    /* ------------------------------------------------------------ */
+    /** Add a Role definition.
+     * May be called on running webapplication as an annotated servlet is instantiated.
+     * @param role
+     */
+    void addRole(String role);
+    
+    /**
+     * See Servlet Spec 31, sec 13.8.4, pg 145
+     * When true, requests with http methods not explicitly covered either by inclusion or omissions
+     * in constraints, will have access denied.
+     * @param deny
+     */
+    void setDenyUncoveredHttpMethods(boolean deny);
+    
+    boolean isDenyUncoveredHttpMethods();
+    
+    /**
+     * See Servlet Spec 31, sec 13.8.4, pg 145
+     * Container must check if there are urls with uncovered http methods
+     */
+    boolean checkPathsWithUncoveredHttpMethods();
+}
diff --git a/lib/jetty/org/eclipse/jetty/security/ConstraintMapping.java b/lib/jetty/org/eclipse/jetty/security/ConstraintMapping.java
new file mode 100644 (file)
index 0000000..dd99c5b
--- /dev/null
@@ -0,0 +1,100 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.security;
+
+import org.eclipse.jetty.util.security.Constraint;
+
+public class ConstraintMapping
+{
+    String _method;
+    String[] _methodOmissions;
+
+    String _pathSpec;
+
+    Constraint _constraint;
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Returns the constraint.
+     */
+    public Constraint getConstraint()
+    {
+        return _constraint;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param constraint The constraint to set.
+     */
+    public void setConstraint(Constraint constraint)
+    {
+        this._constraint = constraint;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Returns the method.
+     */
+    public String getMethod()
+    {
+        return _method;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param method The method to set.
+     */
+    public void setMethod(String method)
+    {
+        this._method = method;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Returns the pathSpec.
+     */
+    public String getPathSpec()
+    {
+        return _pathSpec;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param pathSpec The pathSpec to set.
+     */
+    public void setPathSpec(String pathSpec)
+    {
+        this._pathSpec = pathSpec;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * @param omissions The http-method-omission
+     */
+    public void setMethodOmissions(String[] omissions)
+    {
+        _methodOmissions = omissions;
+    }
+    
+    /* ------------------------------------------------------------ */
+    public String[] getMethodOmissions()
+    {
+        return _methodOmissions;
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/security/ConstraintSecurityHandler.java b/lib/jetty/org/eclipse/jetty/security/ConstraintSecurityHandler.java
new file mode 100644 (file)
index 0000000..201618d
--- /dev/null
@@ -0,0 +1,928 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.security;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.CopyOnWriteArraySet;
+
+import javax.servlet.HttpConstraintElement;
+import javax.servlet.HttpMethodConstraintElement;
+import javax.servlet.ServletSecurityElement;
+import javax.servlet.annotation.ServletSecurity.EmptyRoleSemantic;
+import javax.servlet.annotation.ServletSecurity.TransportGuarantee;
+
+import org.eclipse.jetty.http.HttpStatus;
+import org.eclipse.jetty.http.PathMap;
+import org.eclipse.jetty.server.HttpChannel;
+import org.eclipse.jetty.server.HttpConfiguration;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.Response;
+import org.eclipse.jetty.server.UserIdentity;
+import org.eclipse.jetty.server.handler.ContextHandler;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.security.Constraint;
+
+/* ------------------------------------------------------------ */
+/**
+ * ConstraintSecurityHandler
+ * 
+ * Handler to enforce SecurityConstraints. This implementation is servlet spec
+ * 3.1 compliant and pre-computes the constraint combinations for runtime
+ * efficiency.
+ *
+ */
+public class ConstraintSecurityHandler extends SecurityHandler implements ConstraintAware
+{
+    private static final Logger LOG = Log.getLogger(SecurityHandler.class); //use same as SecurityHandler
+    
+    private static final String OMISSION_SUFFIX = ".omission";
+    private static final String ALL_METHODS = "*";
+    private final List<ConstraintMapping> _constraintMappings= new CopyOnWriteArrayList<>();
+    private final Set<String> _roles = new CopyOnWriteArraySet<>();
+    private final PathMap<Map<String, RoleInfo>> _constraintMap = new PathMap<>();
+    private boolean _denyUncoveredMethods = false;
+
+
+    /* ------------------------------------------------------------ */
+    public static Constraint createConstraint()
+    {
+        return new Constraint();
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * @param constraint
+     */
+    public static Constraint createConstraint(Constraint constraint)
+    {
+        try
+        {
+            return (Constraint)constraint.clone();
+        }
+        catch (CloneNotSupportedException e)
+        {
+            throw new IllegalStateException (e);
+        }
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * Create a security constraint
+     * 
+     * @param name
+     * @param authenticate
+     * @param roles
+     * @param dataConstraint
+     */
+    public static Constraint createConstraint (String name, boolean authenticate, String[] roles, int dataConstraint)
+    {
+        Constraint constraint = createConstraint();
+        if (name != null)
+            constraint.setName(name);
+        constraint.setAuthenticate(authenticate);
+        constraint.setRoles(roles);
+        constraint.setDataConstraint(dataConstraint);
+        return constraint;
+    }
+    
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param name
+     * @param element
+     */
+    public static Constraint createConstraint (String name, HttpConstraintElement element)
+    {
+        return createConstraint(name, element.getRolesAllowed(), element.getEmptyRoleSemantic(), element.getTransportGuarantee());     
+    }
+
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param name
+     * @param rolesAllowed
+     * @param permitOrDeny
+     * @param transport
+     */
+    public static Constraint createConstraint (String name, String[] rolesAllowed, EmptyRoleSemantic permitOrDeny, TransportGuarantee transport)
+    {
+        Constraint constraint = createConstraint();
+        
+        if (rolesAllowed == null || rolesAllowed.length==0)
+        {           
+            if (permitOrDeny.equals(EmptyRoleSemantic.DENY))
+            {
+                //Equivalent to <auth-constraint> with no roles
+                constraint.setName(name+"-Deny");
+                constraint.setAuthenticate(true);
+            }
+            else
+            {
+                //Equivalent to no <auth-constraint>
+                constraint.setName(name+"-Permit");
+                constraint.setAuthenticate(false);
+            }
+        }
+        else
+        {
+            //Equivalent to <auth-constraint> with list of <security-role-name>s
+            constraint.setAuthenticate(true);
+            constraint.setRoles(rolesAllowed);
+            constraint.setName(name+"-RolesAllowed");           
+        } 
+
+        //Equivalent to //<user-data-constraint><transport-guarantee>CONFIDENTIAL</transport-guarantee></user-data-constraint>
+        constraint.setDataConstraint((transport.equals(TransportGuarantee.CONFIDENTIAL)?Constraint.DC_CONFIDENTIAL:Constraint.DC_NONE));
+        return constraint; 
+    }
+    
+    
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param pathSpec
+     * @param constraintMappings
+     */
+    public static List<ConstraintMapping> getConstraintMappingsForPath(String pathSpec, List<ConstraintMapping> constraintMappings)
+    {
+        if (pathSpec == null || "".equals(pathSpec.trim()) || constraintMappings == null || constraintMappings.size() == 0)
+            return Collections.emptyList();
+        
+        List<ConstraintMapping> mappings = new ArrayList<ConstraintMapping>();
+        for (ConstraintMapping mapping:constraintMappings)
+        {
+            if (pathSpec.equals(mapping.getPathSpec()))
+            {
+               mappings.add(mapping);
+            }
+        }
+        return mappings;
+    }
+    
+    
+    /* ------------------------------------------------------------ */
+    /** Take out of the constraint mappings those that match the 
+     * given path.
+     * 
+     * @param pathSpec
+     * @param constraintMappings a new list minus the matching constraints
+     */
+    public static List<ConstraintMapping> removeConstraintMappingsForPath(String pathSpec, List<ConstraintMapping> constraintMappings)
+    {
+        if (pathSpec == null || "".equals(pathSpec.trim()) || constraintMappings == null || constraintMappings.size() == 0)
+            return Collections.emptyList();
+        
+        List<ConstraintMapping> mappings = new ArrayList<ConstraintMapping>();
+        for (ConstraintMapping mapping:constraintMappings)
+        {
+            //Remove the matching mappings by only copying in non-matching mappings
+            if (!pathSpec.equals(mapping.getPathSpec()))
+            {
+               mappings.add(mapping);
+            }
+        }
+        return mappings;
+    }
+    
+    
+    
+    /* ------------------------------------------------------------ */
+    /** Generate Constraints and ContraintMappings for the given url pattern and ServletSecurityElement
+     * 
+     * @param name
+     * @param pathSpec
+     * @param securityElement
+     * @return
+     */
+    public static List<ConstraintMapping> createConstraintsWithMappingsForPath (String name, String pathSpec, ServletSecurityElement securityElement)
+    {
+        List<ConstraintMapping> mappings = new ArrayList<ConstraintMapping>();
+
+        //Create a constraint that will describe the default case (ie if not overridden by specific HttpMethodConstraints)
+        Constraint httpConstraint = null;
+        ConstraintMapping httpConstraintMapping = null;
+        
+        if (securityElement.getEmptyRoleSemantic() != EmptyRoleSemantic.PERMIT ||
+            securityElement.getRolesAllowed().length != 0 ||
+            securityElement.getTransportGuarantee() != TransportGuarantee.NONE)
+        {
+            httpConstraint = ConstraintSecurityHandler.createConstraint(name, securityElement);
+
+            //Create a mapping for the pathSpec for the default case
+            httpConstraintMapping = new ConstraintMapping();
+            httpConstraintMapping.setPathSpec(pathSpec);
+            httpConstraintMapping.setConstraint(httpConstraint); 
+            mappings.add(httpConstraintMapping);
+        }
+        
+
+        //See Spec 13.4.1.2 p127
+        List<String> methodOmissions = new ArrayList<String>();
+        
+        //make constraint mappings for this url for each of the HttpMethodConstraintElements
+        Collection<HttpMethodConstraintElement> methodConstraintElements = securityElement.getHttpMethodConstraints();
+        if (methodConstraintElements != null)
+        {
+            for (HttpMethodConstraintElement methodConstraintElement:methodConstraintElements)
+            {
+                //Make a Constraint that captures the <auth-constraint> and <user-data-constraint> elements supplied for the HttpMethodConstraintElement
+                Constraint methodConstraint = ConstraintSecurityHandler.createConstraint(name, methodConstraintElement);
+                ConstraintMapping mapping = new ConstraintMapping();
+                mapping.setConstraint(methodConstraint);
+                mapping.setPathSpec(pathSpec);
+                if (methodConstraintElement.getMethodName() != null)
+                {
+                    mapping.setMethod(methodConstraintElement.getMethodName());
+                    //See spec 13.4.1.2 p127 - add an omission for every method name to the default constraint
+                    methodOmissions.add(methodConstraintElement.getMethodName());
+                }
+                mappings.add(mapping);
+            }
+        }
+        //See spec 13.4.1.2 p127 - add an omission for every method name to the default constraint
+        //UNLESS the default constraint contains all default values. In that case, we won't add it. See Servlet Spec 3.1 pg 129
+        if (methodOmissions.size() > 0  && httpConstraintMapping != null)
+            httpConstraintMapping.setMethodOmissions(methodOmissions.toArray(new String[methodOmissions.size()]));
+     
+        return mappings;
+    }
+    
+    
+
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Returns the constraintMappings.
+     */
+    @Override
+    public List<ConstraintMapping> getConstraintMappings()
+    {
+        return _constraintMappings;
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public Set<String> getRoles()
+    {
+        return _roles;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Process the constraints following the combining rules in Servlet 3.0 EA
+     * spec section 13.7.1 Note that much of the logic is in the RoleInfo class.
+     *
+     * @param constraintMappings
+     *            The constraintMappings to set, from which the set of known roles
+     *            is determined.
+     */
+    public void setConstraintMappings(List<ConstraintMapping> constraintMappings)
+    {
+        setConstraintMappings(constraintMappings,null);
+    }
+
+    /**
+     * Process the constraints following the combining rules in Servlet 3.0 EA
+     * spec section 13.7.1 Note that much of the logic is in the RoleInfo class.
+     *
+     * @param constraintMappings
+     *            The constraintMappings to set as array, from which the set of known roles
+     *            is determined.  Needed to retain API compatibility for 7.x
+     */
+    public void setConstraintMappings( ConstraintMapping[] constraintMappings )
+    {
+        setConstraintMappings( Arrays.asList(constraintMappings), null);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Process the constraints following the combining rules in Servlet 3.0 EA
+     * spec section 13.7.1 Note that much of the logic is in the RoleInfo class.
+     *
+     * @param constraintMappings
+     *            The constraintMappings to set.
+     * @param roles The known roles (or null to determine them from the mappings)
+     */
+    @Override
+    public void setConstraintMappings(List<ConstraintMapping> constraintMappings, Set<String> roles)
+    {
+        _constraintMappings.clear();
+        _constraintMappings.addAll(constraintMappings);
+
+        if (roles==null)
+        {
+            roles = new HashSet<>();
+            for (ConstraintMapping cm : constraintMappings)
+            {
+                String[] cmr = cm.getConstraint().getRoles();
+                if (cmr!=null)
+                {
+                    for (String r : cmr)
+                        if (!ALL_METHODS.equals(r))
+                            roles.add(r);
+                }
+            }
+        }
+        setRoles(roles);
+        
+        if (isStarted())
+        {
+            for (ConstraintMapping mapping : _constraintMappings)
+            {
+                processConstraintMapping(mapping);
+            }
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Set the known roles.
+     * This may be overridden by a subsequent call to {@link #setConstraintMappings(ConstraintMapping[])} or
+     * {@link #setConstraintMappings(List, Set)}.
+     * @param roles The known roles (or null to determine them from the mappings)
+     */
+    public void setRoles(Set<String> roles)
+    {
+        _roles.clear();
+        _roles.addAll(roles);
+    }
+
+
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.security.ConstraintAware#addConstraintMapping(org.eclipse.jetty.security.ConstraintMapping)
+     */
+    @Override
+    public void addConstraintMapping(ConstraintMapping mapping)
+    {
+        _constraintMappings.add(mapping);
+        if (mapping.getConstraint()!=null && mapping.getConstraint().getRoles()!=null)
+        {
+            //allow for lazy role naming: if a role is named in a security constraint, try and
+            //add it to the list of declared roles (ie as if it was declared with a security-role
+            for (String role :  mapping.getConstraint().getRoles())
+            {
+                if ("*".equals(role) || "**".equals(role))
+                    continue;
+                addRole(role);
+            }
+        }
+
+        if (isStarted())
+        {
+            processConstraintMapping(mapping);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.security.ConstraintAware#addRole(java.lang.String)
+     */
+    @Override
+    public void addRole(String role)
+    {
+        //add to list of declared roles
+        boolean modified = _roles.add(role);
+        if (isStarted() && modified)
+        {
+            // Add the new role to currently defined any role role infos
+            for (Map<String,RoleInfo> map : _constraintMap.values())
+            {
+                for (RoleInfo info : map.values())
+                {
+                    if (info.isAnyRole())
+                        info.addRole(role);
+                }
+            }
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.security.SecurityHandler#doStart()
+     */
+    @Override
+    protected void doStart() throws Exception
+    {
+        _constraintMap.clear();
+        if (_constraintMappings!=null)
+        {
+            for (ConstraintMapping mapping : _constraintMappings)
+            {
+                processConstraintMapping(mapping);
+            }
+        }
+        
+        //Servlet Spec 3.1 pg 147 sec 13.8.4.2 log paths for which there are uncovered http methods
+        checkPathsWithUncoveredHttpMethods();        
+       
+        super.doStart();
+    }
+
+    
+    /* ------------------------------------------------------------ */
+    @Override
+    protected void doStop() throws Exception
+    {
+        super.doStop();
+        _constraintMap.clear();
+    }
+    
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * Create and combine the constraint with the existing processed
+     * constraints.
+     * 
+     * @param mapping
+     */
+    protected void processConstraintMapping(ConstraintMapping mapping)
+    {
+        Map<String, RoleInfo> mappings = _constraintMap.get(mapping.getPathSpec());
+        if (mappings == null)
+        {
+            mappings = new HashMap<String,RoleInfo>();
+            _constraintMap.put(mapping.getPathSpec(),mappings);
+        }
+        RoleInfo allMethodsRoleInfo = mappings.get(ALL_METHODS);
+        if (allMethodsRoleInfo != null && allMethodsRoleInfo.isForbidden())
+            return;
+
+        if (mapping.getMethodOmissions() != null && mapping.getMethodOmissions().length > 0)
+        {
+            processConstraintMappingWithMethodOmissions(mapping, mappings);
+            return;
+        }
+
+        String httpMethod = mapping.getMethod();
+        if (httpMethod==null)
+            httpMethod=ALL_METHODS;
+        RoleInfo roleInfo = mappings.get(httpMethod);
+        if (roleInfo == null)
+        {
+            roleInfo = new RoleInfo();
+            mappings.put(httpMethod,roleInfo);
+            if (allMethodsRoleInfo != null)
+            {
+                roleInfo.combine(allMethodsRoleInfo);
+            }
+        }
+        if (roleInfo.isForbidden())
+            return;
+
+        //add in info from the constraint
+        configureRoleInfo(roleInfo, mapping);
+        
+        if (roleInfo.isForbidden())
+        {
+            if (httpMethod.equals(ALL_METHODS))
+            {
+                mappings.clear();
+                mappings.put(ALL_METHODS,roleInfo);
+            }
+        }
+        else
+        {
+            //combine with any entry that covers all methods
+            if (httpMethod == null)
+            {
+                for (Map.Entry<String, RoleInfo> entry : mappings.entrySet())
+                {
+                    if (entry.getKey() != null)
+                    {
+                        RoleInfo specific = entry.getValue();
+                        specific.combine(roleInfo);
+                    }
+                }
+            }
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Constraints that name method omissions are dealt with differently.
+     * We create an entry in the mappings with key "&lt;method&gt;.omission". This entry
+     * is only ever combined with other omissions for the same method to produce a
+     * consolidated RoleInfo. Then, when we wish to find the relevant constraints for
+     *  a given Request (in prepareConstraintInfo()), we consult 3 types of entries in 
+     * the mappings: an entry that names the method of the Request specifically, an
+     * entry that names constraints that apply to all methods, entries of the form
+     * &lt;method&gt;.omission, where the method of the Request is not named in the omission.
+     * @param mapping
+     * @param mappings
+     */
+    protected void processConstraintMappingWithMethodOmissions (ConstraintMapping mapping, Map<String, RoleInfo> mappings)
+    {
+        String[] omissions = mapping.getMethodOmissions();
+        StringBuilder sb = new StringBuilder();
+        for (int i=0; i<omissions.length; i++)
+        {
+            if (i > 0)
+                sb.append(".");
+            sb.append(omissions[i]);
+        }
+        sb.append(OMISSION_SUFFIX);
+        RoleInfo ri = new RoleInfo();
+        mappings.put(sb.toString(), ri);
+        configureRoleInfo(ri, mapping);
+    }
+
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * Initialize or update the RoleInfo from the constraint
+     * @param ri
+     * @param mapping
+     */
+    protected void configureRoleInfo (RoleInfo ri, ConstraintMapping mapping)
+    { 
+        Constraint constraint = mapping.getConstraint();
+        boolean forbidden = constraint.isForbidden();
+        ri.setForbidden(forbidden);
+        
+        //set up the data constraint (NOTE: must be done after setForbidden, as it nulls out the data constraint
+        //which we need in order to do combining of omissions in prepareConstraintInfo
+        UserDataConstraint userDataConstraint = UserDataConstraint.get(mapping.getConstraint().getDataConstraint());
+        ri.setUserDataConstraint(userDataConstraint);
+
+        //if forbidden, no point setting up roles
+        if (!ri.isForbidden())
+        {
+            //add in the roles
+            boolean checked = mapping.getConstraint().getAuthenticate();
+            ri.setChecked(checked);
+
+            if (ri.isChecked())
+            {
+                if (mapping.getConstraint().isAnyRole())
+                {
+                    // * means matches any defined role
+                    for (String role : _roles)
+                        ri.addRole(role);
+                    ri.setAnyRole(true);
+                }
+                else if (mapping.getConstraint().isAnyAuth())
+                {
+                    //being authenticated is sufficient, not necessary to check roles
+                    ri.setAnyAuth(true);
+                }
+                else
+                {   
+                    //user must be in one of the named roles
+                    String[] newRoles = mapping.getConstraint().getRoles();
+                     for (String role : newRoles)
+                     {
+                         //check role has been defined
+                         if (!_roles.contains(role))
+                             throw new IllegalArgumentException("Attempt to use undeclared role: " + role + ", known roles: " + _roles);
+                        ri.addRole(role);
+                     }
+                 }
+             }
+         }
+     }
+
+   
+    /* ------------------------------------------------------------ */
+    /** 
+     * Find constraints that apply to the given path.
+     * In order to do this, we consult 3 different types of information stored in the mappings for each path - each mapping
+     * represents a merged set of user data constraints, roles etc -:
+     * <ol>
+     * <li>A mapping of an exact method name </li>
+     * <li>A mapping with key * that matches every method name</li>
+     * <li>Mappings with keys of the form "&lt;method&gt;.&lt;method&gt;.&lt;method&gt;.omission" that indicates it will match every method name EXCEPT those given</li>
+     * </ol>
+     * 
+     * @see org.eclipse.jetty.security.SecurityHandler#prepareConstraintInfo(java.lang.String, org.eclipse.jetty.server.Request)
+     */
+    @Override
+    protected RoleInfo prepareConstraintInfo(String pathInContext, Request request)
+    {
+        Map<String, RoleInfo> mappings = (Map<String, RoleInfo>)_constraintMap.match(pathInContext);
+
+        if (mappings != null)
+        {
+            String httpMethod = request.getMethod();
+            RoleInfo roleInfo = mappings.get(httpMethod);
+            if (roleInfo == null)
+            {
+                //No specific http-method names matched
+                List<RoleInfo> applicableConstraints = new ArrayList<RoleInfo>();
+
+                //Get info for constraint that matches all methods if it exists
+                RoleInfo all = mappings.get(ALL_METHODS);
+                if (all != null)
+                    applicableConstraints.add(all);
+          
+                
+                //Get info for constraints that name method omissions where target method name is not omitted
+                //(ie matches because target method is not omitted, hence considered covered by the constraint)
+                for (Entry<String, RoleInfo> entry: mappings.entrySet())
+                {
+                    if (entry.getKey() != null && entry.getKey().endsWith(OMISSION_SUFFIX) && ! entry.getKey().contains(httpMethod))
+                        applicableConstraints.add(entry.getValue());
+                }
+                
+                if (applicableConstraints.size() == 0 && isDenyUncoveredHttpMethods())
+                {
+                    roleInfo = new RoleInfo();
+                    roleInfo.setForbidden(true);
+                }
+                else if (applicableConstraints.size() == 1)
+                    roleInfo = applicableConstraints.get(0);
+                else
+                {
+                    roleInfo = new RoleInfo();
+                    roleInfo.setUserDataConstraint(UserDataConstraint.None);
+                    
+                    for (RoleInfo r:applicableConstraints)
+                        roleInfo.combine(r);
+                }
+
+            }
+           
+            return roleInfo;
+        }
+
+        return null;
+    }
+
+    @Override
+    protected boolean checkUserDataPermissions(String pathInContext, Request request, Response response, RoleInfo roleInfo) throws IOException
+    {
+        if (roleInfo == null)
+            return true;
+
+        if (roleInfo.isForbidden())
+            return false;
+
+        UserDataConstraint dataConstraint = roleInfo.getUserDataConstraint();
+        if (dataConstraint == null || dataConstraint == UserDataConstraint.None)
+            return true;
+
+        HttpConfiguration httpConfig = HttpChannel.getCurrentHttpChannel().getHttpConfiguration();
+
+        if (dataConstraint == UserDataConstraint.Confidential || dataConstraint == UserDataConstraint.Integral)
+        {
+            if (request.isSecure())
+                return true;
+
+            if (httpConfig.getSecurePort() > 0)
+            {
+                String scheme = httpConfig.getSecureScheme();
+                int port = httpConfig.getSecurePort();
+                String url = ("https".equalsIgnoreCase(scheme) && port==443)
+                    ? "https://"+request.getServerName()+request.getRequestURI()
+                    : scheme + "://" + request.getServerName() + ":" + port + request.getRequestURI();                    
+                if (request.getQueryString() != null)
+                    url += "?" + request.getQueryString();
+                response.setContentLength(0);
+                response.sendRedirect(url);
+            }
+            else
+                response.sendError(HttpStatus.FORBIDDEN_403,"!Secure");
+
+            request.setHandled(true);
+            return false;
+        }
+        else
+        {
+            throw new IllegalArgumentException("Invalid dataConstraint value: " + dataConstraint);
+        }
+
+    }
+
+    @Override
+    protected boolean isAuthMandatory(Request baseRequest, Response base_response, Object constraintInfo)
+    {
+        return constraintInfo != null && ((RoleInfo)constraintInfo).isChecked();
+    }
+    
+    
+    /* ------------------------------------------------------------ */
+    /** 
+     * @see org.eclipse.jetty.security.SecurityHandler#checkWebResourcePermissions(java.lang.String, org.eclipse.jetty.server.Request, org.eclipse.jetty.server.Response, java.lang.Object, org.eclipse.jetty.server.UserIdentity)
+     */
+    @Override
+    protected boolean checkWebResourcePermissions(String pathInContext, Request request, Response response, Object constraintInfo, UserIdentity userIdentity)
+            throws IOException
+    {
+        if (constraintInfo == null)
+        {
+            return true;
+        }
+        RoleInfo roleInfo = (RoleInfo)constraintInfo;
+
+        if (!roleInfo.isChecked())
+        {
+            return true;
+        }
+
+        //handle ** role constraint
+        if (roleInfo.isAnyAuth() &&  request.getUserPrincipal() != null)
+        {
+            return true;
+        }
+        
+        //check if user is any of the allowed roles
+        boolean isUserInRole = false;
+        for (String role : roleInfo.getRoles())
+        {
+            if (userIdentity.isUserInRole(role, null))
+            {
+                isUserInRole = true;
+                break;
+            }
+        }
+        
+        //handle * role constraint
+        if (roleInfo.isAnyRole() && request.getUserPrincipal() != null && isUserInRole)
+        {
+            return true;
+        }
+
+        //normal role check
+        if (isUserInRole)
+        {
+            return true;
+        }
+       
+        return false;
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public void dump(Appendable out,String indent) throws IOException
+    {
+        // TODO these should all be beans
+        dumpBeans(out,indent,
+                Collections.singleton(getLoginService()),
+                Collections.singleton(getIdentityService()),
+                Collections.singleton(getAuthenticator()),
+                Collections.singleton(_roles),
+                _constraintMap.entrySet());
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** 
+     * @see org.eclipse.jetty.security.ConstraintAware#setDenyUncoveredHttpMethods(boolean)
+     */
+    @Override
+    public void setDenyUncoveredHttpMethods(boolean deny)
+    {
+        _denyUncoveredMethods = deny;
+    }
+    
+    /* ------------------------------------------------------------ */
+    @Override
+    public boolean isDenyUncoveredHttpMethods()
+    {
+        return _denyUncoveredMethods;
+    }
+    
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * Servlet spec 3.1 pg. 147.
+     */
+    @Override
+    public boolean checkPathsWithUncoveredHttpMethods()
+    {
+        Set<String> paths = getPathsWithUncoveredHttpMethods();
+        if (paths != null && !paths.isEmpty())
+        {
+            for (String p:paths)
+                LOG.warn("{} has uncovered http methods for path: {}",ContextHandler.getCurrentContext(), p);
+            if (LOG.isDebugEnabled())
+                LOG.debug(new Throwable());
+            return true;
+        }
+        return false; 
+    }
+    
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Servlet spec 3.1 pg. 147.
+     * The container must check all the combined security constraint
+     * information and log any methods that are not protected and the
+     * urls at which they are not protected
+     * 
+     * @return list of paths for which there are uncovered methods
+     */
+    public Set<String> getPathsWithUncoveredHttpMethods ()
+    {
+        //if automatically denying uncovered methods, there are no uncovered methods
+        if (_denyUncoveredMethods)
+            return Collections.emptySet();
+        
+        Set<String> uncoveredPaths = new HashSet<String>();
+        
+        for (String path:_constraintMap.keySet())
+        {
+            Map<String, RoleInfo> methodMappings = _constraintMap.get(path);
+            //Each key is either:
+            // : an exact method name
+            // : * which means that the constraint applies to every method
+            // : a name of the form <method>.<method>.<method>.omission, which means it applies to every method EXCEPT those named
+            if (methodMappings.get(ALL_METHODS) != null)
+                continue; //can't be any uncovered methods for this url path
+          
+            boolean hasOmissions = omissionsExist(path, methodMappings);
+            
+            for (String method:methodMappings.keySet())
+            {
+                if (method.endsWith(OMISSION_SUFFIX))
+                {
+                    Set<String> omittedMethods = getOmittedMethods(method);
+                    for (String m:omittedMethods)
+                    {
+                        if (!methodMappings.containsKey(m))
+                            uncoveredPaths.add(path);
+                    }
+                }
+                else
+                {
+                    //an exact method name
+                    if (!hasOmissions)
+                        //a http-method does not have http-method-omission to cover the other method names
+                        uncoveredPaths.add(path);
+                }
+                
+            }
+        }
+        return uncoveredPaths;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * Check if any http method omissions exist in the list of method
+     * to auth info mappings.
+     * 
+     * @param path
+     * @param methodMappings
+     * @return
+     */
+    protected boolean omissionsExist (String path, Map<String, RoleInfo> methodMappings)
+    {
+        if (methodMappings == null)
+            return false;
+        boolean hasOmissions = false;
+        for (String m:methodMappings.keySet())
+        {
+            if (m.endsWith(OMISSION_SUFFIX))
+                hasOmissions = true;
+        }
+        return hasOmissions;
+    }
+    
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * Given a string of the form &lt;method&gt;.&lt;method&gt;.omission
+     * split out the individual method names.
+     * 
+     * @param omission
+     * @return
+     */
+    protected Set<String> getOmittedMethods (String omission)
+    {
+        if (omission == null || !omission.endsWith(OMISSION_SUFFIX))
+            return Collections.emptySet();
+        
+        String[] strings = omission.split("\\.");
+        Set<String> methods = new HashSet<String>();
+        for (int i=0;i<strings.length-1;i++)
+            methods.add(strings[i]);
+        return methods;
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/security/CrossContextPsuedoSession.java b/lib/jetty/org/eclipse/jetty/security/CrossContextPsuedoSession.java
new file mode 100644 (file)
index 0000000..e2de9f7
--- /dev/null
@@ -0,0 +1,36 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.security;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ * @version $Rev: 4466 $ $Date: 2009-02-10 23:42:54 +0100 (Tue, 10 Feb 2009) $
+ */
+public interface CrossContextPsuedoSession<T>
+{
+
+    T fetch(HttpServletRequest request);
+
+    void store(T data, HttpServletResponse response);
+
+    void clear(HttpServletRequest request);
+
+}
diff --git a/lib/jetty/org/eclipse/jetty/security/DefaultAuthenticatorFactory.java b/lib/jetty/org/eclipse/jetty/security/DefaultAuthenticatorFactory.java
new file mode 100644 (file)
index 0000000..534a6d4
--- /dev/null
@@ -0,0 +1,96 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.security;
+
+import javax.servlet.ServletContext;
+
+import org.eclipse.jetty.security.Authenticator.AuthConfiguration;
+import org.eclipse.jetty.security.authentication.BasicAuthenticator;
+import org.eclipse.jetty.security.authentication.ClientCertAuthenticator;
+import org.eclipse.jetty.security.authentication.DigestAuthenticator;
+import org.eclipse.jetty.security.authentication.FormAuthenticator;
+import org.eclipse.jetty.security.authentication.SpnegoAuthenticator;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.util.security.Constraint;
+
+/* ------------------------------------------------------------ */
+/**
+ * The Default Authenticator Factory.
+ * Uses the {@link AuthConfiguration#getAuthMethod()} to select an {@link Authenticator} from: <ul>
+ * <li>{@link org.eclipse.jetty.security.authentication.BasicAuthenticator}</li>
+ * <li>{@link org.eclipse.jetty.security.authentication.DigestAuthenticator}</li>
+ * <li>{@link org.eclipse.jetty.security.authentication.FormAuthenticator}</li>
+ * <li>{@link org.eclipse.jetty.security.authentication.ClientCertAuthenticator}</li>
+ * </ul>
+ * All authenticators derived from {@link org.eclipse.jetty.security.authentication.LoginAuthenticator} are 
+ * wrapped with a {@link org.eclipse.jetty.security.authentication.DeferredAuthentication}
+ * instance, which is used if authentication is not mandatory.
+ * 
+ * The Authentications from the {@link org.eclipse.jetty.security.authentication.FormAuthenticator} are always wrapped in a 
+ * {@link org.eclipse.jetty.security.authentication.SessionAuthentication}
+ * <p>
+ * If a {@link LoginService} has not been set on this factory, then
+ * the service is selected by searching the {@link Server#getBeans(Class)} results for
+ * a service that matches the realm name, else the first LoginService found is used.
+ *
+ */
+public class DefaultAuthenticatorFactory implements Authenticator.Factory
+{
+    LoginService _loginService;
+    
+    public Authenticator getAuthenticator(Server server, ServletContext context, AuthConfiguration configuration, IdentityService identityService, LoginService loginService)
+    {
+        String auth=configuration.getAuthMethod();
+        Authenticator authenticator=null;
+        
+        if (auth==null || Constraint.__BASIC_AUTH.equalsIgnoreCase(auth))
+            authenticator=new BasicAuthenticator();
+        else if (Constraint.__DIGEST_AUTH.equalsIgnoreCase(auth))
+            authenticator=new DigestAuthenticator();
+        else if (Constraint.__FORM_AUTH.equalsIgnoreCase(auth))
+            authenticator=new FormAuthenticator();
+        else if ( Constraint.__SPNEGO_AUTH.equalsIgnoreCase(auth) )
+            authenticator = new SpnegoAuthenticator();
+        else if ( Constraint.__NEGOTIATE_AUTH.equalsIgnoreCase(auth) ) // see Bug #377076
+            authenticator = new SpnegoAuthenticator(Constraint.__NEGOTIATE_AUTH);
+        if (Constraint.__CERT_AUTH.equalsIgnoreCase(auth)||Constraint.__CERT_AUTH2.equalsIgnoreCase(auth))
+            authenticator=new ClientCertAuthenticator();
+        
+        return authenticator;
+    }
+   
+    /* ------------------------------------------------------------ */
+    /**
+     * @return the loginService
+     */
+    public LoginService getLoginService()
+    {
+        return _loginService;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param loginService the loginService to set
+     */
+    public void setLoginService(LoginService loginService)
+    {
+        _loginService = loginService;
+    }
+
+}
diff --git a/lib/jetty/org/eclipse/jetty/security/DefaultIdentityService.java b/lib/jetty/org/eclipse/jetty/security/DefaultIdentityService.java
new file mode 100644 (file)
index 0000000..c8ae0c2
--- /dev/null
@@ -0,0 +1,90 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.security;
+
+import java.security.Principal;
+
+import javax.security.auth.Subject;
+
+import org.eclipse.jetty.server.UserIdentity;
+
+
+/* ------------------------------------------------------------ */
+/**
+ * Default Identity Service implementation.
+ * This service handles only role reference maps passed in an
+ * associated {@link org.eclipse.jetty.server.UserIdentity.Scope}.  If there are roles
+ * refs present, then associate will wrap the UserIdentity with one
+ * that uses the role references in the
+ * {@link org.eclipse.jetty.server.UserIdentity#isUserInRole(String, org.eclipse.jetty.server.UserIdentity.Scope)}
+ * implementation. All other operations are effectively noops.
+ *
+ */
+public class DefaultIdentityService implements IdentityService
+{
+    /* ------------------------------------------------------------ */
+    public DefaultIdentityService()
+    {
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * If there are roles refs present in the scope, then wrap the UserIdentity
+     * with one that uses the role references in the {@link UserIdentity#isUserInRole(String, org.eclipse.jetty.server.UserIdentity.Scope)}
+     */
+    public Object associate(UserIdentity user)
+    {
+        return null;
+    }
+
+    /* ------------------------------------------------------------ */
+    public void disassociate(Object previous)
+    {
+    }
+
+    /* ------------------------------------------------------------ */
+    public Object setRunAs(UserIdentity user, RunAsToken token)
+    {
+        return token;
+    }
+
+    /* ------------------------------------------------------------ */
+    public void unsetRunAs(Object lastToken)
+    {
+    }
+
+    /* ------------------------------------------------------------ */
+    public RunAsToken newRunAsToken(String runAsName)
+    {
+        return new RoleRunAsToken(runAsName);
+    }
+
+    /* ------------------------------------------------------------ */
+    public UserIdentity getSystemUserIdentity()
+    {
+        return null;
+    }
+
+    /* ------------------------------------------------------------ */
+    public UserIdentity newUserIdentity(final Subject subject, final Principal userPrincipal, final String[] roles)
+    {
+        return new DefaultUserIdentity(subject,userPrincipal,roles);
+    }
+
+}
diff --git a/lib/jetty/org/eclipse/jetty/security/DefaultUserIdentity.java b/lib/jetty/org/eclipse/jetty/security/DefaultUserIdentity.java
new file mode 100644 (file)
index 0000000..65e083d
--- /dev/null
@@ -0,0 +1,81 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.security;
+
+import java.security.Principal;
+
+import javax.security.auth.Subject;
+
+import org.eclipse.jetty.server.UserIdentity;
+
+
+/* ------------------------------------------------------------ */
+/**
+ * The default implementation of UserIdentity.
+ *
+ */
+public class DefaultUserIdentity implements UserIdentity
+{
+    private final Subject _subject;
+    private final Principal _userPrincipal;
+    private final String[] _roles;
+
+    public DefaultUserIdentity(Subject subject, Principal userPrincipal, String[] roles)
+    {
+        _subject=subject;
+        _userPrincipal=userPrincipal;
+        _roles=roles;
+    }
+
+    public Subject getSubject()
+    {
+        return _subject;
+    }
+
+    public Principal getUserPrincipal()
+    {
+        return _userPrincipal;
+    }
+
+    public boolean isUserInRole(String role, Scope scope)
+    {
+        //Servlet Spec 3.1, pg 125
+        if ("*".equals(role))
+            return false;
+        
+        String roleToTest = null;
+        if (scope!=null && scope.getRoleRefMap()!=null)
+            roleToTest=scope.getRoleRefMap().get(role);
+
+        //Servlet Spec 3.1, pg 125
+        if (roleToTest == null)
+            roleToTest = role;
+       
+        for (String r :_roles)
+            if (r.equals(roleToTest))
+                return true;
+        return false;
+    }
+
+    @Override
+    public String toString()
+    {
+        return DefaultUserIdentity.class.getSimpleName()+"('"+_userPrincipal+"')";
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/security/HashCrossContextPsuedoSession.java b/lib/jetty/org/eclipse/jetty/security/HashCrossContextPsuedoSession.java
new file mode 100644 (file)
index 0000000..8499a60
--- /dev/null
@@ -0,0 +1,99 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.security;
+
+import java.security.SecureRandom;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Random;
+
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ * @version $Rev: 4660 $ $Date: 2009-02-25 17:29:53 +0100 (Wed, 25 Feb 2009) $
+ */
+public class HashCrossContextPsuedoSession<T> implements CrossContextPsuedoSession<T>
+{
+    private final String _cookieName;
+
+    private final String _cookiePath;
+
+    private final Random _random = new SecureRandom();
+
+    private final Map<String, T> _data = new HashMap<String, T>();
+
+    public HashCrossContextPsuedoSession(String cookieName, String cookiePath)
+    {
+        this._cookieName = cookieName;
+        this._cookiePath = cookiePath == null ? "/" : cookiePath;
+    }
+
+    public T fetch(HttpServletRequest request)
+    {
+        Cookie[] cookies = request.getCookies();
+        if (cookies == null)
+            return null;
+        
+        for (Cookie cookie : cookies)
+        {
+            if (_cookieName.equals(cookie.getName()))
+            {
+                String key = cookie.getValue();
+                return _data.get(key);
+            }
+        }
+        return null;
+    }
+
+    public void store(T datum, HttpServletResponse response)
+    {
+        String key;
+
+        synchronized (_data)
+        {
+            // Create new ID
+            while (true)
+            {
+                key = Long.toString(Math.abs(_random.nextLong()), 30 + (int) (System.currentTimeMillis() % 7));
+                if (!_data.containsKey(key)) break;
+            }
+
+            _data.put(key, datum);
+        }
+
+        Cookie cookie = new Cookie(_cookieName, key);
+        cookie.setPath(_cookiePath);
+        response.addCookie(cookie);
+    }
+
+    public void clear(HttpServletRequest request)
+    {
+        for (Cookie cookie : request.getCookies())
+        {
+            if (_cookieName.equals(cookie.getName()))
+            {
+                String key = cookie.getValue();
+                _data.remove(key);
+                break;
+            }
+        }
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/security/HashLoginService.java b/lib/jetty/org/eclipse/jetty/security/HashLoginService.java
new file mode 100644 (file)
index 0000000..335aabd
--- /dev/null
@@ -0,0 +1,184 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.security;
+
+import java.io.IOException;
+
+import org.eclipse.jetty.security.PropertyUserStore.UserListener;
+import org.eclipse.jetty.server.UserIdentity;
+import org.eclipse.jetty.util.Scanner;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.resource.Resource;
+import org.eclipse.jetty.util.security.Credential;
+
+/* ------------------------------------------------------------ */
+/**
+ * Properties User Realm.
+ * 
+ * An implementation of UserRealm that stores users and roles in-memory in HashMaps.
+ * <P>
+ * Typically these maps are populated by calling the load() method or passing a properties resource to the constructor. The format of the properties file is:
+ * 
+ * <PRE>
+ *  username: password [,rolename ...]
+ * </PRE>
+ * 
+ * Passwords may be clear text, obfuscated or checksummed. The class com.eclipse.Util.Password should be used to generate obfuscated passwords or password
+ * checksums.
+ * 
+ * If DIGEST Authentication is used, the password must be in a recoverable format, either plain text or OBF:.
+ */
+public class HashLoginService extends MappedLoginService implements UserListener
+{
+    private static final Logger LOG = Log.getLogger(HashLoginService.class);
+
+    private PropertyUserStore _propertyUserStore;
+    private String _config;
+    private Resource _configResource;
+    private Scanner _scanner;
+    private int _refreshInterval = 0;// default is not to reload
+
+    /* ------------------------------------------------------------ */
+    public HashLoginService()
+    {
+    }
+
+    /* ------------------------------------------------------------ */
+    public HashLoginService(String name)
+    {
+        setName(name);
+    }
+
+    /* ------------------------------------------------------------ */
+    public HashLoginService(String name, String config)
+    {
+        setName(name);
+        setConfig(config);
+    }
+
+    /* ------------------------------------------------------------ */
+    public String getConfig()
+    {
+        return _config;
+    }
+
+    /* ------------------------------------------------------------ */
+    public void getConfig(String config)
+    {
+        _config = config;
+    }
+
+    /* ------------------------------------------------------------ */
+    public Resource getConfigResource()
+    {
+        return _configResource;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Load realm users from properties file. The property file maps usernames to password specs followed by an optional comma separated list of role names.
+     * 
+     * @param config
+     *            Filename or url of user properties file.
+     */
+    public void setConfig(String config)
+    {
+        _config = config;
+    }
+
+    /* ------------------------------------------------------------ */
+    public void setRefreshInterval(int msec)
+    {
+        _refreshInterval = msec;
+    }
+
+    /* ------------------------------------------------------------ */
+    public int getRefreshInterval()
+    {
+        return _refreshInterval;
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    protected UserIdentity loadUser(String username)
+    {
+        return null;
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public void loadUsers() throws IOException
+    {
+        // TODO: Consider refactoring MappedLoginService to not have to override with unused methods
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.util.component.AbstractLifeCycle#doStart()
+     */
+    @Override
+    protected void doStart() throws Exception
+    {
+        super.doStart();
+        
+        if (_propertyUserStore == null)
+        {
+            if(LOG.isDebugEnabled())
+                LOG.debug("doStart: Starting new PropertyUserStore. PropertiesFile: " + _config + " refreshInterval: " + _refreshInterval);
+            
+            _propertyUserStore = new PropertyUserStore();
+            _propertyUserStore.setRefreshInterval(_refreshInterval);
+            _propertyUserStore.setConfig(_config);
+            _propertyUserStore.registerUserListener(this);
+            _propertyUserStore.start();
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.util.component.AbstractLifeCycle#doStop()
+     */
+    @Override
+    protected void doStop() throws Exception
+    {
+        super.doStop();
+        if (_scanner != null)
+            _scanner.stop();
+        _scanner = null;
+    }
+    
+    /* ------------------------------------------------------------ */
+    @Override
+    public void update(String userName, Credential credential, String[] roleArray)
+    {
+        if (LOG.isDebugEnabled())
+            LOG.debug("update: " + userName + " Roles: " + roleArray.length);
+        putUser(userName,credential,roleArray);
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public void remove(String userName)
+    {
+        if (LOG.isDebugEnabled())
+            LOG.debug("remove: " + userName);
+        removeUser(userName);
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/security/IdentityService.java b/lib/jetty/org/eclipse/jetty/security/IdentityService.java
new file mode 100644 (file)
index 0000000..99094c1
--- /dev/null
@@ -0,0 +1,95 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.security;
+
+import java.security.Principal;
+
+import javax.security.auth.Subject;
+
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.UserIdentity;
+
+/* ------------------------------------------------------------ */
+/**
+ * Associates UserIdentities from with threads and UserIdentity.Contexts.
+ *
+ */
+public interface IdentityService
+{
+    final static String[] NO_ROLES = new String[]{};
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Associate a user identity with the current thread.
+     * This is called with as a thread enters the
+     * {@link SecurityHandler#handle(String, Request, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)}
+     * method and then again with a null argument as that call exits.
+     * @param user The current user or null for no user to associated.
+     * @return an object representing the previous associated state
+     */
+    Object associate(UserIdentity user);
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Disassociate the user identity from the current thread
+     * and restore previous identity.
+     * @param previous The opaque object returned from a call to {@link IdentityService#associate(UserIdentity)}
+     */
+    void disassociate(Object previous);
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Associate a runas Token with the current user and thread.
+     * @param user The UserIdentity
+     * @param token The runAsToken to associate.
+     * @return The previous runAsToken or null.
+     */
+    Object setRunAs(UserIdentity user, RunAsToken token);
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Disassociate the current runAsToken from the thread
+     * and reassociate the previous token.
+     * @param token RUNAS returned from previous associateRunAs call
+     */
+    void unsetRunAs(Object token);
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Create a new UserIdentity for use with this identity service.
+     * The UserIdentity should be immutable and able to be cached.
+     *
+     * @param subject Subject to include in UserIdentity
+     * @param userPrincipal Principal to include in UserIdentity.  This will be returned from getUserPrincipal calls
+     * @param roles set of roles to include in UserIdentity.
+     * @return A new immutable UserIdententity
+     */
+    UserIdentity newUserIdentity(Subject subject, Principal userPrincipal, String[] roles);
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Create a new RunAsToken from a runAsName (normally a role).
+     * @param runAsName Normally a role name
+     * @return A new immutable RunAsToken
+     */
+    RunAsToken newRunAsToken(String runAsName);
+
+    /* ------------------------------------------------------------ */
+    UserIdentity getSystemUserIdentity();
+}
diff --git a/lib/jetty/org/eclipse/jetty/security/JDBCLoginService.java b/lib/jetty/org/eclipse/jetty/security/JDBCLoginService.java
new file mode 100644 (file)
index 0000000..30b0a83
--- /dev/null
@@ -0,0 +1,297 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.security;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Properties;
+
+import org.eclipse.jetty.server.UserIdentity;
+import org.eclipse.jetty.util.Loader;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.resource.Resource;
+import org.eclipse.jetty.util.security.Credential;
+
+/* ------------------------------------------------------------ */
+/**
+ * HashMapped User Realm with JDBC as data source. JDBCLoginService extends
+ * HashULoginService and adds a method to fetch user information from database.
+ * The login() method checks the inherited Map for the user. If the user is not
+ * found, it will fetch details from the database and populate the inherited
+ * Map. It then calls the superclass login() method to perform the actual
+ * authentication. Periodically (controlled by configuration parameter),
+ * internal hashes are cleared. Caching can be disabled by setting cache refresh
+ * interval to zero. Uses one database connection that is initialized at
+ * startup. Reconnect on failures. authenticate() is 'synchronized'.
+ * 
+ * An example properties file for configuration is in
+ * $JETTY_HOME/etc/jdbcRealm.properties
+ * 
+ * @version $Id: JDBCLoginService.java 4792 2009-03-18 21:55:52Z gregw $
+ * 
+ * 
+ * 
+ * 
+ */
+
+public class JDBCLoginService extends MappedLoginService
+{
+    private static final Logger LOG = Log.getLogger(JDBCLoginService.class);
+
+    protected String _config;
+    protected String _jdbcDriver;
+    protected String _url;
+    protected String _userName;
+    protected String _password;
+    protected String _userTableKey;
+    protected String _userTablePasswordField;
+    protected String _roleTableRoleField;
+    protected int _cacheTime;
+    protected long _lastHashPurge;
+    protected Connection _con;
+    protected String _userSql;
+    protected String _roleSql;
+
+
+    /* ------------------------------------------------------------ */
+    public JDBCLoginService()
+        throws IOException
+    {
+    }
+    
+    /* ------------------------------------------------------------ */
+    public JDBCLoginService(String name)
+        throws IOException
+    {
+        setName(name);
+    }
+    
+    /* ------------------------------------------------------------ */
+    public JDBCLoginService(String name, String config)
+        throws IOException
+    {
+        setName(name);
+        setConfig(config);
+    }
+    
+    /* ------------------------------------------------------------ */
+    public JDBCLoginService(String name, IdentityService identityService, String config)
+        throws IOException
+    {
+        setName(name);
+        setIdentityService(identityService);
+        setConfig(config);
+    }
+
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.security.MappedLoginService#doStart()
+     */
+    @Override
+    protected void doStart() throws Exception
+    {
+        Properties properties = new Properties();
+        Resource resource = Resource.newResource(_config);
+        try (InputStream in = resource.getInputStream())
+        {
+            properties.load(in);
+        }
+        _jdbcDriver = properties.getProperty("jdbcdriver");
+        _url = properties.getProperty("url");
+        _userName = properties.getProperty("username");
+        _password = properties.getProperty("password");
+        String _userTable = properties.getProperty("usertable");
+        _userTableKey = properties.getProperty("usertablekey");
+        String _userTableUserField = properties.getProperty("usertableuserfield");
+        _userTablePasswordField = properties.getProperty("usertablepasswordfield");
+        String _roleTable = properties.getProperty("roletable");
+        String _roleTableKey = properties.getProperty("roletablekey");
+        _roleTableRoleField = properties.getProperty("roletablerolefield");
+        String _userRoleTable = properties.getProperty("userroletable");
+        String _userRoleTableUserKey = properties.getProperty("userroletableuserkey");
+        String _userRoleTableRoleKey = properties.getProperty("userroletablerolekey");
+        _cacheTime = new Integer(properties.getProperty("cachetime"));
+
+        if (_jdbcDriver == null || _jdbcDriver.equals("")
+            || _url == null
+            || _url.equals("")
+            || _userName == null
+            || _userName.equals("")
+            || _password == null
+            || _cacheTime < 0)
+        {
+            LOG.warn("UserRealm " + getName() + " has not been properly configured");
+        }
+        _cacheTime *= 1000;
+        _lastHashPurge = 0;
+        _userSql = "select " + _userTableKey + "," + _userTablePasswordField + " from " + _userTable + " where " + _userTableUserField + " = ?";
+        _roleSql = "select r." + _roleTableRoleField
+                   + " from "
+                   + _roleTable
+                   + " r, "
+                   + _userRoleTable
+                   + " u where u."
+                   + _userRoleTableUserKey
+                   + " = ?"
+                   + " and r."
+                   + _roleTableKey
+                   + " = u."
+                   + _userRoleTableRoleKey;
+        
+        Loader.loadClass(this.getClass(), _jdbcDriver).newInstance();
+        super.doStart();
+    }
+
+
+    /* ------------------------------------------------------------ */
+    public String getConfig()
+    {
+        return _config;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Load JDBC connection configuration from properties file.
+     * 
+     * @param config Filename or url of user properties file.
+     */
+    public void setConfig(String config)
+    {        
+        if (isRunning())
+            throw new IllegalStateException("Running");
+        _config=config;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * (re)Connect to database with parameters setup by loadConfig()
+     */
+    public void connectDatabase()
+    {
+        try
+        {
+            Class.forName(_jdbcDriver);
+            _con = DriverManager.getConnection(_url, _userName, _password);
+        }
+        catch (SQLException e)
+        {
+            LOG.warn("UserRealm " + getName() + " could not connect to database; will try later", e);
+        }
+        catch (ClassNotFoundException e)
+        {
+            LOG.warn("UserRealm " + getName() + " could not connect to database; will try later", e);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public UserIdentity login(String username, Object credentials)
+    {
+        long now = System.currentTimeMillis();
+        if (now - _lastHashPurge > _cacheTime || _cacheTime == 0)
+        {
+            _users.clear();
+            _lastHashPurge = now;
+            closeConnection();
+        }
+        
+        return super.login(username,credentials);
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    protected void loadUsers()
+    {   
+    }
+    
+    /* ------------------------------------------------------------ */
+    @Override
+    protected UserIdentity loadUser(String username)
+    {
+        try
+        {
+            if (null == _con) 
+                connectDatabase();
+
+            if (null == _con) 
+                throw new SQLException("Can't connect to database");
+
+            try (PreparedStatement stat1 = _con.prepareStatement(_userSql))
+            {
+                stat1.setObject(1, username);
+                try (ResultSet rs1 = stat1.executeQuery())
+                {
+                    if (rs1.next())
+                    {
+                        int key = rs1.getInt(_userTableKey);
+                        String credentials = rs1.getString(_userTablePasswordField);
+                        List<String> roles = new ArrayList<String>();
+
+                        try (PreparedStatement stat2 = _con.prepareStatement(_roleSql))
+                        {
+                            stat2.setInt(1, key);
+                            try (ResultSet rs2 = stat2.executeQuery())
+                            {
+                                while (rs2.next())
+                                    roles.add(rs2.getString(_roleTableRoleField));
+                            }
+                        }
+                        return putUser(username, credentials, roles.toArray(new String[roles.size()]));
+                    }
+                }
+            }
+        }
+        catch (SQLException e)
+        {
+            LOG.warn("UserRealm " + getName() + " could not load user information from database", e);
+            closeConnection();
+        }
+        return null;
+    }
+    
+    /* ------------------------------------------------------------ */
+    protected UserIdentity putUser (String username, String credentials, String[] roles)
+    {
+        return putUser(username, Credential.getCredential(credentials),roles);
+    }
+    
+
+    /**
+     * Close an existing connection
+     */
+    private void closeConnection ()
+    {
+        if (_con != null)
+        {
+            if (LOG.isDebugEnabled()) LOG.debug("Closing db connection for JDBCUserRealm");
+            try { _con.close(); }catch (Exception e) {LOG.ignore(e);}
+        }
+        _con = null;
+    }
+
+}
diff --git a/lib/jetty/org/eclipse/jetty/security/LoginService.java b/lib/jetty/org/eclipse/jetty/security/LoginService.java
new file mode 100644 (file)
index 0000000..1e64141
--- /dev/null
@@ -0,0 +1,72 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.security;
+
+import org.eclipse.jetty.server.UserIdentity;
+
+
+/* ------------------------------------------------------------ */
+/**
+ * Login Service Interface.
+ * <p>
+ * The Login service provides an abstract mechanism for an {@link Authenticator}
+ * to check credentials and to create a {@link UserIdentity} using the 
+ * set {@link IdentityService}.
+ */
+public interface LoginService
+{
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Get the name of the login service (aka Realm name)
+     */
+    String getName();
+    
+    /* ------------------------------------------------------------ */
+    /** Login a user.
+     * @param username The user name
+     * @param credentials The users credentials
+     * @return A UserIdentity if the credentials matched, otherwise null
+     */
+    UserIdentity login(String username,Object credentials);
+    
+    /* ------------------------------------------------------------ */
+    /** Validate a user identity.
+     * Validate that a UserIdentity previously created by a call 
+     * to {@link #login(String, Object)} is still valid.
+     * @param user The user to validate
+     * @return true if authentication has not been revoked for the user.
+     */
+    boolean validate(UserIdentity user);
+    
+    /* ------------------------------------------------------------ */
+    /** Get the IdentityService associated with this Login Service.
+     * @return the IdentityService associated with this Login Service.
+     */
+    IdentityService getIdentityService();
+    
+    /* ------------------------------------------------------------ */
+    /** Set the IdentityService associated with this Login Service.
+     * @param service the IdentityService associated with this Login Service.
+     */
+    void setIdentityService(IdentityService service);
+    
+    void logout(UserIdentity user);
+
+}
diff --git a/lib/jetty/org/eclipse/jetty/security/MappedLoginService.java b/lib/jetty/org/eclipse/jetty/security/MappedLoginService.java
new file mode 100644 (file)
index 0000000..480131d
--- /dev/null
@@ -0,0 +1,343 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+
+package org.eclipse.jetty.security;
+
+import java.io.IOException;
+import java.io.Serializable;
+import java.security.Principal;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+import javax.security.auth.Subject;
+
+import org.eclipse.jetty.server.UserIdentity;
+import org.eclipse.jetty.util.component.AbstractLifeCycle;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.security.Credential;
+
+
+
+/* ------------------------------------------------------------ */
+/**
+ * A login service that keeps UserIdentities in a concurrent map
+ * either as the source or a cache of the users.
+ *
+ */
+public abstract class MappedLoginService extends AbstractLifeCycle implements LoginService
+{
+    private static final Logger LOG = Log.getLogger(MappedLoginService.class);
+
+    protected IdentityService _identityService=new DefaultIdentityService();
+    protected String _name;
+    protected final ConcurrentMap<String, UserIdentity> _users=new ConcurrentHashMap<String, UserIdentity>();
+
+    /* ------------------------------------------------------------ */
+    protected MappedLoginService()
+    {
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Get the name.
+     * @return the name
+     */
+    public String getName()
+    {
+        return _name;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Get the identityService.
+     * @return the identityService
+     */
+    public IdentityService getIdentityService()
+    {
+        return _identityService;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Get the users.
+     * @return the users
+     */
+    public ConcurrentMap<String, UserIdentity> getUsers()
+    {
+        return _users;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Set the identityService.
+     * @param identityService the identityService to set
+     */
+    public void setIdentityService(IdentityService identityService)
+    {
+        if (isRunning())
+            throw new IllegalStateException("Running");
+        _identityService = identityService;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Set the name.
+     * @param name the name to set
+     */
+    public void setName(String name)
+    {
+        if (isRunning())
+            throw new IllegalStateException("Running");
+        _name = name;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Set the users.
+     * @param users the users to set
+     */
+    public void setUsers(Map<String, UserIdentity> users)
+    {
+        if (isRunning())
+            throw new IllegalStateException("Running");
+        _users.clear();
+        _users.putAll(users);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.util.component.AbstractLifeCycle#doStart()
+     */
+    @Override
+    protected void doStart() throws Exception
+    {
+        loadUsers();
+        super.doStart();
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    protected void doStop() throws Exception
+    {
+        super.doStop();
+    }
+
+    /* ------------------------------------------------------------ */
+    public void logout(UserIdentity identity)
+    {
+        LOG.debug("logout {}",identity);
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public String toString()
+    {
+        return this.getClass().getSimpleName()+"["+_name+"]";
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Put user into realm.
+     * Called by implementations to put the user data loaded from
+     * file/db etc into the user structure.
+     * @param userName User name
+     * @param info a UserIdentity instance, or a String password or Credential instance
+     * @return User instance
+     */
+    protected synchronized UserIdentity putUser(String userName, Object info)
+    {
+        final UserIdentity identity;
+        if (info instanceof UserIdentity)
+            identity=(UserIdentity)info;
+        else
+        {
+            Credential credential = (info instanceof Credential)?(Credential)info:Credential.getCredential(info.toString());
+
+            Principal userPrincipal = new KnownUser(userName,credential);
+            Subject subject = new Subject();
+            subject.getPrincipals().add(userPrincipal);
+            subject.getPrivateCredentials().add(credential);
+            subject.setReadOnly();
+            identity=_identityService.newUserIdentity(subject,userPrincipal,IdentityService.NO_ROLES);
+        }
+
+        _users.put(userName,identity);
+        return identity;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Put user into realm.
+     * @param userName The user to add
+     * @param credential The users Credentials
+     * @param roles The users roles
+     * @return UserIdentity
+     */
+    public synchronized UserIdentity putUser(String userName, Credential credential, String[] roles)
+    {
+        Principal userPrincipal = new KnownUser(userName,credential);
+        Subject subject = new Subject();
+        subject.getPrincipals().add(userPrincipal);
+        subject.getPrivateCredentials().add(credential);
+
+        if (roles!=null)
+            for (String role : roles)
+                subject.getPrincipals().add(new RolePrincipal(role));
+
+        subject.setReadOnly();
+        UserIdentity identity=_identityService.newUserIdentity(subject,userPrincipal,roles);
+        _users.put(userName,identity);
+        return identity;
+    }
+
+    /* ------------------------------------------------------------ */
+    public void removeUser(String username)
+    {
+        _users.remove(username);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.security.LoginService#login(java.lang.String, java.lang.Object)
+     */
+    public UserIdentity login(String username, Object credentials)
+    {
+        if (username == null)
+            return null;
+        
+        UserIdentity user = _users.get(username);
+
+        if (user==null)
+            user = loadUser(username);
+
+        if (user!=null)
+        {
+            UserPrincipal principal = (UserPrincipal)user.getUserPrincipal();
+            if (principal.authenticate(credentials))
+                return user;
+        }
+        return null;
+    }
+
+    /* ------------------------------------------------------------ */
+    public boolean validate(UserIdentity user)
+    {
+        if (_users.containsKey(user.getUserPrincipal().getName()))
+            return true;
+
+        if (loadUser(user.getUserPrincipal().getName())!=null)
+            return true;
+
+        return false;
+    }
+
+    /* ------------------------------------------------------------ */
+    protected abstract UserIdentity loadUser(String username);
+
+    /* ------------------------------------------------------------ */
+    protected abstract void loadUsers() throws IOException;
+
+
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    public interface UserPrincipal extends Principal,Serializable
+    {
+        boolean authenticate(Object credentials);
+        public boolean isAuthenticated();
+    }
+
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    public static class RolePrincipal implements Principal,Serializable
+    {
+        private static final long serialVersionUID = 2998397924051854402L;
+        private final String _roleName;
+        public RolePrincipal(String name)
+        {
+            _roleName=name;
+        }
+        public String getName()
+        {
+            return _roleName;
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    public static class Anonymous implements UserPrincipal,Serializable
+    {
+        private static final long serialVersionUID = 1097640442553284845L;
+
+        public boolean isAuthenticated()
+        {
+            return false;
+        }
+
+        public String getName()
+        {
+            return "Anonymous";
+        }
+
+        public boolean authenticate(Object credentials)
+        {
+            return false;
+        }
+
+    }
+
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    public static class KnownUser implements UserPrincipal,Serializable
+    {
+        private static final long serialVersionUID = -6226920753748399662L;
+        private final String _name;
+        private final Credential _credential;
+
+        /* -------------------------------------------------------- */
+        public KnownUser(String name,Credential credential)
+        {
+            _name=name;
+            _credential=credential;
+        }
+
+        /* -------------------------------------------------------- */
+        public boolean authenticate(Object credentials)
+        {
+            return _credential!=null && _credential.check(credentials);
+        }
+
+        /* ------------------------------------------------------------ */
+        public String getName()
+        {
+            return _name;
+        }
+
+        /* -------------------------------------------------------- */
+        public boolean isAuthenticated()
+        {
+            return true;
+        }
+
+        /* -------------------------------------------------------- */
+        @Override
+        public String toString()
+        {
+            return _name;
+        }
+    }
+}
+
diff --git a/lib/jetty/org/eclipse/jetty/security/PropertyUserStore.java b/lib/jetty/org/eclipse/jetty/security/PropertyUserStore.java
new file mode 100644 (file)
index 0000000..7a8b5e7
--- /dev/null
@@ -0,0 +1,356 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.security;
+
+import java.io.File;
+import java.io.FilenameFilter;
+import java.io.IOException;
+import java.security.Principal;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+import java.util.Set;
+
+import javax.security.auth.Subject;
+
+import org.eclipse.jetty.security.MappedLoginService.KnownUser;
+import org.eclipse.jetty.security.MappedLoginService.RolePrincipal;
+import org.eclipse.jetty.server.UserIdentity;
+import org.eclipse.jetty.util.Scanner;
+import org.eclipse.jetty.util.Scanner.BulkListener;
+import org.eclipse.jetty.util.component.AbstractLifeCycle;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.resource.Resource;
+import org.eclipse.jetty.util.security.Credential;
+
+/**
+ * PropertyUserStore
+ *
+ * This class monitors a property file of the format mentioned below and notifies registered listeners of the changes to the the given file.
+ *
+ * <PRE>
+ *  username: password [,rolename ...]
+ * </PRE>
+ *
+ * Passwords may be clear text, obfuscated or checksummed. The class com.eclipse.Util.Password should be used to generate obfuscated passwords or password
+ * checksums.
+ *
+ * If DIGEST Authentication is used, the password must be in a recoverable format, either plain text or OBF:.
+ */
+public class PropertyUserStore extends AbstractLifeCycle
+{
+    private static final Logger LOG = Log.getLogger(PropertyUserStore.class);
+
+    private String _config;
+    private Resource _configResource;
+    private Scanner _scanner;
+    private int _refreshInterval = 0;// default is not to reload
+
+    private IdentityService _identityService = new DefaultIdentityService();
+    private boolean _firstLoad = true; // true if first load, false from that point on
+    private final List<String> _knownUsers = new ArrayList<String>();
+    private final Map<String, UserIdentity> _knownUserIdentities = new HashMap<String, UserIdentity>();
+    private List<UserListener> _listeners;
+
+    /* ------------------------------------------------------------ */
+    public String getConfig()
+    {
+        return _config;
+    }
+
+    /* ------------------------------------------------------------ */
+    public void setConfig(String config)
+    {
+        _config = config;
+    }
+
+    /* ------------------------------------------------------------ */
+        public UserIdentity getUserIdentity(String userName)
+        {
+            return _knownUserIdentities.get(userName);
+        }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * returns the resource associated with the configured properties file, creating it if necessary
+     */
+    public Resource getConfigResource() throws IOException
+    {
+        if (_configResource == null)
+        {
+            _configResource = Resource.newResource(_config);
+        }
+
+        return _configResource;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * sets the refresh interval (in seconds)
+     */
+    public void setRefreshInterval(int msec)
+    {
+        _refreshInterval = msec;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * refresh interval in seconds for how often the properties file should be checked for changes
+     */
+    public int getRefreshInterval()
+    {
+        return _refreshInterval;
+    }
+
+    /* ------------------------------------------------------------ */
+    private void loadUsers() throws IOException
+    {
+        if (_config == null)
+            return;
+
+        if (LOG.isDebugEnabled())
+            LOG.debug("Load " + this + " from " + _config);
+        Properties properties = new Properties();
+        if (getConfigResource().exists())
+            properties.load(getConfigResource().getInputStream());
+        Set<String> known = new HashSet<String>();
+
+        for (Map.Entry<Object, Object> entry : properties.entrySet())
+        {
+            String username = ((String)entry.getKey()).trim();
+            String credentials = ((String)entry.getValue()).trim();
+            String roles = null;
+            int c = credentials.indexOf(',');
+            if (c > 0)
+            {
+                roles = credentials.substring(c + 1).trim();
+                credentials = credentials.substring(0,c).trim();
+            }
+
+            if (username != null && username.length() > 0 && credentials != null && credentials.length() > 0)
+            {
+                String[] roleArray = IdentityService.NO_ROLES;
+                if (roles != null && roles.length() > 0)
+                {
+                    roleArray = roles.split(",");
+                }
+                known.add(username);
+                Credential credential = Credential.getCredential(credentials);
+
+                Principal userPrincipal = new KnownUser(username,credential);
+                Subject subject = new Subject();
+                subject.getPrincipals().add(userPrincipal);
+                subject.getPrivateCredentials().add(credential);
+
+                if (roles != null)
+                {
+                    for (String role : roleArray)
+                    {
+                        subject.getPrincipals().add(new RolePrincipal(role));
+                    }
+                }
+
+                subject.setReadOnly();
+
+                _knownUserIdentities.put(username,_identityService.newUserIdentity(subject,userPrincipal,roleArray));
+                notifyUpdate(username,credential,roleArray);
+            }
+        }
+
+        synchronized (_knownUsers)
+        {
+            /*
+             * if its not the initial load then we want to process removed users
+             */
+            if (!_firstLoad)
+            {
+                Iterator<String> users = _knownUsers.iterator();
+                while (users.hasNext())
+                {
+                    String user = users.next();
+                    if (!known.contains(user))
+                    {
+                        _knownUserIdentities.remove(user);
+                        notifyRemove(user);
+                    }
+                }
+            }
+
+            /*
+             * reset the tracked _users list to the known users we just processed
+             */
+
+            _knownUsers.clear();
+            _knownUsers.addAll(known);
+
+        }
+
+        /*
+         * set initial load to false as there should be no more initial loads
+         */
+        _firstLoad = false;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Depending on the value of the refresh interval, this method will either start up a scanner thread that will monitor the properties file for changes after
+     * it has initially loaded it. Otherwise the users will be loaded and there will be no active monitoring thread so changes will not be detected.
+     *
+     *
+     * @see org.eclipse.jetty.util.component.AbstractLifeCycle#doStart()
+     */
+    protected void doStart() throws Exception
+    {
+        super.doStart();
+
+        if (getRefreshInterval() > 0)
+        {
+            _scanner = new Scanner();
+            _scanner.setScanInterval(getRefreshInterval());
+            List<File> dirList = new ArrayList<File>(1);
+            dirList.add(getConfigResource().getFile().getParentFile());
+            _scanner.setScanDirs(dirList);
+            _scanner.setFilenameFilter(new FilenameFilter()
+            {
+                public boolean accept(File dir, String name)
+                {
+                    File f = new File(dir,name);
+                    try
+                    {
+                        if (f.compareTo(getConfigResource().getFile()) == 0)
+                        {
+                            return true;
+                        }
+                    }
+                    catch (IOException e)
+                    {
+                        return false;
+                    }
+
+                    return false;
+                }
+
+            });
+
+            _scanner.addListener(new BulkListener()
+            {
+                public void filesChanged(List<String> filenames) throws Exception
+                {
+                    if (filenames == null)
+                        return;
+                    if (filenames.isEmpty())
+                        return;
+                    if (filenames.size() == 1)
+                    {
+                        Resource r = Resource.newResource(filenames.get(0));
+                        if (r.getFile().equals(_configResource.getFile()))
+                            loadUsers();
+                    }
+                }
+
+                public String toString()
+                {
+                    return "PropertyUserStore$Scanner";
+                }
+
+            });
+
+            _scanner.setReportExistingFilesOnStartup(true);
+            _scanner.setRecursive(false);
+            _scanner.start();
+        }
+        else
+        {
+            loadUsers();
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.util.component.AbstractLifeCycle#doStop()
+     */
+    protected void doStop() throws Exception
+    {
+        super.doStop();
+        if (_scanner != null)
+            _scanner.stop();
+        _scanner = null;
+    }
+
+    /**
+     * Notifies the registered listeners of potential updates to a user
+     *
+     * @param username
+     * @param credential
+     * @param roleArray
+     */
+    private void notifyUpdate(String username, Credential credential, String[] roleArray)
+    {
+        if (_listeners != null)
+        {
+            for (Iterator<UserListener> i = _listeners.iterator(); i.hasNext();)
+            {
+                i.next().update(username,credential,roleArray);
+            }
+        }
+    }
+
+    /**
+     * notifies the registered listeners that a user has been removed.
+     *
+     * @param username
+     */
+    private void notifyRemove(String username)
+    {
+        if (_listeners != null)
+        {
+            for (Iterator<UserListener> i = _listeners.iterator(); i.hasNext();)
+            {
+                i.next().remove(username);
+            }
+        }
+    }
+
+    /**
+     * registers a listener to be notified of the contents of the property file
+     */
+    public void registerUserListener(UserListener listener)
+    {
+        if (_listeners == null)
+        {
+            _listeners = new ArrayList<UserListener>();
+        }
+        _listeners.add(listener);
+    }
+
+    /**
+     * UserListener
+     */
+    public interface UserListener
+    {
+        public void update(String username, Credential credential, String[] roleArray);
+
+        public void remove(String username);
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/security/RoleInfo.java b/lib/jetty/org/eclipse/jetty/security/RoleInfo.java
new file mode 100644 (file)
index 0000000..55f1ae2
--- /dev/null
@@ -0,0 +1,162 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.security;
+
+import java.util.Set;
+import java.util.concurrent.CopyOnWriteArraySet;
+
+/**
+ * RoleInfo
+ * 
+ * Badly named class that holds the role and user data constraint info for a
+ * path/http method combination, extracted and combined from security
+ * constraints.
+ * 
+ * @version $Rev: 4793 $ $Date: 2009-03-19 00:00:01 +0100 (Thu, 19 Mar 2009) $
+ */
+public class RoleInfo
+{
+    private boolean _isAnyAuth;
+    private boolean _isAnyRole;
+    private boolean _checked;
+    private boolean _forbidden;
+    private UserDataConstraint _userDataConstraint;
+
+    /**
+     * List of permitted roles
+     */
+    private final Set<String> _roles = new CopyOnWriteArraySet<String>();
+
+    public RoleInfo()
+    {    
+    }
+    
+    public boolean isChecked()
+    {
+        return _checked;
+    }
+
+    public void setChecked(boolean checked)
+    {
+        this._checked = checked;
+        if (!checked)
+        {
+            _forbidden=false;
+            _roles.clear();
+            _isAnyRole=false;
+            _isAnyAuth=false;
+        }
+    }
+
+    public boolean isForbidden()
+    {
+        return _forbidden;
+    }
+
+    public void setForbidden(boolean forbidden)
+    {
+        this._forbidden = forbidden;
+        if (forbidden)
+        {
+            _checked = true;
+            _userDataConstraint = null;
+            _isAnyRole=false;
+            _isAnyAuth=false;
+            _roles.clear();
+        }
+    }
+
+    public boolean isAnyRole()
+    {
+        return _isAnyRole;
+    }
+
+    public void setAnyRole(boolean anyRole)
+    {
+        this._isAnyRole=anyRole;
+        if (anyRole)
+            _checked = true;
+    }
+    
+    public boolean isAnyAuth ()
+    {
+        return _isAnyAuth;
+    }
+    
+    public void setAnyAuth(boolean anyAuth)
+    {
+        this._isAnyAuth=anyAuth;
+        if (anyAuth)
+            _checked = true;
+    }
+
+    public UserDataConstraint getUserDataConstraint()
+    {
+        return _userDataConstraint;
+    }
+
+    public void setUserDataConstraint(UserDataConstraint userDataConstraint)
+    {
+        if (userDataConstraint == null) throw new NullPointerException("Null UserDataConstraint");
+        if (this._userDataConstraint == null)
+        {
+           
+            this._userDataConstraint = userDataConstraint;
+        }
+        else
+        {
+            this._userDataConstraint = this._userDataConstraint.combine(userDataConstraint);
+        }
+    }
+
+    public Set<String> getRoles()
+    {
+        return _roles;
+    }
+    
+    public void addRole(String role)
+    {
+        _roles.add(role);
+    }
+
+    public void combine(RoleInfo other)
+    {
+        if (other._forbidden)
+            setForbidden(true);
+        else if (!other._checked) // TODO is this the right way around???
+            setChecked(true);
+        else if (other._isAnyRole)
+            setAnyRole(true);
+        else if (other._isAnyAuth)
+            setAnyAuth(true);
+        else if (!_isAnyRole)
+        {
+            for (String r : other._roles)
+                _roles.add(r);
+        }
+        
+        setUserDataConstraint(other._userDataConstraint);
+    }
+    
+    @Override
+    public String toString()
+    {
+        return "{RoleInfo"+(_forbidden?",F":"")+(_checked?",C":"")+(_isAnyRole?",*":_roles)+(_userDataConstraint!=null?","+_userDataConstraint:"")+"}";
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/security/RoleRunAsToken.java b/lib/jetty/org/eclipse/jetty/security/RoleRunAsToken.java
new file mode 100644 (file)
index 0000000..13a7ea7
--- /dev/null
@@ -0,0 +1,44 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.security;
+
+
+
+/**
+ * @version $Rev: 4701 $ $Date: 2009-03-03 13:01:26 +0100 (Tue, 03 Mar 2009) $
+ */
+public class RoleRunAsToken implements RunAsToken
+{
+    private final String _runAsRole;
+
+    public RoleRunAsToken(String runAsRole)
+    {
+        this._runAsRole = runAsRole;
+    }
+
+    public String getRunAsRole()
+    {
+        return _runAsRole;
+    }
+
+    public String toString()
+    {
+        return "RoleRunAsToken("+_runAsRole+")";
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/security/RunAsToken.java b/lib/jetty/org/eclipse/jetty/security/RunAsToken.java
new file mode 100644 (file)
index 0000000..639c972
--- /dev/null
@@ -0,0 +1,27 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.security;
+
+/**
+ * marker interface for run-as-role tokens
+ * @version $Rev: 4701 $ $Date: 2009-03-03 13:01:26 +0100 (Tue, 03 Mar 2009) $
+ */
+public interface RunAsToken
+{
+}
diff --git a/lib/jetty/org/eclipse/jetty/security/SecurityHandler.java b/lib/jetty/org/eclipse/jetty/security/SecurityHandler.java
new file mode 100644 (file)
index 0000000..a6e108e
--- /dev/null
@@ -0,0 +1,708 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.security;
+
+import java.io.IOException;
+import java.security.Principal;
+import java.util.Collection;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpSessionEvent;
+import javax.servlet.http.HttpSessionListener;
+
+import org.eclipse.jetty.security.authentication.DeferredAuthentication;
+import org.eclipse.jetty.server.Authentication;
+import org.eclipse.jetty.server.Handler;
+import org.eclipse.jetty.server.HttpChannel;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.Response;
+import org.eclipse.jetty.server.UserIdentity;
+import org.eclipse.jetty.server.handler.ContextHandler;
+import org.eclipse.jetty.server.handler.ContextHandler.Context;
+import org.eclipse.jetty.server.handler.HandlerWrapper;
+import org.eclipse.jetty.server.session.AbstractSession;
+import org.eclipse.jetty.util.component.LifeCycle;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/**
+ * Abstract SecurityHandler.
+ * Select and apply an {@link Authenticator} to a request.
+ * <p>
+ * The Authenticator may either be directly set on the handler
+ * or will be create during {@link #start()} with a call to
+ * either the default or set AuthenticatorFactory.
+ * <p>
+ * SecurityHandler has a set of initparameters that are used by the
+ * Authentication.Configuration. At startup, any context init parameters
+ * that start with "org.eclipse.jetty.security." that do not have
+ * values in the SecurityHandler init parameters, are copied.
+ *
+ */
+public abstract class SecurityHandler extends HandlerWrapper implements Authenticator.AuthConfiguration
+{
+    private static final Logger LOG = Log.getLogger(SecurityHandler.class);
+
+    /* ------------------------------------------------------------ */
+    private boolean _checkWelcomeFiles = false;
+    private Authenticator _authenticator;
+    private Authenticator.Factory _authenticatorFactory=new DefaultAuthenticatorFactory();
+    private String _realmName;
+    private String _authMethod;
+    private final Map<String,String> _initParameters=new HashMap<String,String>();
+    private LoginService _loginService;
+    private IdentityService _identityService;
+    private boolean _renewSession=true;
+
+    /* ------------------------------------------------------------ */
+    protected SecurityHandler()
+    {
+        addBean(_authenticatorFactory);
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Get the identityService.
+     * @return the identityService
+     */
+    @Override
+    public IdentityService getIdentityService()
+    {
+        return _identityService;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Set the identityService.
+     * @param identityService the identityService to set
+     */
+    public void setIdentityService(IdentityService identityService)
+    {
+        if (isStarted())
+            throw new IllegalStateException("Started");
+        updateBean(_identityService,identityService);
+        _identityService = identityService;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Get the loginService.
+     * @return the loginService
+     */
+    @Override
+    public LoginService getLoginService()
+    {
+        return _loginService;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Set the loginService.
+     * @param loginService the loginService to set
+     */
+    public void setLoginService(LoginService loginService)
+    {
+        if (isStarted())
+            throw new IllegalStateException("Started");
+        updateBean(_loginService,loginService);
+        _loginService = loginService;
+    }
+
+
+    /* ------------------------------------------------------------ */
+    public Authenticator getAuthenticator()
+    {
+        return _authenticator;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Set the authenticator.
+     * @param authenticator
+     * @throws IllegalStateException if the SecurityHandler is running
+     */
+    public void setAuthenticator(Authenticator authenticator)
+    {
+        if (isStarted())
+            throw new IllegalStateException("Started");
+        updateBean(_authenticator,authenticator);
+        _authenticator = authenticator;
+        if (_authenticator!=null)
+            _authMethod=_authenticator.getAuthMethod();
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return the authenticatorFactory
+     */
+    public Authenticator.Factory getAuthenticatorFactory()
+    {
+        return _authenticatorFactory;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param authenticatorFactory the authenticatorFactory to set
+     * @throws IllegalStateException if the SecurityHandler is running
+     */
+    public void setAuthenticatorFactory(Authenticator.Factory authenticatorFactory)
+    {
+        if (isRunning())
+            throw new IllegalStateException("running");
+        updateBean(_authenticatorFactory,authenticatorFactory);
+        _authenticatorFactory = authenticatorFactory;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return the realmName
+     */
+    @Override
+    public String getRealmName()
+    {
+        return _realmName;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param realmName the realmName to set
+     * @throws IllegalStateException if the SecurityHandler is running
+     */
+    public void setRealmName(String realmName)
+    {
+        if (isRunning())
+            throw new IllegalStateException("running");
+        _realmName = realmName;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return the authMethod
+     */
+    @Override
+    public String getAuthMethod()
+    {
+        return _authMethod;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param authMethod the authMethod to set
+     * @throws IllegalStateException if the SecurityHandler is running
+     */
+    public void setAuthMethod(String authMethod)
+    {
+        if (isRunning())
+            throw new IllegalStateException("running");
+        _authMethod = authMethod;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return True if forwards to welcome files are authenticated
+     */
+    public boolean isCheckWelcomeFiles()
+    {
+        return _checkWelcomeFiles;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param authenticateWelcomeFiles True if forwards to welcome files are
+     *                authenticated
+     * @throws IllegalStateException if the SecurityHandler is running
+     */
+    public void setCheckWelcomeFiles(boolean authenticateWelcomeFiles)
+    {
+        if (isRunning())
+            throw new IllegalStateException("running");
+        _checkWelcomeFiles = authenticateWelcomeFiles;
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public String getInitParameter(String key)
+    {
+        return _initParameters.get(key);
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public Set<String> getInitParameterNames()
+    {
+        return _initParameters.keySet();
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Set an initialization parameter.
+     * @param key
+     * @param value
+     * @return previous value
+     * @throws IllegalStateException if the SecurityHandler is running
+     */
+    public String setInitParameter(String key, String value)
+    {
+        if (isRunning())
+            throw new IllegalStateException("running");
+        return _initParameters.put(key,value);
+    }
+
+    /* ------------------------------------------------------------ */
+    protected LoginService findLoginService() throws Exception
+    {
+        Collection<LoginService> list = getServer().getBeans(LoginService.class);
+        LoginService service = null;
+        String realm=getRealmName();
+        if (realm!=null)
+        {
+            for (LoginService s : list)
+                if (s.getName()!=null && s.getName().equals(realm))
+                {
+                    service=s;
+                    break;
+                }
+        }
+        else if (list.size()==1)
+            service =  list.iterator().next();
+        
+        return service;
+    }
+
+    /* ------------------------------------------------------------ */
+    protected IdentityService findIdentityService()
+    {
+        return getServer().getBean(IdentityService.class);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     */
+    @Override
+    protected void doStart()
+        throws Exception
+    {
+        // copy security init parameters
+        ContextHandler.Context context =ContextHandler.getCurrentContext();
+        if (context!=null)
+        {
+            Enumeration<String> names=context.getInitParameterNames();
+            while (names!=null && names.hasMoreElements())
+            {
+                String name =names.nextElement();
+                if (name.startsWith("org.eclipse.jetty.security.") &&
+                        getInitParameter(name)==null)
+                    setInitParameter(name,context.getInitParameter(name));
+            }
+            
+            //register a session listener to handle securing sessions when authentication is performed
+            context.getContextHandler().addEventListener(new HttpSessionListener()
+            {
+                @Override
+                public void sessionDestroyed(HttpSessionEvent se)
+                {
+                }
+
+                @Override
+                public void sessionCreated(HttpSessionEvent se)
+                {                    
+                    //if current request is authenticated, then as we have just created the session, mark it as secure, as it has not yet been returned to a user
+                    HttpChannel<?> channel = HttpChannel.getCurrentHttpChannel();              
+                    
+                    if (channel == null)
+                        return;
+                    Request request = channel.getRequest();
+                    if (request == null)
+                        return;
+                    
+                    if (request.isSecure())
+                    {
+                        se.getSession().setAttribute(AbstractSession.SESSION_KNOWN_ONLY_TO_AUTHENTICATED, Boolean.TRUE);
+                    }
+                }
+            });
+        }
+
+        // complicated resolution of login and identity service to handle
+        // many different ways these can be constructed and injected.
+
+        if (_loginService==null)
+        {
+            setLoginService(findLoginService());
+            if (_loginService!=null)
+                unmanage(_loginService);
+        }
+        
+        if (_identityService==null)
+        {
+            if (_loginService!=null)
+                setIdentityService(_loginService.getIdentityService());
+
+            if (_identityService==null)
+                setIdentityService(findIdentityService());
+
+            if (_identityService==null)
+            {
+                if (_realmName!=null)
+                { 
+                    setIdentityService(new DefaultIdentityService());
+                    manage(_identityService);
+                }
+            }
+            else
+                unmanage(_identityService);
+        }
+
+        if (_loginService!=null)
+        {
+            if (_loginService.getIdentityService()==null)
+                _loginService.setIdentityService(_identityService);
+            else if (_loginService.getIdentityService()!=_identityService)
+                throw new IllegalStateException("LoginService has different IdentityService to "+this);
+        }
+
+        Authenticator.Factory authenticatorFactory = getAuthenticatorFactory();
+        if (_authenticator==null && authenticatorFactory!=null && _identityService!=null)
+            setAuthenticator(authenticatorFactory.getAuthenticator(getServer(),ContextHandler.getCurrentContext(),this, _identityService, _loginService));
+
+        if (_authenticator!=null)
+            _authenticator.setConfiguration(this);
+        else if (_realmName!=null)
+        {
+            LOG.warn("No Authenticator for "+this);
+            throw new IllegalStateException("No Authenticator");
+        }
+
+        super.doStart();
+    }
+
+    @Override
+    /* ------------------------------------------------------------ */
+    protected void doStop() throws Exception
+    {
+        //if we discovered the services (rather than had them explicitly configured), remove them.
+        if (!isManaged(_identityService))
+        {
+            removeBean(_identityService);
+            _identityService = null;   
+        }
+        
+        if (!isManaged(_loginService))
+        {
+            removeBean(_loginService);
+            _loginService=null;
+        }
+        
+        super.doStop();
+    }
+
+    /* ------------------------------------------------------------ */
+    protected boolean checkSecurity(Request request)
+    {
+        switch(request.getDispatcherType())
+        {
+            case REQUEST:
+            case ASYNC:
+                return true;
+            case FORWARD:
+                if (isCheckWelcomeFiles() && request.getAttribute("org.eclipse.jetty.server.welcome") != null)
+                {
+                    request.removeAttribute("org.eclipse.jetty.server.welcome");
+                    return true;
+                }
+                return false;
+            default:
+                return false;
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.security.Authenticator.AuthConfiguration#isSessionRenewedOnAuthentication()
+     */
+    @Override
+    public boolean isSessionRenewedOnAuthentication()
+    {
+        return _renewSession;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Set renew the session on Authentication.
+     * <p>
+     * If set to true, then on authentication, the session associated with a reqeuest is invalidated and replaced with a new session.
+     * @see org.eclipse.jetty.security.Authenticator.AuthConfiguration#isSessionRenewedOnAuthentication()
+     */
+    public void setSessionRenewedOnAuthentication(boolean renew)
+    {
+        _renewSession=renew;
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see org.eclipse.jetty.server.Handler#handle(java.lang.String,
+     *      javax.servlet.http.HttpServletRequest,
+     *      javax.servlet.http.HttpServletResponse, int)
+     */
+    @Override
+    public void handle(String pathInContext, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+    {
+        final Response base_response = baseRequest.getResponse();
+        final Handler handler=getHandler();
+
+        if (handler==null)
+            return;
+
+        final Authenticator authenticator = _authenticator;
+
+        if (checkSecurity(baseRequest))
+        {
+            //See Servlet Spec 3.1 sec 13.6.3
+            if (authenticator != null)
+                authenticator.prepareRequest(baseRequest);
+            
+            RoleInfo roleInfo = prepareConstraintInfo(pathInContext, baseRequest);
+
+            // Check data constraints
+            if (!checkUserDataPermissions(pathInContext, baseRequest, base_response, roleInfo))
+            {
+                if (!baseRequest.isHandled())
+                {
+                    response.sendError(HttpServletResponse.SC_FORBIDDEN);
+                    baseRequest.setHandled(true);
+                }
+                return;
+            }
+
+            // is Auth mandatory?
+            boolean isAuthMandatory =
+                isAuthMandatory(baseRequest, base_response, roleInfo);
+
+            if (isAuthMandatory && authenticator==null)
+            {
+                LOG.warn("No authenticator for: "+roleInfo);
+                if (!baseRequest.isHandled())
+                {
+                    response.sendError(HttpServletResponse.SC_FORBIDDEN);
+                    baseRequest.setHandled(true);
+                }
+                return;
+            }
+
+            // check authentication
+            Object previousIdentity = null;
+            try
+            {
+                Authentication authentication = baseRequest.getAuthentication();
+                if (authentication==null || authentication==Authentication.NOT_CHECKED)
+                    authentication=authenticator==null?Authentication.UNAUTHENTICATED:authenticator.validateRequest(request, response, isAuthMandatory);
+
+                if (authentication instanceof Authentication.Wrapped)
+                {
+                    request=((Authentication.Wrapped)authentication).getHttpServletRequest();
+                    response=((Authentication.Wrapped)authentication).getHttpServletResponse();
+                }
+
+                if (authentication instanceof Authentication.ResponseSent)
+                {
+                    baseRequest.setHandled(true);
+                }
+                else if (authentication instanceof Authentication.User)
+                {
+                    Authentication.User userAuth = (Authentication.User)authentication;
+                    baseRequest.setAuthentication(authentication);
+                    if (_identityService!=null)
+                        previousIdentity = _identityService.associate(userAuth.getUserIdentity());
+
+                    if (isAuthMandatory)
+                    {
+                        boolean authorized=checkWebResourcePermissions(pathInContext, baseRequest, base_response, roleInfo, userAuth.getUserIdentity());
+                        if (!authorized)
+                        {
+                            response.sendError(HttpServletResponse.SC_FORBIDDEN, "!role");
+                            baseRequest.setHandled(true);
+                            return;
+                        }
+                    }
+
+                    handler.handle(pathInContext, baseRequest, request, response);
+                    if (authenticator!=null)
+                        authenticator.secureResponse(request, response, isAuthMandatory, userAuth);
+                }
+                else if (authentication instanceof Authentication.Deferred)
+                {
+                    DeferredAuthentication deferred= (DeferredAuthentication)authentication;
+                    baseRequest.setAuthentication(authentication);
+
+                    try
+                    {
+                        handler.handle(pathInContext, baseRequest, request, response);
+                    }
+                    finally
+                    {
+                        previousIdentity = deferred.getPreviousAssociation();
+                    }
+
+                    if (authenticator!=null)
+                    {
+                        Authentication auth=baseRequest.getAuthentication();
+                        if (auth instanceof Authentication.User)
+                        {
+                            Authentication.User userAuth = (Authentication.User)auth;
+                            authenticator.secureResponse(request, response, isAuthMandatory, userAuth);
+                        }
+                        else
+                            authenticator.secureResponse(request, response, isAuthMandatory, null);
+                    }
+                }
+                else
+                {
+                    baseRequest.setAuthentication(authentication);
+                    if (_identityService!=null)
+                        previousIdentity = _identityService.associate(null);
+                    handler.handle(pathInContext, baseRequest, request, response);
+                    if (authenticator!=null)
+                        authenticator.secureResponse(request, response, isAuthMandatory, null);
+                }
+            }
+            catch (ServerAuthException e)
+            {
+                // jaspi 3.8.3 send HTTP 500 internal server error, with message
+                // from AuthException
+                response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.getMessage());
+            }
+            finally
+            {
+                if (_identityService!=null)
+                    _identityService.disassociate(previousIdentity);
+            }
+        }
+        else
+            handler.handle(pathInContext, baseRequest, request, response);
+    }
+
+
+    /* ------------------------------------------------------------ */
+    public static SecurityHandler getCurrentSecurityHandler()
+    {
+        Context context = ContextHandler.getCurrentContext();
+        if (context==null)
+            return null;
+
+        return context.getContextHandler().getChildHandlerByClass(SecurityHandler.class);
+    }
+
+    /* ------------------------------------------------------------ */
+    public void logout(Authentication.User user)
+    {
+        LOG.debug("logout {}",user);
+        LoginService login_service=getLoginService();
+        if (login_service!=null)
+        {
+            login_service.logout(user.getUserIdentity());
+        }
+
+        IdentityService identity_service=getIdentityService();
+        if (identity_service!=null)
+        {
+            // TODO recover previous from threadlocal (or similar)
+            Object previous=null;
+            identity_service.disassociate(previous);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    protected abstract RoleInfo prepareConstraintInfo(String pathInContext, Request request);
+
+    /* ------------------------------------------------------------ */
+    protected abstract boolean checkUserDataPermissions(String pathInContext, Request request, Response response, RoleInfo constraintInfo) throws IOException;
+
+    /* ------------------------------------------------------------ */
+    protected abstract boolean isAuthMandatory(Request baseRequest, Response base_response, Object constraintInfo);
+
+    /* ------------------------------------------------------------ */
+    protected abstract boolean checkWebResourcePermissions(String pathInContext, Request request, Response response, Object constraintInfo,
+                                                           UserIdentity userIdentity) throws IOException;
+
+
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    public class NotChecked implements Principal
+    {
+        @Override
+        public String getName()
+        {
+            return null;
+        }
+
+        @Override
+        public String toString()
+        {
+            return "NOT CHECKED";
+        }
+
+        public SecurityHandler getSecurityHandler()
+        {
+            return SecurityHandler.this;
+        }
+    }
+
+
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    public static final Principal __NO_USER = new Principal()
+    {
+        @Override
+        public String getName()
+        {
+            return null;
+        }
+
+        @Override
+        public String toString()
+        {
+            return "No User";
+        }
+    };
+
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    /**
+     * Nobody user. The Nobody UserPrincipal is used to indicate a partial state
+     * of authentication. A request with a Nobody UserPrincipal will be allowed
+     * past all authentication constraints - but will not be considered an
+     * authenticated request. It can be used by Authenticators such as
+     * FormAuthenticator to allow access to logon and error pages within an
+     * authenticated URI tree.
+     */
+    public static final Principal __NOBODY = new Principal()
+    {
+        @Override
+        public String getName()
+        {
+            return "Nobody";
+        }
+
+        @Override
+        public String toString()
+        {
+            return getName();
+        }
+    };
+
+}
diff --git a/lib/jetty/org/eclipse/jetty/security/ServerAuthException.java b/lib/jetty/org/eclipse/jetty/security/ServerAuthException.java
new file mode 100644 (file)
index 0000000..85f532f
--- /dev/null
@@ -0,0 +1,47 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.security;
+
+import java.security.GeneralSecurityException;
+
+/**
+ * @version $Rev: 4466 $ $Date: 2009-02-10 23:42:54 +0100 (Tue, 10 Feb 2009) $
+ */
+public class ServerAuthException extends GeneralSecurityException
+{
+
+    public ServerAuthException()
+    {
+    }
+
+    public ServerAuthException(String s)
+    {
+        super(s);
+    }
+
+    public ServerAuthException(String s, Throwable throwable)
+    {
+        super(s, throwable);
+    }
+
+    public ServerAuthException(Throwable throwable)
+    {
+        super(throwable);
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/security/SpnegoLoginService.java b/lib/jetty/org/eclipse/jetty/security/SpnegoLoginService.java
new file mode 100644 (file)
index 0000000..ba160f0
--- /dev/null
@@ -0,0 +1,191 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.security;
+
+import java.util.Properties;
+
+import javax.security.auth.Subject;
+
+import org.eclipse.jetty.server.UserIdentity;
+import org.eclipse.jetty.util.B64Code;
+import org.eclipse.jetty.util.component.AbstractLifeCycle;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.resource.Resource;
+import org.ietf.jgss.GSSContext;
+import org.ietf.jgss.GSSCredential;
+import org.ietf.jgss.GSSException;
+import org.ietf.jgss.GSSManager;
+import org.ietf.jgss.GSSName;
+import org.ietf.jgss.Oid;
+
+public class SpnegoLoginService extends AbstractLifeCycle implements LoginService
+{
+    private static final Logger LOG = Log.getLogger(SpnegoLoginService.class);
+
+    protected IdentityService _identityService;// = new LdapIdentityService();
+    protected String _name;
+    private String _config;
+
+    private String _targetName;
+
+    public SpnegoLoginService()
+    {
+
+    }
+
+    public SpnegoLoginService( String name )
+    {
+        setName(name);
+    }
+
+    public SpnegoLoginService( String name, String config )
+    {
+        setName(name);
+        setConfig(config);
+    }
+
+    @Override
+    public String getName()
+    {
+        return _name;
+    }
+
+    public void setName(String name)
+    {
+        if (isRunning())
+        {
+            throw new IllegalStateException("Running");
+        }
+
+        _name = name;
+    }
+
+    public String getConfig()
+    {
+        return _config;
+    }
+
+    public void setConfig( String config )
+    {
+        if (isRunning())
+        {
+            throw new IllegalStateException("Running");
+        }
+
+        _config = config;
+    }
+
+
+
+    @Override
+    protected void doStart() throws Exception
+    {
+        Properties properties = new Properties();
+        Resource resource = Resource.newResource(_config);
+        properties.load(resource.getInputStream());
+
+        _targetName = properties.getProperty("targetName");
+
+        LOG.debug("Target Name {}", _targetName);
+
+        super.doStart();
+    }
+
+    /**
+     * username will be null since the credentials will contain all the relevant info
+     */
+    @Override
+    public UserIdentity login(String username, Object credentials)
+    {
+        String encodedAuthToken = (String)credentials;
+
+        byte[] authToken = B64Code.decode(encodedAuthToken);
+
+        GSSManager manager = GSSManager.getInstance();
+        try
+        {
+            Oid krb5Oid = new Oid("1.3.6.1.5.5.2"); // http://java.sun.com/javase/6/docs/technotes/guides/security/jgss/jgss-features.html
+            GSSName gssName = manager.createName(_targetName,null);
+            GSSCredential serverCreds = manager.createCredential(gssName,GSSCredential.INDEFINITE_LIFETIME,krb5Oid,GSSCredential.ACCEPT_ONLY);
+            GSSContext gContext = manager.createContext(serverCreds);
+
+            if (gContext == null)
+            {
+                LOG.debug("SpnegoUserRealm: failed to establish GSSContext");
+            }
+            else
+            {
+                while (!gContext.isEstablished())
+                {
+                    authToken = gContext.acceptSecContext(authToken,0,authToken.length);
+                }
+                if (gContext.isEstablished())
+                {
+                    String clientName = gContext.getSrcName().toString();
+                    String role = clientName.substring(clientName.indexOf('@') + 1);
+
+                    LOG.debug("SpnegoUserRealm: established a security context");
+                    LOG.debug("Client Principal is: " + gContext.getSrcName());
+                    LOG.debug("Server Principal is: " + gContext.getTargName());
+                    LOG.debug("Client Default Role: " + role);
+
+                    SpnegoUserPrincipal user = new SpnegoUserPrincipal(clientName,authToken);
+
+                    Subject subject = new Subject();
+                    subject.getPrincipals().add(user);
+
+                    return _identityService.newUserIdentity(subject,user, new String[]{role});
+                }
+            }
+
+        }
+        catch (GSSException gsse)
+        {
+            LOG.warn(gsse);
+        }
+
+        return null;
+    }
+
+    @Override
+    public boolean validate(UserIdentity user)
+    {
+        return false;
+    }
+
+    @Override
+    public IdentityService getIdentityService()
+    {
+        return _identityService;
+    }
+
+    @Override
+    public void setIdentityService(IdentityService service)
+    {
+        _identityService = service;
+    }
+
+    @Override
+    public void logout(UserIdentity user) 
+    {
+        // TODO Auto-generated method stub
+    }
+
+}
diff --git a/lib/jetty/org/eclipse/jetty/security/SpnegoUserIdentity.java b/lib/jetty/org/eclipse/jetty/security/SpnegoUserIdentity.java
new file mode 100644 (file)
index 0000000..13cf0bb
--- /dev/null
@@ -0,0 +1,57 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.security;
+
+import java.security.Principal;
+import java.util.List;
+
+import javax.security.auth.Subject;
+
+import org.eclipse.jetty.server.UserIdentity;
+
+public class SpnegoUserIdentity implements UserIdentity
+{
+    private Subject _subject;
+    private Principal _principal;
+    private List<String> _roles;
+
+    public SpnegoUserIdentity( Subject subject, Principal principal, List<String> roles )
+    {
+        _subject = subject;
+        _principal = principal;
+        _roles = roles;
+    }
+
+
+    public Subject getSubject()
+    {
+        return _subject;
+    }
+
+    public Principal getUserPrincipal()
+    {
+        return _principal;
+    }
+
+    public boolean isUserInRole(String role, Scope scope)
+    {
+        return _roles.contains(role);
+    }
+
+}
diff --git a/lib/jetty/org/eclipse/jetty/security/SpnegoUserPrincipal.java b/lib/jetty/org/eclipse/jetty/security/SpnegoUserPrincipal.java
new file mode 100644 (file)
index 0000000..3fe9445
--- /dev/null
@@ -0,0 +1,65 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.security;
+
+import java.security.Principal;
+
+import org.eclipse.jetty.util.B64Code;
+
+public class SpnegoUserPrincipal implements Principal
+{
+    private final String _name;
+    private byte[] _token;
+    private String _encodedToken;
+
+    public SpnegoUserPrincipal( String name, String encodedToken )
+    {
+        _name = name;
+        _encodedToken = encodedToken;
+    }
+
+    public SpnegoUserPrincipal( String name, byte[] token )
+    {
+        _name = name;
+        _token = token;
+    }
+
+    public String getName()
+    {
+        return _name;
+    }
+
+    public byte[] getToken()
+    {
+        if ( _token == null )
+        {
+            _token = B64Code.decode(_encodedToken);
+        }
+        return _token;
+    }
+
+    public String getEncodedToken()
+    {
+        if ( _encodedToken == null )
+        {
+            _encodedToken = new String(B64Code.encode(_token,true));
+        }
+        return _encodedToken;
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/security/UserAuthentication.java b/lib/jetty/org/eclipse/jetty/security/UserAuthentication.java
new file mode 100644 (file)
index 0000000..9174a06
--- /dev/null
@@ -0,0 +1,48 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.security;
+
+import org.eclipse.jetty.server.UserIdentity;
+
+
+/**
+ * @version $Rev: 4793 $ $Date: 2009-03-19 00:00:01 +0100 (Thu, 19 Mar 2009) $
+ */
+public class UserAuthentication extends AbstractUserAuthentication
+{
+   
+    public UserAuthentication(String method, UserIdentity userIdentity)
+    {
+        super(method, userIdentity);
+    }
+
+    
+    @Override
+    public String toString()
+    {
+        return "{User,"+getAuthMethod()+","+_userIdentity+"}";
+    }
+
+    public void logout()
+    {
+        SecurityHandler security=SecurityHandler.getCurrentSecurityHandler();
+        if (security!=null)
+            security.logout(this);
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/security/UserDataConstraint.java b/lib/jetty/org/eclipse/jetty/security/UserDataConstraint.java
new file mode 100644 (file)
index 0000000..c288e1d
--- /dev/null
@@ -0,0 +1,40 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.security;
+
+/**
+ * @version $Rev: 4466 $ $Date: 2009-02-10 23:42:54 +0100 (Tue, 10 Feb 2009) $
+ */
+public enum UserDataConstraint
+{
+    None, Integral, Confidential;
+
+    public static UserDataConstraint get(int dataConstraint)
+    {
+        if (dataConstraint < -1 || dataConstraint > 2) throw new IllegalArgumentException("Expected -1, 0, 1, or 2, not: " + dataConstraint);
+        if (dataConstraint == -1) return None;
+        return values()[dataConstraint];
+    }
+
+    public UserDataConstraint combine(UserDataConstraint other)
+    {
+        if (this.compareTo(other) < 0) return this;
+        return other;
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/security/authentication/BasicAuthenticator.java b/lib/jetty/org/eclipse/jetty/security/authentication/BasicAuthenticator.java
new file mode 100644 (file)
index 0000000..caa784f
--- /dev/null
@@ -0,0 +1,121 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.security.authentication;
+
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.http.HttpHeader;
+import org.eclipse.jetty.security.ServerAuthException;
+import org.eclipse.jetty.security.UserAuthentication;
+import org.eclipse.jetty.server.Authentication;
+import org.eclipse.jetty.server.Authentication.User;
+import org.eclipse.jetty.server.UserIdentity;
+import org.eclipse.jetty.util.B64Code;
+import org.eclipse.jetty.util.security.Constraint;
+
+/**
+ * @version $Rev: 4793 $ $Date: 2009-03-19 00:00:01 +0100 (Thu, 19 Mar 2009) $
+ */
+public class BasicAuthenticator extends LoginAuthenticator
+{
+    /* ------------------------------------------------------------ */
+    public BasicAuthenticator()
+    {
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.security.Authenticator#getAuthMethod()
+     */
+    @Override
+    public String getAuthMethod()
+    {
+        return Constraint.__BASIC_AUTH;
+    }
+
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.security.Authenticator#validateRequest(javax.servlet.ServletRequest, javax.servlet.ServletResponse, boolean)
+     */
+    @Override
+    public Authentication validateRequest(ServletRequest req, ServletResponse res, boolean mandatory) throws ServerAuthException
+    {
+        HttpServletRequest request = (HttpServletRequest)req;
+        HttpServletResponse response = (HttpServletResponse)res;
+        String credentials = request.getHeader(HttpHeader.AUTHORIZATION.asString());
+
+        try
+        {
+            if (!mandatory)
+                return new DeferredAuthentication(this);
+
+            if (credentials != null)
+            {
+                int space=credentials.indexOf(' ');
+                if (space>0)
+                {
+                    String method=credentials.substring(0,space);
+                    if ("basic".equalsIgnoreCase(method))
+                    {
+                        credentials = credentials.substring(space+1);
+                        credentials = B64Code.decode(credentials, StandardCharsets.ISO_8859_1);
+                        int i = credentials.indexOf(':');
+                        if (i>0)
+                        {
+                            String username = credentials.substring(0,i);
+                            String password = credentials.substring(i+1);
+
+                            UserIdentity user = login (username, password, request);
+                            if (user!=null)
+                            {
+                                return new UserAuthentication(getAuthMethod(),user);
+                            }
+                        }
+                    }
+                }
+            }
+
+            if (DeferredAuthentication.isDeferred(response))
+                return Authentication.UNAUTHENTICATED;
+
+            response.setHeader(HttpHeader.WWW_AUTHENTICATE.asString(), "basic realm=\"" + _loginService.getName() + '"');
+            response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
+            return Authentication.SEND_CONTINUE;
+        }
+        catch (IOException e)
+        {
+            throw new ServerAuthException(e);
+        }
+    }
+
+    @Override
+    public boolean secureResponse(ServletRequest req, ServletResponse res, boolean mandatory, User validatedUser) throws ServerAuthException
+    {
+        return true;
+    }
+
+}
diff --git a/lib/jetty/org/eclipse/jetty/security/authentication/ClientCertAuthenticator.java b/lib/jetty/org/eclipse/jetty/security/authentication/ClientCertAuthenticator.java
new file mode 100644 (file)
index 0000000..9694cf1
--- /dev/null
@@ -0,0 +1,371 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.security.authentication;
+
+import java.io.InputStream;
+import java.security.KeyStore;
+import java.security.Principal;
+import java.security.cert.CRL;
+import java.security.cert.X509Certificate;
+import java.util.Collection;
+
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.security.ServerAuthException;
+import org.eclipse.jetty.security.UserAuthentication;
+import org.eclipse.jetty.server.Authentication;
+import org.eclipse.jetty.server.Authentication.User;
+import org.eclipse.jetty.server.UserIdentity;
+import org.eclipse.jetty.util.B64Code;
+import org.eclipse.jetty.util.security.CertificateUtils;
+import org.eclipse.jetty.util.security.CertificateValidator;
+import org.eclipse.jetty.util.security.Constraint;
+import org.eclipse.jetty.util.security.Password;
+
+/**
+ * @version $Rev: 4793 $ $Date: 2009-03-19 00:00:01 +0100 (Thu, 19 Mar 2009) $
+ */
+public class ClientCertAuthenticator extends LoginAuthenticator
+{
+    /** String name of keystore password property. */
+    private static final String PASSWORD_PROPERTY = "org.eclipse.jetty.ssl.password";
+
+    /** Truststore path */
+    private String _trustStorePath;
+    /** Truststore provider name */
+    private String _trustStoreProvider;
+    /** Truststore type */
+    private String _trustStoreType = "JKS";
+    /** Truststore password */
+    private transient Password _trustStorePassword;
+
+    /** Set to true if SSL certificate validation is required */
+    private boolean _validateCerts;
+    /** Path to file that contains Certificate Revocation List */
+    private String _crlPath;
+    /** Maximum certification path length (n - number of intermediate certs, -1 for unlimited) */
+    private int _maxCertPathLength = -1;
+    /** CRL Distribution Points (CRLDP) support */
+    private boolean _enableCRLDP = false;
+    /** On-Line Certificate Status Protocol (OCSP) support */
+    private boolean _enableOCSP = false;
+    /** Location of OCSP Responder */
+    private String _ocspResponderURL;
+
+    public ClientCertAuthenticator()
+    {
+        super();
+    }
+
+    @Override
+    public String getAuthMethod()
+    {
+        return Constraint.__CERT_AUTH;
+    }
+
+    
+
+    /**
+     * @return Authentication for request
+     * @throws ServerAuthException
+     */
+    @Override
+    public Authentication validateRequest(ServletRequest req, ServletResponse res, boolean mandatory) throws ServerAuthException
+    {
+        if (!mandatory)
+            return new DeferredAuthentication(this);
+
+        HttpServletRequest request = (HttpServletRequest)req;
+        HttpServletResponse response = (HttpServletResponse)res;
+        X509Certificate[] certs = (X509Certificate[]) request.getAttribute("javax.servlet.request.X509Certificate");
+
+        try
+        {
+            // Need certificates.
+            if (certs != null && certs.length > 0)
+            {
+
+                if (_validateCerts)
+                {
+                    KeyStore trustStore = getKeyStore(null,
+                            _trustStorePath, _trustStoreType, _trustStoreProvider,
+                            _trustStorePassword == null ? null :_trustStorePassword.toString());
+                    Collection<? extends CRL> crls = loadCRL(_crlPath);
+                    CertificateValidator validator = new CertificateValidator(trustStore, crls);
+                    validator.validate(certs);
+                }
+
+                for (X509Certificate cert: certs)
+                {
+                    if (cert==null)
+                        continue;
+
+                    Principal principal = cert.getSubjectDN();
+                    if (principal == null) principal = cert.getIssuerDN();
+                    final String username = principal == null ? "clientcert" : principal.getName();
+
+                    final char[] credential = B64Code.encode(cert.getSignature());
+
+                    UserIdentity user = login(username, credential, req);
+                    if (user!=null)
+                    {
+                        return new UserAuthentication(getAuthMethod(),user);
+                    }
+                }
+            }
+
+            if (!DeferredAuthentication.isDeferred(response))
+            {
+                response.sendError(HttpServletResponse.SC_FORBIDDEN);
+                return Authentication.SEND_FAILURE;
+            }
+
+            return Authentication.UNAUTHENTICATED;
+        }
+        catch (Exception e)
+        {
+            throw new ServerAuthException(e.getMessage());
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Loads keystore using an input stream or a file path in the same
+     * order of precedence.
+     *
+     * Required for integrations to be able to override the mechanism
+     * used to load a keystore in order to provide their own implementation.
+     *
+     * @param storeStream keystore input stream
+     * @param storePath path of keystore file
+     * @param storeType keystore type
+     * @param storeProvider keystore provider
+     * @param storePassword keystore password
+     * @return created keystore
+     * @throws Exception
+     */
+    protected KeyStore getKeyStore(InputStream storeStream, String storePath, String storeType, String storeProvider, String storePassword) throws Exception
+    {
+        return CertificateUtils.getKeyStore(storeStream, storePath, storeType, storeProvider, storePassword);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Loads certificate revocation list (CRL) from a file.
+     *
+     * Required for integrations to be able to override the mechanism used to
+     * load CRL in order to provide their own implementation.
+     *
+     * @param crlPath path of certificate revocation list file
+     * @return a (possibly empty) collection view of java.security.cert.CRL objects initialized with the data from the
+     *         input stream.
+     * @throws Exception
+     */
+    protected Collection<? extends CRL> loadCRL(String crlPath) throws Exception
+    {
+        return CertificateUtils.loadCRL(crlPath);
+    }
+
+    @Override
+    public boolean secureResponse(ServletRequest req, ServletResponse res, boolean mandatory, User validatedUser) throws ServerAuthException
+    {
+        return true;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return true if SSL certificate has to be validated
+     */
+    public boolean isValidateCerts()
+    {
+        return _validateCerts;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param validateCerts
+     *            true if SSL certificates have to be validated
+     */
+    public void setValidateCerts(boolean validateCerts)
+    {
+        _validateCerts = validateCerts;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return The file name or URL of the trust store location
+     */
+    public String getTrustStore()
+    {
+        return _trustStorePath;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param trustStorePath
+     *            The file name or URL of the trust store location
+     */
+    public void setTrustStore(String trustStorePath)
+    {
+        _trustStorePath = trustStorePath;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return The provider of the trust store
+     */
+    public String getTrustStoreProvider()
+    {
+        return _trustStoreProvider;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param trustStoreProvider
+     *            The provider of the trust store
+     */
+    public void setTrustStoreProvider(String trustStoreProvider)
+    {
+        _trustStoreProvider = trustStoreProvider;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return The type of the trust store (default "JKS")
+     */
+    public String getTrustStoreType()
+    {
+        return _trustStoreType;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param trustStoreType
+     *            The type of the trust store (default "JKS")
+     */
+    public void setTrustStoreType(String trustStoreType)
+    {
+        _trustStoreType = trustStoreType;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param password
+     *            The password for the trust store
+     */
+    public void setTrustStorePassword(String password)
+    {
+        _trustStorePassword = Password.getPassword(PASSWORD_PROPERTY,password,null);
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Get the crlPath.
+     * @return the crlPath
+     */
+    public String getCrlPath()
+    {
+        return _crlPath;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Set the crlPath.
+     * @param crlPath the crlPath to set
+     */
+    public void setCrlPath(String crlPath)
+    {
+        _crlPath = crlPath;
+    }
+
+    /**
+     * @return Maximum number of intermediate certificates in
+     * the certification path (-1 for unlimited)
+     */
+    public int getMaxCertPathLength()
+    {
+        return _maxCertPathLength;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param maxCertPathLength
+     *            maximum number of intermediate certificates in
+     *            the certification path (-1 for unlimited)
+     */
+    public void setMaxCertPathLength(int maxCertPathLength)
+    {
+        _maxCertPathLength = maxCertPathLength;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return true if CRL Distribution Points support is enabled
+     */
+    public boolean isEnableCRLDP()
+    {
+        return _enableCRLDP;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Enables CRL Distribution Points Support
+     * @param enableCRLDP true - turn on, false - turns off
+     */
+    public void setEnableCRLDP(boolean enableCRLDP)
+    {
+        _enableCRLDP = enableCRLDP;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return true if On-Line Certificate Status Protocol support is enabled
+     */
+    public boolean isEnableOCSP()
+    {
+        return _enableOCSP;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Enables On-Line Certificate Status Protocol support
+     * @param enableOCSP true - turn on, false - turn off
+     */
+    public void setEnableOCSP(boolean enableOCSP)
+    {
+        _enableOCSP = enableOCSP;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Location of the OCSP Responder
+     */
+    public String getOcspResponderURL()
+    {
+        return _ocspResponderURL;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Set the location of the OCSP Responder.
+     * @param ocspResponderURL location of the OCSP Responder
+     */
+    public void setOcspResponderURL(String ocspResponderURL)
+    {
+        _ocspResponderURL = ocspResponderURL;
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/security/authentication/DeferredAuthentication.java b/lib/jetty/org/eclipse/jetty/security/authentication/DeferredAuthentication.java
new file mode 100644 (file)
index 0000000..df0f7d9
--- /dev/null
@@ -0,0 +1,395 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+
+package org.eclipse.jetty.security.authentication;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Locale;
+
+import javax.servlet.ServletOutputStream;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.WriteListener;
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.security.IdentityService;
+import org.eclipse.jetty.security.LoginService;
+import org.eclipse.jetty.security.ServerAuthException;
+import org.eclipse.jetty.security.UserAuthentication;
+import org.eclipse.jetty.server.Authentication;
+import org.eclipse.jetty.server.UserIdentity;
+import org.eclipse.jetty.util.IO;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+public class DeferredAuthentication implements Authentication.Deferred
+{
+    private static final Logger LOG = Log.getLogger(DeferredAuthentication.class);
+    protected final LoginAuthenticator _authenticator;
+    private Object _previousAssociation;
+
+    /* ------------------------------------------------------------ */
+    public DeferredAuthentication(LoginAuthenticator authenticator)
+    {
+        if (authenticator == null)
+            throw new NullPointerException("No Authenticator");
+        this._authenticator = authenticator;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.server.Authentication.Deferred#authenticate(ServletRequest)
+     */
+    @Override
+    public Authentication authenticate(ServletRequest request)
+    {
+        try
+        {
+            Authentication authentication = _authenticator.validateRequest(request,__deferredResponse,true);
+
+            if (authentication!=null && (authentication instanceof Authentication.User) && !(authentication instanceof Authentication.ResponseSent))
+            {
+                LoginService login_service= _authenticator.getLoginService();
+                IdentityService identity_service=login_service.getIdentityService();
+                
+                if (identity_service!=null)
+                    _previousAssociation=identity_service.associate(((Authentication.User)authentication).getUserIdentity());
+                
+                return authentication;
+            }
+        }
+        catch (ServerAuthException e)
+        {
+            LOG.debug(e);
+        }
+
+        return this;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.server.Authentication.Deferred#authenticate(javax.servlet.ServletRequest, javax.servlet.ServletResponse)
+     */
+    @Override
+    public Authentication authenticate(ServletRequest request, ServletResponse response)
+    {
+        try
+        {
+            LoginService login_service= _authenticator.getLoginService();
+            IdentityService identity_service=login_service.getIdentityService();
+            
+            Authentication authentication = _authenticator.validateRequest(request,response,true);
+            if (authentication instanceof Authentication.User && identity_service!=null)
+                _previousAssociation=identity_service.associate(((Authentication.User)authentication).getUserIdentity());
+            return authentication;
+        }
+        catch (ServerAuthException e)
+        {
+            LOG.debug(e);
+        }
+        return this;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.server.Authentication.Deferred#login(String, Object, ServletRequest)
+     */
+    @Override
+    public Authentication login(String username, Object password, ServletRequest request)
+    {
+        if (username == null)
+            return null;
+        
+        UserIdentity identity = _authenticator.login(username, password, request);
+        if (identity != null)
+        {
+            IdentityService identity_service = _authenticator.getLoginService().getIdentityService();
+            UserAuthentication authentication = new UserAuthentication("API",identity);
+            if (identity_service != null)
+                _previousAssociation=identity_service.associate(identity);
+            return authentication;
+        }
+        return null;
+    }
+
+    /* ------------------------------------------------------------ */
+    public Object getPreviousAssociation()
+    {
+        return _previousAssociation;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param response
+     * @return true if this response is from a deferred call to {@link #authenticate(ServletRequest)}
+     */
+    public static boolean isDeferred(HttpServletResponse response)
+    {
+        return response==__deferredResponse;
+    }
+
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    final static HttpServletResponse __deferredResponse = new HttpServletResponse()
+    {
+        @Override
+        public void addCookie(Cookie cookie)
+        {
+        }
+
+        @Override
+        public void addDateHeader(String name, long date)
+        {
+        }
+
+        @Override
+        public void addHeader(String name, String value)
+        {
+        }
+
+        @Override
+        public void addIntHeader(String name, int value)
+        {
+        }
+
+        @Override
+        public boolean containsHeader(String name)
+        {
+            return false;
+        }
+
+        @Override
+        public String encodeRedirectURL(String url)
+        {
+            return null;
+        }
+
+        @Override
+        public String encodeRedirectUrl(String url)
+        {
+            return null;
+        }
+
+        @Override
+        public String encodeURL(String url)
+        {
+            return null;
+        }
+
+        @Override
+        public String encodeUrl(String url)
+        {
+            return null;
+        }
+
+        @Override
+        public void sendError(int sc) throws IOException
+        {
+        }
+
+        @Override
+        public void sendError(int sc, String msg) throws IOException
+        {
+        }
+
+        @Override
+        public void sendRedirect(String location) throws IOException
+        {
+        }
+
+        @Override
+        public void setDateHeader(String name, long date)
+        {
+        }
+
+        @Override
+        public void setHeader(String name, String value)
+        {
+        }
+
+        @Override
+        public void setIntHeader(String name, int value)
+        {
+        }
+
+        @Override
+        public void setStatus(int sc)
+        {
+        }
+
+        @Override
+        public void setStatus(int sc, String sm)
+        {
+        }
+
+        @Override
+        public void flushBuffer() throws IOException
+        {
+        }
+
+        @Override
+        public int getBufferSize()
+        {
+            return 1024;
+        }
+
+        @Override
+        public String getCharacterEncoding()
+        {
+            return null;
+        }
+
+        @Override
+        public String getContentType()
+        {
+            return null;
+        }
+
+        @Override
+        public Locale getLocale()
+        {
+            return null;
+        }
+
+        @Override
+        public ServletOutputStream getOutputStream() throws IOException
+        {
+            return __nullOut;
+        }
+
+        @Override
+        public PrintWriter getWriter() throws IOException
+        {
+            return IO.getNullPrintWriter();
+        }
+
+        @Override
+        public boolean isCommitted()
+        {
+            return true;
+        }
+
+        @Override
+        public void reset()
+        {
+        }
+
+        @Override
+        public void resetBuffer()
+        {
+        }
+
+        @Override
+        public void setBufferSize(int size)
+        {
+        }
+
+        @Override
+        public void setCharacterEncoding(String charset)
+        {
+        }
+
+        @Override
+        public void setContentLength(int len)
+        {
+        }
+        
+        public void setContentLengthLong(long len)
+        {
+           
+        }
+
+        @Override
+        public void setContentType(String type)
+        {
+        }
+
+        @Override
+        public void setLocale(Locale loc)
+        {
+        }
+
+        @Override
+       public Collection<String> getHeaderNames()
+       {
+           return Collections.emptyList();
+       }
+
+       @Override
+       public String getHeader(String arg0)
+       {
+           return null;
+       }
+
+       @Override
+       public Collection<String> getHeaders(String arg0)
+       {
+            return Collections.emptyList();
+       }
+
+       @Override
+       public int getStatus()
+       {
+           return 0;
+       }
+
+
+    };
+
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    private static ServletOutputStream __nullOut = new ServletOutputStream()
+    {
+        @Override
+        public void write(int b) throws IOException
+        {
+        }
+        
+        @Override
+        public void print(String s) throws IOException
+        {
+        }
+        
+        @Override
+        public void println(String s) throws IOException
+        {
+        }
+
+     
+        @Override
+        public void setWriteListener(WriteListener writeListener)
+        {
+            
+        }
+
+        @Override
+        public boolean isReady()
+        {
+            return false;
+        }
+    };
+
+
+}
diff --git a/lib/jetty/org/eclipse/jetty/security/authentication/DigestAuthenticator.java b/lib/jetty/org/eclipse/jetty/security/authentication/DigestAuthenticator.java
new file mode 100644 (file)
index 0000000..13537e6
--- /dev/null
@@ -0,0 +1,421 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.security.authentication;
+
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.security.MessageDigest;
+import java.security.SecureRandom;
+import java.util.BitSet;
+import java.util.Queue;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.ConcurrentMap;
+
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.http.HttpHeader;
+import org.eclipse.jetty.security.SecurityHandler;
+import org.eclipse.jetty.security.ServerAuthException;
+import org.eclipse.jetty.security.UserAuthentication;
+import org.eclipse.jetty.server.Authentication;
+import org.eclipse.jetty.server.Authentication.User;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.UserIdentity;
+import org.eclipse.jetty.util.B64Code;
+import org.eclipse.jetty.util.QuotedStringTokenizer;
+import org.eclipse.jetty.util.TypeUtil;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.security.Constraint;
+import org.eclipse.jetty.util.security.Credential;
+
+/**
+ * @version $Rev: 4793 $ $Date: 2009-03-19 00:00:01 +0100 (Thu, 19 Mar 2009) $
+ *
+ * The nonce max age in ms can be set with the {@link SecurityHandler#setInitParameter(String, String)}
+ * using the name "maxNonceAge".  The nonce max count can be set with {@link SecurityHandler#setInitParameter(String, String)}
+ * using the name "maxNonceCount".  When the age or count is exceeded, the nonce is considered stale.
+ */
+public class DigestAuthenticator extends LoginAuthenticator
+{
+    private static final Logger LOG = Log.getLogger(DigestAuthenticator.class);
+    SecureRandom _random = new SecureRandom();
+    private long _maxNonceAgeMs = 60*1000;
+    private int _maxNC=1024;
+    private ConcurrentMap<String, Nonce> _nonceMap = new ConcurrentHashMap<String, Nonce>();
+    private Queue<Nonce> _nonceQueue = new ConcurrentLinkedQueue<Nonce>();
+    private static class Nonce
+    {
+        final String _nonce;
+        final long _ts;
+        final BitSet _seen; 
+
+        public Nonce(String nonce, long ts, int size)
+        {
+            _nonce=nonce;
+            _ts=ts;
+            _seen = new BitSet(size);
+        }
+
+        public boolean seen(int count)
+        {
+            synchronized (this)
+            {
+                if (count>=_seen.size())
+                    return true;
+                boolean s=_seen.get(count);
+                _seen.set(count);
+                return s;
+            }
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    public DigestAuthenticator()
+    {
+        super();
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.security.authentication.LoginAuthenticator#setConfiguration(org.eclipse.jetty.security.Authenticator.AuthConfiguration)
+     */
+    @Override
+    public void setConfiguration(AuthConfiguration configuration)
+    {
+        super.setConfiguration(configuration);
+
+        String mna=configuration.getInitParameter("maxNonceAge");
+        if (mna!=null)
+        {
+            _maxNonceAgeMs=Long.valueOf(mna);
+        }
+        String mnc=configuration.getInitParameter("maxNonceCount");
+        if (mnc!=null)
+        {
+            _maxNC=Integer.valueOf(mnc);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    public int getMaxNonceCount()
+    {
+        return _maxNC;
+    }
+
+    /* ------------------------------------------------------------ */
+    public void setMaxNonceCount(int maxNC)
+    {
+        _maxNC = maxNC;
+    }
+
+    /* ------------------------------------------------------------ */
+    public long getMaxNonceAge()
+    {
+        return _maxNonceAgeMs;
+    }
+
+    /* ------------------------------------------------------------ */
+    public synchronized void setMaxNonceAge(long maxNonceAgeInMillis)
+    {
+        _maxNonceAgeMs = maxNonceAgeInMillis;
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public String getAuthMethod()
+    {
+        return Constraint.__DIGEST_AUTH;
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public boolean secureResponse(ServletRequest req, ServletResponse res, boolean mandatory, User validatedUser) throws ServerAuthException
+    {
+        return true;
+    }
+    
+
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public Authentication validateRequest(ServletRequest req, ServletResponse res, boolean mandatory) throws ServerAuthException
+    {
+        if (!mandatory)
+            return new DeferredAuthentication(this);
+
+        HttpServletRequest request = (HttpServletRequest)req;
+        HttpServletResponse response = (HttpServletResponse)res;
+        String credentials = request.getHeader(HttpHeader.AUTHORIZATION.asString());
+
+        try
+        {
+            boolean stale = false;
+            if (credentials != null)
+            {
+                if (LOG.isDebugEnabled())
+                    LOG.debug("Credentials: " + credentials);
+                QuotedStringTokenizer tokenizer = new QuotedStringTokenizer(credentials, "=, ", true, false);
+                final Digest digest = new Digest(request.getMethod());
+                String last = null;
+                String name = null;
+
+                while (tokenizer.hasMoreTokens())
+                {
+                    String tok = tokenizer.nextToken();
+                    char c = (tok.length() == 1) ? tok.charAt(0) : '\0';
+
+                    switch (c)
+                    {
+                        case '=':
+                            name = last;
+                            last = tok;
+                            break;
+                        case ',':
+                            name = null;
+                            break;
+                        case ' ':
+                            break;
+
+                        default:
+                            last = tok;
+                            if (name != null)
+                            {
+                                if ("username".equalsIgnoreCase(name))
+                                    digest.username = tok;
+                                else if ("realm".equalsIgnoreCase(name))
+                                    digest.realm = tok;
+                                else if ("nonce".equalsIgnoreCase(name))
+                                    digest.nonce = tok;
+                                else if ("nc".equalsIgnoreCase(name))
+                                    digest.nc = tok;
+                                else if ("cnonce".equalsIgnoreCase(name))
+                                    digest.cnonce = tok;
+                                else if ("qop".equalsIgnoreCase(name))
+                                    digest.qop = tok;
+                                else if ("uri".equalsIgnoreCase(name))
+                                    digest.uri = tok;
+                                else if ("response".equalsIgnoreCase(name))
+                                    digest.response = tok;
+                                name=null;
+                            }
+                    }
+                }
+
+                int n = checkNonce(digest,(Request)request);
+
+                if (n > 0)
+                {
+                    //UserIdentity user = _loginService.login(digest.username,digest);
+                    UserIdentity user = login(digest.username, digest, req);
+                    if (user!=null)
+                    {
+                        return new UserAuthentication(getAuthMethod(),user);
+                    }
+                }
+                else if (n == 0)
+                    stale = true;
+
+            }
+
+            if (!DeferredAuthentication.isDeferred(response))
+            {
+                String domain = request.getContextPath();
+                if (domain == null)
+                    domain = "/";
+                response.setHeader(HttpHeader.WWW_AUTHENTICATE.asString(), "Digest realm=\"" + _loginService.getName()
+                        + "\", domain=\""
+                        + domain
+                        + "\", nonce=\""
+                        + newNonce((Request)request)
+                        + "\", algorithm=MD5, qop=\"auth\","
+                        + " stale=" + stale);
+                response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
+
+                return Authentication.SEND_CONTINUE;
+            }
+
+            return Authentication.UNAUTHENTICATED;
+        }
+        catch (IOException e)
+        {
+            throw new ServerAuthException(e);
+        }
+
+    }
+
+    /* ------------------------------------------------------------ */
+    public String newNonce(Request request)
+    {
+        Nonce nonce;
+
+        do
+        {
+            byte[] nounce = new byte[24];
+            _random.nextBytes(nounce);
+
+            nonce = new Nonce(new String(B64Code.encode(nounce)),request.getTimeStamp(),_maxNC);
+        }
+        while (_nonceMap.putIfAbsent(nonce._nonce,nonce)!=null);
+        _nonceQueue.add(nonce);
+
+        return nonce._nonce;
+    }
+
+    /**
+     * @param nstring nonce to check
+     * @param request
+     * @return -1 for a bad nonce, 0 for a stale none, 1 for a good nonce
+     */
+    /* ------------------------------------------------------------ */
+    private int checkNonce(Digest digest, Request request)
+    {
+        // firstly let's expire old nonces
+        long expired = request.getTimeStamp()-_maxNonceAgeMs;
+        Nonce nonce=_nonceQueue.peek();
+        while (nonce!=null && nonce._ts<expired)
+        {
+            _nonceQueue.remove(nonce);
+            _nonceMap.remove(nonce._nonce);
+            nonce=_nonceQueue.peek();
+        }
+
+        // Now check the requested nonce
+        try
+        {
+            nonce = _nonceMap.get(digest.nonce);
+            if (nonce==null)
+                return 0;
+
+            long count = Long.parseLong(digest.nc,16);
+            if (count>=_maxNC)
+                return 0;
+            
+            if (nonce.seen((int)count))
+                return -1;
+
+            return 1;
+        }
+        catch (Exception e)
+        {
+            LOG.ignore(e);
+        }
+        return -1;
+    }
+
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    private static class Digest extends Credential
+    {
+        private static final long serialVersionUID = -2484639019549527724L;
+        final String method;
+        String username = "";
+        String realm = "";
+        String nonce = "";
+        String nc = "";
+        String cnonce = "";
+        String qop = "";
+        String uri = "";
+        String response = "";
+
+        /* ------------------------------------------------------------ */
+        Digest(String m)
+        {
+            method = m;
+        }
+
+        /* ------------------------------------------------------------ */
+        @Override
+        public boolean check(Object credentials)
+        {
+            if (credentials instanceof char[])
+                credentials=new String((char[])credentials);
+            String password = (credentials instanceof String) ? (String) credentials : credentials.toString();
+
+            try
+            {
+                MessageDigest md = MessageDigest.getInstance("MD5");
+                byte[] ha1;
+                if (credentials instanceof Credential.MD5)
+                {
+                    // Credentials are already a MD5 digest - assume it's in
+                    // form user:realm:password (we have no way to know since
+                    // it's a digest, alright?)
+                    ha1 = ((Credential.MD5) credentials).getDigest();
+                }
+                else
+                {
+                    // calc A1 digest
+                    md.update(username.getBytes(StandardCharsets.ISO_8859_1));
+                    md.update((byte) ':');
+                    md.update(realm.getBytes(StandardCharsets.ISO_8859_1));
+                    md.update((byte) ':');
+                    md.update(password.getBytes(StandardCharsets.ISO_8859_1));
+                    ha1 = md.digest();
+                }
+                // calc A2 digest
+                md.reset();
+                md.update(method.getBytes(StandardCharsets.ISO_8859_1));
+                md.update((byte) ':');
+                md.update(uri.getBytes(StandardCharsets.ISO_8859_1));
+                byte[] ha2 = md.digest();
+
+                // calc digest
+                // request-digest = <"> < KD ( H(A1), unq(nonce-value) ":"
+                // nc-value ":" unq(cnonce-value) ":" unq(qop-value) ":" H(A2) )
+                // <">
+                // request-digest = <"> < KD ( H(A1), unq(nonce-value) ":" H(A2)
+                // ) > <">
+
+                md.update(TypeUtil.toString(ha1, 16).getBytes(StandardCharsets.ISO_8859_1));
+                md.update((byte) ':');
+                md.update(nonce.getBytes(StandardCharsets.ISO_8859_1));
+                md.update((byte) ':');
+                md.update(nc.getBytes(StandardCharsets.ISO_8859_1));
+                md.update((byte) ':');
+                md.update(cnonce.getBytes(StandardCharsets.ISO_8859_1));
+                md.update((byte) ':');
+                md.update(qop.getBytes(StandardCharsets.ISO_8859_1));
+                md.update((byte) ':');
+                md.update(TypeUtil.toString(ha2, 16).getBytes(StandardCharsets.ISO_8859_1));
+                byte[] digest = md.digest();
+
+                // check digest
+                return (TypeUtil.toString(digest, 16).equalsIgnoreCase(response));
+            }
+            catch (Exception e)
+            {
+                LOG.warn(e);
+            }
+
+            return false;
+        }
+
+        @Override
+        public String toString()
+        {
+            return username + "," + response;
+        }
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/security/authentication/FormAuthenticator.java b/lib/jetty/org/eclipse/jetty/security/authentication/FormAuthenticator.java
new file mode 100644 (file)
index 0000000..dcfea41
--- /dev/null
@@ -0,0 +1,564 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.security.authentication;
+
+import java.io.IOException;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.Locale;
+import javax.servlet.RequestDispatcher;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletRequestWrapper;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpServletResponseWrapper;
+import javax.servlet.http.HttpSession;
+
+import org.eclipse.jetty.http.HttpHeader;
+import org.eclipse.jetty.http.HttpHeaderValue;
+import org.eclipse.jetty.http.HttpMethod;
+import org.eclipse.jetty.http.HttpVersion;
+import org.eclipse.jetty.http.MimeTypes;
+import org.eclipse.jetty.security.ServerAuthException;
+import org.eclipse.jetty.security.UserAuthentication;
+import org.eclipse.jetty.server.Authentication;
+import org.eclipse.jetty.server.Authentication.User;
+import org.eclipse.jetty.server.HttpChannel;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.Response;
+import org.eclipse.jetty.server.UserIdentity;
+import org.eclipse.jetty.util.MultiMap;
+import org.eclipse.jetty.util.StringUtil;
+import org.eclipse.jetty.util.URIUtil;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.security.Constraint;
+
+/**
+ * FORM Authenticator.
+ *
+ * <p>This authenticator implements form authentication will use dispatchers to
+ * the login page if the {@link #__FORM_DISPATCH} init parameter is set to true.
+ * Otherwise it will redirect.</p>
+ *
+ * <p>The form authenticator redirects unauthenticated requests to a log page
+ * which should use a form to gather username/password from the user and send them
+ * to the /j_security_check URI within the context.  FormAuthentication uses
+ * {@link SessionAuthentication} to wrap Authentication results so that they
+ * are  associated with the session.</p>
+ *
+ *
+ */
+public class FormAuthenticator extends LoginAuthenticator
+{
+    private static final Logger LOG = Log.getLogger(FormAuthenticator.class);
+
+    public final static String __FORM_LOGIN_PAGE="org.eclipse.jetty.security.form_login_page";
+    public final static String __FORM_ERROR_PAGE="org.eclipse.jetty.security.form_error_page";
+    public final static String __FORM_DISPATCH="org.eclipse.jetty.security.dispatch";
+    public final static String __J_URI = "org.eclipse.jetty.security.form_URI";
+    public final static String __J_POST = "org.eclipse.jetty.security.form_POST";
+    public final static String __J_METHOD = "org.eclipse.jetty.security.form_METHOD";
+    public final static String __J_SECURITY_CHECK = "/j_security_check";
+    public final static String __J_USERNAME = "j_username";
+    public final static String __J_PASSWORD = "j_password";
+
+    private String _formErrorPage;
+    private String _formErrorPath;
+    private String _formLoginPage;
+    private String _formLoginPath;
+    private boolean _dispatch;
+    private boolean _alwaysSaveUri;
+
+    public FormAuthenticator()
+    {
+    }
+
+    /* ------------------------------------------------------------ */
+    public FormAuthenticator(String login,String error,boolean dispatch)
+    {
+        this();
+        if (login!=null)
+            setLoginPage(login);
+        if (error!=null)
+            setErrorPage(error);
+        _dispatch=dispatch;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * If true, uris that cause a redirect to a login page will always
+     * be remembered. If false, only the first uri that leads to a login
+     * page redirect is remembered.
+     * See https://bugs.eclipse.org/bugs/show_bug.cgi?id=379909
+     * @param alwaysSave
+     */
+    public void setAlwaysSaveUri (boolean alwaysSave)
+    {
+        _alwaysSaveUri = alwaysSave;
+    }
+
+
+    /* ------------------------------------------------------------ */
+    public boolean getAlwaysSaveUri ()
+    {
+        return _alwaysSaveUri;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.security.authentication.LoginAuthenticator#setConfiguration(org.eclipse.jetty.security.Authenticator.AuthConfiguration)
+     */
+    @Override
+    public void setConfiguration(AuthConfiguration configuration)
+    {
+        super.setConfiguration(configuration);
+        String login=configuration.getInitParameter(FormAuthenticator.__FORM_LOGIN_PAGE);
+        if (login!=null)
+            setLoginPage(login);
+        String error=configuration.getInitParameter(FormAuthenticator.__FORM_ERROR_PAGE);
+        if (error!=null)
+            setErrorPage(error);
+        String dispatch=configuration.getInitParameter(FormAuthenticator.__FORM_DISPATCH);
+        _dispatch = dispatch==null?_dispatch:Boolean.valueOf(dispatch);
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public String getAuthMethod()
+    {
+        return Constraint.__FORM_AUTH;
+    }
+
+    /* ------------------------------------------------------------ */
+    private void setLoginPage(String path)
+    {
+        if (!path.startsWith("/"))
+        {
+            LOG.warn("form-login-page must start with /");
+            path = "/" + path;
+        }
+        _formLoginPage = path;
+        _formLoginPath = path;
+        if (_formLoginPath.indexOf('?') > 0)
+            _formLoginPath = _formLoginPath.substring(0, _formLoginPath.indexOf('?'));
+    }
+
+    /* ------------------------------------------------------------ */
+    private void setErrorPage(String path)
+    {
+        if (path == null || path.trim().length() == 0)
+        {
+            _formErrorPath = null;
+            _formErrorPage = null;
+        }
+        else
+        {
+            if (!path.startsWith("/"))
+            {
+                LOG.warn("form-error-page must start with /");
+                path = "/" + path;
+            }
+            _formErrorPage = path;
+            _formErrorPath = path;
+
+            if (_formErrorPath.indexOf('?') > 0)
+                _formErrorPath = _formErrorPath.substring(0, _formErrorPath.indexOf('?'));
+        }
+    }
+    
+    
+    /* ------------------------------------------------------------ */
+    @Override
+    public UserIdentity login(String username, Object password, ServletRequest request)
+    {
+        
+        UserIdentity user = super.login(username,password,request);
+        if (user!=null)
+        {
+            HttpSession session = ((HttpServletRequest)request).getSession(true);
+            Authentication cached=new SessionAuthentication(getAuthMethod(),user,password);
+            session.setAttribute(SessionAuthentication.__J_AUTHENTICATED, cached);
+        }
+        return user;
+    }
+    
+    
+    /* ------------------------------------------------------------ */
+    @Override
+    public void prepareRequest(ServletRequest request)
+    {
+        //if this is a request resulting from a redirect after auth is complete
+        //(ie its from a redirect to the original request uri) then due to 
+        //browser handling of 302 redirects, the method may not be the same as
+        //that of the original request. Replace the method and original post
+        //params (if it was a post).
+        //
+        //See Servlet Spec 3.1 sec 13.6.3
+        HttpServletRequest httpRequest = (HttpServletRequest)request;
+        HttpSession session = httpRequest.getSession(false);
+        if (session == null || session.getAttribute(SessionAuthentication.__J_AUTHENTICATED) == null)
+            return; //not authenticated yet
+        
+        String juri = (String)session.getAttribute(__J_URI);
+        if (juri == null || juri.length() == 0)
+            return; //no original uri saved
+        
+        String method = (String)session.getAttribute(__J_METHOD);
+        if (method == null || method.length() == 0)
+            return; //didn't save original request method
+       
+        StringBuffer buf = httpRequest.getRequestURL();
+        if (httpRequest.getQueryString() != null)
+            buf.append("?").append(httpRequest.getQueryString());
+        
+        if (!juri.equals(buf.toString()))
+            return; //this request is not for the same url as the original
+        
+        //restore the original request's method on this request
+        if (LOG.isDebugEnabled()) LOG.debug("Restoring original method {} for {} with method {}", method, juri,httpRequest.getMethod());
+        Request base_request = HttpChannel.getCurrentHttpChannel().getRequest();
+        HttpMethod m = HttpMethod.fromString(method);
+        base_request.setMethod(m,m.asString());
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public Authentication validateRequest(ServletRequest req, ServletResponse res, boolean mandatory) throws ServerAuthException
+    {
+        HttpServletRequest request = (HttpServletRequest)req;
+        HttpServletResponse response = (HttpServletResponse)res;
+        String uri = request.getRequestURI();
+        if (uri==null)
+            uri=URIUtil.SLASH;
+
+        mandatory|=isJSecurityCheck(uri);
+        if (!mandatory)
+            return new DeferredAuthentication(this);
+
+        if (isLoginOrErrorPage(URIUtil.addPaths(request.getServletPath(),request.getPathInfo())) &&!DeferredAuthentication.isDeferred(response))
+            return new DeferredAuthentication(this);
+
+        HttpSession session = request.getSession(true);
+
+        try
+        {
+            // Handle a request for authentication.
+            if (isJSecurityCheck(uri))
+            {
+                final String username = request.getParameter(__J_USERNAME);
+                final String password = request.getParameter(__J_PASSWORD);
+
+                UserIdentity user = login(username, password, request);
+                LOG.debug("jsecuritycheck {} {}",username,user);
+                session = request.getSession(true);
+                if (user!=null)
+                {                    
+                    // Redirect to original request
+                    String nuri;
+                    FormAuthentication form_auth;
+                    synchronized(session)
+                    {
+                        nuri = (String) session.getAttribute(__J_URI);
+
+                        if (nuri == null || nuri.length() == 0)
+                        {
+                            nuri = request.getContextPath();
+                            if (nuri.length() == 0)
+                                nuri = URIUtil.SLASH;
+                        }
+                        form_auth = new FormAuthentication(getAuthMethod(),user);
+                    }
+                    LOG.debug("authenticated {}->{}",form_auth,nuri);
+
+                    response.setContentLength(0);
+                    Response base_response = HttpChannel.getCurrentHttpChannel().getResponse();
+                    Request base_request = HttpChannel.getCurrentHttpChannel().getRequest();
+                    int redirectCode = (base_request.getHttpVersion().getVersion() < HttpVersion.HTTP_1_1.getVersion() ? HttpServletResponse.SC_MOVED_TEMPORARILY : HttpServletResponse.SC_SEE_OTHER);
+                    base_response.sendRedirect(redirectCode, response.encodeRedirectURL(nuri));
+                    return form_auth;
+                }
+
+                // not authenticated
+                if (LOG.isDebugEnabled())
+                    LOG.debug("Form authentication FAILED for " + StringUtil.printable(username));
+                if (_formErrorPage == null)
+                {
+                    LOG.debug("auth failed {}->403",username);
+                    if (response != null)
+                        response.sendError(HttpServletResponse.SC_FORBIDDEN);
+                }
+                else if (_dispatch)
+                {
+                    LOG.debug("auth failed {}=={}",username,_formErrorPage);
+                    RequestDispatcher dispatcher = request.getRequestDispatcher(_formErrorPage);
+                    response.setHeader(HttpHeader.CACHE_CONTROL.asString(),HttpHeaderValue.NO_CACHE.asString());
+                    response.setDateHeader(HttpHeader.EXPIRES.asString(),1);
+                    dispatcher.forward(new FormRequest(request), new FormResponse(response));
+                }
+                else
+                {
+                    LOG.debug("auth failed {}->{}",username,_formErrorPage);
+                    Response base_response = HttpChannel.getCurrentHttpChannel().getResponse();
+                    Request base_request = HttpChannel.getCurrentHttpChannel().getRequest();
+                    int redirectCode = (base_request.getHttpVersion().getVersion() < HttpVersion.HTTP_1_1.getVersion() ? HttpServletResponse.SC_MOVED_TEMPORARILY : HttpServletResponse.SC_SEE_OTHER);
+                    base_response.sendRedirect(redirectCode, response.encodeRedirectURL(URIUtil.addPaths(request.getContextPath(),_formErrorPage)));
+                }
+
+                return Authentication.SEND_FAILURE;
+            }
+
+            // Look for cached authentication
+            Authentication authentication = (Authentication) session.getAttribute(SessionAuthentication.__J_AUTHENTICATED);
+            if (authentication != null)
+            {
+                // Has authentication been revoked?
+                if (authentication instanceof Authentication.User &&
+                    _loginService!=null &&
+                    !_loginService.validate(((Authentication.User)authentication).getUserIdentity()))
+                {
+                    LOG.debug("auth revoked {}",authentication);
+                    session.removeAttribute(SessionAuthentication.__J_AUTHENTICATED);
+                }
+                else
+                {
+                    synchronized (session)
+                    {
+                        String j_uri=(String)session.getAttribute(__J_URI);
+                        if (j_uri!=null)
+                        {
+                            //check if the request is for the same url as the original and restore
+                            //params if it was a post
+                            LOG.debug("auth retry {}->{}",authentication,j_uri);
+                            StringBuffer buf = request.getRequestURL();
+                            if (request.getQueryString() != null)
+                                buf.append("?").append(request.getQueryString());
+
+                            if (j_uri.equals(buf.toString()))
+                            {
+                                MultiMap<String> j_post = (MultiMap<String>)session.getAttribute(__J_POST);
+                                if (j_post!=null)
+                                {
+                                    LOG.debug("auth rePOST {}->{}",authentication,j_uri);
+                                    Request base_request = HttpChannel.getCurrentHttpChannel().getRequest();
+                                    base_request.setContentParameters(j_post);
+                                }
+                                session.removeAttribute(__J_URI);
+                                session.removeAttribute(__J_METHOD);
+                                session.removeAttribute(__J_POST);
+                            }
+                        }
+                    }
+                    LOG.debug("auth {}",authentication);
+                    return authentication;
+                }
+            }
+
+            // if we can't send challenge
+            if (DeferredAuthentication.isDeferred(response))
+            {
+                LOG.debug("auth deferred {}",session.getId());
+                return Authentication.UNAUTHENTICATED;
+            }
+
+            // remember the current URI
+            synchronized (session)
+            {
+                // But only if it is not set already, or we save every uri that leads to a login form redirect
+                if (session.getAttribute(__J_URI)==null || _alwaysSaveUri)
+                {
+                    StringBuffer buf = request.getRequestURL();
+                    if (request.getQueryString() != null)
+                        buf.append("?").append(request.getQueryString());
+                    session.setAttribute(__J_URI, buf.toString());
+                    session.setAttribute(__J_METHOD, request.getMethod());
+
+                    if (MimeTypes.Type.FORM_ENCODED.is(req.getContentType()) && HttpMethod.POST.is(request.getMethod()))
+                    {
+                        Request base_request = (req instanceof Request)?(Request)req:HttpChannel.getCurrentHttpChannel().getRequest();
+                        MultiMap<String> formParameters = new MultiMap<>();
+                        base_request.extractFormParameters(formParameters);
+                        session.setAttribute(__J_POST, formParameters);
+                    }
+                }
+            }
+
+            // send the the challenge
+            if (_dispatch)
+            {
+                LOG.debug("challenge {}=={}",session.getId(),_formLoginPage);
+                RequestDispatcher dispatcher = request.getRequestDispatcher(_formLoginPage);
+                response.setHeader(HttpHeader.CACHE_CONTROL.asString(),HttpHeaderValue.NO_CACHE.asString());
+                response.setDateHeader(HttpHeader.EXPIRES.asString(),1);
+                dispatcher.forward(new FormRequest(request), new FormResponse(response));
+            }
+            else
+            {
+                LOG.debug("challenge {}->{}",session.getId(),_formLoginPage);
+                Response base_response = HttpChannel.getCurrentHttpChannel().getResponse();
+                Request base_request = HttpChannel.getCurrentHttpChannel().getRequest();
+                int redirectCode = (base_request.getHttpVersion().getVersion() < HttpVersion.HTTP_1_1.getVersion() ? HttpServletResponse.SC_MOVED_TEMPORARILY : HttpServletResponse.SC_SEE_OTHER);
+                base_response.sendRedirect(redirectCode, response.encodeRedirectURL(URIUtil.addPaths(request.getContextPath(),_formLoginPage)));
+            }
+            return Authentication.SEND_CONTINUE;
+        }
+        catch (IOException | ServletException e)
+        {
+            throw new ServerAuthException(e);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    public boolean isJSecurityCheck(String uri)
+    {
+        int jsc = uri.indexOf(__J_SECURITY_CHECK);
+
+        if (jsc<0)
+            return false;
+        int e=jsc+__J_SECURITY_CHECK.length();
+        if (e==uri.length())
+            return true;
+        char c = uri.charAt(e);
+        return c==';'||c=='#'||c=='/'||c=='?';
+    }
+
+    /* ------------------------------------------------------------ */
+    public boolean isLoginOrErrorPage(String pathInContext)
+    {
+        return pathInContext != null && (pathInContext.equals(_formErrorPath) || pathInContext.equals(_formLoginPath));
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public boolean secureResponse(ServletRequest req, ServletResponse res, boolean mandatory, User validatedUser) throws ServerAuthException
+    {
+        return true;
+    }
+
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    protected static class FormRequest extends HttpServletRequestWrapper
+    {
+        public FormRequest(HttpServletRequest request)
+        {
+            super(request);
+        }
+
+        @Override
+        public long getDateHeader(String name)
+        {
+            if (name.toLowerCase(Locale.ENGLISH).startsWith("if-"))
+                return -1;
+            return super.getDateHeader(name);
+        }
+
+        @Override
+        public String getHeader(String name)
+        {
+            if (name.toLowerCase(Locale.ENGLISH).startsWith("if-"))
+                return null;
+            return super.getHeader(name);
+        }
+
+        @Override
+        public Enumeration<String> getHeaderNames()
+        {
+            return Collections.enumeration(Collections.list(super.getHeaderNames()));
+        }
+
+        @Override
+        public Enumeration<String> getHeaders(String name)
+        {
+            if (name.toLowerCase(Locale.ENGLISH).startsWith("if-"))
+                return Collections.<String>enumeration(Collections.<String>emptyList());
+            return super.getHeaders(name);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    protected static class FormResponse extends HttpServletResponseWrapper
+    {
+        public FormResponse(HttpServletResponse response)
+        {
+            super(response);
+        }
+
+        @Override
+        public void addDateHeader(String name, long date)
+        {
+            if (notIgnored(name))
+                super.addDateHeader(name,date);
+        }
+
+        @Override
+        public void addHeader(String name, String value)
+        {
+            if (notIgnored(name))
+                super.addHeader(name,value);
+        }
+
+        @Override
+        public void setDateHeader(String name, long date)
+        {
+            if (notIgnored(name))
+                super.setDateHeader(name,date);
+        }
+
+        @Override
+        public void setHeader(String name, String value)
+        {
+            if (notIgnored(name))
+                super.setHeader(name,value);
+        }
+
+        private boolean notIgnored(String name)
+        {
+            if (HttpHeader.CACHE_CONTROL.is(name) ||
+                HttpHeader.PRAGMA.is(name) ||
+                HttpHeader.ETAG.is(name) ||
+                HttpHeader.EXPIRES.is(name) ||
+                HttpHeader.LAST_MODIFIED.is(name) ||
+                HttpHeader.AGE.is(name))
+                return false;
+            return true;
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /** This Authentication represents a just completed Form authentication.
+     * Subsequent requests from the same user are authenticated by the presents
+     * of a {@link SessionAuthentication} instance in their session.
+     */
+    public static class FormAuthentication extends UserAuthentication implements Authentication.ResponseSent
+    {
+        public FormAuthentication(String method, UserIdentity userIdentity)
+        {
+            super(method,userIdentity);
+        }
+
+        @Override
+        public String toString()
+        {
+            return "Form"+super.toString();
+        }
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/security/authentication/LoginAuthenticator.java b/lib/jetty/org/eclipse/jetty/security/authentication/LoginAuthenticator.java
new file mode 100644 (file)
index 0000000..9a1940d
--- /dev/null
@@ -0,0 +1,133 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.security.authentication;
+
+import javax.servlet.ServletRequest;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpSession;
+
+import org.eclipse.jetty.security.Authenticator;
+import org.eclipse.jetty.security.IdentityService;
+import org.eclipse.jetty.security.LoginService;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.Response;
+import org.eclipse.jetty.server.UserIdentity;
+import org.eclipse.jetty.server.session.AbstractSession;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+public abstract class LoginAuthenticator implements Authenticator
+{
+    private static final Logger LOG = Log.getLogger(LoginAuthenticator.class);
+
+    protected LoginService _loginService;
+    protected IdentityService _identityService;
+    private boolean _renewSession;
+    
+    
+    /* ------------------------------------------------------------ */
+    protected LoginAuthenticator()
+    {
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public void prepareRequest(ServletRequest request)
+    {
+        //empty implementation as the default
+    }
+
+
+    /* ------------------------------------------------------------ */
+    public UserIdentity login(String username, Object password, ServletRequest request)
+    {
+        UserIdentity user = _loginService.login(username,password);
+        if (user!=null)
+        {
+            renewSession((HttpServletRequest)request, (request instanceof Request? ((Request)request).getResponse() : null));
+            return user;
+        }
+        return null;
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public void setConfiguration(AuthConfiguration configuration)
+    {
+        _loginService=configuration.getLoginService();
+        if (_loginService==null)
+            throw new IllegalStateException("No LoginService for "+this+" in "+configuration);
+        _identityService=configuration.getIdentityService();
+        if (_identityService==null)
+            throw new IllegalStateException("No IdentityService for "+this+" in "+configuration);
+        _renewSession=configuration.isSessionRenewedOnAuthentication();
+    }
+    
+    
+    /* ------------------------------------------------------------ */
+    public LoginService getLoginService()
+    {
+        return _loginService;
+    }
+    
+    
+    /* ------------------------------------------------------------ */
+    /** Change the session id.
+     * The session is changed to a new instance with a new ID if and only if:<ul>
+     * <li>A session exists.
+     * <li>The {@link AuthConfiguration#isSessionRenewedOnAuthentication()} returns true.
+     * <li>The session ID has been given to unauthenticated responses
+     * </ul>
+     * @param request
+     * @param response
+     * @return The new session.
+     */
+    protected HttpSession renewSession(HttpServletRequest request, HttpServletResponse response)
+    {
+        HttpSession httpSession = request.getSession(false);
+
+        if (_renewSession && httpSession!=null)
+        {
+            synchronized (httpSession)
+            {
+                //if we should renew sessions, and there is an existing session that may have been seen by non-authenticated users
+                //(indicated by SESSION_SECURED not being set on the session) then we should change id
+                if (httpSession.getAttribute(AbstractSession.SESSION_KNOWN_ONLY_TO_AUTHENTICATED)!=Boolean.TRUE)
+                {
+                    if (httpSession instanceof AbstractSession)
+                    {
+                        AbstractSession abstractSession = (AbstractSession)httpSession;
+                        String oldId = abstractSession.getId();
+                        abstractSession.renewId(request);
+                        abstractSession.setAttribute(AbstractSession.SESSION_KNOWN_ONLY_TO_AUTHENTICATED, Boolean.TRUE);
+                        if (abstractSession.isIdChanged() && response != null && (response instanceof Response))
+                            ((Response)response).addCookie(abstractSession.getSessionManager().getSessionCookie(abstractSession, request.getContextPath(), request.isSecure()));
+                        LOG.debug("renew {}->{}",oldId,abstractSession.getId());
+                    }
+                    else
+                        LOG.warn("Unable to renew session "+httpSession);
+                    
+                    return httpSession;
+                }
+            }
+        }
+        return httpSession;
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/security/authentication/LoginCallback.java b/lib/jetty/org/eclipse/jetty/security/authentication/LoginCallback.java
new file mode 100644 (file)
index 0000000..e123b22
--- /dev/null
@@ -0,0 +1,55 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.security.authentication;
+
+import java.security.Principal;
+
+import javax.security.auth.Subject;
+
+
+/**
+ * This is similar to the jaspi PasswordValidationCallback but includes user
+ * principal and group info as well.
+ *
+ * @version $Rev: 4792 $ $Date: 2009-03-18 22:55:52 +0100 (Wed, 18 Mar 2009) $
+ */
+public interface LoginCallback
+{
+    public Subject getSubject();
+
+    public String getUserName();
+
+    public Object getCredential();
+
+    public boolean isSuccess();
+
+    public void setSuccess(boolean success);
+
+    public Principal getUserPrincipal();
+
+    public void setUserPrincipal(Principal userPrincipal);
+
+    public String[] getRoles();
+
+    public void setRoles(String[] roles);
+
+    public void clearPassword();
+
+
+}
diff --git a/lib/jetty/org/eclipse/jetty/security/authentication/LoginCallbackImpl.java b/lib/jetty/org/eclipse/jetty/security/authentication/LoginCallbackImpl.java
new file mode 100644 (file)
index 0000000..5939caf
--- /dev/null
@@ -0,0 +1,109 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.security.authentication;
+
+import java.security.Principal;
+
+import javax.security.auth.Subject;
+
+import org.eclipse.jetty.security.IdentityService;
+
+/**
+ * This is similar to the jaspi PasswordValidationCallback but includes user
+ * principal and group info as well.
+ *
+ * @version $Rev: 4793 $ $Date: 2009-03-19 00:00:01 +0100 (Thu, 19 Mar 2009) $
+ */
+public class LoginCallbackImpl implements LoginCallback
+{
+    // initial data
+    private final Subject subject;
+
+    private final String userName;
+
+    private Object credential;
+
+    private boolean success;
+
+    private Principal userPrincipal;
+
+    private String[] roles = IdentityService.NO_ROLES;
+
+    //TODO could use Credential instance instead of Object if Basic/Form create a Password object
+    public LoginCallbackImpl (Subject subject, String userName, Object credential)
+    {
+        this.subject = subject;
+        this.userName = userName;
+        this.credential = credential;
+    }
+
+    public Subject getSubject()
+    {
+        return subject;
+    }
+
+    public String getUserName()
+    {
+        return userName;
+    }
+
+    public Object getCredential()
+    {
+        return credential;
+    }
+
+    public boolean isSuccess()
+    {
+        return success;
+    }
+
+    public void setSuccess(boolean success)
+    {
+        this.success = success;
+    }
+
+    public Principal getUserPrincipal()
+    {
+        return userPrincipal;
+    }
+
+    public void setUserPrincipal(Principal userPrincipal)
+    {
+        this.userPrincipal = userPrincipal;
+    }
+
+    public String[] getRoles()
+    {
+        return roles;
+    }
+
+    public void setRoles(String[] groups)
+    {
+        this.roles = groups;
+    }
+
+    public void clearPassword()
+    {
+        if (credential != null)
+        {
+            credential = null;
+        }
+    }
+
+}
diff --git a/lib/jetty/org/eclipse/jetty/security/authentication/SessionAuthentication.java b/lib/jetty/org/eclipse/jetty/security/authentication/SessionAuthentication.java
new file mode 100644 (file)
index 0000000..e3be584
--- /dev/null
@@ -0,0 +1,131 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+
+package org.eclipse.jetty.security.authentication;
+
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.Serializable;
+
+import javax.servlet.http.HttpSession;
+import javax.servlet.http.HttpSessionActivationListener;
+import javax.servlet.http.HttpSessionBindingEvent;
+import javax.servlet.http.HttpSessionBindingListener;
+import javax.servlet.http.HttpSessionEvent;
+
+import org.eclipse.jetty.security.AbstractUserAuthentication;
+import org.eclipse.jetty.security.LoginService;
+import org.eclipse.jetty.security.SecurityHandler;
+import org.eclipse.jetty.server.UserIdentity;
+import org.eclipse.jetty.server.session.AbstractSession;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+public class SessionAuthentication extends AbstractUserAuthentication implements Serializable, HttpSessionActivationListener, HttpSessionBindingListener
+{
+    private static final Logger LOG = Log.getLogger(SessionAuthentication.class);
+
+    private static final long serialVersionUID = -4643200685888258706L;
+
+
+
+    public final static String __J_AUTHENTICATED="org.eclipse.jetty.security.UserIdentity";
+
+    private final String _name;
+    private final Object _credentials;
+    private transient HttpSession _session;
+
+    public SessionAuthentication(String method, UserIdentity userIdentity, Object credentials)
+    {
+        super(method, userIdentity);
+        _name=userIdentity.getUserPrincipal().getName();
+        _credentials=credentials;
+    }
+
+
+    private void readObject(ObjectInputStream stream)
+        throws IOException, ClassNotFoundException
+    {
+        stream.defaultReadObject();
+
+        SecurityHandler security=SecurityHandler.getCurrentSecurityHandler();
+        if (security==null)
+            throw new IllegalStateException("!SecurityHandler");
+        LoginService login_service=security.getLoginService();
+        if (login_service==null)
+            throw new IllegalStateException("!LoginService");
+
+        _userIdentity=login_service.login(_name,_credentials);
+        LOG.debug("Deserialized and relogged in {}",this);
+    }
+
+    public void logout()
+    {
+        if (_session!=null && _session.getAttribute(__J_AUTHENTICATED)!=null)
+            _session.removeAttribute(__J_AUTHENTICATED);
+
+        doLogout();
+    }
+
+    private void doLogout()
+    {
+        SecurityHandler security=SecurityHandler.getCurrentSecurityHandler();
+        if (security!=null)
+            security.logout(this);
+        if (_session!=null)
+            _session.removeAttribute(AbstractSession.SESSION_KNOWN_ONLY_TO_AUTHENTICATED);
+    }
+
+    @Override
+    public String toString()
+    {
+        return String.format("%s@%x{%s,%s}",this.getClass().getSimpleName(),hashCode(),_session==null?"-":_session.getId(),_userIdentity);
+    }
+
+    @Override
+    public void sessionWillPassivate(HttpSessionEvent se)
+    {
+       
+    }
+
+    @Override
+    public void sessionDidActivate(HttpSessionEvent se)
+    {
+        if (_session==null)
+        {
+            _session=se.getSession();
+        }
+    }
+
+    @Override
+    public void valueBound(HttpSessionBindingEvent event)
+    {
+        if (_session==null)
+        {
+            _session=event.getSession();
+        }
+    }
+
+    @Override
+    public void valueUnbound(HttpSessionBindingEvent event)
+    {
+        doLogout();
+    }
+
+}
diff --git a/lib/jetty/org/eclipse/jetty/security/authentication/SpnegoAuthenticator.java b/lib/jetty/org/eclipse/jetty/security/authentication/SpnegoAuthenticator.java
new file mode 100644 (file)
index 0000000..8469c0a
--- /dev/null
@@ -0,0 +1,116 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.security.authentication;
+
+import java.io.IOException;
+
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.http.HttpHeader;
+import org.eclipse.jetty.security.ServerAuthException;
+import org.eclipse.jetty.security.UserAuthentication;
+import org.eclipse.jetty.server.Authentication;
+import org.eclipse.jetty.server.Authentication.User;
+import org.eclipse.jetty.server.UserIdentity;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.security.Constraint;
+
+public class SpnegoAuthenticator extends LoginAuthenticator
+{
+    private static final Logger LOG = Log.getLogger(SpnegoAuthenticator.class);
+    private String _authMethod = Constraint.__SPNEGO_AUTH;
+
+    public SpnegoAuthenticator()
+    {
+    }
+
+    /**
+     * Allow for a custom authMethod value to be set for instances where SPENGO may not be appropriate
+     * @param authMethod
+     */
+    public SpnegoAuthenticator( String authMethod )
+    {
+       _authMethod = authMethod;
+    }
+
+    @Override
+    public String getAuthMethod()
+    {
+        return _authMethod;
+    }
+
+    @Override
+    public Authentication validateRequest(ServletRequest request, ServletResponse response, boolean mandatory) throws ServerAuthException
+    {
+        HttpServletRequest req = (HttpServletRequest)request;
+        HttpServletResponse res = (HttpServletResponse)response;
+
+        String header = req.getHeader(HttpHeader.AUTHORIZATION.asString());
+
+        if (!mandatory)
+        {
+            return new DeferredAuthentication(this);
+        }
+
+        // check to see if we have authorization headers required to continue
+        if ( header == null )
+        {
+            try
+            {
+                if (DeferredAuthentication.isDeferred(res))
+                {
+                     return Authentication.UNAUTHENTICATED;
+                }
+
+                LOG.debug("SpengoAuthenticator: sending challenge");
+                res.setHeader(HttpHeader.WWW_AUTHENTICATE.asString(), HttpHeader.NEGOTIATE.asString());
+                res.sendError(HttpServletResponse.SC_UNAUTHORIZED);
+                return Authentication.SEND_CONTINUE;
+            }
+            catch (IOException ioe)
+            {
+                throw new ServerAuthException(ioe);
+            }
+        }
+        else if (header != null && header.startsWith(HttpHeader.NEGOTIATE.asString()))
+        {
+            String spnegoToken = header.substring(10);
+
+            UserIdentity user = login(null,spnegoToken, request);
+
+            if ( user != null )
+            {
+                return new UserAuthentication(getAuthMethod(),user);
+            }
+        }
+
+        return Authentication.UNAUTHENTICATED;
+    }
+
+    @Override
+    public boolean secureResponse(ServletRequest request, ServletResponse response, boolean mandatory, User validatedUser) throws ServerAuthException
+    {
+        return true;
+    }
+
+}
diff --git a/lib/jetty/org/eclipse/jetty/security/authentication/package-info.java b/lib/jetty/org/eclipse/jetty/security/authentication/package-info.java
new file mode 100644 (file)
index 0000000..0e0c617
--- /dev/null
@@ -0,0 +1,23 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+/**
+ * Jetty Security : Authenticators and Callbacks
+ */
+package org.eclipse.jetty.security.authentication;
+
diff --git a/lib/jetty/org/eclipse/jetty/security/package-info.java b/lib/jetty/org/eclipse/jetty/security/package-info.java
new file mode 100644 (file)
index 0000000..edb5ea4
--- /dev/null
@@ -0,0 +1,23 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+/**
+ * Jetty Security : Modular Support for Security in Jetty
+ */
+package org.eclipse.jetty.security;
+
diff --git a/lib/jetty/org/eclipse/jetty/server/AbstractConnectionFactory.java b/lib/jetty/org/eclipse/jetty/server/AbstractConnectionFactory.java
new file mode 100644 (file)
index 0000000..96fb3a2
--- /dev/null
@@ -0,0 +1,92 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server;
+
+import org.eclipse.jetty.io.AbstractConnection;
+import org.eclipse.jetty.io.Connection;
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.util.ArrayUtil;
+import org.eclipse.jetty.util.component.ContainerLifeCycle;
+import org.eclipse.jetty.util.ssl.SslContextFactory;
+
+public abstract class AbstractConnectionFactory extends ContainerLifeCycle implements ConnectionFactory
+{
+    private final String _protocol;
+    private int _inputbufferSize = 8192;
+
+    protected AbstractConnectionFactory(String protocol)
+    {
+        _protocol=protocol;
+    }
+
+    @Override
+    public String getProtocol()
+    {
+        return _protocol;
+    }
+
+    public int getInputBufferSize()
+    {
+        return _inputbufferSize;
+    }
+
+    public void setInputBufferSize(int size)
+    {
+        _inputbufferSize=size;
+    }
+
+    protected AbstractConnection configure(AbstractConnection connection, Connector connector, EndPoint endPoint)
+    {
+        connection.setInputBufferSize(getInputBufferSize());
+
+        if (connector instanceof ContainerLifeCycle)
+        {
+            ContainerLifeCycle aggregate = (ContainerLifeCycle)connector;
+            for (Connection.Listener listener : aggregate.getBeans(Connection.Listener.class))
+                connection.addListener(listener);
+        }
+        return connection;
+    }
+
+    @Override
+    public String toString()
+    {
+        return String.format("%s@%x{%s}",this.getClass().getSimpleName(),hashCode(),getProtocol());
+    }
+
+    public static ConnectionFactory[] getFactories(SslContextFactory sslContextFactory, ConnectionFactory... factories)
+    {
+        factories=ArrayUtil.removeNulls(factories);
+
+        if (sslContextFactory==null)
+            return factories;
+
+        for (ConnectionFactory factory : factories)
+        {
+            if (factory instanceof HttpConfiguration.ConnectionFactory)
+            {
+                HttpConfiguration config = ((HttpConfiguration.ConnectionFactory)factory).getHttpConfiguration();
+                if (config.getCustomizer(SecureRequestCustomizer.class)==null)
+                    config.addCustomizer(new SecureRequestCustomizer());
+            }
+        }
+        return ArrayUtil.prependToArray(new SslConnectionFactory(sslContextFactory,factories[0].getProtocol()),factories,ConnectionFactory.class);
+
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/server/AbstractConnector.java b/lib/jetty/org/eclipse/jetty/server/AbstractConnector.java
new file mode 100644 (file)
index 0000000..7d3402f
--- /dev/null
@@ -0,0 +1,564 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server;
+
+import java.io.IOException;
+import java.net.Socket;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+
+import org.eclipse.jetty.io.ArrayByteBufferPool;
+import org.eclipse.jetty.io.ByteBufferPool;
+import org.eclipse.jetty.io.Connection;
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.io.ssl.SslConnection;
+import org.eclipse.jetty.util.FutureCallback;
+import org.eclipse.jetty.util.annotation.ManagedAttribute;
+import org.eclipse.jetty.util.annotation.ManagedObject;
+import org.eclipse.jetty.util.component.ContainerLifeCycle;
+import org.eclipse.jetty.util.component.Dumpable;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.ssl.SslContextFactory;
+import org.eclipse.jetty.util.thread.ScheduledExecutorScheduler;
+import org.eclipse.jetty.util.thread.Scheduler;
+
+/**
+ * <p>An abstract implementation of {@link Connector} that provides a {@link ConnectionFactory} mechanism
+ * for creating {@link Connection} instances for various protocols (HTTP, SSL, SPDY, etc).</p>
+ *
+ * <h2>Connector Services</h2>
+ * The abstract connector manages the dependent services needed by all specific connector instances:
+ * <ul>
+ * <li>The {@link Executor} service is used to run all active tasks needed by this connector such as accepting connections
+ * or handle HTTP requests. The default is to use the {@link Server#getThreadPool()} as an executor.
+ * </li>
+ * <li>The {@link Scheduler} service is used to monitor the idle timeouts of all connections and is also made available
+ * to the connections to time such things as asynchronous request timeouts.  The default is to use a new
+ * {@link ScheduledExecutorScheduler} instance.
+ * </li>
+ * <li>The {@link ByteBufferPool} service is made available to all connections to be used to acquire and release
+ * {@link ByteBuffer} instances from a pool.  The default is to use a new {@link ArrayByteBufferPool} instance.
+ * </li>
+ * </ul>
+ * These services are managed as aggregate beans by the {@link ContainerLifeCycle} super class and
+ * may either be managed or unmanaged beans.
+ *
+ * <h2>Connection Factories</h2>
+ * The connector keeps a collection of {@link ConnectionFactory} instances, each of which are known by their
+ * protocol name.  The protocol name may be a real protocol (eg http/1.1 or spdy/3) or it may be a private name
+ * that represents a special connection factory. For example, the name "SSL-http/1.1" is used for
+ * an {@link SslConnectionFactory} that has been instantiated with the {@link HttpConnectionFactory} as it's
+ * next protocol.
+ *
+ * <h4>Configuring Connection Factories</h4>
+ * The collection of available {@link ConnectionFactory} may be constructor injected or modified with the
+ * methods {@link #addConnectionFactory(ConnectionFactory)}, {@link #removeConnectionFactory(String)} and
+ * {@link #setConnectionFactories(Collection)}.  Only a single {@link ConnectionFactory} instance may be configured
+ * per protocol name, so if two factories with the same {@link ConnectionFactory#getProtocol()} are set, then
+ * the second will replace the first.
+ * <p>
+ * The protocol factory used for newly accepted connections is specified by
+ * the method {@link #setDefaultProtocol(String)} or defaults to the protocol of the first configured factory.
+ * <p>
+ * Each Connection factory type is responsible for the configuration of the protocols that it accepts. Thus to
+ * configure the HTTP protocol, you pass a {@link HttpConfiguration} instance to the {@link HttpConnectionFactory}
+ * (or the SPDY factories that can also provide HTTP Semantics).  Similarly the {@link SslConnectionFactory} is
+ * configured by passing it a {@link SslContextFactory} and a next protocol name.
+ *
+ * <h4>Connection Factory Operation</h4>
+ * {@link ConnectionFactory}s may simply create a {@link Connection} instance to support a specific
+ * protocol.  For example, the {@link HttpConnectionFactory} will create a {@link HttpConnection} instance
+ * that can handle http/1.1, http/1.0 and http/0.9.
+ * <p>
+ * {@link ConnectionFactory}s may also create a chain of {@link Connection} instances, using other {@link ConnectionFactory} instances.
+ * For example, the {@link SslConnectionFactory} is configured with a next protocol name, so that once it has accepted
+ * a connection and created an {@link SslConnection}, it then used the next {@link ConnectionFactory} from the
+ * connector using the {@link #getConnectionFactory(String)} method, to create a {@link Connection} instance that
+ * will handle the unecrypted bytes from the {@link SslConnection}.   If the next protocol is "http/1.1", then the
+ * {@link SslConnectionFactory} will have a protocol name of "SSL-http/1.1" and lookup "http/1.1" for the protocol
+ * to run over the SSL connection.
+ * <p>
+ * {@link ConnectionFactory}s may also create temporary {@link Connection} instances that will exchange bytes
+ * over the connection to determine what is the next protocol to use.  For example the NPN protocol is an extension
+ * of SSL to allow a protocol to be specified during the SSL handshake. NPN is used by the SPDY protocol to
+ * negotiate the version of SPDY or HTTP that the client and server will speak.  Thus to accept a SPDY connection, the
+ * connector will be configured with {@link ConnectionFactory}s for "SSL-NPN", "NPN", "spdy/3", "spdy/2", "http/1.1"
+ * with the default protocol being "SSL-NPN".  Thus a newly accepted connection uses "SSL-NPN", which specifies a
+ * SSLConnectionFactory with "NPN" as the next protocol.  Thus an SslConnection instance is created chained to an NPNConnection
+ * instance.  The NPN connection then negotiates with the client to determined the next protocol, which could be
+ * "spdy/3", "spdy/2" or the default of "http/1.1".  Once the next protocol is determined, the NPN connection
+ * calls {@link #getConnectionFactory(String)} to create a connection instance that will replace the NPN connection as
+ * the connection chained to the SSLConnection.
+ * <p>
+ * <h2>Acceptors</h2>
+ * The connector will execute a number of acceptor tasks to the {@link Exception} service passed to the constructor.
+ * The acceptor tasks run in a loop while the connector is running and repeatedly call the abstract {@link #accept(int)} method.
+ * The implementation of the accept method must:
+ * <nl>
+ * <li>block waiting for new connections
+ * <li>accept the connection (eg socket accept)
+ * <li>perform any configuration of the connection (eg. socket linger times)
+ * <li>call the {@link #getDefaultConnectionFactory()} {@link ConnectionFactory#newConnection(Connector, org.eclipse.jetty.io.EndPoint)}
+ * method to create a new Connection instance.
+ * </nl>
+ * The default number of acceptor tasks is the minimum of 1 and half the number of available CPUs. Having more acceptors may reduce
+ * the latency for servers that see a high rate of new connections (eg HTTP/1.0 without keep-alive).  Typically the default is
+ * sufficient for modern persistent protocols (HTTP/1.1, SPDY etc.)
+ */
+@ManagedObject("Abstract implementation of the Connector Interface")
+public abstract class AbstractConnector extends ContainerLifeCycle implements Connector, Dumpable
+{
+    protected final Logger LOG = Log.getLogger(getClass());
+    // Order is important on server side, so we use a LinkedHashMap
+    private final Map<String, ConnectionFactory> _factories = new LinkedHashMap<>();
+    private final Server _server;
+    private final Executor _executor;
+    private final Scheduler _scheduler;
+    private final ByteBufferPool _byteBufferPool;
+    private final Thread[] _acceptors;
+    private final Set<EndPoint> _endpoints = Collections.newSetFromMap(new ConcurrentHashMap());
+    private final Set<EndPoint> _immutableEndPoints = Collections.unmodifiableSet(_endpoints);
+    private volatile CountDownLatch _stopping;
+    private long _idleTimeout = 30000;
+    private String _defaultProtocol;
+    private ConnectionFactory _defaultConnectionFactory;
+    private String _name;
+
+
+    /**
+     * @param server The server this connector will be added to. Must not be null.
+     * @param executor An executor for this connector or null to use the servers executor
+     * @param scheduler A scheduler for this connector or null to either a {@link Scheduler} set as a server bean or if none set, then a new {@link ScheduledExecutorScheduler} instance.
+     * @param pool A buffer pool for this connector or null to either a {@link ByteBufferPool} set as a server bean or none set, the new  {@link ArrayByteBufferPool} instance.
+     * @param acceptors the number of acceptor threads to use, or 0 for a default value.
+     * @param factories The Connection Factories to use.
+     */
+    public AbstractConnector(
+            Server server,
+            Executor executor,
+            Scheduler scheduler,
+            ByteBufferPool pool,
+            int acceptors,
+            ConnectionFactory... factories)
+    {
+        _server=server;
+        _executor=executor!=null?executor:_server.getThreadPool();
+        if (scheduler==null)
+            scheduler=_server.getBean(Scheduler.class);
+        _scheduler=scheduler!=null?scheduler:new ScheduledExecutorScheduler();
+        if (pool==null)
+            pool=_server.getBean(ByteBufferPool.class);
+        _byteBufferPool = pool!=null?pool:new ArrayByteBufferPool();
+
+        addBean(_server,false);
+        addBean(_executor);
+        if (executor==null)
+            unmanage(_executor); // inherited from server
+        addBean(_scheduler);
+        addBean(_byteBufferPool);
+
+        for (ConnectionFactory factory:factories)
+            addConnectionFactory(factory);
+
+        int cores = Runtime.getRuntime().availableProcessors();
+        if (acceptors < 0)
+            acceptors = 1 + cores / 16;
+        if (acceptors > 2 * cores)
+            LOG.warn("Acceptors should be <= 2*availableProcessors: " + this);
+        _acceptors = new Thread[acceptors];
+    }
+
+
+    @Override
+    public Server getServer()
+    {
+        return _server;
+    }
+
+    @Override
+    public Executor getExecutor()
+    {
+        return _executor;
+    }
+
+    @Override
+    public ByteBufferPool getByteBufferPool()
+    {
+        return _byteBufferPool;
+    }
+
+    @Override
+    @ManagedAttribute("Idle timeout")
+    public long getIdleTimeout()
+    {
+        return _idleTimeout;
+    }
+
+    /**
+     * <p>Sets the maximum Idle time for a connection, which roughly translates to the {@link Socket#setSoTimeout(int)}
+     * call, although with NIO implementations other mechanisms may be used to implement the timeout.</p>
+     * <p>The max idle time is applied:</p>
+     * <ul>
+     * <li>When waiting for a new message to be received on a connection</li>
+     * <li>When waiting for a new message to be sent on a connection</li>
+     * </ul>
+     * <p>This value is interpreted as the maximum time between some progress being made on the connection.
+     * So if a single byte is read or written, then the timeout is reset.</p>
+     *
+     * @param idleTimeout the idle timeout
+     */
+    public void setIdleTimeout(long idleTimeout)
+    {
+        _idleTimeout = idleTimeout;
+    }
+
+    /**
+     * @return Returns the number of acceptor threads.
+     */
+    @ManagedAttribute("number of acceptor threads")
+    public int getAcceptors()
+    {
+        return _acceptors.length;
+    }
+
+    @Override
+    protected void doStart() throws Exception
+    {
+        _defaultConnectionFactory = getConnectionFactory(_defaultProtocol);
+        if(_defaultConnectionFactory==null)
+            throw new IllegalStateException("No protocol factory for default protocol: "+_defaultProtocol);
+
+        super.doStart();
+
+        _stopping=new CountDownLatch(_acceptors.length);
+        for (int i = 0; i < _acceptors.length; i++)
+            getExecutor().execute(new Acceptor(i));
+
+        LOG.info("Started {}", this);
+    }
+
+
+    protected void interruptAcceptors()
+    {
+        synchronized (this)
+        {
+            for (Thread thread : _acceptors)
+            {
+                if (thread != null)
+                    thread.interrupt();
+            }
+        }
+    }
+
+    @Override
+    public Future<Void> shutdown()
+    {
+        return new FutureCallback(true);
+    }
+
+    @Override
+    protected void doStop() throws Exception
+    {
+        // Tell the acceptors we are stopping
+        interruptAcceptors();
+
+        // If we have a stop timeout
+        long stopTimeout = getStopTimeout();
+        CountDownLatch stopping=_stopping;
+        if (stopTimeout > 0 && stopping!=null)
+            stopping.await(stopTimeout,TimeUnit.MILLISECONDS);
+        _stopping=null;
+
+        super.doStop();
+
+        LOG.info("Stopped {}", this);
+    }
+
+    public void join() throws InterruptedException
+    {
+        join(0);
+    }
+
+    public void join(long timeout) throws InterruptedException
+    {
+        synchronized (this)
+        {
+            for (Thread thread : _acceptors)
+                if (thread != null)
+                    thread.join(timeout);
+        }
+    }
+
+    protected abstract void accept(int acceptorID) throws IOException, InterruptedException;
+
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Is the connector accepting new connections
+     */
+    protected boolean isAccepting()
+    {
+        return isRunning();
+    }
+
+    @Override
+    public ConnectionFactory getConnectionFactory(String protocol)
+    {
+        synchronized (_factories)
+        {
+            return _factories.get(protocol.toLowerCase(Locale.ENGLISH));
+        }
+    }
+
+    @Override
+    public <T> T getConnectionFactory(Class<T> factoryType)
+    {
+        synchronized (_factories)
+        {
+            for (ConnectionFactory f : _factories.values())
+                if (factoryType.isAssignableFrom(f.getClass()))
+                    return (T)f;
+            return null;
+        }
+    }
+
+    public void addConnectionFactory(ConnectionFactory factory)
+    {
+        synchronized (_factories)
+        {
+            ConnectionFactory old=_factories.remove(factory.getProtocol());
+            if (old!=null)
+                removeBean(old);
+            _factories.put(factory.getProtocol().toLowerCase(Locale.ENGLISH), factory);
+            addBean(factory);
+            if (_defaultProtocol==null)
+                _defaultProtocol=factory.getProtocol();
+        }
+    }
+
+    public ConnectionFactory removeConnectionFactory(String protocol)
+    {
+        synchronized (_factories)
+        {
+            ConnectionFactory factory= _factories.remove(protocol.toLowerCase(Locale.ENGLISH));
+            removeBean(factory);
+            return factory;
+        }
+    }
+
+    @Override
+    public Collection<ConnectionFactory> getConnectionFactories()
+    {
+        synchronized (_factories)
+        {
+            return _factories.values();
+        }
+    }
+
+    public void setConnectionFactories(Collection<ConnectionFactory> factories)
+    {
+        synchronized (_factories)
+        {
+            List<ConnectionFactory> existing = new ArrayList<>(_factories.values());
+            for (ConnectionFactory factory: existing)
+                removeConnectionFactory(factory.getProtocol());
+            for (ConnectionFactory factory: factories)
+                if (factory!=null)
+                    addConnectionFactory(factory);
+        }
+    }
+
+
+    @Override
+    @ManagedAttribute("Protocols supported by this connector")
+    public List<String> getProtocols()
+    {
+        synchronized (_factories)
+        {
+            return new ArrayList<>(_factories.keySet());
+        }
+    }
+
+    public void clearConnectionFactories()
+    {
+        synchronized (_factories)
+        {
+            _factories.clear();
+        }
+    }
+
+    @ManagedAttribute("This connector's default protocol")
+    public String getDefaultProtocol()
+    {
+        return _defaultProtocol;
+    }
+
+    public void setDefaultProtocol(String defaultProtocol)
+    {
+        _defaultProtocol = defaultProtocol.toLowerCase(Locale.ENGLISH);
+        if (isRunning())
+            _defaultConnectionFactory=getConnectionFactory(_defaultProtocol);
+    }
+
+    @Override
+    public ConnectionFactory getDefaultConnectionFactory()
+    {
+        if (isStarted())
+            return _defaultConnectionFactory;
+        return getConnectionFactory(_defaultProtocol);
+    }
+
+    private class Acceptor implements Runnable
+    {
+        private final int _acceptor;
+
+        private Acceptor(int id)
+        {
+            _acceptor = id;
+        }
+
+        @Override
+        public void run()
+        {
+            Thread current = Thread.currentThread();
+            String name = current.getName();
+            current.setName(name + "-acceptor-" + _acceptor + "-" + AbstractConnector.this);
+
+            synchronized (AbstractConnector.this)
+            {
+                _acceptors[_acceptor] = current;
+            }
+
+            try
+            {
+                while (isAccepting())
+                {
+                    try
+                    {
+                        accept(_acceptor);
+                    }
+                    catch (Throwable e)
+                    {
+                        if (isAccepting())
+                            LOG.warn(e);
+                        else
+                            LOG.ignore(e);
+                    }
+                }
+            }
+            finally
+            {
+                current.setName(name);
+
+                synchronized (AbstractConnector.this)
+                {
+                    _acceptors[_acceptor] = null;
+                }
+                CountDownLatch stopping=_stopping;
+                if (stopping!=null)
+                    stopping.countDown();
+            }
+        }
+    }
+
+
+
+
+//    protected void connectionOpened(Connection connection)
+//    {
+//        _stats.connectionOpened();
+//        connection.onOpen();
+//    }
+//
+//    protected void connectionClosed(Connection connection)
+//    {
+//        connection.onClose();
+//        long duration = System.currentTimeMillis() - connection.getEndPoint().getCreatedTimeStamp();
+//        _stats.connectionClosed(duration, connection.getMessagesIn(), connection.getMessagesOut());
+//    }
+//
+//    public void connectionUpgraded(Connection oldConnection, Connection newConnection)
+//    {
+//        oldConnection.onClose();
+//        _stats.connectionUpgraded(oldConnection.getMessagesIn(), oldConnection.getMessagesOut());
+//        newConnection.onOpen();
+//    }
+
+    @Override
+    public Collection<EndPoint> getConnectedEndPoints()
+    {
+        return _immutableEndPoints;
+    }
+
+    protected void onEndPointOpened(EndPoint endp)
+    {
+        _endpoints.add(endp);
+    }
+
+    protected void onEndPointClosed(EndPoint endp)
+    {
+        _endpoints.remove(endp);
+    }
+
+    @Override
+    public Scheduler getScheduler()
+    {
+        return _scheduler;
+    }
+
+    @Override
+    public String getName()
+    {
+        return _name;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * Set a connector name.   A context may be configured with
+     * virtual hosts in the form "@contextname" and will only serve
+     * requests from the named connector,
+     * @param name A connector name.
+     */
+    public void setName(String name)
+    {
+        _name=name;
+    }
+    
+    @Override
+    public String toString()
+    {
+        return String.format("%s@%x{%s}",
+                _name==null?getClass().getSimpleName():_name,
+                hashCode(),
+                getDefaultProtocol());
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/server/AbstractNCSARequestLog.java b/lib/jetty/org/eclipse/jetty/server/AbstractNCSARequestLog.java
new file mode 100644 (file)
index 0000000..dcaecbe
--- /dev/null
@@ -0,0 +1,483 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server;
+
+import java.io.IOException;
+import java.util.Locale;
+
+import javax.servlet.http.Cookie;
+
+import org.eclipse.jetty.http.HttpHeader;
+import org.eclipse.jetty.http.PathMap;
+import org.eclipse.jetty.server.handler.StatisticsHandler;
+import org.eclipse.jetty.util.DateCache;
+import org.eclipse.jetty.util.annotation.ManagedAttribute;
+import org.eclipse.jetty.util.component.AbstractLifeCycle;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/**
+ * Base implementation of the {@link RequestLog} outputs logs in the pseudo-standard NCSA common log format.
+ * Configuration options allow a choice between the standard Common Log Format (as used in the 3 log format) and the
+ * Combined Log Format (single log format). This log format can be output by most web servers, and almost all web log
+ * analysis software can understand these formats.
+ */
+public abstract class AbstractNCSARequestLog extends AbstractLifeCycle implements RequestLog
+{
+    protected static final Logger LOG = Log.getLogger(AbstractNCSARequestLog.class);
+
+    private static ThreadLocal<StringBuilder> _buffers = new ThreadLocal<StringBuilder>()
+    {
+        @Override
+        protected StringBuilder initialValue()
+        {
+            return new StringBuilder(256);
+        }
+    };
+
+
+    private String[] _ignorePaths;
+    private boolean _extended;
+    private transient PathMap<String> _ignorePathMap;
+    private boolean _logLatency = false;
+    private boolean _logCookies = false;
+    private boolean _logServer = false;
+    private boolean _preferProxiedForAddress;
+    private transient DateCache _logDateCache;
+    private String _logDateFormat = "dd/MMM/yyyy:HH:mm:ss Z";
+    private Locale _logLocale = Locale.getDefault();
+    private String _logTimeZone = "GMT";
+
+    /* ------------------------------------------------------------ */
+
+    /**
+     * Is logging enabled
+     */
+    protected abstract boolean isEnabled();
+    
+    /* ------------------------------------------------------------ */
+
+    /**
+     * Write requestEntry out. (to disk or slf4j log)
+     */
+    public abstract void write(String requestEntry) throws IOException;
+
+    /* ------------------------------------------------------------ */
+
+    /**
+     * Writes the request and response information to the output stream.
+     *
+     * @see org.eclipse.jetty.server.RequestLog#log(org.eclipse.jetty.server.Request,
+     *      org.eclipse.jetty.server.Response)
+     */
+    @Override
+    public void log(Request request, Response response)
+    {
+        try
+        {
+            if (_ignorePathMap != null && _ignorePathMap.getMatch(request.getRequestURI()) != null)
+                return;
+
+            if (!isEnabled())
+                return;
+
+            StringBuilder buf = _buffers.get();
+            buf.setLength(0);
+
+            if (_logServer)
+            {
+                buf.append(request.getServerName());
+                buf.append(' ');
+            }
+
+            String addr = null;
+            if (_preferProxiedForAddress)
+            {
+                addr = request.getHeader(HttpHeader.X_FORWARDED_FOR.toString());
+            }
+
+            if (addr == null)
+                addr = request.getRemoteAddr();
+
+            buf.append(addr);
+            buf.append(" - ");
+            Authentication authentication = request.getAuthentication();
+            if (authentication instanceof Authentication.User)
+                buf.append(((Authentication.User)authentication).getUserIdentity().getUserPrincipal().getName());
+            else
+                buf.append(" - ");
+
+            buf.append(" [");
+            if (_logDateCache != null)
+                buf.append(_logDateCache.format(request.getTimeStamp()));
+            else
+                buf.append(request.getTimeStamp());
+
+            buf.append("] \"");
+            buf.append(request.getMethod());
+            buf.append(' ');
+            buf.append(request.getUri().toString());
+            buf.append(' ');
+            buf.append(request.getProtocol());
+            buf.append("\" ");
+
+            int status = response.getStatus();
+            if (status <= 0)
+                status = 404;
+            buf.append((char)('0' + ((status / 100) % 10)));
+            buf.append((char)('0' + ((status / 10) % 10)));
+            buf.append((char)('0' + (status % 10)));
+
+            long responseLength = response.getLongContentLength();
+            if (responseLength >= 0)
+            {
+                buf.append(' ');
+                if (responseLength > 99999)
+                    buf.append(responseLength);
+                else
+                {
+                    if (responseLength > 9999)
+                        buf.append((char)('0' + ((responseLength / 10000) % 10)));
+                    if (responseLength > 999)
+                        buf.append((char)('0' + ((responseLength / 1000) % 10)));
+                    if (responseLength > 99)
+                        buf.append((char)('0' + ((responseLength / 100) % 10)));
+                    if (responseLength > 9)
+                        buf.append((char)('0' + ((responseLength / 10) % 10)));
+                    buf.append((char)('0' + (responseLength) % 10));
+                }
+                buf.append(' ');
+            }
+            else
+                buf.append(" - ");
+
+
+            if (_extended)
+                logExtended(request, response, buf);
+
+            if (_logCookies)
+            {
+                Cookie[] cookies = request.getCookies();
+                if (cookies == null || cookies.length == 0)
+                    buf.append(" -");
+                else
+                {
+                    buf.append(" \"");
+                    for (int i = 0; i < cookies.length; i++)
+                    {
+                        if (i != 0)
+                            buf.append(';');
+                        buf.append(cookies[i].getName());
+                        buf.append('=');
+                        buf.append(cookies[i].getValue());
+                    }
+                    buf.append('\"');
+                }
+            }
+
+            if (_logLatency)
+            {
+                long now = System.currentTimeMillis();
+
+                if (_logLatency)
+                {
+                    buf.append(' ');
+                    buf.append(now - request.getTimeStamp());
+                }
+            }
+
+            String log = buf.toString();
+            write(log);
+        }
+        catch (IOException e)
+        {
+            LOG.warn(e);
+        }
+    }
+    
+    /* ------------------------------------------------------------ */
+
+    /**
+     * Writes extended request and response information to the output stream.
+     *
+     * @param request  request object
+     * @param response response object
+     * @param b        StringBuilder to write to
+     * @throws IOException
+     */
+    protected void logExtended(Request request,
+                               Response response,
+                               StringBuilder b) throws IOException
+    {
+        String referer = request.getHeader(HttpHeader.REFERER.toString());
+        if (referer == null)
+            b.append("\"-\" ");
+        else
+        {
+            b.append('"');
+            b.append(referer);
+            b.append("\" ");
+        }
+
+        String agent = request.getHeader(HttpHeader.USER_AGENT.toString());
+        if (agent == null)
+            b.append("\"-\" ");
+        else
+        {
+            b.append('"');
+            b.append(agent);
+            b.append('"');
+        }
+    }
+
+
+    /**
+     * Set request paths that will not be logged.
+     *
+     * @param ignorePaths array of request paths
+     */
+    public void setIgnorePaths(String[] ignorePaths)
+    {
+        _ignorePaths = ignorePaths;
+    }
+
+    /**
+     * Retrieve the request paths that will not be logged.
+     *
+     * @return array of request paths
+     */
+    public String[] getIgnorePaths()
+    {
+        return _ignorePaths;
+    }
+
+    /**
+     * Controls logging of the request cookies.
+     *
+     * @param logCookies true - values of request cookies will be logged, false - values of request cookies will not be
+     *                   logged
+     */
+    public void setLogCookies(boolean logCookies)
+    {
+        _logCookies = logCookies;
+    }
+
+    /**
+     * Retrieve log cookies flag
+     *
+     * @return value of the flag
+     */
+    public boolean getLogCookies()
+    {
+        return _logCookies;
+    }
+
+    /**
+     * Controls logging of the request hostname.
+     *
+     * @param logServer true - request hostname will be logged, false - request hostname will not be logged
+     */
+    public void setLogServer(boolean logServer)
+    {
+        _logServer = logServer;
+    }
+
+    /**
+     * Retrieve log hostname flag.
+     *
+     * @return value of the flag
+     */
+    public boolean getLogServer()
+    {
+        return _logServer;
+    }
+
+    /**
+     * Controls logging of request processing time.
+     *
+     * @param logLatency true - request processing time will be logged false - request processing time will not be
+     *                   logged
+     */
+    public void setLogLatency(boolean logLatency)
+    {
+        _logLatency = logLatency;
+    }
+
+    /**
+     * Retrieve log request processing time flag.
+     *
+     * @return value of the flag
+     */
+    public boolean getLogLatency()
+    {
+        return _logLatency;
+    }
+
+    /**
+     * @deprecated use {@link StatisticsHandler}
+     */
+    public void setLogDispatch(boolean value)
+    {
+    }
+
+    /**
+     * @deprecated use {@link StatisticsHandler}
+     */
+    public boolean isLogDispatch()
+    {
+        return false;
+    }
+
+    /**
+     * Controls whether the actual IP address of the connection or the IP address from the X-Forwarded-For header will
+     * be logged.
+     *
+     * @param preferProxiedForAddress true - IP address from header will be logged, false - IP address from the
+     *                                connection will be logged
+     */
+    public void setPreferProxiedForAddress(boolean preferProxiedForAddress)
+    {
+        _preferProxiedForAddress = preferProxiedForAddress;
+    }
+
+    /**
+     * Retrieved log X-Forwarded-For IP address flag.
+     *
+     * @return value of the flag
+     */
+    public boolean getPreferProxiedForAddress()
+    {
+        return _preferProxiedForAddress;
+    }
+
+    /**
+     * Set the extended request log format flag.
+     *
+     * @param extended true - log the extended request information, false - do not log the extended request information
+     */
+    public void setExtended(boolean extended)
+    {
+        _extended = extended;
+    }
+
+    /**
+     * Retrieve the extended request log format flag.
+     *
+     * @return value of the flag
+     */
+    @ManagedAttribute("use extended NCSA format")
+    public boolean isExtended()
+    {
+        return _extended;
+    }
+
+    /**
+     * Set up request logging and open log file.
+     *
+     * @see org.eclipse.jetty.util.component.AbstractLifeCycle#doStart()
+     */
+    @Override
+    protected synchronized void doStart() throws Exception
+    {
+        if (_logDateFormat != null)
+        {
+            _logDateCache = new DateCache(_logDateFormat, _logLocale ,_logTimeZone);
+        }
+
+        if (_ignorePaths != null && _ignorePaths.length > 0)
+        {
+            _ignorePathMap = new PathMap<>();
+            for (int i = 0; i < _ignorePaths.length; i++)
+                _ignorePathMap.put(_ignorePaths[i], _ignorePaths[i]);
+        }
+        else
+            _ignorePathMap = null;
+
+        super.doStart();
+    }
+
+    @Override
+    protected void doStop() throws Exception
+    {
+        _logDateCache = null;
+        super.doStop();
+    }
+
+    /**
+     * Set the timestamp format for request log entries in the file. If this is not set, the pre-formated request
+     * timestamp is used.
+     *
+     * @param format timestamp format string
+     */
+    public void setLogDateFormat(String format)
+    {
+        _logDateFormat = format;
+    }
+
+    /**
+     * Retrieve the timestamp format string for request log entries.
+     *
+     * @return timestamp format string.
+     */
+    public String getLogDateFormat()
+    {
+        return _logDateFormat;
+    }
+
+    /**
+     * Set the locale of the request log.
+     *
+     * @param logLocale locale object
+     */
+    public void setLogLocale(Locale logLocale)
+    {
+        _logLocale = logLocale;
+    }
+
+    /**
+     * Retrieve the locale of the request log.
+     *
+     * @return locale object
+     */
+    public Locale getLogLocale()
+    {
+        return _logLocale;
+    }
+
+    /**
+     * Set the timezone of the request log.
+     *
+     * @param tz timezone string
+     */
+    public void setLogTimeZone(String tz)
+    {
+        _logTimeZone = tz;
+    }
+
+    /**
+     * Retrieve the timezone of the request log.
+     *
+     * @return timezone string
+     */
+    @ManagedAttribute("the timezone")
+    public String getLogTimeZone()
+    {
+        return _logTimeZone;
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/server/AbstractNetworkConnector.java b/lib/jetty/org/eclipse/jetty/server/AbstractNetworkConnector.java
new file mode 100644 (file)
index 0000000..08b65bc
--- /dev/null
@@ -0,0 +1,125 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server;
+
+import java.io.IOException;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Future;
+
+import org.eclipse.jetty.io.ByteBufferPool;
+import org.eclipse.jetty.util.annotation.ManagedAttribute;
+import org.eclipse.jetty.util.annotation.ManagedObject;
+import org.eclipse.jetty.util.thread.Scheduler;
+
+/**
+ * An abstract Network Connector.
+ * <p>
+ * Extends the {@link AbstractConnector} support for the {@link NetworkConnector} interface.
+ */
+@ManagedObject("AbstractNetworkConnector")
+public abstract class AbstractNetworkConnector extends AbstractConnector implements NetworkConnector
+{
+
+    private volatile String _host;
+    private volatile int _port = 0;
+
+    public AbstractNetworkConnector(Server server, Executor executor, Scheduler scheduler, ByteBufferPool pool, int acceptors, ConnectionFactory... factories)
+    {
+        super(server,executor,scheduler,pool,acceptors,factories);
+    }
+
+    public void setHost(String host)
+    {
+        _host = host;
+    }
+
+    @Override
+    @ManagedAttribute("The network interface this connector binds to as an IP address or a hostname.  If null or 0.0.0.0, then bind to all interfaces.")
+    public String getHost()
+    {
+        return _host;
+    }
+
+    public void setPort(int port)
+    {
+        _port = port;
+    }
+
+    @Override
+    @ManagedAttribute("Port this connector listens on. If set the 0 a random port is assigned which may be obtained with getLocalPort()")
+    public int getPort()
+    {
+        return _port;
+    }
+
+    @Override
+    public int getLocalPort()
+    {
+        return -1;
+    }
+
+    @Override
+    protected void doStart() throws Exception
+    {
+        open();
+        super.doStart();
+    }
+
+    @Override
+    protected void doStop() throws Exception
+    {
+        close();
+        super.doStop();
+    }
+
+    @Override
+    public void open() throws IOException
+    {
+    }
+
+    @Override
+    public void close()
+    {
+        // Interrupting is often sufficient to close the channel
+        interruptAcceptors();
+    }
+    
+
+    @Override
+    public Future<Void> shutdown()
+    {
+        close();
+        return super.shutdown();
+    }
+
+    @Override
+    protected boolean isAccepting()
+    {
+        return super.isAccepting() && isOpen();
+    }
+
+    @Override
+    public String toString()
+    {
+        return String.format("%s{%s:%d}",
+                super.toString(),
+                getHost() == null ? "0.0.0.0" : getHost(),
+                getLocalPort() <= 0 ? getPort() : getLocalPort());
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/server/AsyncContextEvent.java b/lib/jetty/org/eclipse/jetty/server/AsyncContextEvent.java
new file mode 100644 (file)
index 0000000..cc0eec8
--- /dev/null
@@ -0,0 +1,164 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server;
+
+
+import javax.servlet.AsyncContext;
+import javax.servlet.AsyncEvent;
+import javax.servlet.RequestDispatcher;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+
+import org.eclipse.jetty.server.handler.ContextHandler.Context;
+import org.eclipse.jetty.util.thread.Scheduler;
+
+public class AsyncContextEvent extends AsyncEvent implements Runnable
+{
+    final private Context _context;
+    final private AsyncContextState _asyncContext;
+    private volatile HttpChannelState _state;
+    private ServletContext _dispatchContext;
+    private String _dispatchPath;
+    private volatile Scheduler.Task _timeoutTask;
+    private Throwable _throwable;
+
+    public AsyncContextEvent(Context context,AsyncContextState asyncContext, HttpChannelState state, Request baseRequest, ServletRequest request, ServletResponse response)
+    {
+        super(null,request,response,null);
+        _context=context;
+        _asyncContext=asyncContext;
+        _state=state;
+
+        // If we haven't been async dispatched before
+        if (baseRequest.getAttribute(AsyncContext.ASYNC_REQUEST_URI)==null)
+        {
+            // We are setting these attributes during startAsync, when the spec implies that
+            // they are only available after a call to AsyncContext.dispatch(...);
+
+            // have we been forwarded before?
+            String uri=(String)baseRequest.getAttribute(RequestDispatcher.FORWARD_REQUEST_URI);
+            if (uri!=null)
+            {
+                baseRequest.setAttribute(AsyncContext.ASYNC_REQUEST_URI,uri);
+                baseRequest.setAttribute(AsyncContext.ASYNC_CONTEXT_PATH,baseRequest.getAttribute(RequestDispatcher.FORWARD_CONTEXT_PATH));
+                baseRequest.setAttribute(AsyncContext.ASYNC_SERVLET_PATH,baseRequest.getAttribute(RequestDispatcher.FORWARD_SERVLET_PATH));
+                baseRequest.setAttribute(AsyncContext.ASYNC_PATH_INFO,baseRequest.getAttribute(RequestDispatcher.FORWARD_PATH_INFO));
+                baseRequest.setAttribute(AsyncContext.ASYNC_QUERY_STRING,baseRequest.getAttribute(RequestDispatcher.FORWARD_QUERY_STRING));
+            }
+            else
+            {
+                baseRequest.setAttribute(AsyncContext.ASYNC_REQUEST_URI,baseRequest.getRequestURI());
+                baseRequest.setAttribute(AsyncContext.ASYNC_CONTEXT_PATH,baseRequest.getContextPath());
+                baseRequest.setAttribute(AsyncContext.ASYNC_SERVLET_PATH,baseRequest.getServletPath());
+                baseRequest.setAttribute(AsyncContext.ASYNC_PATH_INFO,baseRequest.getPathInfo());
+                baseRequest.setAttribute(AsyncContext.ASYNC_QUERY_STRING,baseRequest.getQueryString());
+            }
+        }
+    }
+
+    public ServletContext getSuspendedContext()
+    {
+        return _context;
+    }
+    
+    public Context getContext()
+    {
+        return _context;
+    }
+
+    public ServletContext getDispatchContext()
+    {
+        return _dispatchContext;
+    }
+
+    public ServletContext getServletContext()
+    {
+        return _dispatchContext==null?_context:_dispatchContext;
+    }
+
+    /**
+     * @return The path in the context
+     */
+    public String getPath()
+    {
+        return _dispatchPath;
+    }
+    
+    public void setTimeoutTask(Scheduler.Task task)
+    {
+        _timeoutTask = task;
+    }
+    
+    public void cancelTimeoutTask()
+    {
+        Scheduler.Task task=_timeoutTask;
+        _timeoutTask=null;
+        if (task!=null)
+            task.cancel();
+    }
+
+    @Override
+    public AsyncContext getAsyncContext()
+    {
+        return _asyncContext;
+    }
+    
+    @Override
+    public Throwable getThrowable()
+    {
+        return _throwable;
+    }
+    
+    public void setThrowable(Throwable throwable)
+    {
+        _throwable=throwable;
+    }
+
+    public void setDispatchContext(ServletContext context)
+    {
+        _dispatchContext=context;
+    }
+    
+    public void setDispatchPath(String path)
+    {
+        _dispatchPath=path;
+    }
+    
+    public void completed()
+    {
+        _timeoutTask=null;
+        _asyncContext.reset();
+    }
+
+    public HttpChannelState getHttpChannelState()
+    {
+        return _state;
+    }
+
+    @Override
+    public void run()
+    {
+        Scheduler.Task task=_timeoutTask;
+        _timeoutTask=null;
+        if (task!=null)
+            _state.expired();
+    }
+
+}
diff --git a/lib/jetty/org/eclipse/jetty/server/AsyncContextState.java b/lib/jetty/org/eclipse/jetty/server/AsyncContextState.java
new file mode 100644 (file)
index 0000000..6503424
--- /dev/null
@@ -0,0 +1,185 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server;
+
+import java.io.IOException;
+
+import javax.servlet.AsyncContext;
+import javax.servlet.AsyncEvent;
+import javax.servlet.AsyncListener;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+
+import org.eclipse.jetty.server.handler.ContextHandler;
+
+
+public class AsyncContextState implements AsyncContext
+{
+    volatile HttpChannelState _state;
+
+    public AsyncContextState(HttpChannelState state)
+    {
+        _state=state;
+    }
+    
+    HttpChannelState state()
+    {
+        HttpChannelState state=_state;
+        if (state==null)
+            throw new IllegalStateException("AsyncContext completed");
+        return state;
+    }
+
+    @Override
+    public void addListener(final AsyncListener listener, final ServletRequest request, final ServletResponse response)
+    {
+        AsyncListener wrap = new AsyncListener()
+        {
+            @Override
+            public void onTimeout(AsyncEvent event) throws IOException
+            {
+                listener.onTimeout(new AsyncEvent(event.getAsyncContext(),request,response,event.getThrowable()));
+            }
+            
+            @Override
+            public void onStartAsync(AsyncEvent event) throws IOException
+            {
+                listener.onStartAsync(new AsyncEvent(event.getAsyncContext(),request,response,event.getThrowable()));
+            }
+            
+            @Override
+            public void onError(AsyncEvent event) throws IOException
+            {
+                listener.onComplete(new AsyncEvent(event.getAsyncContext(),request,response,event.getThrowable()));
+            }
+            
+            @Override
+            public void onComplete(AsyncEvent event) throws IOException
+            {                
+                listener.onComplete(new AsyncEvent(event.getAsyncContext(),request,response,event.getThrowable()));
+            }
+        };
+        state().addListener(wrap);
+    }
+
+    @Override
+    public void addListener(AsyncListener listener)
+    {
+        state().addListener(listener);
+    }
+
+    @Override
+    public void complete()
+    {
+        state().complete();
+    }
+
+    @Override
+    public <T extends AsyncListener> T createListener(Class<T> clazz) throws ServletException
+    {    
+        ContextHandler contextHandler = state().getContextHandler();
+        if (contextHandler != null)
+            return contextHandler.getServletContext().createListener(clazz);
+        try
+        {
+            return clazz.newInstance();
+        }
+        catch (Exception e)
+        {
+            throw new ServletException(e);
+        }
+    }
+
+    @Override
+    public void dispatch()
+    {
+        state().dispatch(null,null);
+    }
+
+    @Override
+    public void dispatch(String path)
+    {
+        state().dispatch(null,path);
+    }
+    
+    @Override
+    public void dispatch(ServletContext context, String path)
+    {
+        state().dispatch(context,path);
+    }
+
+    @Override
+    public ServletRequest getRequest()
+    {
+        return state().getAsyncContextEvent().getSuppliedRequest();
+    }
+
+    @Override
+    public ServletResponse getResponse()
+    {
+        return state().getAsyncContextEvent().getSuppliedResponse();
+    }
+
+    @Override
+    public long getTimeout()
+    {
+        return state().getTimeout();
+    }
+
+    @Override
+    public boolean hasOriginalRequestAndResponse()
+    {
+        HttpChannel<?> channel=state().getHttpChannel();
+        return channel.getRequest()==getRequest() && channel.getResponse()==getResponse();
+    }
+
+    @Override
+    public void setTimeout(long arg0)
+    {
+        state().setTimeout(arg0);
+    }
+
+    @Override
+    public void start(final Runnable task)
+    {
+        state().getHttpChannel().execute(new Runnable()
+        {
+            @Override
+            public void run()
+            {
+                state().getAsyncContextEvent().getContext().getContextHandler().handle(task);
+            }
+        });
+    }
+
+    public void reset()
+    {
+        _state=null;
+    }
+
+    public HttpChannelState getHttpChannelState()
+    {
+        return state();
+    }
+
+    
+
+}
diff --git a/lib/jetty/org/eclipse/jetty/server/AsyncNCSARequestLog.java b/lib/jetty/org/eclipse/jetty/server/AsyncNCSARequestLog.java
new file mode 100644 (file)
index 0000000..33ba21d
--- /dev/null
@@ -0,0 +1,129 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server;
+
+import java.io.IOException;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.TimeUnit;
+
+import org.eclipse.jetty.util.BlockingArrayQueue;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+
+/* ------------------------------------------------------------ */
+/**
+ * An asynchronously writing NCSA Request Log
+ */
+public class AsyncNCSARequestLog extends NCSARequestLog
+{
+    private static final Logger LOG = Log.getLogger(AsyncNCSARequestLog.class);
+    private final BlockingQueue<String> _queue;
+    private transient WriterThread _thread;
+    private boolean _warnedFull;
+
+    public AsyncNCSARequestLog()
+    {
+        this(null,null);
+    }
+
+    public AsyncNCSARequestLog(BlockingQueue<String> queue)
+    {
+        this(null,queue);
+    }
+
+    public AsyncNCSARequestLog(String filename)
+    {
+        this(filename,null);
+    }
+
+    public AsyncNCSARequestLog(String filename,BlockingQueue<String> queue)
+    {
+        super(filename);
+        if (queue==null)
+            queue=new BlockingArrayQueue<>(1024);
+        _queue=queue;
+    }
+
+    private class WriterThread extends Thread
+    {
+        WriterThread()
+        {
+            setName("AsyncNCSARequestLog@"+Integer.toString(AsyncNCSARequestLog.this.hashCode(),16));
+        }
+
+        @Override
+        public void run()
+        {
+            while (isRunning())
+            {
+                try
+                {
+                    String log = _queue.poll(10,TimeUnit.SECONDS);
+                    if (log!=null)
+                        AsyncNCSARequestLog.super.write(log);
+
+                    while(!_queue.isEmpty())
+                    {
+                        log=_queue.poll();
+                        if (log!=null)
+                            AsyncNCSARequestLog.super.write(log);
+                    }
+                }
+                catch (IOException e)
+                {
+                    LOG.warn(e);
+                }
+                catch (InterruptedException e)
+                {
+                    LOG.ignore(e);
+                }
+            }
+        }
+    }
+
+    @Override
+    protected synchronized void doStart() throws Exception
+    {
+        super.doStart();
+        _thread = new WriterThread();
+        _thread.start();
+    }
+
+    @Override
+    protected void doStop() throws Exception
+    {
+        _thread.interrupt();
+        _thread.join();
+        super.doStop();
+        _thread=null;
+    }
+
+    @Override
+    public void write(String log) throws IOException
+    {
+        if (!_queue.offer(log))
+        {
+            if (_warnedFull)
+                LOG.warn("Log Queue overflow");
+            _warnedFull=true;
+        }
+    }
+
+}
diff --git a/lib/jetty/org/eclipse/jetty/server/Authentication.java b/lib/jetty/org/eclipse/jetty/server/Authentication.java
new file mode 100644 (file)
index 0000000..ccdf4c0
--- /dev/null
@@ -0,0 +1,164 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server;
+
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+
+/* ------------------------------------------------------------ */
+/** The Authentication state of a request.
+ * <p>
+ * The Authentication state can be one of several sub-types that
+ * reflects where the request is in the many different authentication
+ * cycles. Authentication might not yet be checked or it might be checked
+ * and failed, checked and deferred or succeeded. 
+ * 
+ */
+public interface Authentication
+{
+    /* ------------------------------------------------------------ */
+    public static class Failed extends QuietServletException
+    {
+       public Failed(String message)
+       {
+           super(message);
+       }
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** A successful Authentication with User information.
+     */
+    public interface User extends Authentication
+    {
+        String getAuthMethod();
+        UserIdentity getUserIdentity(); 
+        boolean isUserInRole(UserIdentity.Scope scope,String role);
+        void logout();
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** A wrapped authentication with methods provide the
+     * wrapped request/response for use by the application
+     */
+    public interface Wrapped extends Authentication
+    {
+        HttpServletRequest getHttpServletRequest();
+        HttpServletResponse getHttpServletResponse();
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** A deferred authentication with methods to progress 
+     * the authentication process.
+     */
+    public interface Deferred extends Authentication
+    {
+        /* ------------------------------------------------------------ */
+        /** Authenticate if possible without sending a challenge.
+         * This is used to check credentials that have been sent for 
+         * non-manditory authentication.
+         * @return The new Authentication state.
+         */
+        Authentication authenticate(ServletRequest request);
+
+        /* ------------------------------------------------------------ */
+        /** Authenticate and possibly send a challenge.
+         * This is used to initiate authentication for previously 
+         * non-manditory authentication.
+         * @return The new Authentication state.
+         */
+        Authentication authenticate(ServletRequest request,ServletResponse response);
+        
+        
+        /* ------------------------------------------------------------ */
+        /** Login with the LOGIN authenticator
+         * @param username
+         * @param password
+         * @return The new Authentication state
+         */
+        Authentication login(String username,Object password,ServletRequest request);
+    }
+
+    
+    /* ------------------------------------------------------------ */
+    /** Authentication Response sent state.
+     * Responses are sent by authenticators either to issue an
+     * authentication challenge or on successful authentication in
+     * order to redirect the user to the original URL.
+     */
+    public interface ResponseSent extends Authentication
+    { 
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** An Authentication Challenge has been sent.
+     */
+    public interface Challenge extends ResponseSent
+    { 
+    }
+
+    /* ------------------------------------------------------------ */
+    /** An Authentication Failure has been sent.
+     */
+    public interface Failure extends ResponseSent
+    { 
+    }
+
+    public interface SendSuccess extends ResponseSent
+    {
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Unauthenticated state.
+     * <p> 
+     * This convenience instance is for non mandatory authentication where credentials
+     * have been presented and checked, but failed authentication. 
+     */
+    public final static Authentication UNAUTHENTICATED = new Authentication(){@Override
+    public String toString(){return "UNAUTHENTICATED";}};
+
+    /* ------------------------------------------------------------ */
+    /** Authentication not checked
+     * <p>
+     * This convenience instance us for non mandatory authentication when no 
+     * credentials are present to be checked.
+     */
+    public final static Authentication NOT_CHECKED = new Authentication(){@Override
+    public String toString(){return "NOT CHECKED";}};
+
+    /* ------------------------------------------------------------ */
+    /** Authentication challenge sent.
+     * <p>
+     * This convenience instance is for when an authentication challenge has been sent.
+     */
+    public final static Authentication SEND_CONTINUE = new Authentication.Challenge(){@Override
+    public String toString(){return "CHALLENGE";}};
+
+    /* ------------------------------------------------------------ */
+    /** Authentication failure sent.
+     * <p>
+     * This convenience instance is for when an authentication failure has been sent.
+     */
+    public final static Authentication SEND_FAILURE = new Authentication.Failure(){@Override
+    public String toString(){return "FAILURE";}};
+    public final static Authentication SEND_SUCCESS = new SendSuccess(){@Override
+    public String toString(){return "SEND_SUCCESS";}};
+}
diff --git a/lib/jetty/org/eclipse/jetty/server/ByteBufferQueuedHttpInput.java b/lib/jetty/org/eclipse/jetty/server/ByteBufferQueuedHttpInput.java
new file mode 100644 (file)
index 0000000..cd2d12e
--- /dev/null
@@ -0,0 +1,53 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server;
+
+import java.nio.ByteBuffer;
+
+/**
+ * <p>An implementation of HttpInput using {@link ByteBuffer} as items.</p>
+ */
+public class ByteBufferQueuedHttpInput extends QueuedHttpInput<ByteBuffer>
+{
+    @Override
+    protected int remaining(ByteBuffer item)
+    {
+        return item.remaining();
+    }
+
+    @Override
+    protected int get(ByteBuffer item, byte[] buffer, int offset, int length)
+    {
+        int l = Math.min(item.remaining(), length);
+        item.get(buffer, offset, l);
+        return l;
+    }
+    
+    @Override
+    protected void consume(ByteBuffer item, int length)
+    {
+        item.position(item.position()+length);
+    }
+
+    @Override
+    protected void onContentConsumed(ByteBuffer item)
+    {
+    }
+
+}
diff --git a/lib/jetty/org/eclipse/jetty/server/ClassLoaderDump.java b/lib/jetty/org/eclipse/jetty/server/ClassLoaderDump.java
new file mode 100644 (file)
index 0000000..466775e
--- /dev/null
@@ -0,0 +1,66 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server;
+
+import java.io.IOException;
+import java.net.URLClassLoader;
+import java.util.Collections;
+
+import org.eclipse.jetty.util.TypeUtil;
+import org.eclipse.jetty.util.component.ContainerLifeCycle;
+import org.eclipse.jetty.util.component.Dumpable;
+
+public class ClassLoaderDump implements Dumpable
+{
+    final ClassLoader _loader;
+
+    public ClassLoaderDump(ClassLoader loader)
+    {
+        _loader = loader;
+    }
+
+    @Override
+    public String dump()
+    {
+        return ContainerLifeCycle.dump(this);
+    }
+
+    @Override
+    public void dump(Appendable out, String indent) throws IOException
+    {
+        if (_loader==null)
+            out.append("No ClassLoader\n");
+        else
+        {
+            out.append(String.valueOf(_loader)).append("\n");
+
+            Object parent = _loader.getParent();
+            if (parent != null)
+            {
+                if (!(parent instanceof Dumpable))
+                    parent = new ClassLoaderDump((ClassLoader)parent);
+
+                if (_loader instanceof URLClassLoader)
+                    ContainerLifeCycle.dump(out,indent,TypeUtil.asList(((URLClassLoader)_loader).getURLs()),Collections.singleton(parent));
+                else
+                    ContainerLifeCycle.dump(out,indent,Collections.singleton(parent));
+            }
+        }
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/server/ConnectionFactory.java b/lib/jetty/org/eclipse/jetty/server/ConnectionFactory.java
new file mode 100644 (file)
index 0000000..e23739d
--- /dev/null
@@ -0,0 +1,57 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server;
+
+
+import org.eclipse.jetty.io.Connection;
+import org.eclipse.jetty.io.EndPoint;
+
+/**
+ * <p>A Factory to create {@link Connection} instances for {@link Connector}s.</p>
+ * <p>A Connection factory is responsible for instantiating and configuring a {@link Connection} instance
+ * to handle an {@link EndPoint} accepted by a {@link Connector}.</p>
+ * <p>
+ * A ConnectionFactory has a protocol name that represents the protocol of the Connections
+ * created.  Example of protocol names include:<dl>
+ * <dt>http</dt><dd>Creates a HTTP connection that can handle multiple versions of HTTP from 0.9 to 1.1</dd>
+ * <dt>spdy/2</dt><dd>Creates a HTTP connection that handles a specific version of the SPDY protocol</dd>
+ * <dt>SSL-XYZ</dt><dd>Create an SSL connection chained to a connection obtained from a connection factory 
+ * with a protocol "XYZ".</dd>
+ * <dt>SSL-http</dt><dd>Create an SSL connection chained to a HTTP connection (aka https)</dd>
+ * <dt>SSL-npn</dt><dd>Create an SSL connection chained to a NPN connection, that uses a negotiation with
+ * the client to determine the next protocol.</dd>
+ * </dl>
+ */
+public interface ConnectionFactory
+{
+    /* ------------------------------------------------------------ */
+    /**
+     * @return A string representing the protocol name.
+     */
+    public String getProtocol();
+    
+    /**
+     * <p>Creates a new {@link Connection} with the given parameters</p>
+     * @param connector The {@link Connector} creating this connection
+     * @param endPoint the {@link EndPoint} associated with the connection
+     * @return a new {@link Connection}
+     */
+    public Connection newConnection(Connector connector, EndPoint endPoint);
+    
+}
diff --git a/lib/jetty/org/eclipse/jetty/server/Connector.java b/lib/jetty/org/eclipse/jetty/server/Connector.java
new file mode 100644 (file)
index 0000000..ce4544c
--- /dev/null
@@ -0,0 +1,105 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.concurrent.Executor;
+
+import org.eclipse.jetty.io.ByteBufferPool;
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.server.handler.ContextHandler;
+import org.eclipse.jetty.util.annotation.ManagedAttribute;
+import org.eclipse.jetty.util.annotation.ManagedObject;
+import org.eclipse.jetty.util.component.Graceful;
+import org.eclipse.jetty.util.component.LifeCycle;
+import org.eclipse.jetty.util.thread.Scheduler;
+
+/**
+ * <p>A {@link Connector} accept connections and data from remote peers,
+ * and allows applications to send data to remote peers, by setting up
+ * the machinery needed to handle such tasks.</p>
+ */
+@ManagedObject("Connector Interface")
+public interface Connector extends LifeCycle, Graceful
+{
+    /**
+     * @return the {@link Server} instance associated with this {@link Connector}
+     */
+    public Server getServer();
+
+    /**
+     * @return the {@link Executor} used to submit tasks
+     */
+    public Executor getExecutor();
+
+    /**
+     * @return the {@link Scheduler} used to schedule tasks
+     */
+    public Scheduler getScheduler();
+
+    /**
+     * @return the {@link ByteBufferPool} to acquire buffers from and release buffers to
+     */
+    public ByteBufferPool getByteBufferPool();
+
+    /**
+     * @return the {@link ConnectionFactory} associated with the protocol name
+     */
+    public ConnectionFactory getConnectionFactory(String nextProtocol);
+    
+
+    public <T> T getConnectionFactory(Class<T> factoryType);
+    
+    /**
+     * @return the default {@link ConnectionFactory} associated with the default protocol name
+     */
+    public ConnectionFactory getDefaultConnectionFactory();
+    
+    public Collection<ConnectionFactory> getConnectionFactories();
+    
+    public List<String> getProtocols();
+    
+    /**
+     * @return the max idle timeout for connections in milliseconds
+     */
+    @ManagedAttribute("maximum time a connection can be idle before being closed (in ms)")
+    public long getIdleTimeout();
+
+    /**
+     * @return the underlying socket, channel, buffer etc. for the connector.
+     */
+    public Object getTransport();
+    
+    /**
+     * @return immutable collection of connected endpoints
+     */
+    public Collection<EndPoint> getConnectedEndPoints();
+
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * Get the connector name if set.
+     * <p>A {@link ContextHandler} may be configured with
+     * virtual hosts in the form "@connectorName" and will only serve
+     * requests from the named connector.
+     * @return The connector name or null.
+     */
+    public String getName();
+}
diff --git a/lib/jetty/org/eclipse/jetty/server/ConnectorStatistics.java b/lib/jetty/org/eclipse/jetty/server/ConnectorStatistics.java
new file mode 100644 (file)
index 0000000..cae5e6e
--- /dev/null
@@ -0,0 +1,308 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicLong;
+
+import org.eclipse.jetty.io.Connection;
+import org.eclipse.jetty.util.annotation.ManagedAttribute;
+import org.eclipse.jetty.util.annotation.ManagedObject;
+import org.eclipse.jetty.util.annotation.ManagedOperation;
+import org.eclipse.jetty.util.component.AbstractLifeCycle;
+import org.eclipse.jetty.util.component.Container;
+import org.eclipse.jetty.util.component.ContainerLifeCycle;
+import org.eclipse.jetty.util.component.Dumpable;
+import org.eclipse.jetty.util.statistic.CounterStatistic;
+import org.eclipse.jetty.util.statistic.SampleStatistic;
+
+
+/* ------------------------------------------------------------ */
+/** A Connector.Listener that gathers Connector and Connections Statistics.
+ * Adding an instance of this class as with {@link AbstractConnector#addBean(Object)} 
+ * will register the listener with all connections accepted by that connector.
+ */
+@ManagedObject("Connector Statistics")
+public class ConnectorStatistics extends AbstractLifeCycle implements Dumpable, Connection.Listener
+{
+    private final static Sample ZERO=new Sample();
+    private final AtomicLong _startMillis = new AtomicLong(-1L);
+    private final CounterStatistic _connectionStats = new CounterStatistic();
+    private final SampleStatistic _messagesIn = new SampleStatistic();
+    private final SampleStatistic _messagesOut = new SampleStatistic();
+    private final SampleStatistic _connectionDurationStats = new SampleStatistic();
+    private final ConcurrentMap<Connection, Sample> _samples = new ConcurrentHashMap<>();
+    private final AtomicInteger _closedIn = new AtomicInteger();
+    private final AtomicInteger _closedOut = new AtomicInteger();
+    private AtomicLong _nanoStamp=new AtomicLong();
+    private volatile int _messagesInPerSecond;
+    private volatile int _messagesOutPerSecond;
+
+    @Override
+    public void onOpened(Connection connection)
+    {
+        if (isStarted())
+        {
+            _connectionStats.increment();
+            _samples.put(connection,ZERO);
+        }
+    }
+
+    @Override
+    public void onClosed(Connection connection)
+    {
+        if (isStarted())
+        {
+            int msgsIn=connection.getMessagesIn();
+            int msgsOut=connection.getMessagesOut();
+            _messagesIn.set(msgsIn);
+            _messagesOut.set(msgsOut);
+            _connectionStats.decrement();
+            _connectionDurationStats.set(System.currentTimeMillis()-connection.getCreatedTimeStamp());
+
+            Sample sample=_samples.remove(connection);
+            if (sample!=null)
+            {
+                _closedIn.addAndGet(msgsIn-sample._messagesIn);
+                _closedOut.addAndGet(msgsOut-sample._messagesOut);
+            }
+        }
+    }
+
+    @ManagedAttribute("Total number of bytes received by this connector")
+    public int getBytesIn()
+    {
+        // TODO
+        return -1;
+    }
+
+    @ManagedAttribute("Total number of bytes sent by this connector")
+    public int getBytesOut()
+    {
+        // TODO
+        return -1;
+    }
+
+    @ManagedAttribute("Total number of connections seen by this connector")
+    public int getConnections()
+    {
+        return (int)_connectionStats.getTotal();
+    }
+
+    @ManagedAttribute("Connection duration maximum in ms")
+    public long getConnectionDurationMax()
+    {
+        return _connectionDurationStats.getMax();
+    }
+
+    @ManagedAttribute("Connection duration mean in ms")
+    public double getConnectionDurationMean()
+    {
+        return _connectionDurationStats.getMean();
+    }
+
+    @ManagedAttribute("Connection duration standard deviation")
+    public double getConnectionDurationStdDev()
+    {
+        return _connectionDurationStats.getStdDev();
+    }
+
+    @ManagedAttribute("Messages In for all connections")
+    public int getMessagesIn()
+    {
+        return (int)_messagesIn.getTotal();
+    }
+
+    @ManagedAttribute("Messages In per connection maximum")
+    public int getMessagesInPerConnectionMax()
+    {
+        return (int)_messagesIn.getMax();
+    }
+
+    @ManagedAttribute("Messages In per connection mean")
+    public double getMessagesInPerConnectionMean()
+    {
+        return _messagesIn.getMean();
+    }
+
+    @ManagedAttribute("Messages In per connection standard deviation")
+    public double getMessagesInPerConnectionStdDev()
+    {
+        return _messagesIn.getStdDev();
+    }
+
+    @ManagedAttribute("Connections open")
+    public int getConnectionsOpen()
+    {
+        return (int)_connectionStats.getCurrent();
+    }
+
+    @ManagedAttribute("Connections open maximum")
+    public int getConnectionsOpenMax()
+    {
+        return (int)_connectionStats.getMax();
+    }
+
+    @ManagedAttribute("Messages Out for all connections")
+    public int getMessagesOut()
+    {
+        return (int)_messagesIn.getTotal();
+    }
+
+    @ManagedAttribute("Messages In per connection maximum")
+    public int getMessagesOutPerConnectionMax()
+    {
+        return (int)_messagesIn.getMax();
+    }
+
+    @ManagedAttribute("Messages In per connection mean")
+    public double getMessagesOutPerConnectionMean()
+    {
+        return _messagesIn.getMean();
+    }
+
+    @ManagedAttribute("Messages In per connection standard deviation")
+    public double getMessagesOutPerConnectionStdDev()
+    {
+        return _messagesIn.getStdDev();
+    }
+
+    @ManagedAttribute("Connection statistics started ms since epoch")
+    public long getStartedMillis()
+    {
+        long start = _startMillis.get();
+        return start < 0 ? 0 : System.currentTimeMillis() - start;
+    }
+
+    @ManagedAttribute("Messages in per second calculated over period since last called")
+    public int getMessagesInPerSecond()
+    {
+        update();
+        return _messagesInPerSecond;
+    }
+
+    @ManagedAttribute("Messages out per second calculated over period since last called")
+    public int getMessagesOutPerSecond()
+    {
+        update();
+        return _messagesOutPerSecond;
+    }
+
+    @Override
+    public void doStart()
+    {
+        reset();
+    }
+
+    @Override
+    public void doStop()
+    {
+        _samples.clear();
+    }
+
+    @ManagedOperation("Reset the statistics")
+    public void reset()
+    {
+        _startMillis.set(System.currentTimeMillis());
+        _messagesIn.reset();
+        _messagesOut.reset();
+        _connectionStats.reset();
+        _connectionDurationStats.reset();
+        _samples.clear();
+    }
+
+    @Override
+    @ManagedOperation("dump thread state")
+    public String dump()
+    {
+        return ContainerLifeCycle.dump(this);
+    }
+
+    @Override
+    public void dump(Appendable out, String indent) throws IOException
+    {
+        ContainerLifeCycle.dumpObject(out,this);
+        ContainerLifeCycle.dump(out,indent,Arrays.asList(new String[]{"connections="+_connectionStats,"duration="+_connectionDurationStats,"in="+_messagesIn,"out="+_messagesOut}));
+    }
+    
+    public static void addToAllConnectors(Server server)
+    {
+        for (Connector connector : server.getConnectors())
+        {
+            if (connector instanceof Container)
+             ((Container)connector).addBean(new ConnectorStatistics());
+        }
+    }  
+    
+    private static final long SECOND_NANOS=TimeUnit.SECONDS.toNanos(1);
+    private synchronized void update()
+    {
+        long now=System.nanoTime();
+        long then=_nanoStamp.get();
+        long duration=now-then;
+                
+        if (duration>SECOND_NANOS/2)
+        {
+            if (_nanoStamp.compareAndSet(then,now))
+            {
+                long msgsIn=_closedIn.getAndSet(0);
+                long msgsOut=_closedOut.getAndSet(0);
+
+                for (Map.Entry<Connection, Sample> entry : _samples.entrySet())
+                {
+                    Connection connection=entry.getKey();
+                    Sample sample = entry.getValue();
+                    Sample next = new Sample(connection);
+                    if (_samples.replace(connection,sample,next))
+                    {
+                        msgsIn+=next._messagesIn-sample._messagesIn;
+                        msgsOut+=next._messagesOut-sample._messagesOut;
+                    }
+                }
+                
+                _messagesInPerSecond=(int)(msgsIn*SECOND_NANOS/duration);
+                _messagesOutPerSecond=(int)(msgsOut*SECOND_NANOS/duration);
+            }
+        }
+    }
+    
+    private static class Sample
+    {
+        Sample()
+        {
+            _messagesIn=0;
+            _messagesOut=0;
+        }
+        
+        Sample(Connection connection)
+        {
+            _messagesIn=connection.getMessagesIn();
+            _messagesOut=connection.getMessagesOut();
+        }
+        
+        final int _messagesIn;
+        final int _messagesOut;
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/server/CookieCutter.java b/lib/jetty/org/eclipse/jetty/server/CookieCutter.java
new file mode 100644 (file)
index 0000000..703f804
--- /dev/null
@@ -0,0 +1,328 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Locale;
+
+import javax.servlet.http.Cookie;
+
+import org.eclipse.jetty.util.QuotedStringTokenizer;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+
+/* ------------------------------------------------------------ */
+/** Cookie parser
+ * <p>Optimized stateful cookie parser.  Cookies fields are added with the
+ * {@link #addCookieField(String)} method and parsed on the next subsequent
+ * call to {@link #getCookies()}.
+ * If the added fields are identical to those last added (as strings), then the 
+ * cookies are not re parsed.
+ * 
+ *
+ */
+public class CookieCutter
+{
+    private static final Logger LOG = Log.getLogger(CookieCutter.class);
+
+    private Cookie[] _cookies;
+    private Cookie[] _lastCookies;
+    private final List<String> _fieldList = new ArrayList<>();
+    int _fields;
+    
+    public CookieCutter()
+    {  
+    }
+    
+    public Cookie[] getCookies()
+    {
+        if (_cookies!=null)
+            return _cookies;
+        
+        if (_lastCookies!=null && _fields==_fieldList.size())
+            _cookies=_lastCookies;
+        else
+            parseFields();
+        _lastCookies=_cookies;
+        return _cookies;
+    }
+    
+    public void setCookies(Cookie[] cookies)
+    {
+        _cookies=cookies;
+        _lastCookies=null;
+        _fieldList.clear();
+        _fields=0;
+    }
+    
+    public void reset()
+    {
+        _cookies=null;
+        _fields=0;
+    }
+    
+    public void addCookieField(String f)
+    {
+        if (f==null)
+            return;
+        f=f.trim();
+        if (f.length()==0)
+            return;
+            
+        if (_fieldList.size()>_fields)
+        {
+            if (f.equals(_fieldList.get(_fields)))
+            {
+                _fields++;
+                return;
+            }
+            
+            while (_fieldList.size()>_fields)
+                _fieldList.remove(_fields);
+        }
+        _cookies=null;
+        _lastCookies=null;
+        _fieldList.add(_fields++,f);
+    }
+    
+    
+    protected void parseFields()
+    {
+        _lastCookies=null;
+        _cookies=null;
+        
+        List<Cookie> cookies = new ArrayList<>();
+
+        int version = 0;
+
+        // delete excess fields
+        while (_fieldList.size()>_fields)
+            _fieldList.remove(_fields);
+        
+        // For each cookie field
+        for (String hdr : _fieldList)
+        {
+            // Parse the header
+            String name = null;
+            String value = null;
+
+            Cookie cookie = null;
+
+            boolean invalue=false;
+            boolean quoted=false;
+            boolean escaped=false;
+            int tokenstart=-1;
+            int tokenend=-1;
+            for (int i = 0, length = hdr.length(), last=length-1; i < length; i++)
+            {
+                char c = hdr.charAt(i);
+                
+                // Handle quoted values for name or value
+                if (quoted)
+                {
+                    if (escaped)
+                    {
+                        escaped=false;
+                        continue;
+                    }
+                    
+                    switch (c)
+                    {
+                        case '"':
+                            tokenend=i;
+                            quoted=false;
+
+                            // handle quote as last character specially
+                            if (i==last)
+                            {
+                                if (invalue)
+                                    value = hdr.substring(tokenstart, tokenend+1);
+                                else
+                                {
+                                    name = hdr.substring(tokenstart, tokenend+1);
+                                    value = "";
+                                }
+                            }
+                            break;
+                            
+                        case '\\':
+                            escaped=true;
+                            continue;
+                        default:
+                            continue;
+                    }
+                }
+                else
+                {
+                    // Handle name and value state machines
+                    if (invalue)
+                    {
+                        // parse the value
+                        switch (c)
+                        {
+                            case ' ':
+                            case '\t':
+                                continue;
+                                
+                            case '"':
+                                if (tokenstart<0)
+                                {
+                                    quoted=true;
+                                    tokenstart=i;
+                                }
+                                tokenend=i;
+                                if (i==last)
+                                {
+                                    value = hdr.substring(tokenstart, tokenend+1);
+                                    break;
+                                }
+                                continue;
+
+                            case ';':
+                                if (tokenstart>=0)
+                                    value = hdr.substring(tokenstart, tokenend+1);
+                                else
+                                    value="";
+                                tokenstart = -1;
+                                invalue=false;
+                                break;
+                                
+                            default:
+                                if (tokenstart<0)
+                                    tokenstart=i;
+                                tokenend=i;
+                                if (i==last)
+                                {
+                                    value = hdr.substring(tokenstart, tokenend+1);
+                                    break;
+                                }
+                                continue;
+                        }
+                    }
+                    else
+                    {
+                        // parse the name
+                        switch (c)
+                        {
+                            case ' ':
+                            case '\t':
+                                continue;
+                                
+                            case '"':
+                                if (tokenstart<0)
+                                {
+                                    quoted=true;
+                                    tokenstart=i;
+                                }
+                                tokenend=i;
+                                if (i==last)
+                                {
+                                    name = hdr.substring(tokenstart, tokenend+1);
+                                    value = "";
+                                    break;
+                                }
+                                continue;
+
+                            case ';':
+                                if (tokenstart>=0)
+                                {
+                                    name = hdr.substring(tokenstart, tokenend+1);
+                                    value = "";
+                                }
+                                tokenstart = -1;
+                                break;
+
+                            case '=':
+                                if (tokenstart>=0)
+                                    name = hdr.substring(tokenstart, tokenend+1);
+                                tokenstart = -1;
+                                invalue=true;
+                                continue;
+                                
+                            default:
+                                if (tokenstart<0)
+                                    tokenstart=i;
+                                tokenend=i;
+                                if (i==last)
+                                {
+                                    name = hdr.substring(tokenstart, tokenend+1);
+                                    value = "";
+                                    break;
+                                }
+                                continue;
+                        }
+                    }
+                }
+
+                // If after processing the current character we have a value and a name, then it is a cookie
+                if (value!=null && name!=null)
+                {
+                    name=QuotedStringTokenizer.unquoteOnly(name);
+                    value=QuotedStringTokenizer.unquoteOnly(value);
+                    
+                    try
+                    {
+                        if (name.startsWith("$"))
+                        {
+                            String lowercaseName = name.toLowerCase(Locale.ENGLISH);
+                            if ("$path".equals(lowercaseName))
+                            {
+                                if (cookie!=null)
+                                    cookie.setPath(value);
+                            }
+                            else if ("$domain".equals(lowercaseName))
+                            {
+                                if (cookie!=null)
+                                    cookie.setDomain(value);
+                            }
+                            else if ("$port".equals(lowercaseName))
+                            {
+                                if (cookie!=null)
+                                    cookie.setComment("$port="+value);
+                            }
+                            else if ("$version".equals(lowercaseName))
+                            {
+                                version = Integer.parseInt(value);
+                            }
+                        }
+                        else
+                        {
+                            cookie = new Cookie(name, value);
+                            if (version > 0)
+                                cookie.setVersion(version);
+                            cookies.add(cookie);
+                        }
+                    }
+                    catch (Exception e)
+                    {
+                        LOG.debug(e);
+                    }
+
+                    name = null;
+                    value = null;
+                }
+            }
+        }
+
+        _cookies = (Cookie[]) cookies.toArray(new Cookie[cookies.size()]);
+        _lastCookies=_cookies;
+    }
+    
+}
diff --git a/lib/jetty/org/eclipse/jetty/server/Dispatcher.java b/lib/jetty/org/eclipse/jetty/server/Dispatcher.java
new file mode 100644 (file)
index 0000000..2567289
--- /dev/null
@@ -0,0 +1,456 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server;
+
+import java.io.IOException;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.HashSet;
+import javax.servlet.DispatcherType;
+import javax.servlet.RequestDispatcher;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.server.handler.ContextHandler;
+import org.eclipse.jetty.util.Attributes;
+import org.eclipse.jetty.util.MultiMap;
+
+public class Dispatcher implements RequestDispatcher
+{
+    /** Dispatch include attribute names */
+    public final static String __INCLUDE_PREFIX="javax.servlet.include.";
+
+    /** Dispatch include attribute names */
+    public final static String __FORWARD_PREFIX="javax.servlet.forward.";
+
+    private final ContextHandler _contextHandler;
+    private final String _uri;
+    private final String _path;
+    private final String _query;
+    private final String _named;
+
+    public Dispatcher(ContextHandler contextHandler, String uri, String pathInContext, String query)
+    {
+        _contextHandler=contextHandler;
+        _uri=uri;
+        _path=pathInContext;
+        _query=query;
+        _named=null;
+    }
+
+    public Dispatcher(ContextHandler contextHandler, String name) throws IllegalStateException
+    {
+        _contextHandler=contextHandler;
+        _named=name;
+        _uri=null;
+        _path=null;
+        _query=null;
+    }
+
+    @Override
+    public void forward(ServletRequest request, ServletResponse response) throws ServletException, IOException
+    {
+        forward(request, response, DispatcherType.FORWARD);
+    }
+
+    public void error(ServletRequest request, ServletResponse response) throws ServletException, IOException
+    {
+        forward(request, response, DispatcherType.ERROR);
+    }
+
+    @Override
+    public void include(ServletRequest request, ServletResponse response) throws ServletException, IOException
+    {
+        Request baseRequest=(request instanceof Request)?((Request)request):HttpChannel.getCurrentHttpChannel().getRequest();
+
+        if (!(request instanceof HttpServletRequest))
+            request = new ServletRequestHttpWrapper(request);
+        if (!(response instanceof HttpServletResponse))
+            response = new ServletResponseHttpWrapper(response);
+
+        final DispatcherType old_type = baseRequest.getDispatcherType();
+        final Attributes old_attr=baseRequest.getAttributes();
+        final MultiMap<String> old_query_params=baseRequest.getQueryParameters();
+        try
+        {
+            baseRequest.setDispatcherType(DispatcherType.INCLUDE);
+            baseRequest.getResponse().include();
+            if (_named!=null)
+            {
+                _contextHandler.handle(_named,baseRequest, (HttpServletRequest)request, (HttpServletResponse)response);
+            }
+            else
+            {
+                IncludeAttributes attr = new IncludeAttributes(old_attr);
+
+                attr._requestURI=_uri;
+                attr._contextPath=_contextHandler.getContextPath();
+                attr._servletPath=null; // set by ServletHandler
+                attr._pathInfo=_path;
+                attr._query=_query;
+
+                if (_query!=null)
+                    baseRequest.mergeQueryParameters(_query, false);
+                baseRequest.setAttributes(attr);
+
+                _contextHandler.handle(_path, baseRequest, (HttpServletRequest)request, (HttpServletResponse)response);
+            }
+        }
+        finally
+        {
+            baseRequest.setAttributes(old_attr);
+            baseRequest.getResponse().included();
+            baseRequest.setQueryParameters(old_query_params);
+            baseRequest.resetParameters();
+            baseRequest.setDispatcherType(old_type);
+        }
+    }
+
+    protected void forward(ServletRequest request, ServletResponse response, DispatcherType dispatch) throws ServletException, IOException
+    {
+        Request baseRequest=(request instanceof Request)?((Request)request):HttpChannel.getCurrentHttpChannel().getRequest();
+        Response base_response=baseRequest.getResponse();
+        base_response.resetForForward();
+
+        if (!(request instanceof HttpServletRequest))
+            request = new ServletRequestHttpWrapper(request);
+        if (!(response instanceof HttpServletResponse))
+            response = new ServletResponseHttpWrapper(response);
+
+        final boolean old_handled=baseRequest.isHandled();
+        final String old_uri=baseRequest.getRequestURI();
+        final String old_context_path=baseRequest.getContextPath();
+        final String old_servlet_path=baseRequest.getServletPath();
+        final String old_path_info=baseRequest.getPathInfo();
+        final String old_query=baseRequest.getQueryString();
+        final MultiMap<String> old_query_params=baseRequest.getQueryParameters();
+        final Attributes old_attr=baseRequest.getAttributes();
+        final DispatcherType old_type=baseRequest.getDispatcherType();
+
+        try
+        {
+            baseRequest.setHandled(false);
+            baseRequest.setDispatcherType(dispatch);
+
+            if (_named!=null)
+            {
+                _contextHandler.handle(_named, baseRequest, (HttpServletRequest)request, (HttpServletResponse)response);
+            }
+            else
+            {
+                ForwardAttributes attr = new ForwardAttributes(old_attr);
+
+                //If we have already been forwarded previously, then keep using the established
+                //original value. Otherwise, this is the first forward and we need to establish the values.
+                //Note: the established value on the original request for pathInfo and
+                //for queryString is allowed to be null, but cannot be null for the other values.
+                if (old_attr.getAttribute(FORWARD_REQUEST_URI) != null)
+                {
+                    attr._pathInfo=(String)old_attr.getAttribute(FORWARD_PATH_INFO);
+                    attr._query=(String)old_attr.getAttribute(FORWARD_QUERY_STRING);
+                    attr._requestURI=(String)old_attr.getAttribute(FORWARD_REQUEST_URI);
+                    attr._contextPath=(String)old_attr.getAttribute(FORWARD_CONTEXT_PATH);
+                    attr._servletPath=(String)old_attr.getAttribute(FORWARD_SERVLET_PATH);
+                }
+                else
+                {
+                    attr._pathInfo=old_path_info;
+                    attr._query=old_query;
+                    attr._requestURI=old_uri;
+                    attr._contextPath=old_context_path;
+                    attr._servletPath=old_servlet_path;
+                }
+
+                baseRequest.setRequestURI(_uri);
+                baseRequest.setContextPath(_contextHandler.getContextPath());
+                baseRequest.setServletPath(null);
+                baseRequest.setPathInfo(_uri);
+                if (_query!=null)
+                    baseRequest.mergeQueryParameters(_query, true);
+                baseRequest.setAttributes(attr);
+
+                _contextHandler.handle(_path, baseRequest, (HttpServletRequest)request, (HttpServletResponse)response);
+
+                if (!baseRequest.getHttpChannelState().isAsync())
+                    commitResponse(response,baseRequest);
+            }
+        }
+        finally
+        {
+            baseRequest.setHandled(old_handled);
+            baseRequest.setRequestURI(old_uri);
+            baseRequest.setContextPath(old_context_path);
+            baseRequest.setServletPath(old_servlet_path);
+            baseRequest.setPathInfo(old_path_info);
+            baseRequest.setQueryString(old_query);
+            baseRequest.setQueryParameters(old_query_params);
+            baseRequest.resetParameters();
+            baseRequest.setAttributes(old_attr);
+            baseRequest.setDispatcherType(old_type);
+        }
+    }
+
+    private void commitResponse(ServletResponse response, Request baseRequest) throws IOException
+    {
+        if (baseRequest.getResponse().isWriting())
+        {
+            try
+            {
+                response.getWriter().close();
+            }
+            catch (IllegalStateException e)
+            {
+                response.getOutputStream().close();
+            }
+        }
+        else
+        {
+            try
+            {
+                response.getOutputStream().close();
+            }
+            catch (IllegalStateException e)
+            {
+                response.getWriter().close();
+            }
+        }
+    }
+
+    private class ForwardAttributes implements Attributes
+    {
+        final Attributes _attr;
+
+        String _requestURI;
+        String _contextPath;
+        String _servletPath;
+        String _pathInfo;
+        String _query;
+
+        ForwardAttributes(Attributes attributes)
+        {
+            _attr=attributes;
+        }
+
+        /* ------------------------------------------------------------ */
+        @Override
+        public Object getAttribute(String key)
+        {
+            if (Dispatcher.this._named==null)
+            {
+                if (key.equals(FORWARD_PATH_INFO))
+                    return _pathInfo;
+                if (key.equals(FORWARD_REQUEST_URI))
+                    return _requestURI;
+                if (key.equals(FORWARD_SERVLET_PATH))
+                    return _servletPath;
+                if (key.equals(FORWARD_CONTEXT_PATH))
+                    return _contextPath;
+                if (key.equals(FORWARD_QUERY_STRING))
+                    return _query;
+            }
+
+            if (key.startsWith(__INCLUDE_PREFIX))
+                return null;
+
+            return _attr.getAttribute(key);
+        }
+
+        @Override
+        public Enumeration<String> getAttributeNames()
+        {
+            HashSet<String> set=new HashSet<>();
+            Enumeration<String> e=_attr.getAttributeNames();
+            while(e.hasMoreElements())
+            {
+                String name=e.nextElement();
+                if (!name.startsWith(__INCLUDE_PREFIX) &&
+                    !name.startsWith(__FORWARD_PREFIX))
+                    set.add(name);
+            }
+
+            if (_named==null)
+            {
+                if (_pathInfo!=null)
+                    set.add(FORWARD_PATH_INFO);
+                else
+                    set.remove(FORWARD_PATH_INFO);
+                set.add(FORWARD_REQUEST_URI);
+                set.add(FORWARD_SERVLET_PATH);
+                set.add(FORWARD_CONTEXT_PATH);
+                if (_query!=null)
+                    set.add(FORWARD_QUERY_STRING);
+                else
+                    set.remove(FORWARD_QUERY_STRING);
+            }
+
+            return Collections.enumeration(set);
+        }
+
+        @Override
+        public void setAttribute(String key, Object value)
+        {
+            if (_named==null && key.startsWith("javax.servlet."))
+            {
+                if (key.equals(FORWARD_PATH_INFO))
+                    _pathInfo=(String)value;
+                else if (key.equals(FORWARD_REQUEST_URI))
+                    _requestURI=(String)value;
+                else if (key.equals(FORWARD_SERVLET_PATH))
+                    _servletPath=(String)value;
+                else if (key.equals(FORWARD_CONTEXT_PATH))
+                    _contextPath=(String)value;
+                else if (key.equals(FORWARD_QUERY_STRING))
+                    _query=(String)value;
+
+                else if (value==null)
+                    _attr.removeAttribute(key);
+                else
+                    _attr.setAttribute(key,value);
+            }
+            else if (value==null)
+                _attr.removeAttribute(key);
+            else
+                _attr.setAttribute(key,value);
+        }
+
+        @Override
+        public String toString()
+        {
+            return "FORWARD+"+_attr.toString();
+        }
+
+        @Override
+        public void clearAttributes()
+        {
+            throw new IllegalStateException();
+        }
+
+        @Override
+        public void removeAttribute(String name)
+        {
+            setAttribute(name,null);
+        }
+    }
+
+    private class IncludeAttributes implements Attributes
+    {
+        final Attributes _attr;
+
+        String _requestURI;
+        String _contextPath;
+        String _servletPath;
+        String _pathInfo;
+        String _query;
+
+        IncludeAttributes(Attributes attributes)
+        {
+            _attr=attributes;
+        }
+
+        @Override
+        public Object getAttribute(String key)
+        {
+            if (Dispatcher.this._named==null)
+            {
+                if (key.equals(INCLUDE_PATH_INFO))    return _pathInfo;
+                if (key.equals(INCLUDE_SERVLET_PATH)) return _servletPath;
+                if (key.equals(INCLUDE_CONTEXT_PATH)) return _contextPath;
+                if (key.equals(INCLUDE_QUERY_STRING)) return _query;
+                if (key.equals(INCLUDE_REQUEST_URI))  return _requestURI;
+            }
+            else if (key.startsWith(__INCLUDE_PREFIX))
+                    return null;
+
+
+            return _attr.getAttribute(key);
+        }
+
+        @Override
+        public Enumeration<String> getAttributeNames()
+        {
+            HashSet<String> set=new HashSet<>();
+            Enumeration<String> e=_attr.getAttributeNames();
+            while(e.hasMoreElements())
+            {
+                String name=e.nextElement();
+                if (!name.startsWith(__INCLUDE_PREFIX))
+                    set.add(name);
+            }
+
+            if (_named==null)
+            {
+                if (_pathInfo!=null)
+                    set.add(INCLUDE_PATH_INFO);
+                else
+                    set.remove(INCLUDE_PATH_INFO);
+                set.add(INCLUDE_REQUEST_URI);
+                set.add(INCLUDE_SERVLET_PATH);
+                set.add(INCLUDE_CONTEXT_PATH);
+                if (_query!=null)
+                    set.add(INCLUDE_QUERY_STRING);
+                else
+                    set.remove(INCLUDE_QUERY_STRING);
+            }
+
+            return Collections.enumeration(set);
+        }
+
+        @Override
+        public void setAttribute(String key, Object value)
+        {
+            if (_named==null && key.startsWith("javax.servlet."))
+            {
+                if (key.equals(INCLUDE_PATH_INFO))         _pathInfo=(String)value;
+                else if (key.equals(INCLUDE_REQUEST_URI))  _requestURI=(String)value;
+                else if (key.equals(INCLUDE_SERVLET_PATH)) _servletPath=(String)value;
+                else if (key.equals(INCLUDE_CONTEXT_PATH)) _contextPath=(String)value;
+                else if (key.equals(INCLUDE_QUERY_STRING)) _query=(String)value;
+                else if (value==null)
+                    _attr.removeAttribute(key);
+                else
+                    _attr.setAttribute(key,value);
+            }
+            else if (value==null)
+                _attr.removeAttribute(key);
+            else
+                _attr.setAttribute(key,value);
+        }
+
+        @Override
+        public String toString()
+        {
+            return "INCLUDE+"+_attr.toString();
+        }
+
+        @Override
+        public void clearAttributes()
+        {
+            throw new IllegalStateException();
+        }
+
+        @Override
+        public void removeAttribute(String name)
+        {
+            setAttribute(name,null);
+        }
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/server/EncodingHttpWriter.java b/lib/jetty/org/eclipse/jetty/server/EncodingHttpWriter.java
new file mode 100644 (file)
index 0000000..88301d4
--- /dev/null
@@ -0,0 +1,70 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server;
+
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.io.UnsupportedEncodingException;
+import java.io.Writer;
+
+/**
+ *
+ */
+public class EncodingHttpWriter extends HttpWriter
+{
+    final Writer _converter;
+
+    /* ------------------------------------------------------------ */
+    public EncodingHttpWriter(HttpOutput out, String encoding)
+    {
+        super(out);
+        try
+        {
+            _converter = new OutputStreamWriter(_bytes, encoding);
+        }
+        catch (UnsupportedEncodingException e)
+        {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public void write (char[] s,int offset, int length) throws IOException
+    {
+        HttpOutput out = _out;
+        if (length==0 && out.isAllContentWritten())
+        {
+            out.close();
+            return;
+        }
+            
+        while (length > 0)
+        {
+            _bytes.reset();
+            int chars = length>MAX_OUTPUT_CHARS?MAX_OUTPUT_CHARS:length;
+
+            _converter.write(s, offset, chars);
+            _converter.flush();
+            _bytes.writeTo(out);
+            length-=chars;
+            offset+=chars;
+        }
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/server/ForwardedRequestCustomizer.java b/lib/jetty/org/eclipse/jetty/server/ForwardedRequestCustomizer.java
new file mode 100644 (file)
index 0000000..891e8ee
--- /dev/null
@@ -0,0 +1,291 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server;
+
+import java.net.InetSocketAddress;
+
+import javax.servlet.ServletRequest;
+
+import org.eclipse.jetty.http.HttpFields;
+import org.eclipse.jetty.http.HttpHeader;
+import org.eclipse.jetty.http.HttpScheme;
+import org.eclipse.jetty.server.HttpConfiguration.Customizer;
+
+
+/* ------------------------------------------------------------ */
+/** Customize Requests for Proxy Forwarding.
+ * <p>
+ * This customizer looks at at HTTP request for headers that indicate
+ * it has been forwarded by one or more proxies.  Specifically handled are:
+ * <ul>
+ * <li>X-Forwarded-Host</li>
+ * <li>X-Forwarded-Server</li>
+ * <li>X-Forwarded-For</li>
+ * <li>X-Forwarded-Proto</li>
+ * </ul>
+ * <p>If these headers are present, then the {@link Request} object is updated
+ * so that the proxy is not seen as the other end point of the connection on which
+ * the request came</p>
+ * <p>Headers can also be defined so that forwarded SSL Session IDs and Cipher
+ * suites may be customised</p> 
+ * @see <a href="http://en.wikipedia.org/wiki/X-Forwarded-For">Wikipedia: X-Forwarded-For</a>
+ */
+public class ForwardedRequestCustomizer implements Customizer
+{
+    private String _hostHeader;
+    private String _forwardedHostHeader = HttpHeader.X_FORWARDED_HOST.toString();
+    private String _forwardedServerHeader = HttpHeader.X_FORWARDED_SERVER.toString();
+    private String _forwardedForHeader = HttpHeader.X_FORWARDED_FOR.toString();
+    private String _forwardedProtoHeader = HttpHeader.X_FORWARDED_PROTO.toString();
+    private String _forwardedCipherSuiteHeader;
+    private String _forwardedSslSessionIdHeader;
+    
+
+    /* ------------------------------------------------------------ */
+    public String getHostHeader()
+    {
+        return _hostHeader;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Set a forced valued for the host header to control what is returned by {@link ServletRequest#getServerName()} and {@link ServletRequest#getServerPort()}.
+     *
+     * @param hostHeader
+     *            The value of the host header to force.
+     */
+    public void setHostHeader(String hostHeader)
+    {
+        _hostHeader = hostHeader;
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     *
+     * @see #setForwarded(boolean)
+     */
+    public String getForwardedHostHeader()
+    {
+        return _forwardedHostHeader;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param forwardedHostHeader
+     *            The header name for forwarded hosts (default x-forwarded-host)
+     */
+    public void setForwardedHostHeader(String forwardedHostHeader)
+    {
+        _forwardedHostHeader = forwardedHostHeader;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return the header name for forwarded server.
+     */
+    public String getForwardedServerHeader()
+    {
+        return _forwardedServerHeader;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param forwardedServerHeader
+     *            The header name for forwarded server (default x-forwarded-server)
+     */
+    public void setForwardedServerHeader(String forwardedServerHeader)
+    {
+        _forwardedServerHeader = forwardedServerHeader;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return the forwarded for header
+     */
+    public String getForwardedForHeader()
+    {
+        return _forwardedForHeader;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param forwardedRemoteAddressHeader
+     *            The header name for forwarded for (default x-forwarded-for)
+     */
+    public void setForwardedForHeader(String forwardedRemoteAddressHeader)
+    {
+        _forwardedForHeader = forwardedRemoteAddressHeader;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Get the forwardedProtoHeader.
+     *
+     * @return the forwardedProtoHeader (default X-Forwarded-For)
+     */
+    public String getForwardedProtoHeader()
+    {
+        return _forwardedProtoHeader;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Set the forwardedProtoHeader.
+     *
+     * @param forwardedProtoHeader
+     *            the forwardedProtoHeader to set (default X-Forwarded-For)
+     */
+    public void setForwardedProtoHeader(String forwardedProtoHeader)
+    {
+        _forwardedProtoHeader = forwardedProtoHeader;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return The header name holding a forwarded cipher suite (default null)
+     */
+    public String getForwardedCipherSuiteHeader()
+    {
+        return _forwardedCipherSuiteHeader;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param forwardedCipherSuite
+     *            The header name holding a forwarded cipher suite (default null)
+     */
+    public void setForwardedCipherSuiteHeader(String forwardedCipherSuite)
+    {
+        _forwardedCipherSuiteHeader = forwardedCipherSuite;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return The header name holding a forwarded SSL Session ID (default null)
+     */
+    public String getForwardedSslSessionIdHeader()
+    {
+        return _forwardedSslSessionIdHeader;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param forwardedSslSessionId
+     *            The header name holding a forwarded SSL Session ID (default null)
+     */
+    public void setForwardedSslSessionIdHeader(String forwardedSslSessionId)
+    {
+        _forwardedSslSessionIdHeader = forwardedSslSessionId;
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public void customize(Connector connector, HttpConfiguration config, Request request)
+    {
+        HttpFields httpFields = request.getHttpFields();
+
+        // Do SSL first
+        if (getForwardedCipherSuiteHeader()!=null)
+        {
+            String cipher_suite=httpFields.getStringField(getForwardedCipherSuiteHeader());
+            if (cipher_suite!=null)
+                request.setAttribute("javax.servlet.request.cipher_suite",cipher_suite);
+        }
+        if (getForwardedSslSessionIdHeader()!=null)
+        {
+            String ssl_session_id=httpFields.getStringField(getForwardedSslSessionIdHeader());
+            if(ssl_session_id!=null)
+            {
+                request.setAttribute("javax.servlet.request.ssl_session_id", ssl_session_id);
+                request.setScheme(HttpScheme.HTTPS.asString());
+            }
+        }
+
+        // Retrieving headers from the request
+        String forwardedHost = getLeftMostFieldValue(httpFields,getForwardedHostHeader());
+        String forwardedServer = getLeftMostFieldValue(httpFields,getForwardedServerHeader());
+        String forwardedFor = getLeftMostFieldValue(httpFields,getForwardedForHeader());
+        String forwardedProto = getLeftMostFieldValue(httpFields,getForwardedProtoHeader());
+
+        if (_hostHeader != null)
+        {
+            // Update host header
+            httpFields.put(HttpHeader.HOST.toString(),_hostHeader);
+            request.setServerName(null);
+            request.setServerPort(-1);
+            request.getServerName();
+        }
+        else if (forwardedHost != null)
+        {
+            // Update host header
+            httpFields.put(HttpHeader.HOST.toString(),forwardedHost);
+            request.setServerName(null);
+            request.setServerPort(-1);
+            request.getServerName();
+        }
+        else if (forwardedServer != null)
+        {
+            // Use provided server name
+            request.setServerName(forwardedServer);
+        }
+
+        if (forwardedFor != null)
+        {
+            request.setRemoteAddr(InetSocketAddress.createUnresolved(forwardedFor,request.getRemotePort()));
+        }
+
+        if (forwardedProto != null)
+        {
+            request.setScheme(forwardedProto);
+            if (forwardedProto.equals(config.getSecureScheme()))
+                request.setSecure(true);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    protected String getLeftMostFieldValue(HttpFields fields, String header)
+    {
+        if (header == null)
+            return null;
+
+        String headerValue = fields.getStringField(header);
+
+        if (headerValue == null)
+            return null;
+
+        int commaIndex = headerValue.indexOf(',');
+
+        if (commaIndex == -1)
+        {
+            // Single value
+            return headerValue;
+        }
+
+        // The left-most value is the farthest downstream client
+        return headerValue.substring(0,commaIndex);
+    }
+
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public String toString()
+    {
+        return String.format("%s@%x",this.getClass().getSimpleName(),hashCode());
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/server/Handler.java b/lib/jetty/org/eclipse/jetty/server/Handler.java
new file mode 100644 (file)
index 0000000..cfe7b9a
--- /dev/null
@@ -0,0 +1,79 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server;
+
+import java.io.IOException;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.server.handler.HandlerCollection;
+import org.eclipse.jetty.server.handler.HandlerWrapper;
+import org.eclipse.jetty.util.annotation.ManagedAttribute;
+import org.eclipse.jetty.util.annotation.ManagedObject;
+import org.eclipse.jetty.util.annotation.ManagedOperation;
+import org.eclipse.jetty.util.component.Destroyable;
+import org.eclipse.jetty.util.component.LifeCycle;
+
+/* ------------------------------------------------------------ */
+/** A Jetty Server Handler.
+ *
+ * A Handler instance is required by a {@link Server} to handle incoming
+ * HTTP requests.  A Handler may: <ul>
+ * <li>Completely generate the HTTP Response</li>
+ * <li>Examine/modify the request and call another Handler (see {@link HandlerWrapper}).
+ * <li>Pass the request to one or more other Handlers (see {@link HandlerCollection}).
+ * </ul>
+ *
+ * Handlers are passed the servlet API request and response object, but are
+ * not Servlets.  The servlet container is implemented by handlers for
+ * context, security, session and servlet that modify the request object
+ * before passing it to the next stage of handling.
+ *
+ */
+@ManagedObject("Jetty Handler")
+public interface Handler extends LifeCycle, Destroyable
+{
+    /* ------------------------------------------------------------ */
+    /** Handle a request.
+     * @param target The target of the request - either a URI or a name.
+     * @param baseRequest The original unwrapped request object.
+     * @param request The request either as the {@link Request}
+     * object or a wrapper of that request. The {@link HttpChannel#getCurrentHttpChannel()}
+     * method can be used access the Request object if required.
+     * @param response The response as the {@link Response}
+     * object or a wrapper of that request. The {@link HttpChannel#getCurrentHttpChannel()}
+     * method can be used access the Response object if required.
+     * @throws IOException
+     * @throws ServletException
+     */
+    public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response)
+        throws IOException, ServletException;
+
+    public void setServer(Server server);
+
+    @ManagedAttribute(value="the jetty server for this handler", readonly=true)
+    public Server getServer();
+
+    @ManagedOperation(value="destroy associated resources", impact="ACTION")
+    public void destroy();
+
+}
+
diff --git a/lib/jetty/org/eclipse/jetty/server/HandlerContainer.java b/lib/jetty/org/eclipse/jetty/server/HandlerContainer.java
new file mode 100644 (file)
index 0000000..51fd479
--- /dev/null
@@ -0,0 +1,62 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server;
+
+import org.eclipse.jetty.util.annotation.ManagedAttribute;
+import org.eclipse.jetty.util.annotation.ManagedObject;
+import org.eclipse.jetty.util.component.LifeCycle;
+
+/**
+ * A Handler that contains other Handlers.
+ * <p>
+ * The contained handlers may be one (see @{link {@link org.eclipse.jetty.server.handler.HandlerWrapper})
+ * or many (see {@link org.eclipse.jetty.server.handler.HandlerList} or {@link org.eclipse.jetty.server.handler.HandlerCollection}. 
+ *
+ */
+@ManagedObject("Handler of Multiple Handlers")
+public interface HandlerContainer extends LifeCycle
+{
+    /* ------------------------------------------------------------ */
+    /**
+     * @return array of handlers directly contained by this handler.
+     */
+    @ManagedAttribute("handlers in this container")
+    public Handler[] getHandlers();
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * @return array of all handlers contained by this handler and it's children
+     */
+    @ManagedAttribute("all contained handlers")
+    public Handler[] getChildHandlers();
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * @param byclass
+     * @return array of all handlers contained by this handler and it's children of the passed type.
+     */
+    public Handler[] getChildHandlersByClass(Class<?> byclass);
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * @param byclass
+     * @return first handler of all handlers contained by this handler and it's children of the passed type.
+     */
+    public <T extends Handler> T getChildHandlerByClass(Class<T> byclass);
+}
diff --git a/lib/jetty/org/eclipse/jetty/server/HostHeaderCustomizer.java b/lib/jetty/org/eclipse/jetty/server/HostHeaderCustomizer.java
new file mode 100644 (file)
index 0000000..b39b25d
--- /dev/null
@@ -0,0 +1,73 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server;
+
+import java.util.Objects;
+
+import javax.servlet.http.HttpServletRequest;
+
+/**
+ * Customizes requests that lack the {@code Host} header (for example, HTTP 1.0 requests).
+ * <p />
+ * In case of HTTP 1.0 requests that lack the {@code Host} header, the application may issue
+ * a redirect, and the {@code Location} header is usually constructed from the {@code Host}
+ * header; if the {@code Host} header is missing, the server may query the connector for its
+ * IP address in order to construct the {@code Location} header, and thus leak to clients
+ * internal IP addresses.
+ * <p />
+ * This {@link HttpConfiguration.Customizer} is configured with a {@code serverName} and
+ * optionally a {@code serverPort}.
+ * If the {@code Host} header is absent, the configured {@code serverName} will be set on
+ * the request so that {@link HttpServletRequest#getServerName()} will return that value,
+ * and likewise for {@code serverPort} and {@link HttpServletRequest#getServerPort()}.
+ */
+public class HostHeaderCustomizer implements HttpConfiguration.Customizer
+{
+    private final String serverName;
+    private final int serverPort;
+
+    /**
+     * @param serverName the {@code serverName} to set on the request (the {@code serverPort} will not be set)
+     */
+    public HostHeaderCustomizer(String serverName)
+    {
+        this(serverName, 0);
+    }
+
+    /**
+     * @param serverName the {@code serverName} to set on the request
+     * @param serverPort the {@code serverPort} to set on the request
+     */
+    public HostHeaderCustomizer(String serverName, int serverPort)
+    {
+        this.serverName = Objects.requireNonNull(serverName);
+        this.serverPort = serverPort;
+    }
+
+    @Override
+    public void customize(Connector connector, HttpConfiguration channelConfig, Request request)
+    {
+        if (request.getHeader("Host") == null)
+        {
+            request.setServerName(serverName);
+            if (serverPort > 0)
+                request.setServerPort(serverPort);
+        }
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/server/HttpChannel.java b/lib/jetty/org/eclipse/jetty/server/HttpChannel.java
new file mode 100644 (file)
index 0000000..07b09ab
--- /dev/null
@@ -0,0 +1,860 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server;
+
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.nio.ByteBuffer;
+import java.nio.channels.ClosedChannelException;
+import java.nio.charset.StandardCharsets;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import javax.servlet.DispatcherType;
+import javax.servlet.RequestDispatcher;
+import javax.servlet.http.HttpServletRequest;
+
+import org.eclipse.jetty.http.HttpField;
+import org.eclipse.jetty.http.HttpFields;
+import org.eclipse.jetty.http.HttpGenerator;
+import org.eclipse.jetty.http.HttpGenerator.ResponseInfo;
+import org.eclipse.jetty.http.HttpHeader;
+import org.eclipse.jetty.http.HttpHeaderValue;
+import org.eclipse.jetty.http.HttpMethod;
+import org.eclipse.jetty.http.HttpParser;
+import org.eclipse.jetty.http.HttpStatus;
+import org.eclipse.jetty.http.HttpURI;
+import org.eclipse.jetty.http.HttpVersion;
+import org.eclipse.jetty.http.MimeTypes;
+import org.eclipse.jetty.io.ByteBufferPool;
+import org.eclipse.jetty.io.ChannelEndPoint;
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.io.EofException;
+import org.eclipse.jetty.server.HttpChannelState.Action;
+import org.eclipse.jetty.server.handler.ContextHandler;
+import org.eclipse.jetty.server.handler.ErrorHandler;
+import org.eclipse.jetty.util.Callback;
+import org.eclipse.jetty.util.SharedBlockingCallback.Blocker;
+import org.eclipse.jetty.util.URIUtil;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.thread.Scheduler;
+
+
+/* ------------------------------------------------------------ */
+/** HttpChannel.
+ * Represents a single endpoint for HTTP semantic processing.
+ * The HttpChannel is both a HttpParser.RequestHandler, where it passively receives events from
+ * an incoming HTTP request, and a Runnable, where it actively takes control of the request/response
+ * life cycle and calls the application (perhaps suspending and resuming with multiple calls to run).
+ * The HttpChannel signals the switch from passive mode to active mode by returning true to one of the
+ * HttpParser.RequestHandler callbacks.   The completion of the active phase is signalled by a call to
+ * HttpTransport.completed().
+ *
+ */
+public class HttpChannel<T> implements HttpParser.RequestHandler<T>, Runnable, HttpParser.ProxyHandler
+{
+    private static final Logger LOG = Log.getLogger(HttpChannel.class);
+    private static final ThreadLocal<HttpChannel<?>> __currentChannel = new ThreadLocal<>();
+
+    /* ------------------------------------------------------------ */
+    /** Get the current channel that this thread is dispatched to.
+     * @see Request#getAttribute(String) for a more general way to access the HttpChannel
+     * @return the current HttpChannel or null
+     */
+    public static HttpChannel<?> getCurrentHttpChannel()
+    {
+        return __currentChannel.get();
+    }
+
+    protected static HttpChannel<?> setCurrentHttpChannel(HttpChannel<?> channel)
+    {
+        HttpChannel<?> last=__currentChannel.get();
+        if (channel==null)
+            __currentChannel.remove();
+        else 
+            __currentChannel.set(channel);
+        return last;
+    }
+
+    private final AtomicBoolean _committed = new AtomicBoolean();
+    private final AtomicInteger _requests = new AtomicInteger();
+    private final Connector _connector;
+    private final HttpConfiguration _configuration;
+    private final EndPoint _endPoint;
+    private final HttpTransport _transport;
+    private final HttpURI _uri;
+    private final HttpChannelState _state;
+    private final Request _request;
+    private final Response _response;
+    private HttpVersion _version = HttpVersion.HTTP_1_1;
+    private boolean _expect = false;
+    private boolean _expect100Continue = false;
+    private boolean _expect102Processing = false;
+
+    public HttpChannel(Connector connector, HttpConfiguration configuration, EndPoint endPoint, HttpTransport transport, HttpInput<T> input)
+    {
+        _connector = connector;
+        _configuration = configuration;
+        _endPoint = endPoint;
+        _transport = transport;
+
+        _uri = new HttpURI(URIUtil.__CHARSET);
+        _state = new HttpChannelState(this);
+        input.init(_state);
+        _request = new Request(this, input);
+        _response = new Response(this, new HttpOutput(this));
+    }
+
+    public HttpChannelState getState()
+    {
+        return _state;
+    }
+
+    public HttpVersion getHttpVersion()
+    {
+        return _version;
+    }
+    /**
+     * @return the number of requests handled by this connection
+     */
+    public int getRequests()
+    {
+        return _requests.get();
+    }
+
+    public Connector getConnector()
+    {
+        return _connector;
+    }
+
+    public HttpTransport getHttpTransport()
+    {
+        return _transport;
+    }
+    
+    public ByteBufferPool getByteBufferPool()
+    {
+        return _connector.getByteBufferPool();
+    }
+
+    public HttpConfiguration getHttpConfiguration()
+    {
+        return _configuration;
+    }
+
+    public Server getServer()
+    {
+        return _connector.getServer();
+    }
+
+    public Request getRequest()
+    {
+        return _request;
+    }
+
+    public Response getResponse()
+    {
+        return _response;
+    }
+
+    public EndPoint getEndPoint()
+    {
+        return _endPoint;
+    }
+
+    public InetSocketAddress getLocalAddress()
+    {
+        return _endPoint.getLocalAddress();
+    }
+
+    public InetSocketAddress getRemoteAddress()
+    {
+        return _endPoint.getRemoteAddress();
+    }
+
+    @Override
+    public int getHeaderCacheSize()
+    {
+        return _configuration.getHeaderCacheSize();
+    }
+
+    /**
+     * If the associated response has the Expect header set to 100 Continue,
+     * then accessing the input stream indicates that the handler/servlet
+     * is ready for the request body and thus a 100 Continue response is sent.
+     *
+     * @throws IOException if the InputStream cannot be created
+     */
+    public void continue100(int available) throws IOException
+    {
+        // If the client is expecting 100 CONTINUE, then send it now.
+        // TODO: consider using an AtomicBoolean ?
+        if (isExpecting100Continue())
+        {
+            _expect100Continue = false;
+
+            // is content missing?
+            if (available == 0)
+            {
+                if (_response.isCommitted())
+                    throw new IOException("Committed before 100 Continues");
+
+                // TODO: break this dependency with HttpGenerator
+                boolean committed = sendResponse(HttpGenerator.CONTINUE_100_INFO, null, false);
+                if (!committed)
+                    throw new IOException("Concurrent commit while trying to send 100-Continue");
+            }
+        }
+    }
+
+    public void reset()
+    {
+        _committed.set(false);
+        _expect = false;
+        _expect100Continue = false;
+        _expect102Processing = false;
+        _request.recycle();
+        _response.recycle();
+        _uri.clear();
+    }
+
+    @Override
+    public void run()
+    {
+        handle();
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return True if the channel is ready to continue handling (ie it is not suspended)
+     */
+    public boolean handle()
+    {
+        LOG.debug("{} handle enter", this);
+
+        final HttpChannel<?>last = setCurrentHttpChannel(this);
+
+        String threadName = null;
+        if (LOG.isDebugEnabled())
+        {
+            threadName = Thread.currentThread().getName();
+            Thread.currentThread().setName(threadName + " - " + _uri);
+        }
+
+        HttpChannelState.Action action = _state.handling();
+        try
+        {
+            // Loop here to handle async request redispatches.
+            // The loop is controlled by the call to async.unhandle in the
+            // finally block below.  Unhandle will return false only if an async dispatch has
+            // already happened when unhandle is called.
+            loop: while (action.ordinal()<HttpChannelState.Action.WAIT.ordinal() && getServer().isRunning())
+            {
+                boolean error=false;
+                try
+                {
+                    LOG.debug("{} action {}",this,action);
+
+                    switch(action)
+                    {
+                        case REQUEST_DISPATCH:
+                            _request.setHandled(false);
+                            _response.getHttpOutput().reopen();
+                            _request.setDispatcherType(DispatcherType.REQUEST);
+
+                            for (HttpConfiguration.Customizer customizer : _configuration.getCustomizers())
+                                customizer.customize(getConnector(),_configuration,_request);
+                            getServer().handle(this);
+                            break;
+
+                        case ASYNC_DISPATCH:
+                            _request.setHandled(false);
+                            _response.getHttpOutput().reopen();
+                            _request.setDispatcherType(DispatcherType.ASYNC);
+                            getServer().handleAsync(this);
+                            break;
+
+                        case ASYNC_EXPIRED:
+                            _request.setHandled(false);
+                            _response.getHttpOutput().reopen();
+                            _request.setDispatcherType(DispatcherType.ERROR);
+
+                            Throwable ex=_state.getAsyncContextEvent().getThrowable();
+                            String reason="Async Timeout";
+                            if (ex!=null)
+                            {
+                                reason="Async Exception";
+                                _request.setAttribute(RequestDispatcher.ERROR_EXCEPTION,ex);
+                            }
+                            _request.setAttribute(RequestDispatcher.ERROR_STATUS_CODE,new Integer(500));
+                            _request.setAttribute(RequestDispatcher.ERROR_MESSAGE,reason);
+                            _request.setAttribute(RequestDispatcher.ERROR_REQUEST_URI,_request.getRequestURI());
+
+                            _response.setStatusWithReason(500,reason);
+
+                            
+                            ErrorHandler eh = ErrorHandler.getErrorHandler(getServer(),_state.getContextHandler());                                
+                            if (eh instanceof ErrorHandler.ErrorPageMapper)
+                            {
+                                String error_page=((ErrorHandler.ErrorPageMapper)eh).getErrorPage((HttpServletRequest)_state.getAsyncContextEvent().getSuppliedRequest());
+                                if (error_page!=null)
+                                    _state.getAsyncContextEvent().setDispatchPath(error_page);
+                            }
+
+                            getServer().handleAsync(this);
+                            break;
+
+                        case READ_CALLBACK:
+                        {
+                            ContextHandler handler=_state.getContextHandler();
+                            if (handler!=null)
+                                handler.handle(_request.getHttpInput());
+                            else
+                                _request.getHttpInput().run();
+                            break;
+                        }
+
+                        case WRITE_CALLBACK:
+                        {
+                            ContextHandler handler=_state.getContextHandler();
+
+                            if (handler!=null)
+                                handler.handle(_response.getHttpOutput());
+                            else
+                                _response.getHttpOutput().run();
+                            break;
+                        }   
+
+                        default:
+                            break loop;
+
+                    }
+                }
+                catch (Error e)
+                {
+                    if ("ContinuationThrowable".equals(e.getClass().getSimpleName()))
+                        LOG.ignore(e);
+                    else
+                    {
+                        error=true;
+                        throw e;
+                    }
+                }
+                catch (Exception e)
+                {
+                    error=true;
+                    if (e instanceof EofException)
+                        LOG.debug(e);
+                    else
+                        LOG.warn(String.valueOf(_uri), e);
+                    _state.error(e);
+                    _request.setHandled(true);
+                    handleException(e);
+                }
+                finally
+                {
+                    if (error && _state.isAsyncStarted())
+                        _state.errorComplete();
+                    action = _state.unhandle();
+                }
+            }
+
+        }
+        finally
+        {
+            setCurrentHttpChannel(last);
+            if (threadName != null && LOG.isDebugEnabled())
+                Thread.currentThread().setName(threadName);
+        }
+
+        if (action==Action.COMPLETE)
+        {
+            try
+            {
+                _state.completed();
+
+                if (!_response.isCommitted() && !_request.isHandled())
+                    _response.sendError(404);
+                else
+                    // Complete generating the response
+                    _response.closeOutput();
+            }
+            catch(EofException|ClosedChannelException e)
+            {
+                LOG.debug(e);
+            }
+            catch(Exception e)
+            {
+                LOG.warn("complete failed",e);
+            }
+            finally
+            {
+                _request.setHandled(true);
+                _transport.completed();
+            }
+        }
+
+        LOG.debug("{} handle exit, result {}", this, action);
+
+        return action!=Action.WAIT;
+    }
+
+    /**
+     * <p>Sends an error 500, performing a special logic to detect whether the request is suspended,
+     * to avoid concurrent writes from the application.</p>
+     * <p>It may happen that the application suspends, and then throws an exception, while an application
+     * spawned thread writes the response content; in such case, we attempt to commit the error directly
+     * bypassing the {@link ErrorHandler} mechanisms and the response OutputStream.</p>
+     *
+     * @param x the Throwable that caused the problem
+     */
+    protected void handleException(Throwable x)
+    {
+        try
+        {
+            _request.setAttribute(RequestDispatcher.ERROR_EXCEPTION,x);
+            _request.setAttribute(RequestDispatcher.ERROR_EXCEPTION_TYPE,x.getClass());
+            if (_state.isSuspended())
+            {
+                HttpFields fields = new HttpFields();
+                fields.add(HttpHeader.CONNECTION,HttpHeaderValue.CLOSE);
+                ResponseInfo info = new ResponseInfo(_request.getHttpVersion(), fields, 0, HttpStatus.INTERNAL_SERVER_ERROR_500, null, _request.isHead());
+                boolean committed = sendResponse(info, null, true);
+                if (!committed)
+                    LOG.warn("Could not send response error 500: "+x);
+                _request.getAsyncContext().complete();
+            }
+            else if (isCommitted())
+            {
+                _transport.abort();
+                if (!(x instanceof EofException))
+                    LOG.warn("Could not send response error 500: "+x);
+            }
+            else
+            {
+                _response.setHeader(HttpHeader.CONNECTION.asString(),HttpHeaderValue.CLOSE.asString());
+                _response.sendError(500, x.getMessage());
+            }
+        }
+        catch (IOException e)
+        {
+            // We tried our best, just log
+            LOG.debug("Could not commit response error 500", e);
+        }
+    }
+
+    public boolean isExpecting100Continue()
+    {
+        return _expect100Continue;
+    }
+
+    public boolean isExpecting102Processing()
+    {
+        return _expect102Processing;
+    }
+
+    @Override
+    public String toString()
+    {
+        return String.format("%s@%x{r=%s,a=%s,uri=%s}",
+                getClass().getSimpleName(),
+                hashCode(),
+                _requests,
+                _state.getState(),
+                _state.getState()==HttpChannelState.State.IDLE?"-":_request.getRequestURI()
+            );
+    }
+
+    @Override
+    public void proxied(String protocol, String sAddr, String dAddr, int sPort, int dPort)
+    {
+        _request.setAttribute("PROXY", protocol);
+        _request.setServerName(sAddr);
+        _request.setServerPort(dPort);
+        _request.setRemoteAddr(InetSocketAddress.createUnresolved(sAddr,sPort));
+    }
+    
+    @Override
+    public boolean startRequest(HttpMethod httpMethod, String method, ByteBuffer uri, HttpVersion version)
+    {
+        _expect = false;
+        _expect100Continue = false;
+        _expect102Processing = false;
+
+        _request.setTimeStamp(System.currentTimeMillis());
+        _request.setMethod(httpMethod, method);
+
+        if (httpMethod == HttpMethod.CONNECT)
+            _uri.parseConnect(uri.array(),uri.arrayOffset()+uri.position(),uri.remaining());
+        else
+            _uri.parse(uri.array(),uri.arrayOffset()+uri.position(),uri.remaining());
+        _request.setUri(_uri);
+
+        String path;
+        try
+        {
+            path = _uri.getDecodedPath();
+        }
+        catch (Exception e)
+        {
+            LOG.warn("Failed UTF-8 decode for request path, trying ISO-8859-1");
+            LOG.ignore(e);
+            path = _uri.getDecodedPath(StandardCharsets.ISO_8859_1);
+        }
+        
+        String info = URIUtil.canonicalPath(path);
+
+        if (info == null)
+        {
+            if( path==null && _uri.getScheme()!=null &&_uri.getHost()!=null)
+            {
+                info = "/";
+                _request.setRequestURI("");
+            }
+            else
+            {
+                badMessage(400,null);
+                return true;
+            }
+        }
+        _request.setPathInfo(info);
+        _version = version == null ? HttpVersion.HTTP_0_9 : version;
+        _request.setHttpVersion(_version);
+
+        return false;
+    }
+
+    @Override
+    public boolean parsedHeader(HttpField field)
+    {
+        HttpHeader header=field.getHeader();
+        String value=field.getValue();
+        if (value == null)
+            value = "";
+        if (header != null)
+        {
+            switch (header)
+            {
+                case EXPECT:
+                    if (_version.getVersion()>=HttpVersion.HTTP_1_1.getVersion())
+                    {
+                        HttpHeaderValue expect = HttpHeaderValue.CACHE.get(value);
+                        switch (expect == null ? HttpHeaderValue.UNKNOWN : expect)
+                        {
+                            case CONTINUE:
+                                _expect100Continue = true;
+                                break;
+
+                            case PROCESSING:
+                                _expect102Processing = true;
+                                break;
+
+                            default:
+                                String[] values = value.split(",");
+                                for (int i = 0; values != null && i < values.length; i++)
+                                {
+                                    expect = HttpHeaderValue.CACHE.get(values[i].trim());
+                                    if (expect == null)
+                                        _expect = true;
+                                    else
+                                    {
+                                        switch (expect)
+                                        {
+                                            case CONTINUE:
+                                                _expect100Continue = true;
+                                                break;
+                                            case PROCESSING:
+                                                _expect102Processing = true;
+                                                break;
+                                            default:
+                                                _expect = true;
+                                        }
+                                    }
+                                }
+                        }
+                    }
+                    break;
+
+                case CONTENT_TYPE:
+                    MimeTypes.Type mime = MimeTypes.CACHE.get(value);
+                    String charset = (mime == null || mime.getCharset() == null) ? MimeTypes.getCharsetFromContentType(value) : mime.getCharset().toString();
+                    if (charset != null)
+                        _request.setCharacterEncodingUnchecked(charset);
+                    break;
+                default:
+            }
+        }
+
+        if (field.getName()!=null)
+            _request.getHttpFields().add(field);
+        return false;
+    }
+
+    @Override
+    public boolean parsedHostHeader(String host, int port)
+    {
+        if (_uri.getHost()==null)
+        {
+            _request.setServerName(host);
+            _request.setServerPort(port);
+        }
+        return false;
+    }
+
+    @Override
+    public boolean headerComplete()
+    {
+        _requests.incrementAndGet();
+        switch (_version)
+        {
+            case HTTP_0_9:
+                break;
+
+            case HTTP_1_0:
+                if (_configuration.getSendDateHeader())
+                    _response.getHttpFields().put(_connector.getServer().getDateField());
+                break;
+
+            case HTTP_1_1:
+                if (_configuration.getSendDateHeader())
+                    _response.getHttpFields().put(_connector.getServer().getDateField());
+
+                if (_expect)
+                {
+                    badMessage(HttpStatus.EXPECTATION_FAILED_417,null);
+                    return true;
+                }
+
+                break;
+
+            default:
+                throw new IllegalStateException();
+        }
+
+        return true;
+    }
+
+    @Override
+    public boolean content(T item)
+    {
+        if (LOG.isDebugEnabled())
+            LOG.debug("{} content {}", this, item);
+        @SuppressWarnings("unchecked")
+        HttpInput<T> input = (HttpInput<T>)_request.getHttpInput();
+        input.content(item);
+
+        return false;
+    }
+
+    @Override
+    public boolean messageComplete()
+    {
+        LOG.debug("{} messageComplete", this);
+        _request.getHttpInput().messageComplete();
+        return true;
+    }
+
+    @Override
+    public void earlyEOF()
+    {
+        _request.getHttpInput().earlyEOF();
+    }
+
+    @Override
+    public void badMessage(int status, String reason)
+    {
+        if (status < 400 || status > 599)
+            status = HttpStatus.BAD_REQUEST_400;
+
+        try
+        {
+            if (_state.handling()==Action.REQUEST_DISPATCH)
+            {
+                ByteBuffer content=null;
+                HttpFields fields=new HttpFields();
+
+                ErrorHandler handler=getServer().getBean(ErrorHandler.class);
+                if (handler!=null)
+                    content=handler.badMessageError(status,reason,fields);
+
+                sendResponse(new ResponseInfo(HttpVersion.HTTP_1_1,fields,0,status,reason,false),content ,true);
+            }
+        }
+        catch (IOException e)
+        {
+            LOG.debug(e);
+        }
+        finally
+        {
+            if (_state.unhandle()==Action.COMPLETE)
+                _state.completed();
+            else 
+                throw new IllegalStateException();
+        }
+    }
+
+    protected boolean sendResponse(ResponseInfo info, ByteBuffer content, boolean complete, final Callback callback)
+    {
+        // TODO check that complete only set true once by changing _committed to AtomicRef<Enum>
+        boolean committing = _committed.compareAndSet(false, true);
+        if (committing)
+        {
+            // We need an info to commit
+            if (info==null)
+                info = _response.newResponseInfo();
+
+            // wrap callback to process 100 or 500 responses
+            final int status=info.getStatus();
+            final Callback committed = (status<200&&status>=100)?new Commit100Callback(callback):new CommitCallback(callback);
+
+            // committing write
+            _transport.send(info, content, complete, committed);
+        }
+        else if (info==null)
+        {
+            // This is a normal write
+            _transport.send(content, complete, callback);
+        }
+        else
+        {
+            callback.failed(new IllegalStateException("committed"));
+        }
+        return committing;
+    }
+
+    protected boolean sendResponse(ResponseInfo info, ByteBuffer content, boolean complete) throws IOException
+    {
+        try(Blocker blocker = _response.getHttpOutput().acquireWriteBlockingCallback())
+        {
+            boolean committing = sendResponse(info,content,complete,blocker);
+            blocker.block();
+            return committing;
+        }
+    }
+
+    public boolean isCommitted()
+    {
+        return _committed.get();
+    }
+
+    /**
+     * <p>Non-Blocking write, committing the response if needed.</p>
+     *
+     * @param content  the content buffer to write
+     * @param complete whether the content is complete for the response
+     * @param callback Callback when complete or failed
+     */
+    protected void write(ByteBuffer content, boolean complete, Callback callback)
+    {
+        sendResponse(null,content,complete,callback);
+    }
+
+    protected void execute(Runnable task)
+    {
+        _connector.getExecutor().execute(task);
+    }
+
+    public Scheduler getScheduler()
+    {
+        return _connector.getScheduler();
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return true if the HttpChannel can efficiently use direct buffer (typically this means it is not over SSL or a multiplexed protocol)
+     */
+    public boolean useDirectBuffers()
+    {
+        return getEndPoint() instanceof ChannelEndPoint;
+    }
+
+    /**
+     * If a write or similar to this channel fails this method should be called. The standard implementation
+     * of {@link #failed()} is a noop. But the different implementations of HttpChannel might want to take actions.
+     */
+    public void failed()
+    {
+    }
+
+    private class CommitCallback implements Callback
+    {
+        private final Callback _callback;
+
+        private CommitCallback(Callback callback)
+        {
+            _callback = callback;
+        }
+
+        @Override
+        public void succeeded()
+        {
+            _callback.succeeded();
+        }
+
+        @Override
+        public void failed(final Throwable x)
+        {
+            if (x instanceof EofException || x instanceof ClosedChannelException)
+            {
+                LOG.debug(x);
+                _callback.failed(x);
+                _response.getHttpOutput().closed();
+            }
+            else
+            {
+                LOG.warn("Commit failed",x);
+                _transport.send(HttpGenerator.RESPONSE_500_INFO,null,true,new Callback()
+                {
+                    @Override
+                    public void succeeded()
+                    {
+                        _callback.failed(x);
+                        _response.getHttpOutput().closed();
+                    }
+
+                    @Override
+                    public void failed(Throwable th)
+                    {
+                        LOG.ignore(th);
+                        _callback.failed(x);
+                        _response.getHttpOutput().closed();
+                    }
+                });
+            }
+        }
+    }
+
+    private class Commit100Callback extends CommitCallback
+    {
+        private Commit100Callback(Callback callback)
+        {
+            super(callback);
+        }
+
+        @Override
+        public void succeeded()
+        {
+             _committed.set(false);
+             super.succeeded();
+        }
+
+    }
+
+}
diff --git a/lib/jetty/org/eclipse/jetty/server/HttpChannelState.java b/lib/jetty/org/eclipse/jetty/server/HttpChannelState.java
new file mode 100644 (file)
index 0000000..7ff4d5a
--- /dev/null
@@ -0,0 +1,714 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+import javax.servlet.AsyncListener;
+import javax.servlet.RequestDispatcher;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletResponse;
+
+import org.eclipse.jetty.server.handler.ContextHandler;
+import org.eclipse.jetty.server.handler.ContextHandler.Context;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.thread.Scheduler;
+
+/**
+ * Implementation of AsyncContext interface that holds the state of request-response cycle.
+ */
+public class HttpChannelState
+{
+    private static final Logger LOG = Log.getLogger(HttpChannelState.class);
+
+    private final static long DEFAULT_TIMEOUT=30000L;
+
+    /** The dispatched state of the HttpChannel, used to control the overall livecycle
+     */
+    public enum State
+    {
+        IDLE,             // Idle request
+        DISPATCHED,       // Request dispatched to filter/servlet
+        ASYNC_WAIT,       // Suspended and parked
+        ASYNC_WOKEN,      // A thread has been dispatch to handle from ASYNCWAIT
+        ASYNC_IO,         // Has been dispatched for async IO
+        COMPLETING,       // Request is completable
+        COMPLETED         // Request is complete
+    }
+
+    /**
+     * The actions to take as the channel moves from state to state.
+     */
+    public enum Action
+    {
+        REQUEST_DISPATCH, // handle a normal request dispatch  
+        ASYNC_DISPATCH,   // handle an async request dispatch
+        ASYNC_EXPIRED,    // handle an async timeout
+        WRITE_CALLBACK,   // handle an IO write callback
+        READ_CALLBACK,    // handle an IO read callback
+        WAIT,             // Wait for further events 
+        COMPLETE          // Complete the channel
+    }
+    
+    /**
+     * The state of the servlet async API.  This can lead or follow the 
+     * channel dispatch state and also includes reasons such as expired,
+     * dispatched or completed.
+     */
+    public enum Async
+    {
+        STARTED,
+        DISPATCH,
+        COMPLETE,
+        EXPIRING,
+        EXPIRED
+    }
+
+    private final boolean DEBUG=LOG.isDebugEnabled();
+    private final HttpChannel<?> _channel;
+
+    private List<AsyncListener> _asyncListeners;
+    private State _state;
+    private Async _async;
+    private boolean _initial;
+    private boolean _asyncRead;
+    private boolean _asyncWrite;
+    private long _timeoutMs=DEFAULT_TIMEOUT;
+    private AsyncContextEvent _event;
+
+    protected HttpChannelState(HttpChannel<?> channel)
+    {
+        _channel=channel;
+        _state=State.IDLE;
+        _async=null;
+        _initial=true;
+    }
+
+    public State getState()
+    {
+        synchronized(this)
+        {
+            return _state;
+        }
+    }
+
+    public void addListener(AsyncListener listener)
+    {
+        synchronized(this)
+        {
+            if (_asyncListeners==null)
+                _asyncListeners=new ArrayList<>();
+            _asyncListeners.add(listener);
+        }
+    }
+
+    public void setTimeout(long ms)
+    {
+        synchronized(this)
+        {
+            _timeoutMs=ms;
+        }
+    }
+
+    public long getTimeout()
+    {
+        synchronized(this)
+        {
+            return _timeoutMs;
+        }
+    }
+
+    public AsyncContextEvent getAsyncContextEvent()
+    {
+        synchronized(this)
+        {
+            return _event;
+        }
+    }
+
+    @Override
+    public String toString()
+    {
+        synchronized (this)
+        {
+            return String.format("%s@%x{s=%s i=%b a=%s}",getClass().getSimpleName(),hashCode(),_state,_initial,_async);
+        }
+    }
+
+    public String getStatusString()
+    {
+        synchronized (this)
+        {
+            return String.format("s=%s i=%b a=%s",_state,_initial,_async);
+        }
+    }
+
+    /**
+     * @return Next handling of the request should proceed
+     */
+    protected Action handling()
+    {
+        synchronized (this)
+        {
+            if(DEBUG)
+                LOG.debug("{} handling {}",this,_state);
+            switch(_state)
+            {
+                case IDLE:
+                    _initial=true;
+                    _state=State.DISPATCHED;
+                    return Action.REQUEST_DISPATCH;
+
+                case COMPLETING:
+                    return Action.COMPLETE;
+
+                case COMPLETED:
+                    return Action.WAIT;
+
+                case ASYNC_WOKEN:
+                    if (_asyncRead)
+                    {
+                        _state=State.ASYNC_IO;
+                        _asyncRead=false;
+                        return Action.READ_CALLBACK;
+                    }
+                    if (_asyncWrite)
+                    {
+                        _state=State.ASYNC_IO;
+                        _asyncWrite=false;
+                        return Action.WRITE_CALLBACK;
+                    }
+                    
+                    if (_async!=null)
+                    {
+                        Async async=_async;
+                        switch(async)
+                        {
+                            case COMPLETE:
+                                _state=State.COMPLETING;
+                                return Action.COMPLETE;
+                            case DISPATCH:
+                                _state=State.DISPATCHED;
+                                _async=null;
+                                return Action.ASYNC_DISPATCH;
+                            case EXPIRING:
+                                break;
+                            case EXPIRED:
+                                _state=State.DISPATCHED;
+                                _async=null;
+                                return Action.ASYNC_EXPIRED;
+                            case STARTED:
+                                // TODO
+                                if (DEBUG)
+                                    LOG.debug("TODO Fix this double dispatch",new IllegalStateException(this
+                                            .getStatusString()));
+                                return Action.WAIT;
+                        }
+                    }
+                    
+                    return Action.WAIT;
+
+                default:
+                    throw new IllegalStateException(this.getStatusString());
+            }
+        }
+    }
+
+    public void startAsync(AsyncContextEvent event)
+    {
+        final List<AsyncListener> lastAsyncListeners;
+        
+        synchronized (this)
+        {
+            if (_state!=State.DISPATCHED || _async!=null)
+                throw new IllegalStateException(this.getStatusString());
+            
+            _async=Async.STARTED;
+            _event=event;
+            lastAsyncListeners=_asyncListeners;
+            _asyncListeners=null;
+        }
+
+        if (lastAsyncListeners!=null)
+        {
+            for (AsyncListener listener : lastAsyncListeners)
+            {
+                try
+                {
+                    listener.onStartAsync(event);
+                }
+                catch(Exception e)
+                {
+                    LOG.warn(e);
+                }
+            }
+        }
+    }
+
+    protected void error(Throwable th)
+    {
+        synchronized (this)
+        {
+            if (_event!=null)
+                _event.setThrowable(th);
+        }
+    }
+
+    /**
+     * Signal that the HttpConnection has finished handling the request.
+     * For blocking connectors, this call may block if the request has
+     * been suspended (startAsync called).
+     * @return next actions
+     * be handled again (eg because of a resume that happened before unhandle was called)
+     */
+    protected Action unhandle()
+    {
+        synchronized (this)
+        {
+            if(DEBUG)
+                LOG.debug("{} unhandle {}",this,_state);
+            
+            switch(_state)
+            {
+                case DISPATCHED:
+                case ASYNC_IO:
+                    break;
+                default:
+                    throw new IllegalStateException(this.getStatusString());
+            }
+
+            if (_asyncRead)
+            {
+                _state=State.ASYNC_IO;
+                _asyncRead=false;
+                return Action.READ_CALLBACK;
+            }
+            
+            if (_asyncWrite)
+            {
+                _asyncWrite=false;
+                _state=State.ASYNC_IO;
+                return Action.WRITE_CALLBACK;
+            }
+
+            if (_async!=null)
+            {
+                _initial=false;
+                switch(_async)
+                {
+                    case COMPLETE:
+                        _state=State.COMPLETING;
+                        _async=null;
+                        return Action.COMPLETE;
+                    case DISPATCH:
+                        _state=State.DISPATCHED;
+                        _async=null;
+                        return Action.ASYNC_DISPATCH;
+                    case EXPIRED:
+                        _state=State.DISPATCHED;
+                        _async=null;
+                        return Action.ASYNC_EXPIRED;
+                    case EXPIRING:
+                    case STARTED:
+                        scheduleTimeout();
+                        _state=State.ASYNC_WAIT;
+                        return Action.WAIT;
+                }
+            }
+            
+            _state=State.COMPLETING;
+            return Action.COMPLETE;
+        }
+    }
+
+    public void dispatch(ServletContext context, String path)
+    {
+        boolean dispatch;
+        synchronized (this)
+        {
+            if (_async!=Async.STARTED && _async!=Async.EXPIRING)
+                throw new IllegalStateException("AsyncContext#dispath "+this.getStatusString());
+            _async=Async.DISPATCH;
+            
+            if (context!=null)
+                _event.setDispatchContext(context);
+            if (path!=null)
+                _event.setDispatchPath(path);
+           
+            switch(_state)
+            {
+                case DISPATCHED:
+                case ASYNC_IO:
+                    dispatch=false;
+                    break;
+                case ASYNC_WAIT:
+                    _state=State.ASYNC_WOKEN;
+                    dispatch=true;
+                    break;
+                case ASYNC_WOKEN:
+                    dispatch=false;
+                    break;
+                default:
+                    LOG.warn("async dispatched when complete {}",this);
+                    dispatch=false;
+                    break;
+            }
+        }
+
+        cancelTimeout();
+        if (dispatch)
+            scheduleDispatch();
+    }
+
+    protected void expired()
+    {
+        final List<AsyncListener> aListeners;
+        AsyncContextEvent event;
+        synchronized (this)
+        {
+            if (_async!=Async.STARTED)
+                return;
+            _async=Async.EXPIRING;
+            event=_event;
+            aListeners=_asyncListeners;
+        }
+
+        if (aListeners!=null)
+        {
+            for (AsyncListener listener : aListeners)
+            {
+                try
+                {
+                    listener.onTimeout(event);
+                }
+                catch(Exception e)
+                {
+                    LOG.debug(e);
+                    event.setThrowable(e);
+                    _channel.getRequest().setAttribute(RequestDispatcher.ERROR_EXCEPTION,e);
+                    break;
+                }
+            }
+        }
+        
+        boolean dispatch=false;
+        synchronized (this)
+        {
+            if (_async==Async.EXPIRING)
+            {
+                _async=Async.EXPIRED;
+                if (_state==State.ASYNC_WAIT)
+                {
+                    _state=State.ASYNC_WOKEN;
+                    dispatch=true;
+                }
+            }
+        }
+
+        if (dispatch)
+            scheduleDispatch();
+    }
+
+    public void complete()
+    {
+        // just like resume, except don't set _dispatched=true;
+        boolean handle=false;
+        synchronized (this)
+        {
+            if (_async!=Async.STARTED && _async!=Async.EXPIRING)
+                throw new IllegalStateException(this.getStatusString());
+            _async=Async.COMPLETE;
+            if (_state==State.ASYNC_WAIT)
+            {
+                handle=true;
+                _state=State.ASYNC_WOKEN;
+            }
+        }
+
+        cancelTimeout();
+        if (handle)
+        {
+            ContextHandler handler=getContextHandler();
+            if (handler!=null)
+                handler.handle(_channel);
+            else
+                _channel.handle();
+        }
+    }
+
+    public void errorComplete()
+    {
+        synchronized (this)
+        {
+            _async=Async.COMPLETE;
+            _event.setDispatchContext(null);
+            _event.setDispatchPath(null);
+        }
+
+        cancelTimeout();
+    }
+
+    protected void completed()
+    {
+        final List<AsyncListener> aListeners;
+        final AsyncContextEvent event;
+        synchronized (this)
+        {
+            switch(_state)
+            {
+                case COMPLETING:
+                    _state=State.COMPLETED;
+                    aListeners=_asyncListeners;
+                    event=_event;
+                    break;
+
+                default:
+                    throw new IllegalStateException(this.getStatusString());
+            }
+        }
+
+        if (event!=null)
+        {
+            if (aListeners!=null)
+            {
+                if (event.getThrowable()!=null)
+                {
+                    event.getSuppliedRequest().setAttribute(RequestDispatcher.ERROR_EXCEPTION,event.getThrowable());
+                    event.getSuppliedRequest().setAttribute(RequestDispatcher.ERROR_MESSAGE,event.getThrowable().getMessage());
+                }
+
+                for (AsyncListener listener : aListeners)
+                {
+                    try
+                    {
+                        if (event.getThrowable()!=null)
+                            listener.onError(event);
+                        else
+                            listener.onComplete(event);
+                    }
+                    catch(Exception e)
+                    {
+                        LOG.warn(e);
+                    }
+                }
+            }
+
+            event.completed();
+        }
+    }
+
+    protected void recycle()
+    {
+        synchronized (this)
+        {
+            switch(_state)
+            {
+                case DISPATCHED:
+                case ASYNC_IO:
+                    throw new IllegalStateException(getStatusString());
+                default:
+                    break;
+            }
+            _asyncListeners=null;
+            _state=State.IDLE;
+            _async=null;
+            _initial=true;
+            _asyncRead=false;
+            _asyncWrite=false;
+            _timeoutMs=DEFAULT_TIMEOUT;
+            cancelTimeout();
+            _event=null;
+        }
+    }
+
+    protected void scheduleDispatch()
+    {
+        _channel.execute(_channel);
+    }
+
+    protected void scheduleTimeout()
+    {
+        Scheduler scheduler = _channel.getScheduler();
+        if (scheduler!=null && _timeoutMs>0)
+            _event.setTimeoutTask(scheduler.schedule(_event,_timeoutMs,TimeUnit.MILLISECONDS));
+    }
+
+    protected void cancelTimeout()
+    {
+        final AsyncContextEvent event;
+        synchronized (this)
+        { 
+            event=_event;
+        }
+        if (event!=null)
+            event.cancelTimeoutTask();
+    }
+
+    public boolean isExpired()
+    {
+        synchronized (this)
+        {
+            return _async==Async.EXPIRED;
+        }
+    }
+
+    public boolean isInitial()
+    {
+        synchronized(this)
+        {
+            return _initial;
+        }
+    }
+
+    public boolean isSuspended()
+    {
+        synchronized(this)
+        {
+            return _state==State.ASYNC_WAIT || _state==State.DISPATCHED && _async==Async.STARTED;
+        }
+    }
+
+    boolean isCompleting()
+    {
+        synchronized (this)
+        {
+            return _state==State.COMPLETING;
+        }
+    }
+
+    boolean isCompleted()
+    {
+        synchronized (this)
+        {
+            return _state == State.COMPLETED;
+        }
+    }
+
+    public boolean isAsyncStarted()
+    {
+        synchronized (this)
+        {    
+            if (_state==State.DISPATCHED)
+                return _async!=null;
+            return _async==Async.STARTED || _async==Async.EXPIRING;
+        }
+    }
+
+    public boolean isAsync()
+    {
+        synchronized (this)
+        {
+            return !_initial || _async!=null;
+        }
+    }
+
+    public Request getBaseRequest()
+    {
+        return _channel.getRequest();
+    }
+
+    public HttpChannel<?> getHttpChannel()
+    {
+        return _channel;
+    }
+
+    public ContextHandler getContextHandler()
+    {
+        final AsyncContextEvent event;
+        synchronized (this)
+        { 
+            event=_event;
+        }
+       
+        if (event!=null)
+        {
+            Context context=((Context)event.getServletContext());
+            if (context!=null)
+                return context.getContextHandler();
+        }
+        return null;
+    }
+
+    public ServletResponse getServletResponse()
+    {
+        final AsyncContextEvent event;
+        synchronized (this)
+        { 
+            event=_event;
+        }
+        if (event!=null && event.getSuppliedResponse()!=null)
+            return event.getSuppliedResponse();
+        return _channel.getResponse();
+    }
+
+    public Object getAttribute(String name)
+    {
+        return _channel.getRequest().getAttribute(name);
+    }
+
+    public void removeAttribute(String name)
+    {
+        _channel.getRequest().removeAttribute(name);
+    }
+
+    public void setAttribute(String name, Object attribute)
+    {
+        _channel.getRequest().setAttribute(name,attribute);
+    }
+
+    public void onReadPossible()
+    {
+        boolean handle=false;
+
+        synchronized (this)
+        {
+            _asyncRead=true;
+            if (_state==State.ASYNC_WAIT)
+            {
+                _state=State.ASYNC_WOKEN;
+                handle=true;
+            }
+        }
+
+        if (handle)
+            _channel.execute(_channel);
+    }
+    
+    public void onWritePossible()
+    {
+        boolean handle=false;
+
+        synchronized (this)
+        {
+            _asyncWrite=true;
+            if (_state==State.ASYNC_WAIT)
+            {
+                _state=State.ASYNC_WOKEN;
+                handle=true;
+            }
+        }
+
+        if (handle)
+            _channel.execute(_channel);
+    }
+    
+}
diff --git a/lib/jetty/org/eclipse/jetty/server/HttpConfiguration.java b/lib/jetty/org/eclipse/jetty/server/HttpConfiguration.java
new file mode 100644 (file)
index 0000000..352af25
--- /dev/null
@@ -0,0 +1,269 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server;
+
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+import org.eclipse.jetty.http.HttpScheme;
+import org.eclipse.jetty.util.Jetty;
+import org.eclipse.jetty.util.annotation.ManagedAttribute;
+import org.eclipse.jetty.util.annotation.ManagedObject;
+
+
+/* ------------------------------------------------------------ */
+/** HTTP Configuration.
+ * <p>This class is a holder of HTTP configuration for use by the 
+ * {@link HttpChannel} class.  Typically a HTTPConfiguration instance
+ * is instantiated and passed to a {@link ConnectionFactory} that can 
+ * create HTTP channels (eg HTTP, AJP or SPDY).</p>
+ * <p>The configuration held by this class is not for the wire protocol,
+ * but for the interpretation and handling of HTTP requests that could
+ * be transported by a variety of protocols.
+ * </p>
+ */
+@ManagedObject("HTTP Configuration")
+public class HttpConfiguration
+{
+    public static final String SERVER_VERSION = "Jetty(" + Jetty.VERSION + ")";
+
+    private List<Customizer> _customizers=new CopyOnWriteArrayList<>();
+    private int _outputBufferSize=32*1024;
+    private int _requestHeaderSize=8*1024;
+    private int _responseHeaderSize=8*1024;
+    private int _headerCacheSize=512;
+    private int _securePort;
+    private String _secureScheme = HttpScheme.HTTPS.asString();
+    private boolean _sendServerVersion = true; //send Server: header
+    private boolean _sendXPoweredBy = false; //send X-Powered-By: header
+    private boolean _sendDateHeader = true; //send Date: header
+
+    public interface Customizer
+    {
+        public void customize(Connector connector, HttpConfiguration channelConfig, Request request);
+    }
+    
+    public interface ConnectionFactory
+    {
+        HttpConfiguration getHttpConfiguration();
+    }
+    
+    public HttpConfiguration()
+    {
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** Create a configuration from another.
+     * @param config The configuration to copy.
+     */
+    public HttpConfiguration(HttpConfiguration config)
+    {
+        _customizers.addAll(config._customizers);
+        _outputBufferSize=config._outputBufferSize;
+        _requestHeaderSize=config._requestHeaderSize;
+        _responseHeaderSize=config._responseHeaderSize;
+        _securePort=config._securePort;
+        _secureScheme=config._secureScheme;
+        _sendDateHeader=config._sendDateHeader;
+        _sendServerVersion=config._sendServerVersion;
+        _headerCacheSize=config._headerCacheSize;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** 
+     * <p>Add a {@link Customizer} that is invoked for every 
+     * request received.</p>
+     * <p>Customiser are often used to interpret optional headers (eg {@link ForwardedRequestCustomizer}) or 
+     * optional protocol semantics (eg {@link SecureRequestCustomizer}). 
+     * @param customizer A request customizer
+     */
+    public void addCustomizer(Customizer customizer)
+    {
+        _customizers.add(customizer);
+    }
+    
+    /* ------------------------------------------------------------ */
+    public List<Customizer> getCustomizers()
+    {
+        return _customizers;
+    }
+    
+    public <T> T getCustomizer(Class<T> type)
+    {
+        for (Customizer c : _customizers)
+            if (type.isAssignableFrom(c.getClass()))
+                return (T)c;
+        return null;
+    }
+
+    @ManagedAttribute("The size in bytes of the output buffer used to aggregate HTTP output")
+    public int getOutputBufferSize()
+    {
+        return _outputBufferSize;
+    }
+    
+    @ManagedAttribute("The maximum allowed size in bytes for a HTTP request header")
+    public int getRequestHeaderSize()
+    {
+        return _requestHeaderSize;
+    }
+    
+    @ManagedAttribute("The maximum allowed size in bytes for a HTTP response header")
+    public int getResponseHeaderSize()
+    {
+        return _responseHeaderSize;
+    }
+
+    @ManagedAttribute("The maximum allowed size in bytes for a HTTP header field cache")
+    public int getHeaderCacheSize()
+    {
+        return _headerCacheSize;
+    }
+
+    @ManagedAttribute("The port to which Integral or Confidential security constraints are redirected")
+    public int getSecurePort()
+    {
+        return _securePort;
+    }
+    
+    @ManagedAttribute("The scheme with which Integral or Confidential security constraints are redirected")
+    public String getSecureScheme()
+    {
+        return _secureScheme;
+    }
+
+    public void setSendServerVersion (boolean sendServerVersion)
+    {
+        _sendServerVersion = sendServerVersion;
+    }
+
+    @ManagedAttribute("if true, send the Server header in responses")
+    public boolean getSendServerVersion()
+    {
+        return _sendServerVersion;
+    }
+    
+    public void setSendXPoweredBy (boolean sendXPoweredBy)
+    {
+        _sendXPoweredBy=sendXPoweredBy;
+    }
+
+    @ManagedAttribute("if true, send the X-Powered-By header in responses")
+    public boolean getSendXPoweredBy()
+    {
+        return _sendXPoweredBy;
+    }
+
+    public void setSendDateHeader(boolean sendDateHeader)
+    {
+        _sendDateHeader = sendDateHeader;
+    }
+
+    @ManagedAttribute("if true, include the date in HTTP headers")
+    public boolean getSendDateHeader()
+    {
+        return _sendDateHeader;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * <p>Set the {@link Customizer}s that are invoked for every 
+     * request received.</p>
+     * <p>Customisers are often used to interpret optional headers (eg {@link ForwardedRequestCustomizer}) or 
+     * optional protocol semantics (eg {@link SecureRequestCustomizer}). 
+     * @param customizers
+     */
+    public void setCustomizers(List<Customizer> customizers)
+    {
+        _customizers.clear();
+        _customizers.addAll(customizers);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Set the size of the buffer into which response content is aggregated
+     * before being sent to the client.  A larger buffer can improve performance by allowing
+     * a content producer to run without blocking, however larger buffers consume more memory and
+     * may induce some latency before a client starts processing the content.
+     * @param responseBufferSize buffer size in bytes.
+     */
+    public void setOutputBufferSize(int responseBufferSize)
+    {
+        _outputBufferSize = responseBufferSize;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** Set the maximum size of a request header.
+     * <p>Larger headers will allow for more and/or larger cookies plus larger form content encoded 
+     * in a URL. However, larger headers consume more memory and can make a server more vulnerable to denial of service
+     * attacks.</p>
+     * @param requestHeaderSize Max header size in bytes
+     */
+    public void setRequestHeaderSize(int requestHeaderSize)
+    {
+        _requestHeaderSize = requestHeaderSize;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Set the maximum size of a response header.
+     * 
+     * <p>Larger headers will allow for more and/or larger cookies and longer HTTP headers (eg for redirection). 
+     * However, larger headers will also consume more memory.</p>
+     * @param responseHeaderSize Response header size in bytes.
+     */
+    public void setResponseHeaderSize(int responseHeaderSize)
+    {
+        _responseHeaderSize = responseHeaderSize;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Set the header field cache size.
+     * @param headerCacheSize The size in bytes of the header field cache.
+     */
+    public void setHeaderCacheSize(int headerCacheSize)
+    {
+        _headerCacheSize = headerCacheSize;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Set the TCP/IP port used for CONFIDENTIAL and INTEGRAL 
+     * redirections.
+     * @param confidentialPort
+     */
+    public void setSecurePort(int confidentialPort)
+    {
+        _securePort = confidentialPort;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Set the  URI scheme used for CONFIDENTIAL and INTEGRAL 
+     * redirections.
+     * @param confidentialScheme A string like"https"
+     */
+    public void setSecureScheme(String confidentialScheme)
+    {
+        _secureScheme = confidentialScheme;
+    }
+
+    @Override
+    public String toString()
+    {
+        return String.format("%s@%x{%d,%d/%d,%s://:%d,%s}",this.getClass().getSimpleName(),hashCode(),_outputBufferSize,_requestHeaderSize,_responseHeaderSize,_secureScheme,_securePort,_customizers);
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/server/HttpConnection.java b/lib/jetty/org/eclipse/jetty/server/HttpConnection.java
new file mode 100644 (file)
index 0000000..16cbbf9
--- /dev/null
@@ -0,0 +1,790 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.concurrent.RejectedExecutionException;
+
+import org.eclipse.jetty.http.HttpGenerator;
+import org.eclipse.jetty.http.HttpGenerator.ResponseInfo;
+import org.eclipse.jetty.http.HttpHeader;
+import org.eclipse.jetty.http.HttpHeaderValue;
+import org.eclipse.jetty.http.HttpMethod;
+import org.eclipse.jetty.http.HttpParser;
+import org.eclipse.jetty.http.HttpStatus;
+import org.eclipse.jetty.http.HttpVersion;
+import org.eclipse.jetty.io.AbstractConnection;
+import org.eclipse.jetty.io.ByteBufferPool;
+import org.eclipse.jetty.io.Connection;
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.io.EofException;
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.Callback;
+import org.eclipse.jetty.util.IteratingCallback;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/**
+ * <p>A {@link Connection} that handles the HTTP protocol.</p>
+ */
+public class HttpConnection extends AbstractConnection implements Runnable, HttpTransport
+{
+    public static final String UPGRADE_CONNECTION_ATTRIBUTE = "org.eclipse.jetty.server.HttpConnection.UPGRADE";
+    private static final boolean REQUEST_BUFFER_DIRECT=false;
+    private static final boolean HEADER_BUFFER_DIRECT=false;
+    private static final boolean CHUNK_BUFFER_DIRECT=false;
+    private static final Logger LOG = Log.getLogger(HttpConnection.class);
+    private static final ThreadLocal<HttpConnection> __currentConnection = new ThreadLocal<>();
+
+    private final HttpConfiguration _config;
+    private final Connector _connector;
+    private final ByteBufferPool _bufferPool;
+    private final HttpGenerator _generator;
+    private final HttpChannelOverHttp _channel;
+    private final HttpParser _parser;
+    private volatile ByteBuffer _requestBuffer = null;
+    private volatile ByteBuffer _chunk = null;
+
+
+    /* ------------------------------------------------------------ */
+    /** Get the current connection that this thread is dispatched to.
+     * Note that a thread may be processing a request asynchronously and 
+     * thus not be dispatched to the connection.  
+     * @see Request#getAttribute(String) for a more general way to access the HttpConnection
+     * @return the current HttpConnection or null
+     */
+    public static HttpConnection getCurrentConnection()
+    {
+        return __currentConnection.get();
+    }
+
+    protected static HttpConnection setCurrentConnection(HttpConnection connection)
+    {
+        HttpConnection last=__currentConnection.get();
+        if (connection==null)
+            __currentConnection.remove();
+        else 
+            __currentConnection.set(connection);
+        return last;
+    }
+
+    public HttpConfiguration getHttpConfiguration()
+    {
+        return _config;
+    }
+
+    public HttpConnection(HttpConfiguration config, Connector connector, EndPoint endPoint)
+    {
+        // Tell AbstractConnector executeOnFillable==true because we want the same thread that
+        // does the HTTP parsing to handle the request so its cache is hot
+        super(endPoint, connector.getExecutor(),true);
+
+        _config = config;
+        _connector = connector;
+        _bufferPool = _connector.getByteBufferPool();
+        _generator = newHttpGenerator();
+        HttpInput<ByteBuffer> input = newHttpInput();
+        _channel = newHttpChannel(input);
+        _parser = newHttpParser();
+        LOG.debug("New HTTP Connection {}", this);
+    }
+
+    protected HttpGenerator newHttpGenerator()
+    {
+        return new HttpGenerator(_config.getSendServerVersion(),_config.getSendXPoweredBy());
+    }
+    
+    protected HttpInput<ByteBuffer> newHttpInput()
+    {
+        return new HttpInputOverHTTP(this);
+    }
+    
+    protected HttpChannelOverHttp newHttpChannel(HttpInput<ByteBuffer> httpInput)
+    {
+        return new HttpChannelOverHttp(_connector, _config, getEndPoint(), this, httpInput);
+    }
+    
+    protected HttpParser newHttpParser()
+    {
+        return new HttpParser(newRequestHandler(), getHttpConfiguration().getRequestHeaderSize());
+    }
+
+    protected HttpParser.RequestHandler<ByteBuffer> newRequestHandler()
+    {
+        return _channel;
+    }
+
+    public Server getServer()
+    {
+        return _connector.getServer();
+    }
+
+    public Connector getConnector()
+    {
+        return _connector;
+    }
+
+    public HttpChannel<?> getHttpChannel()
+    {
+        return _channel;
+    }
+
+    public HttpParser getParser()
+    {
+        return _parser;
+    }
+
+    @Override
+    public int getMessagesIn()
+    {
+        return getHttpChannel().getRequests();
+    }
+
+    @Override
+    public int getMessagesOut()
+    {
+        return getHttpChannel().getRequests();
+    }
+
+    void releaseRequestBuffer()
+    {
+        if (_requestBuffer != null && !_requestBuffer.hasRemaining())
+        {
+            ByteBuffer buffer=_requestBuffer;
+            _requestBuffer=null;
+            _bufferPool.release(buffer);
+        }
+    }
+    
+    public ByteBuffer getRequestBuffer()
+    {
+        if (_requestBuffer == null)
+            _requestBuffer = _bufferPool.acquire(getInputBufferSize(), REQUEST_BUFFER_DIRECT);
+        return _requestBuffer;
+    }
+
+    /**
+     * <p>Parses and handles HTTP messages.</p>
+     * <p>This method is called when this {@link Connection} is ready to read bytes from the {@link EndPoint}.
+     * However, it can also be called if there is unconsumed data in the _requestBuffer, as a result of
+     * resuming a suspended request when there is a pipelined request already read into the buffer.</p>
+     * <p>This method fills bytes and parses them until either: EOF is filled; 0 bytes are filled;
+     * the HttpChannel finishes handling; or the connection has changed.</p>
+     */
+    @Override
+    public void onFillable()
+    {
+        LOG.debug("{} onFillable {}", this, _channel.getState());
+
+        final HttpConnection last=setCurrentConnection(this);
+        int filled=Integer.MAX_VALUE;
+        boolean suspended=false;
+        try
+        {
+            // while not suspended and not upgraded
+            while (!suspended && getEndPoint().getConnection()==this)
+            {
+                // Do we need some data to parse
+                if (BufferUtil.isEmpty(_requestBuffer))
+                {
+                    // If the previous iteration filled 0 bytes or saw a close, then break here 
+                    if (filled<=0)
+                        break;
+                        
+                    // Can we fill?
+                    if(getEndPoint().isInputShutdown())
+                    {
+                        // No pretend we read -1
+                        filled=-1;
+                        _parser.atEOF();
+                    }
+                    else
+                    {
+                        // Get a buffer
+                        // We are not in a race here for the request buffer as we have not yet received a request,
+                        // so there are not an possible legal threads calling #parseContent or #completed.
+                        _requestBuffer = getRequestBuffer();
+
+                        // fill
+                        filled = getEndPoint().fill(_requestBuffer);
+                        if (filled==0) // Do a retry on fill 0 (optimization for SSL connections)
+                            filled = getEndPoint().fill(_requestBuffer);
+                        
+                        // tell parser
+                        if (filled < 0)
+                            _parser.atEOF();
+                    }
+                }
+                
+                // Parse the buffer
+                if (_parser.parseNext(_requestBuffer==null?BufferUtil.EMPTY_BUFFER:_requestBuffer))
+                {
+                    // The parser returned true, which indicates the channel is ready to handle a request.
+                    // Call the channel and this will either handle the request/response to completion OR,
+                    // if the request suspends, the request/response will be incomplete so the outer loop will exit.
+                    // Not that onFillable no longer manipulates the request buffer from this point and that is
+                    // left to threads calling #completed or #parseContent (which may be this thread inside handle())
+                    suspended = !_channel.handle();
+                }
+                else
+                {
+                    // We parsed what we could, recycle the request buffer
+                    // We are not in a race here for the request buffer as we have not yet received a request,
+                    // so there are not an possible legal threads calling #parseContent or #completed.
+                    releaseRequestBuffer();
+                }
+            }
+        }
+        catch (EofException e)
+        {
+            LOG.debug(e);
+        }
+        catch (Exception e)
+        {
+            if (_parser.isIdle())
+                LOG.debug(e);
+            else
+                LOG.warn(this.toString(), e);
+            close();
+        }
+        finally
+        {                        
+            setCurrentConnection(last);
+            if (!suspended && getEndPoint().isOpen() && getEndPoint().getConnection()==this)
+            {
+                fillInterested();
+            }
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Fill and parse data looking for content
+     * @throws IOException
+     */
+    protected void parseContent() throws IOException
+    {
+        // Not in a race here for the request buffer with #onFillable because an async consumer of
+        // content would only be started after onFillable has given up control.
+        // In a little bit of a race with #completed, but then not sure if it is legal to be doing 
+        // async calls to IO and have a completed call at the same time.
+        ByteBuffer requestBuffer = getRequestBuffer();
+
+        while (_parser.inContentState())
+        {
+            // Can the parser progress (even with an empty buffer)
+            boolean parsed = _parser.parseNext(requestBuffer==null?BufferUtil.EMPTY_BUFFER:requestBuffer);
+
+            // No, we can we try reading some content?
+            if (BufferUtil.isEmpty(requestBuffer) && getEndPoint().isInputShutdown())
+            {
+                _parser.atEOF();
+                if (parsed)
+                    break;
+                continue;
+            }
+
+            if (parsed)
+                break;
+            
+            // OK lets read some data
+            int filled=getEndPoint().fill(requestBuffer);
+            if (LOG.isDebugEnabled()) // Avoid boxing of variable 'filled'
+                LOG.debug("{} filled {}",this,filled);
+            if (filled<=0)
+            {
+                if (filled<0)
+                {
+                    _parser.atEOF();
+                    continue;
+                }
+                break;
+            }
+        }
+    }
+    
+    @Override
+    public void completed()
+    {
+        // Handle connection upgrades
+        if (_channel.getResponse().getStatus() == HttpStatus.SWITCHING_PROTOCOLS_101)
+        {
+            Connection connection = (Connection)_channel.getRequest().getAttribute(UPGRADE_CONNECTION_ATTRIBUTE);
+            if (connection != null)
+            {
+                LOG.debug("Upgrade from {} to {}", this, connection);
+                onClose();
+                getEndPoint().setConnection(connection);
+                connection.onOpen();
+                _channel.reset();
+                _parser.reset();
+                _generator.reset();
+                releaseRequestBuffer();
+                return;
+            }
+        }
+        
+        // Finish consuming the request
+        // If we are still expecting
+        if (_channel.isExpecting100Continue())
+            // close to seek EOF
+            _parser.close();
+        else if (_parser.inContentState() && _generator.isPersistent())
+            // Complete reading the request
+            _channel.getRequest().getHttpInput().consumeAll();
+
+        // Reset the channel, parsers and generator
+        _channel.reset();
+        if (_generator.isPersistent() && !_parser.isClosed())
+            _parser.reset();
+        else
+            _parser.close();
+        
+        // Not in a race here with onFillable, because it has given up control before calling handle.
+        // in a slight race with #completed, but not sure what to do with that anyway.
+        releaseRequestBuffer();
+        if (_chunk!=null)
+            _bufferPool.release(_chunk);
+        _chunk=null;
+        _generator.reset();
+
+        // if we are not called from the onfillable thread, schedule completion
+        if (getCurrentConnection()!=this)
+        {
+            // If we are looking for the next request
+            if (_parser.isStart())
+            {
+                // if the buffer is empty
+                if (BufferUtil.isEmpty(_requestBuffer))
+                {
+                    // look for more data
+                    fillInterested();
+                }
+                // else if we are still running
+                else if (getConnector().isRunning())
+                {
+                    // Dispatched to handle a pipelined request
+                    try
+                    {
+                        getExecutor().execute(this);
+                    }
+                    catch (RejectedExecutionException e)
+                    {
+                        if (getConnector().isRunning())
+                            LOG.warn(e);
+                        else
+                            LOG.ignore(e);
+                        getEndPoint().close();
+                    }
+                }
+                else
+                {
+                    getEndPoint().close();
+                }
+            }
+            // else the parser must be closed, so seek the EOF if we are still open 
+            else if (getEndPoint().isOpen())
+                fillInterested();
+        }
+    }
+
+    @Override
+    protected void onFillInterestedFailed(Throwable cause)
+    {
+        _parser.close();
+        super.onFillInterestedFailed(cause);
+    }
+
+    @Override
+    public void onOpen()
+    {
+        super.onOpen();
+        fillInterested();
+    }
+
+    @Override
+    public void run()
+    {
+        onFillable();
+    }
+
+
+    @Override
+    public void send(ResponseInfo info, ByteBuffer content, boolean lastContent, Callback callback)
+    {
+        if (info==null)
+            new ContentCallback(content,lastContent,callback).iterate();
+        else
+        {
+            // If we are still expecting a 100 continues
+            if (_channel.isExpecting100Continue())
+                // then we can't be persistent
+                _generator.setPersistent(false);
+            new CommitCallback(info,content,lastContent,callback).iterate();
+        }
+    }
+
+    @Override
+    public void send(ByteBuffer content, boolean lastContent, Callback callback)
+    {
+        new ContentCallback(content,lastContent,callback).iterate();
+    }
+
+    
+    protected class HttpChannelOverHttp extends HttpChannel<ByteBuffer>
+    {
+        public HttpChannelOverHttp(Connector connector, HttpConfiguration config, EndPoint endPoint, HttpTransport transport, HttpInput<ByteBuffer> input)
+        {
+            super(connector,config,endPoint,transport,input);
+        }
+        
+        @Override
+        public void earlyEOF()
+        {
+            // If we have no request yet, just close
+            if (getRequest().getMethod()==null)
+                close();
+            else
+                super.earlyEOF();
+        }
+
+        @Override
+        public boolean content(ByteBuffer item)
+        {
+            super.content(item);
+            return true;
+        }
+
+        @Override
+        public void badMessage(int status, String reason)
+        {
+            _generator.setPersistent(false);
+            super.badMessage(status,reason);
+        }
+
+        @Override
+        public boolean headerComplete()
+        {
+            boolean persistent;
+            HttpVersion version = getHttpVersion();
+
+            switch (version)
+            {
+                case HTTP_0_9:
+                {
+                    persistent = false;
+                    break;
+                }
+                case HTTP_1_0:
+                {
+                    persistent = getRequest().getHttpFields().contains(HttpHeader.CONNECTION, HttpHeaderValue.KEEP_ALIVE.asString());
+                    if (!persistent)
+                        persistent = HttpMethod.CONNECT.is(getRequest().getMethod());
+                    if (persistent)
+                        getResponse().getHttpFields().add(HttpHeader.CONNECTION, HttpHeaderValue.KEEP_ALIVE);
+                    break;
+                }
+                case HTTP_1_1:
+                {
+                    persistent = !getRequest().getHttpFields().contains(HttpHeader.CONNECTION, HttpHeaderValue.CLOSE.asString());
+                    if (!persistent)
+                        persistent = HttpMethod.CONNECT.is(getRequest().getMethod());
+                    if (!persistent)
+                        getResponse().getHttpFields().add(HttpHeader.CONNECTION, HttpHeaderValue.CLOSE);
+                    break;
+                }
+                default:
+                {
+                    throw new IllegalStateException();
+                }
+            }
+
+            if (!persistent)
+                _generator.setPersistent(false);
+
+            return super.headerComplete();
+        }
+
+        @Override
+        protected void handleException(Throwable x)
+        {
+            _generator.setPersistent(false);
+            super.handleException(x);
+        }
+
+        @Override
+        public void failed()
+        {
+            getEndPoint().shutdownOutput();
+        }
+        
+
+        @Override
+        public boolean messageComplete()
+        {
+            super.messageComplete();
+            return false;
+        }
+    }
+
+    private class CommitCallback extends IteratingCallback
+    {
+        final ByteBuffer _content;
+        final boolean _lastContent;
+        final ResponseInfo _info;
+        final Callback _callback;
+        ByteBuffer _header;
+
+        CommitCallback(ResponseInfo info, ByteBuffer content, boolean last, Callback callback)
+        {
+            _info=info;
+            _content=content;
+            _lastContent=last;
+            _callback=callback;
+        }
+
+        @Override
+        public Action process() throws Exception
+        {
+            ByteBuffer chunk = _chunk;
+            while (true)
+            {
+                HttpGenerator.Result result = _generator.generateResponse(_info, _header, chunk, _content, _lastContent);
+                if (LOG.isDebugEnabled())
+                    LOG.debug("{} generate: {} ({},{},{})@{}",
+                        this,
+                        result,
+                        BufferUtil.toSummaryString(_header),
+                        BufferUtil.toSummaryString(_content),
+                        _lastContent,
+                        _generator.getState());
+
+                switch (result)
+                {
+                    case NEED_HEADER:
+                    {
+                        // Look for optimisation to avoid allocating a _header buffer
+                        /*
+                         Cannot use this optimisation unless we work out how not to overwrite data in user passed arrays.
+                        if (_lastContent && _content!=null && !_content.isReadOnly() && _content.hasArray() && BufferUtil.space(_content)>_config.getResponseHeaderSize() )
+                        {
+                            // use spare space in content buffer for header buffer
+                            int p=_content.position();
+                            int l=_content.limit();
+                            _content.position(l);
+                            _content.limit(l+_config.getResponseHeaderSize());
+                            _header=_content.slice();
+                            _header.limit(0);
+                            _content.position(p);
+                            _content.limit(l);
+                        }
+                        else
+                        */
+                            _header = _bufferPool.acquire(_config.getResponseHeaderSize(), HEADER_BUFFER_DIRECT);
+                            
+                        continue;
+                    }
+                    case NEED_CHUNK:
+                    {
+                        chunk = _chunk = _bufferPool.acquire(HttpGenerator.CHUNK_SIZE, CHUNK_BUFFER_DIRECT);
+                        continue;
+                    }
+                    case FLUSH:
+                    {
+                        // Don't write the chunk or the content if this is a HEAD response
+                        if (_channel.getRequest().isHead())
+                        {
+                            BufferUtil.clear(chunk);
+                            BufferUtil.clear(_content);
+                        }
+
+                        // If we have a header
+                        if (BufferUtil.hasContent(_header))
+                        {
+                            if (BufferUtil.hasContent(_content))
+                            {
+                                if (BufferUtil.hasContent(chunk))
+                                    getEndPoint().write(this, _header, chunk, _content);
+                                else
+                                    getEndPoint().write(this, _header, _content);
+                            }
+                            else
+                                getEndPoint().write(this, _header);
+                        }
+                        else if (BufferUtil.hasContent(chunk))
+                        {
+                            if (BufferUtil.hasContent(_content))
+                                getEndPoint().write(this, chunk, _content);
+                            else
+                                getEndPoint().write(this, chunk);
+                        }
+                        else if (BufferUtil.hasContent(_content))
+                        {
+                            getEndPoint().write(this, _content);
+                        }
+                        else
+                            continue;
+                        return Action.SCHEDULED;
+                    }
+                    case SHUTDOWN_OUT:
+                    {
+                        getEndPoint().shutdownOutput();
+                        continue;
+                    }
+                    case DONE:
+                    {
+                        if (_header!=null)
+                        {
+                            // don't release header in spare content buffer
+                            if (!_lastContent || _content==null || !_content.hasArray() || !_header.hasArray() ||  _content.array()!=_header.array())
+                                _bufferPool.release(_header);
+                        }
+                        return Action.SUCCEEDED;
+                    }
+                    case CONTINUE:
+                    {
+                        break;
+                    }
+                    default:
+                    {
+                        throw new IllegalStateException("generateResponse="+result);
+                    }
+                }
+            }
+        }
+
+        @Override
+        protected void completed()
+        {
+            _callback.succeeded();
+        }
+
+        @Override
+        public void failed(final Throwable x)
+        {
+            super.failed(x);
+            failedCallback(_callback,x);
+        }
+    }
+
+    private class ContentCallback extends IteratingCallback
+    {
+        final ByteBuffer _content;
+        final boolean _lastContent;
+        final Callback _callback;
+
+        ContentCallback(ByteBuffer content, boolean last, Callback callback)
+        {
+            _content=content;
+            _lastContent=last;
+            _callback=callback;
+        }
+
+        @Override
+        public Action process() throws Exception
+        {
+            ByteBuffer chunk = _chunk;
+            while (true)
+            {
+                HttpGenerator.Result result = _generator.generateResponse(null, null, chunk, _content, _lastContent);
+                if (LOG.isDebugEnabled())
+                    LOG.debug("{} generate: {} ({},{})@{}",
+                        this,
+                        result,
+                        BufferUtil.toSummaryString(_content),
+                        _lastContent,
+                        _generator.getState());
+
+                switch (result)
+                {
+                    case NEED_HEADER:
+                        throw new IllegalStateException();
+                    case NEED_CHUNK:
+                    {
+                        chunk = _chunk = _bufferPool.acquire(HttpGenerator.CHUNK_SIZE, CHUNK_BUFFER_DIRECT);
+                        continue;
+                    }
+                    case FLUSH:
+                    {
+                        // Don't write the chunk or the content if this is a HEAD response
+                        if (_channel.getRequest().isHead())
+                        {
+                            BufferUtil.clear(chunk);
+                            BufferUtil.clear(_content);
+                            continue;
+                        }
+                        else if (BufferUtil.hasContent(chunk))
+                        {
+                            if (BufferUtil.hasContent(_content))
+                                getEndPoint().write(this, chunk, _content);
+                            else
+                                getEndPoint().write(this, chunk);
+                        }
+                        else if (BufferUtil.hasContent(_content))
+                        {
+                            getEndPoint().write(this, _content);
+                        }
+                        else
+                            continue;
+                        return Action.SCHEDULED;
+                    }
+                    case SHUTDOWN_OUT:
+                    {
+                        getEndPoint().shutdownOutput();
+                        continue;
+                    }
+                    case DONE:
+                    {
+                        return Action.SUCCEEDED;
+                    }
+                    case CONTINUE:
+                    {
+                        break;
+                    }
+                    default:
+                    {
+                        throw new IllegalStateException("generateResponse="+result);
+                    }
+                }
+            }
+        }
+
+        @Override
+        protected void completed()
+        {
+            _callback.succeeded();
+        }
+
+        @Override
+        public void failed(final Throwable x)
+        {
+            super.failed(x);
+            failedCallback(_callback,x);
+        }
+    }
+
+    @Override
+    public void abort()
+    {
+        // Do a direct close of the output, as this may indicate to a client that the 
+        // response is bad either with RST or by abnormal completion of chunked response.
+        getEndPoint().close();
+    }
+
+}
diff --git a/lib/jetty/org/eclipse/jetty/server/HttpConnectionFactory.java b/lib/jetty/org/eclipse/jetty/server/HttpConnectionFactory.java
new file mode 100644 (file)
index 0000000..ce6ffdb
--- /dev/null
@@ -0,0 +1,64 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+
+package org.eclipse.jetty.server;
+
+
+import org.eclipse.jetty.http.HttpVersion;
+import org.eclipse.jetty.io.Connection;
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.util.annotation.Name;
+
+
+/* ------------------------------------------------------------ */
+/** A Connection Factory for HTTP Connections.
+ * <p>Accepts connections either directly or via SSL and/or NPN chained connection factories.  The accepted 
+ * {@link HttpConnection}s are configured by a {@link HttpConfiguration} instance that is either created by
+ * default or passed in to the constructor.
+ */
+public class HttpConnectionFactory extends AbstractConnectionFactory implements HttpConfiguration.ConnectionFactory
+{
+    private final HttpConfiguration _config;
+
+    public HttpConnectionFactory()
+    {
+        this(new HttpConfiguration());
+        setInputBufferSize(16384);
+    }
+
+    public HttpConnectionFactory(@Name("config") HttpConfiguration config)
+    {
+        super(HttpVersion.HTTP_1_1.toString());
+        _config=config;
+        addBean(_config);
+    }
+
+    @Override
+    public HttpConfiguration getHttpConfiguration()
+    {
+        return _config;
+    }
+
+    @Override
+    public Connection newConnection(Connector connector, EndPoint endPoint)
+    {
+        return configure(new HttpConnection(_config, connector, endPoint), connector, endPoint);
+    }
+
+}
diff --git a/lib/jetty/org/eclipse/jetty/server/HttpInput.java b/lib/jetty/org/eclipse/jetty/server/HttpInput.java
new file mode 100644 (file)
index 0000000..833ecde
--- /dev/null
@@ -0,0 +1,503 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server;
+
+import java.io.IOException;
+import java.util.Objects;
+import javax.servlet.ReadListener;
+import javax.servlet.ServletInputStream;
+
+import org.eclipse.jetty.io.EofException;
+import org.eclipse.jetty.io.RuntimeIOException;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/**
+ * {@link HttpInput} provides an implementation of {@link ServletInputStream} for {@link HttpChannel}.
+ * <p/>
+ * Content may arrive in patterns such as [content(), content(), messageComplete()] so that this class
+ * maintains two states: the content state that tells whether there is content to consume and the EOF
+ * state that tells whether an EOF has arrived.
+ * Only once the content has been consumed the content state is moved to the EOF state.
+ */
+public abstract class HttpInput<T> extends ServletInputStream implements Runnable
+{
+    private final static Logger LOG = Log.getLogger(HttpInput.class);
+
+    private final byte[] _oneByteBuffer = new byte[1];
+    private final Object _lock;
+    private HttpChannelState _channelState;
+    private ReadListener _listener;
+    private Throwable _onError;
+    private boolean _notReady;
+    private State _contentState = STREAM;
+    private State _eofState;
+    private long _contentRead;
+
+    protected HttpInput()
+    {
+        this(null);
+    }
+
+    protected HttpInput(Object lock)
+    {
+        _lock = lock == null ? this : lock;
+    }
+
+    public void init(HttpChannelState state)
+    {
+        synchronized (lock())
+        {
+            _channelState = state;
+        }
+    }
+
+    public final Object lock()
+    {
+        return _lock;
+    }
+
+    public void recycle()
+    {
+        synchronized (lock())
+        {
+            _listener = null;
+            _onError = null;
+            _notReady = false;
+            _contentState = STREAM;
+            _eofState = null;
+            _contentRead = 0;
+        }
+    }
+
+    @Override
+    public int available()
+    {
+        try
+        {
+            synchronized (lock())
+            {
+                T item = getNextContent();
+                return item == null ? 0 : remaining(item);
+            }
+        }
+        catch (IOException e)
+        {
+            throw new RuntimeIOException(e);
+        }
+    }
+
+    @Override
+    public int read() throws IOException
+    {
+        int read = read(_oneByteBuffer, 0, 1);
+        return read < 0 ? -1 : _oneByteBuffer[0] & 0xFF;
+    }
+
+    @Override
+    public int read(byte[] b, int off, int len) throws IOException
+    {
+        synchronized (lock())
+        {
+            T item = getNextContent();
+            if (item == null)
+            {
+                _contentState.waitForContent(this);
+                item = getNextContent();
+                if (item == null)
+                    return _contentState.noContent();
+            }
+            int l = get(item, b, off, len);
+            _contentRead += l;
+            return l;
+        }
+    }
+
+    /**
+     * A convenience method to call nextContent and to check the return value, which if null then the
+     * a check is made for EOF and the state changed accordingly.
+     *
+     * @return Content or null if none available.
+     * @throws IOException
+     * @see #nextContent()
+     */
+    protected T getNextContent() throws IOException
+    {
+        T content = nextContent();
+        if (content == null)
+        {
+            synchronized (lock())
+            {
+                if (_eofState != null)
+                {
+                    LOG.debug("{} eof {}", this, _eofState);
+                    _contentState = _eofState;
+                }
+            }
+        }
+        return content;
+    }
+
+    /**
+     * Access the next content to be consumed from.   Returning the next item does not consume it
+     * and it may be returned multiple times until it is consumed.
+     * <p/>
+     * Calls to {@link #get(Object, byte[], int, int)}
+     * or {@link #consume(Object, int)} are required to consume data from the content.
+     *
+     * @return the content or null if none available.
+     * @throws IOException if retrieving the content fails
+     */
+    protected abstract T nextContent() throws IOException;
+
+    /**
+     * @param item the content
+     * @return how many bytes remain in the given content
+     */
+    protected abstract int remaining(T item);
+
+    /**
+     * Copies the given content into the given byte buffer.
+     *
+     * @param item   the content to copy from
+     * @param buffer the buffer to copy into
+     * @param offset the buffer offset to start copying from
+     * @param length the space available in the buffer
+     * @return the number of bytes actually copied
+     */
+    protected abstract int get(T item, byte[] buffer, int offset, int length);
+
+    /**
+     * Consumes the given content.
+     *
+     * @param item   the content to consume
+     * @param length the number of bytes to consume
+     */
+    protected abstract void consume(T item, int length);
+
+    /**
+     * Blocks until some content or some end-of-file event arrives.
+     *
+     * @throws IOException if the wait is interrupted
+     */
+    protected abstract void blockForContent() throws IOException;
+
+    /**
+     * Adds some content to this input stream.
+     *
+     * @param item the content to add
+     */
+    public abstract void content(T item);
+
+    protected boolean onAsyncRead()
+    {
+        synchronized (lock())
+        {
+            if (_listener == null)
+                return false;
+        }
+        _channelState.onReadPossible();
+        return true;
+    }
+
+    public long getContentRead()
+    {
+        synchronized (lock())
+        {
+            return _contentRead;
+        }
+    }
+
+    /**
+     * This method should be called to signal that an EOF has been
+     * detected before all the expected content arrived.
+     * <p/>
+     * Typically this will result in an EOFException being thrown
+     * from a subsequent read rather than a -1 return.
+     */
+    public void earlyEOF()
+    {
+        synchronized (lock())
+        {
+            if (!isEOF())
+            {
+                LOG.debug("{} early EOF", this);
+                _eofState = EARLY_EOF;
+                if (_listener == null)
+                    return;
+            }
+        }
+        _channelState.onReadPossible();
+    }
+
+    /**
+     * This method should be called to signal that all the expected
+     * content arrived.
+     */
+    public void messageComplete()
+    {
+        synchronized (lock())
+        {
+            if (!isEOF())
+            {
+                LOG.debug("{} EOF", this);
+                _eofState = EOF;
+                if (_listener == null)
+                    return;
+            }
+        }
+        _channelState.onReadPossible();
+    }
+
+    public void consumeAll()
+    {
+        synchronized (lock())
+        {
+            try
+            {
+                while (!isFinished())
+                {
+                    T item = getNextContent();
+                    if (item == null)
+                        _contentState.waitForContent(this);
+                    else
+                        consume(item, remaining(item));
+                }
+            }
+            catch (IOException e)
+            {
+                LOG.debug(e);
+            }
+        }
+    }
+
+    public boolean isAsync()
+    {
+        synchronized (lock())
+        {
+            return _contentState==ASYNC;
+        }
+    }
+    
+    /**
+     * @return whether an EOF has been detected, even though there may be content to consume.
+     */
+    public boolean isEOF()
+    {
+        synchronized (lock())
+        {
+            return _eofState != null && _eofState.isEOF();
+        }
+    }
+
+    @Override
+    public boolean isFinished()
+    {
+        synchronized (lock())
+        {
+            return _contentState.isEOF();
+        }
+    }
+
+    @Override
+    public boolean isReady()
+    {
+        boolean finished;
+        synchronized (lock())
+        {
+            if (_contentState.isEOF())
+                return true;
+            if (_listener == null )
+                return true;
+            if (available() > 0)
+                return true;
+            if (_notReady)
+                return false;
+            _notReady = true;
+            finished = isFinished();
+        }
+        if (finished)
+            _channelState.onReadPossible();
+        else
+            unready();
+        return false;
+    }
+
+    protected void unready()
+    {
+    }
+
+    @Override
+    public void setReadListener(ReadListener readListener)
+    {
+        readListener = Objects.requireNonNull(readListener);
+        synchronized (lock())
+        {
+            if (_contentState != STREAM)
+                throw new IllegalStateException("state=" + _contentState);
+            _contentState = ASYNC;
+            _listener = readListener;
+            _notReady = true;
+        }
+        _channelState.onReadPossible();
+    }
+
+    public void failed(Throwable x)
+    {
+        synchronized (lock())
+        {
+            if (_onError != null)
+                LOG.warn(x);
+            else
+                _onError = x;
+        }
+    }
+
+    @Override
+    public void run()
+    {
+        final Throwable error;
+        final ReadListener listener;
+        boolean available = false;
+        final boolean eof;
+
+        synchronized (lock())
+        {
+            if (!_notReady || _listener == null)
+                return;
+
+            error = _onError;
+            listener = _listener;
+
+            try
+            {
+                T item = getNextContent();
+                available = item != null && remaining(item) > 0;
+            }
+            catch (Exception e)
+            {
+                failed(e);
+            }
+
+            eof = !available && isFinished();
+            _notReady = !available && !eof;
+        }
+
+        try
+        {
+            if (error != null)
+                listener.onError(error);
+            else if (available)
+                listener.onDataAvailable();
+            else if (eof)
+                listener.onAllDataRead();
+            else
+                unready();
+        }
+        catch (Throwable e)
+        {
+            LOG.warn(e.toString());
+            LOG.debug(e);
+            listener.onError(e);
+        }
+    }
+
+    protected static abstract class State
+    {
+        public void waitForContent(HttpInput<?> in) throws IOException
+        {
+        }
+
+        public int noContent() throws IOException
+        {
+            return -1;
+        }
+
+        public boolean isEOF()
+        {
+            return false;
+        }
+    }
+
+    protected static final State STREAM = new State()
+    {
+        @Override
+        public void waitForContent(HttpInput<?> input) throws IOException
+        {
+            input.blockForContent();
+        }
+
+        @Override
+        public String toString()
+        {
+            return "STREAM";
+        }
+    };
+
+    protected static final State ASYNC = new State()
+    {
+        @Override
+        public int noContent() throws IOException
+        {
+            return 0;
+        }
+
+        @Override
+        public String toString()
+        {
+            return "ASYNC";
+        }
+    };
+
+    protected static final State EARLY_EOF = new State()
+    {
+        @Override
+        public int noContent() throws IOException
+        {
+            throw new EofException();
+        }
+
+        @Override
+        public boolean isEOF()
+        {
+            return true;
+        }
+
+        @Override
+        public String toString()
+        {
+            return "EARLY_EOF";
+        }
+    };
+
+    protected static final State EOF = new State()
+    {
+        @Override
+        public boolean isEOF()
+        {
+            return true;
+        }
+
+        @Override
+        public String toString()
+        {
+            return "EOF";
+        }
+    };
+}
diff --git a/lib/jetty/org/eclipse/jetty/server/HttpInputOverHTTP.java b/lib/jetty/org/eclipse/jetty/server/HttpInputOverHTTP.java
new file mode 100644 (file)
index 0000000..3552450
--- /dev/null
@@ -0,0 +1,145 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.Callback;
+import org.eclipse.jetty.util.SharedBlockingCallback;
+import org.eclipse.jetty.util.SharedBlockingCallback.Blocker;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+public class HttpInputOverHTTP extends HttpInput<ByteBuffer> implements Callback
+{
+    private static final Logger LOG = Log.getLogger(HttpInputOverHTTP.class);
+    private final SharedBlockingCallback _readBlocker = new SharedBlockingCallback();
+    private final HttpConnection _httpConnection;
+    private ByteBuffer _content;
+
+    /**
+     * @param httpConnection
+     */
+    public HttpInputOverHTTP(HttpConnection httpConnection)
+    {
+        _httpConnection = httpConnection;
+    }
+
+    @Override
+    public void recycle()
+    {
+        synchronized (lock())
+        {
+            super.recycle();
+            _content=null;
+        }
+    }
+
+    @Override
+    protected void blockForContent() throws IOException
+    {
+        while(true)
+        {
+            try (Blocker blocker=_readBlocker.acquire())
+            {            
+                _httpConnection.fillInterested(blocker);
+                LOG.debug("{} block readable on {}",this,blocker);
+                blocker.block();
+            }
+
+            Object content=getNextContent();
+            if (content!=null || isFinished())
+                break;
+        }
+    }
+
+    @Override
+    public String toString()
+    {
+        return String.format("%s@%x",getClass().getSimpleName(),hashCode());
+    }
+
+    @Override
+    protected ByteBuffer nextContent() throws IOException
+    {
+        // If we have some content available, return it
+        if (BufferUtil.hasContent(_content))
+            return _content;
+
+        // No - then we are going to need to parse some more content
+        _content=null;
+        _httpConnection.parseContent();
+        
+        // If we have some content available, return it
+        if (BufferUtil.hasContent(_content))
+            return _content;
+
+        return null;
+
+    }
+
+    @Override
+    protected int remaining(ByteBuffer item)
+    {
+        return item.remaining();
+    }
+
+    @Override
+    protected int get(ByteBuffer item, byte[] buffer, int offset, int length)
+    {
+        int l = Math.min(item.remaining(), length);
+        item.get(buffer, offset, l);
+        return l;
+    }
+
+    @Override
+    protected void consume(ByteBuffer item, int length)
+    {
+        item.position(item.position()+length);
+    }
+
+    @Override
+    public void content(ByteBuffer item)
+    {
+        if (BufferUtil.hasContent(_content))
+            throw new IllegalStateException();
+        _content=item;
+    }
+
+    @Override
+    protected void unready()
+    {
+        _httpConnection.fillInterested(this);
+    }
+
+    @Override
+    public void succeeded()
+    {
+        _httpConnection.getHttpChannel().getState().onReadPossible();
+    }
+
+    @Override
+    public void failed(Throwable x)
+    {
+        super.failed(x);
+        _httpConnection.getHttpChannel().getState().onReadPossible();
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/server/HttpOutput.java b/lib/jetty/org/eclipse/jetty/server/HttpOutput.java
new file mode 100644 (file)
index 0000000..7c0fd89
--- /dev/null
@@ -0,0 +1,1096 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.ByteBuffer;
+import java.nio.channels.ReadableByteChannel;
+import java.nio.channels.WritePendingException;
+import java.util.concurrent.atomic.AtomicReference;
+import javax.servlet.RequestDispatcher;
+import javax.servlet.ServletOutputStream;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.WriteListener;
+
+import org.eclipse.jetty.http.HttpContent;
+import org.eclipse.jetty.io.EofException;
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.Callback;
+import org.eclipse.jetty.util.IteratingCallback;
+import org.eclipse.jetty.util.IteratingNestedCallback;
+import org.eclipse.jetty.util.SharedBlockingCallback;
+import org.eclipse.jetty.util.SharedBlockingCallback.Blocker;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/**
+ * <p>{@link HttpOutput} implements {@link ServletOutputStream}
+ * as required by the Servlet specification.</p>
+ * <p>{@link HttpOutput} buffers content written by the application until a
+ * further write will overflow the buffer, at which point it triggers a commit
+ * of the response.</p>
+ * <p>{@link HttpOutput} can be closed and reopened, to allow requests included
+ * via {@link RequestDispatcher#include(ServletRequest, ServletResponse)} to
+ * close the stream, to be reopened after the inclusion ends.</p>
+ */
+public class HttpOutput extends ServletOutputStream implements Runnable
+{
+    private static Logger LOG = Log.getLogger(HttpOutput.class);
+    private final HttpChannel<?> _channel;
+    private final SharedBlockingCallback _writeblock=new SharedBlockingCallback();
+    private long _written;
+    private ByteBuffer _aggregate;
+    private int _bufferSize;
+    private int _commitSize;
+    private WriteListener _writeListener;
+    private volatile Throwable _onError;
+
+    /*
+    ACTION             OPEN       ASYNC      READY      PENDING       UNREADY       CLOSED
+    -----------------------------------------------------------------------------------------------------
+    setWriteListener() READY->owp ise        ise        ise           ise           ise
+    write()            OPEN       ise        PENDING    wpe           wpe           eof
+    flush()            OPEN       ise        PENDING    wpe           wpe           eof
+    close()            CLOSED     CLOSED     CLOSED     CLOSED        wpe           CLOSED
+    isReady()          OPEN:true  READY:true READY:true UNREADY:false UNREADY:false CLOSED:true
+    write completed    -          -          -          ASYNC         READY->owp    -
+    
+    */
+    enum OutputState { OPEN, ASYNC, READY, PENDING, UNREADY, ERROR, CLOSED }
+    private final AtomicReference<OutputState> _state=new AtomicReference<>(OutputState.OPEN);
+
+    public HttpOutput(HttpChannel<?> channel)
+    {
+        _channel = channel;
+        _bufferSize = _channel.getHttpConfiguration().getOutputBufferSize();
+        _commitSize=_bufferSize/4;
+    }
+    
+    public HttpChannel<?> getHttpChannel()
+    {
+        return _channel;
+    }
+    
+    public boolean isWritten()
+    {
+        return _written > 0;
+    }
+
+    public long getWritten()
+    {
+        return _written;
+    }
+
+    public void reset()
+    {
+        _written = 0;
+        reopen();
+    }
+
+    public void reopen()
+    {
+        _state.set(OutputState.OPEN);
+    }
+
+    public boolean isAllContentWritten()
+    {
+        return _channel.getResponse().isAllContentWritten(_written);
+    }
+
+    protected Blocker acquireWriteBlockingCallback() throws IOException
+    {
+        return _writeblock.acquire();
+    }
+    
+    protected void write(ByteBuffer content, boolean complete) throws IOException
+    {
+        try (Blocker blocker=_writeblock.acquire())
+        {        
+            write(content,complete,blocker);
+            blocker.block();
+        }
+    }
+    
+    protected void write(ByteBuffer content, boolean complete, Callback callback)
+    {
+        _channel.write(content,complete,callback);
+    }
+    
+    @Override
+    public void close()
+    {
+        loop: while(true)
+        {
+            OutputState state=_state.get();
+            switch (state)
+            {
+                case CLOSED:
+                    break loop;
+                    
+                case UNREADY:
+                    if (_state.compareAndSet(state,OutputState.ERROR))
+                        _writeListener.onError(_onError==null?new EofException("Async close"):_onError);
+                    continue;
+                    
+                default:
+                    if (_state.compareAndSet(state,OutputState.CLOSED))
+                    {
+                        try
+                        {
+                            write(BufferUtil.hasContent(_aggregate)?_aggregate:BufferUtil.EMPTY_BUFFER,!_channel.getResponse().isIncluding());
+                        }
+                        catch(IOException e)
+                        {
+                            LOG.debug(e);
+                            _channel.failed();
+                        }
+                        releaseBuffer();
+                        return;
+                    }
+            }
+        }
+    }
+
+    /* Called to indicated that the output is already closed (write with last==true performed) and the state needs to be updated to match */
+    void closed()
+    {
+        loop: while(true)
+        {
+            OutputState state=_state.get();
+            switch (state)
+            {
+                case CLOSED:
+                    break loop;
+                    
+                case UNREADY:
+                    if (_state.compareAndSet(state,OutputState.ERROR))
+                        _writeListener.onError(_onError==null?new EofException("Async closed"):_onError);
+                    continue;
+                    
+                default:
+                    if (_state.compareAndSet(state,OutputState.CLOSED))
+                    {
+                        try
+                        {
+                            _channel.getResponse().closeOutput();
+                        }
+                        catch(IOException e)
+                        {
+                            LOG.debug(e);
+                            _channel.failed();
+                        }
+                        releaseBuffer();
+                        return;
+                    }
+            }
+        }
+    }
+
+    private void releaseBuffer()
+    {
+        if (_aggregate != null)
+        {
+            _channel.getConnector().getByteBufferPool().release(_aggregate);
+            _aggregate = null;
+        }
+    }
+
+    public boolean isClosed()
+    {
+        return _state.get()==OutputState.CLOSED;
+    }
+
+    @Override
+    public void flush() throws IOException
+    {
+        while(true)
+        {
+            switch(_state.get())
+            {
+                case OPEN:
+                    write(BufferUtil.hasContent(_aggregate)?_aggregate:BufferUtil.EMPTY_BUFFER, false);
+                    return;
+
+                case ASYNC:
+                    throw new IllegalStateException("isReady() not called");
+
+                case READY:
+                    if (!_state.compareAndSet(OutputState.READY, OutputState.PENDING))
+                        continue;
+                    new AsyncFlush().iterate();
+                    return;
+
+                case PENDING:
+                case UNREADY:
+                    throw new WritePendingException();
+
+                case ERROR:
+                    throw new EofException(_onError);
+                    
+                case CLOSED:
+                    return;
+            }
+            break;
+        }
+    }
+
+
+    @Override
+    public void write(byte[] b, int off, int len) throws IOException
+    {
+        _written+=len;
+        boolean complete=_channel.getResponse().isAllContentWritten(_written);
+
+        // Async or Blocking ?
+        while(true)
+        {
+            switch(_state.get())
+            {
+                case OPEN:
+                    // process blocking below
+                    break;
+
+                case ASYNC:
+                    throw new IllegalStateException("isReady() not called");
+
+                case READY:
+                    if (!_state.compareAndSet(OutputState.READY, OutputState.PENDING))
+                        continue;
+
+                    // Should we aggregate?
+                    if (!complete && len<=_commitSize)
+                    {
+                        if (_aggregate == null)
+                            _aggregate = _channel.getByteBufferPool().acquire(getBufferSize(), false);
+
+                        // YES - fill the aggregate with content from the buffer
+                        int filled = BufferUtil.fill(_aggregate, b, off, len);
+
+                        // return if we are not complete, not full and filled all the content
+                        if (filled==len && !BufferUtil.isFull(_aggregate))
+                        {
+                            if (!_state.compareAndSet(OutputState.PENDING, OutputState.ASYNC))
+                                throw new IllegalStateException();
+                            return;
+                        }
+
+                        // adjust offset/length
+                        off+=filled;
+                        len-=filled;
+                    }
+
+                    // Do the asynchronous writing from the callback
+                    new AsyncWrite(b,off,len,complete).iterate();
+                    return;
+
+                case PENDING:
+                case UNREADY:
+                    throw new WritePendingException();
+
+                case ERROR:
+                    throw new EofException(_onError);
+                    
+                case CLOSED:
+                    throw new EofException("Closed");
+            }
+            break;
+        }
+
+
+        // handle blocking write
+
+        // Should we aggregate?
+        int capacity = getBufferSize();
+        if (!complete && len<=_commitSize)
+        {
+            if (_aggregate == null)
+                _aggregate = _channel.getByteBufferPool().acquire(capacity, false);
+
+            // YES - fill the aggregate with content from the buffer
+            int filled = BufferUtil.fill(_aggregate, b, off, len);
+
+            // return if we are not complete, not full and filled all the content
+            if (filled==len && !BufferUtil.isFull(_aggregate))
+                return;
+
+            // adjust offset/length
+            off+=filled;
+            len-=filled;
+        }
+
+        // flush any content from the aggregate
+        if (BufferUtil.hasContent(_aggregate))
+        {
+            write(_aggregate, complete && len==0);
+
+            // should we fill aggregate again from the buffer?
+            if (len>0 && !complete && len<=_commitSize)
+            {
+                BufferUtil.append(_aggregate, b, off, len);
+                return;
+            }
+        }
+
+        // write any remaining content in the buffer directly
+        if (len>0)
+        {
+            ByteBuffer wrap = ByteBuffer.wrap(b, off, len);
+            ByteBuffer view = wrap.duplicate();
+
+            // write a buffer capacity at a time to avoid JVM pooling large direct buffers
+            // http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6210541
+            while (len>getBufferSize())
+            {
+                int p=view.position();
+                int l=p+getBufferSize();
+                view.limit(p+getBufferSize());
+                write(view,false);
+                len-=getBufferSize();
+                view.limit(l+Math.min(len,getBufferSize()));
+                view.position(l);
+            }
+            write(view,complete);
+        }
+        else if (complete)
+            write(BufferUtil.EMPTY_BUFFER,complete);
+
+        if (complete)
+            closed();
+
+    }
+
+    public void write(ByteBuffer buffer) throws IOException
+    {
+        _written+=buffer.remaining();
+        boolean complete=_channel.getResponse().isAllContentWritten(_written);
+
+        // Async or Blocking ?
+        while(true)
+        {
+            switch(_state.get())
+            {
+                case OPEN:
+                    // process blocking below
+                    break;
+
+                case ASYNC:
+                    throw new IllegalStateException("isReady() not called");
+
+                case READY:
+                    if (!_state.compareAndSet(OutputState.READY, OutputState.PENDING))
+                        continue;
+
+                    // Do the asynchronous writing from the callback
+                    new AsyncWrite(buffer,complete).iterate();
+                    return;
+
+                case PENDING:
+                case UNREADY:
+                    throw new WritePendingException();
+
+                case ERROR:
+                    throw new EofException(_onError);
+                    
+                case CLOSED:
+                    throw new EofException("Closed");
+            }
+            break;
+        }
+
+
+        // handle blocking write
+        int len=BufferUtil.length(buffer);
+
+        // flush any content from the aggregate
+        if (BufferUtil.hasContent(_aggregate))
+            write(_aggregate, complete && len==0);
+
+        // write any remaining content in the buffer directly
+        if (len>0)
+            write(buffer, complete);
+        else if (complete)
+            write(BufferUtil.EMPTY_BUFFER,complete);
+
+        if (complete)
+            closed();
+    }
+
+    @Override
+    public void write(int b) throws IOException
+    {
+        _written+=1;
+        boolean complete=_channel.getResponse().isAllContentWritten(_written);
+
+        // Async or Blocking ?
+        while(true)
+        {
+            switch(_state.get())
+            {
+                case OPEN:
+                    if (_aggregate == null)
+                        _aggregate = _channel.getByteBufferPool().acquire(getBufferSize(), false);
+                    BufferUtil.append(_aggregate, (byte)b);
+
+                    // Check if all written or full
+                    if (complete || BufferUtil.isFull(_aggregate))
+                    {
+                        try(Blocker blocker=_writeblock.acquire())
+                        {
+                            write(_aggregate, complete, blocker);
+                            blocker.block();
+                        }
+                        if (complete)
+                            closed();
+                    }
+                    break;
+
+                case ASYNC:
+                    throw new IllegalStateException("isReady() not called");
+
+                case READY:
+                    if (!_state.compareAndSet(OutputState.READY, OutputState.PENDING))
+                        continue;
+
+                    if (_aggregate == null)
+                        _aggregate = _channel.getByteBufferPool().acquire(getBufferSize(), false);
+                    BufferUtil.append(_aggregate, (byte)b);
+
+                    // Check if all written or full
+                    if (!complete && !BufferUtil.isFull(_aggregate))
+                    {
+                        if (!_state.compareAndSet(OutputState.PENDING, OutputState.ASYNC))
+                            throw new IllegalStateException();
+                        return;
+                    }
+
+                    // Do the asynchronous writing from the callback
+                    new AsyncFlush().iterate();
+                    return;
+
+                case PENDING:
+                case UNREADY:
+                    throw new WritePendingException();
+
+                case ERROR:
+                    throw new EofException(_onError);
+                    
+                case CLOSED:
+                    throw new EofException("Closed");
+            }
+            break;
+        }
+    }
+
+    @Override
+    public void print(String s) throws IOException
+    {
+        if (isClosed())
+            throw new IOException("Closed");
+
+        write(s.getBytes(_channel.getResponse().getCharacterEncoding()));
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Blocking send of content.
+     * @param content The content to send.
+     * @throws IOException
+     */
+    public void sendContent(ByteBuffer content) throws IOException
+    {
+        try(Blocker blocker=_writeblock.acquire())
+        {
+            write(content,true,blocker);
+            blocker.block();
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Blocking send of content.
+     * @param in The content to send
+     * @throws IOException
+     */
+    public void sendContent(InputStream in) throws IOException
+    {
+        try(Blocker blocker=_writeblock.acquire())
+        {
+            new InputStreamWritingCB(in,blocker).iterate();
+            blocker.block();
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Blocking send of content.
+     * @param in The content to send
+     * @throws IOException
+     */
+    public void sendContent(ReadableByteChannel in) throws IOException
+    {
+        try(Blocker blocker=_writeblock.acquire())
+        {
+            new ReadableByteChannelWritingCB(in,blocker).iterate();
+            blocker.block();
+        }
+    }
+
+
+    /* ------------------------------------------------------------ */
+    /** Blocking send of content.
+     * @param content The content to send
+     * @throws IOException
+     */
+    public void sendContent(HttpContent content) throws IOException
+    {
+        try(Blocker blocker=_writeblock.acquire())
+        {
+            sendContent(content,blocker);
+            blocker.block();
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Asynchronous send of content.
+     * @param content The content to send
+     * @param callback The callback to use to notify success or failure
+     */
+    public void sendContent(ByteBuffer content, final Callback callback)
+    {
+        write(content,true,new Callback()
+        {
+            @Override
+            public void succeeded()
+            {
+                closed();
+                callback.succeeded();
+            }
+
+            @Override
+            public void failed(Throwable x)
+            {
+                callback.failed(x);
+            }
+        });
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Asynchronous send of content.
+     * @param in The content to send as a stream.  The stream will be closed
+     * after reading all content.
+     * @param callback The callback to use to notify success or failure
+     */
+    public void sendContent(InputStream in, Callback callback)
+    {
+        new InputStreamWritingCB(in,callback).iterate();
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Asynchronous send of content.
+     * @param in The content to send as a channel.  The channel will be closed
+     * after reading all content.
+     * @param callback The callback to use to notify success or failure
+     */
+    public void sendContent(ReadableByteChannel in, Callback callback)
+    {
+        new ReadableByteChannelWritingCB(in,callback).iterate();
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Asynchronous send of content.
+     * @param httpContent The content to send
+     * @param callback The callback to use to notify success or failure
+     */
+    public void sendContent(HttpContent httpContent, Callback callback) throws IOException
+    {
+        if (BufferUtil.hasContent(_aggregate))
+            throw new IOException("written");
+        if (_channel.isCommitted())
+            throw new IOException("committed");
+
+        while (true)
+        {
+            switch(_state.get())
+            {
+                case OPEN:
+                    if (!_state.compareAndSet(OutputState.OPEN, OutputState.PENDING))
+                        continue;
+                    break;
+                case ERROR:
+                    throw new EofException(_onError);
+                case CLOSED:
+                    throw new EofException("Closed");
+                default:
+                    throw new IllegalStateException();
+            }
+            break;
+        }
+        ByteBuffer buffer= _channel.useDirectBuffers()?httpContent.getDirectBuffer():null;
+        if (buffer == null)
+            buffer = httpContent.getIndirectBuffer();
+
+        if (buffer!=null)
+        {
+            sendContent(buffer,callback);
+            return;
+        }
+
+        ReadableByteChannel rbc=httpContent.getReadableByteChannel();
+        if (rbc!=null)
+        {
+            // Close of the rbc is done by the async sendContent
+            sendContent(rbc,callback);
+            return;
+        }
+
+        InputStream in = httpContent.getInputStream();
+        if ( in!=null )
+        {
+            sendContent(in,callback);
+            return;
+        }
+
+        callback.failed(new IllegalArgumentException("unknown content for "+httpContent));
+    }
+
+    public int getBufferSize()
+    {
+        return _bufferSize;
+    }
+
+    public void setBufferSize(int size)
+    {
+        _bufferSize = size;
+        _commitSize = size;
+    }
+
+    public void resetBuffer()
+    {
+        if (BufferUtil.hasContent(_aggregate))
+            BufferUtil.clear(_aggregate);
+    }
+
+    @Override
+    public void setWriteListener(WriteListener writeListener)
+    {
+        if (!_channel.getState().isAsync())
+            throw new IllegalStateException("!ASYNC");
+
+        if (_state.compareAndSet(OutputState.OPEN, OutputState.READY))
+        {
+            _writeListener = writeListener;
+            _channel.getState().onWritePossible();
+        }
+        else
+            throw new IllegalStateException();
+    }
+
+    /**
+     * @see javax.servlet.ServletOutputStream#isReady()
+     */
+    @Override
+    public boolean isReady()
+    {
+        while (true)
+        {
+            switch(_state.get())
+            {
+                case OPEN:
+                    return true;
+                case ASYNC:
+                    if (!_state.compareAndSet(OutputState.ASYNC, OutputState.READY))
+                        continue;
+                    return true;
+                case READY:
+                    return true;
+                case PENDING:
+                    if (!_state.compareAndSet(OutputState.PENDING, OutputState.UNREADY))
+                        continue;
+                    return false;
+                case UNREADY:
+                    return false;
+
+                case ERROR:
+                    return true;
+                    
+                case CLOSED:
+                    return true;
+            }
+        }
+    }
+
+    @Override
+    public void run()
+    {
+        loop: while (true)
+        {
+            OutputState state = _state.get();
+
+            if(_onError!=null)
+            {
+                switch(state)
+                {
+                    case CLOSED:
+                    case ERROR:
+                        _onError=null;
+                        break loop;
+
+                    default:
+                        if (_state.compareAndSet(state, OutputState.ERROR))
+                        {
+                            Throwable th=_onError;
+                            _onError=null;
+                            LOG.debug("onError",th);
+                            _writeListener.onError(th);
+                            close();
+
+                            break loop;
+                        }
+
+                }
+                continue loop;
+            }
+
+            switch(_state.get())
+            {
+                case READY:
+                case CLOSED:
+                    // even though a write is not possible, because a close has 
+                    // occurred, we need to call onWritePossible to tell async
+                    // producer that the last write completed.
+                    try
+                    {
+                        _writeListener.onWritePossible();
+                        break loop;
+                    }
+                    catch (Throwable e)
+                    {
+                        _onError=e;
+                    }
+                    break;
+                default:
+
+            }
+        }
+    }
+    
+    @Override
+    public String toString()
+    {
+        return String.format("%s@%x{%s}",this.getClass().getSimpleName(),hashCode(),_state.get());
+    }
+    
+    private abstract class AsyncICB extends IteratingCallback
+    {
+        @Override
+        protected void completed()
+        {
+            while(true)
+            {
+                OutputState last=_state.get();
+                switch(last)
+                {
+                    case PENDING:
+                        if (!_state.compareAndSet(OutputState.PENDING, OutputState.ASYNC))
+                            continue;
+                        break;
+
+                    case UNREADY:
+                        if (!_state.compareAndSet(OutputState.UNREADY, OutputState.READY))
+                            continue;
+                        _channel.getState().onWritePossible();
+                        break;
+
+                    case CLOSED:
+                        break;
+
+                    default:
+                        throw new IllegalStateException();
+                }
+                break;
+            }
+        }
+
+        @Override
+        public void failed(Throwable e)
+        {
+            super.failed(e);
+            _onError=e;
+            _channel.getState().onWritePossible();
+        }
+    }
+    
+    
+    private class AsyncFlush extends AsyncICB
+    {
+        protected volatile boolean _flushed;
+
+        public AsyncFlush()
+        {
+        }
+
+        @Override
+        protected Action process()
+        {
+            if (BufferUtil.hasContent(_aggregate))
+            {
+                _flushed=true;
+                write(_aggregate, false, this);
+                return Action.SCHEDULED;
+            }
+
+            if (!_flushed)
+            {
+                _flushed=true;
+                write(BufferUtil.EMPTY_BUFFER,false,this);
+                return Action.SCHEDULED;
+            }
+
+            return Action.SUCCEEDED;
+        }
+    }
+
+
+
+    private class AsyncWrite extends AsyncICB
+    {
+        private final ByteBuffer _buffer;
+        private final ByteBuffer _slice;
+        private final boolean _complete;
+        private final int _len;
+        protected volatile boolean _completed;
+
+        public AsyncWrite(byte[] b, int off, int len, boolean complete)
+        {
+            _buffer=ByteBuffer.wrap(b, off, len);
+            _len=len;
+            // always use a view for large byte arrays to avoid JVM pooling large direct buffers
+            _slice=_len<getBufferSize()?null:_buffer.duplicate();
+            _complete=complete;
+        }
+
+        public AsyncWrite(ByteBuffer buffer, boolean complete)
+        {
+            _buffer=buffer;
+            _len=buffer.remaining();
+            // Use a slice buffer for large indirect to avoid JVM pooling large direct buffers
+            _slice=_buffer.isDirect()||_len<getBufferSize()?null:_buffer.duplicate();
+            _complete=complete;
+        }
+
+        @Override
+        protected Action process()
+        {
+            // flush any content from the aggregate
+            if (BufferUtil.hasContent(_aggregate))
+            {
+                _completed=_len==0;
+                write(_aggregate, _complete && _completed, this);
+                return Action.SCHEDULED;
+            }
+
+            // Can we just aggregate the remainder?
+            if (!_complete && _len<BufferUtil.space(_aggregate) && _len<_commitSize)
+            {
+                int position = BufferUtil.flipToFill(_aggregate);
+                BufferUtil.put(_buffer,_aggregate);
+                BufferUtil.flipToFlush(_aggregate, position);
+                return Action.SUCCEEDED;
+            }
+            
+            // Is there data left to write?
+            if (_buffer.hasRemaining())
+            {
+                // if there is no slice, just write it
+                if (_slice==null)
+                {
+                    _completed=true;
+                    write(_buffer, _complete, this);
+                    return Action.SCHEDULED;
+                }
+                
+                // otherwise take a slice
+                int p=_buffer.position();
+                int l=Math.min(getBufferSize(),_buffer.remaining());
+                int pl=p+l;
+                _slice.limit(pl);
+                _buffer.position(pl);
+                _slice.position(p);
+                _completed=!_buffer.hasRemaining();
+                write(_slice, _complete && _completed, this);
+                return Action.SCHEDULED;
+            }
+            
+            // all content written, but if we have not yet signal completion, we
+            // need to do so
+            if (_complete && !_completed)
+            {
+                _completed=true;
+                write(BufferUtil.EMPTY_BUFFER, _complete, this);
+                return Action.SCHEDULED;
+            }
+
+            return Action.SUCCEEDED;
+        }
+
+        @Override
+        protected void completed()
+        {
+            super.completed();
+            if (_complete)
+                closed();
+        }
+        
+        
+    }
+
+
+    /* ------------------------------------------------------------ */
+    /** An iterating callback that will take content from an
+     * InputStream and write it to the associated {@link HttpChannel}.
+     * A non direct buffer of size {@link HttpOutput#getBufferSize()} is used.
+     * This callback is passed to the {@link HttpChannel#write(ByteBuffer, boolean, Callback)} to
+     * be notified as each buffer is written and only once all the input is consumed will the
+     * wrapped {@link Callback#succeeded()} method be called.
+     */
+    private class InputStreamWritingCB extends IteratingNestedCallback
+    {
+        private final InputStream _in;
+        private final ByteBuffer _buffer;
+        private boolean _eof;
+
+        public InputStreamWritingCB(InputStream in, Callback callback)
+        {
+            super(callback);
+            _in=in;
+            _buffer = _channel.getByteBufferPool().acquire(getBufferSize(), false);
+        }
+
+        @Override
+        protected Action process() throws Exception
+        {
+            // Only return if EOF has previously been read and thus
+            // a write done with EOF=true
+            if (_eof)
+            {
+                // Handle EOF
+                _in.close();
+                closed();
+                _channel.getByteBufferPool().release(_buffer);
+                return Action.SUCCEEDED;
+            }
+            
+            // Read until buffer full or EOF
+            int len=0;
+            while (len<_buffer.capacity() && !_eof)
+            {
+                int r=_in.read(_buffer.array(),_buffer.arrayOffset()+len,_buffer.capacity()-len);
+                if (r<0)
+                    _eof=true;
+                else
+                    len+=r;
+            }
+
+            // write what we have
+            _buffer.position(0);
+            _buffer.limit(len);
+            write(_buffer,_eof,this);
+            return Action.SCHEDULED;
+        }
+
+        @Override
+        public void failed(Throwable x)
+        {
+            super.failed(x);
+            _channel.getByteBufferPool().release(_buffer);
+            try
+            {
+                _in.close();
+            }
+            catch (IOException e)
+            {
+                LOG.ignore(e);
+            }
+        }
+
+    }
+
+    /* ------------------------------------------------------------ */
+    /** An iterating callback that will take content from a
+     * ReadableByteChannel and write it to the {@link HttpChannel}.
+     * A {@link ByteBuffer} of size {@link HttpOutput#getBufferSize()} is used that will be direct if
+     * {@link HttpChannel#useDirectBuffers()} is true.
+     * This callback is passed to the {@link HttpChannel#write(ByteBuffer, boolean, Callback)} to
+     * be notified as each buffer is written and only once all the input is consumed will the
+     * wrapped {@link Callback#succeeded()} method be called.
+     */
+    private class ReadableByteChannelWritingCB extends IteratingNestedCallback
+    {
+        private final ReadableByteChannel _in;
+        private final ByteBuffer _buffer;
+        private boolean _eof;
+
+        public ReadableByteChannelWritingCB(ReadableByteChannel in, Callback callback)
+        {
+            super(callback);
+            _in=in;
+            _buffer = _channel.getByteBufferPool().acquire(getBufferSize(), _channel.useDirectBuffers());
+        }
+
+        @Override
+        protected Action process() throws Exception
+        {
+            // Only return if EOF has previously been read and thus
+            // a write done with EOF=true
+            if (_eof)
+            {
+                _in.close();
+                closed();
+                _channel.getByteBufferPool().release(_buffer);
+                return Action.SUCCEEDED;
+            }
+            
+            // Read from stream until buffer full or EOF
+            _buffer.clear();
+            while (_buffer.hasRemaining() && !_eof)
+              _eof = (_in.read(_buffer)) <  0;
+
+            // write what we have
+            _buffer.flip();
+            write(_buffer,_eof,this);
+
+            return Action.SCHEDULED;
+        }
+
+        @Override
+        public void failed(Throwable x)
+        {
+            super.failed(x);
+            _channel.getByteBufferPool().release(_buffer);
+            try
+            {
+                _in.close();
+            }
+            catch (IOException e)
+            {
+                LOG.ignore(e);
+            }
+        }
+    }
+
+}
diff --git a/lib/jetty/org/eclipse/jetty/server/HttpTransport.java b/lib/jetty/org/eclipse/jetty/server/HttpTransport.java
new file mode 100644 (file)
index 0000000..7058425
--- /dev/null
@@ -0,0 +1,40 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server;
+
+import java.nio.ByteBuffer;
+
+import org.eclipse.jetty.http.HttpGenerator;
+import org.eclipse.jetty.util.Callback;
+
+public interface HttpTransport
+{
+    void send(HttpGenerator.ResponseInfo info, ByteBuffer content, boolean lastContent, Callback callback);
+
+    void send(ByteBuffer content, boolean lastContent, Callback callback);
+    
+    void completed();
+    
+    /* ------------------------------------------------------------ */
+    /** Abort transport.
+     * This is called when an error response needs to be sent, but the response is already committed.
+     * Abort to should terminate the transport in a way that can indicate abnormal response to the client. 
+     */
+    void abort();
+}
diff --git a/lib/jetty/org/eclipse/jetty/server/HttpWriter.java b/lib/jetty/org/eclipse/jetty/server/HttpWriter.java
new file mode 100644 (file)
index 0000000..d02a8c4
--- /dev/null
@@ -0,0 +1,80 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server;
+
+import java.io.IOException;
+import java.io.Writer;
+
+import org.eclipse.jetty.util.ByteArrayOutputStream2;
+
+/**
+ * 
+ */
+public abstract class HttpWriter extends Writer
+{
+    public static final int MAX_OUTPUT_CHARS = 512; 
+    
+    final HttpOutput _out;
+    final ByteArrayOutputStream2 _bytes;
+    final char[] _chars;
+
+    /* ------------------------------------------------------------ */
+    public HttpWriter(HttpOutput out)
+    {
+        _out=out;
+        _chars=new char[MAX_OUTPUT_CHARS];
+        _bytes = new ByteArrayOutputStream2(MAX_OUTPUT_CHARS);   
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public void close() throws IOException
+    {
+        _out.close();
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public void flush() throws IOException
+    {
+        _out.flush();
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public void write (String s,int offset, int length) throws IOException
+    {   
+        while (length > MAX_OUTPUT_CHARS)
+        {
+            write(s, offset, MAX_OUTPUT_CHARS);
+            offset += MAX_OUTPUT_CHARS;
+            length -= MAX_OUTPUT_CHARS;
+        }
+
+        s.getChars(offset, offset + length, _chars, 0);
+        write(_chars, 0, length);
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public void write (char[] s,int offset, int length) throws IOException
+    {         
+        throw new AbstractMethodError();
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/server/InclusiveByteRange.java b/lib/jetty/org/eclipse/jetty/server/InclusiveByteRange.java
new file mode 100644 (file)
index 0000000..cb81d3c
--- /dev/null
@@ -0,0 +1,230 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server;
+
+import java.util.Enumeration;
+import java.util.List;
+import java.util.StringTokenizer;
+
+import org.eclipse.jetty.util.LazyList;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/* ------------------------------------------------------------ */
+/** Byte range inclusive of end points.
+ * <PRE>
+ * 
+ *   parses the following types of byte ranges:
+ * 
+ *       bytes=100-499
+ *       bytes=-300
+ *       bytes=100-
+ *       bytes=1-2,2-3,6-,-2
+ *
+ *   given an entity length, converts range to string
+ * 
+ *       bytes 100-499/500
+ * 
+ * </PRE>
+ * 
+ * Based on RFC2616 3.12, 14.16, 14.35.1, 14.35.2
+ * <p>
+ * And yes the spec does strangely say that while 10-20, is bytes 10 to 20 and 10- is bytes 10 until the end that -20 IS NOT bytes 0-20, but the last 20 bytes of the content.
+ * 
+ * @version $version$
+ * 
+ */
+public class InclusiveByteRange 
+{
+    private static final Logger LOG = Log.getLogger(InclusiveByteRange.class);
+
+    long first = 0;
+    long last  = 0;    
+
+    public InclusiveByteRange(long first, long last)
+    {
+        this.first = first;
+        this.last = last;
+    }
+    
+    public long getFirst()
+    {
+        return first;
+    }
+
+    public long getLast()
+    {
+        return last;
+    }    
+
+
+    
+    /* ------------------------------------------------------------ */
+    /** 
+     * @param headers Enumeration of Range header fields.
+     * @param size Size of the resource.
+     * @return LazyList of satisfiable ranges
+     */
+    public static List<InclusiveByteRange> satisfiableRanges(Enumeration headers, long size)
+    {
+        Object satRanges=null;
+        
+        // walk through all Range headers
+    headers:
+        while (headers.hasMoreElements())
+        {
+            String header = (String) headers.nextElement();
+            StringTokenizer tok = new StringTokenizer(header,"=,",false);
+            String t=null;
+            try
+            {
+                // read all byte ranges for this header 
+                while (tok.hasMoreTokens())
+                {
+                    try
+                    {
+                        t = tok.nextToken().trim();
+
+                        long first = -1;
+                        long last = -1;
+                        int d = t.indexOf('-');
+                        if (d < 0 || t.indexOf("-",d + 1) >= 0)
+                        {
+                            if ("bytes".equals(t))
+                                continue;
+                            LOG.warn("Bad range format: {}",t);
+                            continue headers;
+                        }
+                        else if (d == 0)
+                        {
+                            if (d + 1 < t.length())
+                                last = Long.parseLong(t.substring(d + 1).trim());
+                            else
+                            {
+                                LOG.warn("Bad range format: {}",t);
+                                continue;
+                            }
+                        }
+                        else if (d + 1 < t.length())
+                        {
+                            first = Long.parseLong(t.substring(0,d).trim());
+                            last = Long.parseLong(t.substring(d + 1).trim());
+                        }
+                        else
+                            first = Long.parseLong(t.substring(0,d).trim());
+
+                        if (first == -1 && last == -1)
+                            continue headers;
+
+                        if (first != -1 && last != -1 && (first > last))
+                            continue headers;
+
+                        if (first < size)
+                        {
+                            InclusiveByteRange range = new InclusiveByteRange(first,last);
+                            satRanges = LazyList.add(satRanges,range);
+                        }
+                    }
+                    catch (NumberFormatException e)
+                    {
+                        LOG.warn("Bad range format: {}",t);
+                        LOG.ignore(e);
+                        continue;
+                    }
+                }
+            }
+            catch(Exception e)
+            {
+                LOG.warn("Bad range format: {}",t);
+                LOG.ignore(e);
+            }    
+        }
+        return LazyList.getList(satRanges,true);
+    }
+
+    /* ------------------------------------------------------------ */
+    public long getFirst(long size)
+    {
+        if (first<0)
+        {
+            long tf=size-last;
+            if (tf<0)
+                tf=0;
+            return tf;
+        }
+        return first;
+    }
+    
+    /* ------------------------------------------------------------ */
+    public long getLast(long size)
+    {
+        if (first<0)
+            return size-1;
+        
+        if (last<0 ||last>=size)
+            return size-1;
+        return last;
+    }
+    
+    /* ------------------------------------------------------------ */
+    public long getSize(long size)
+    {
+        return getLast(size)-getFirst(size)+1;
+    }
+
+
+    /* ------------------------------------------------------------ */
+    public String toHeaderRangeString(long size)
+    {
+        StringBuilder sb = new StringBuilder(40);
+        sb.append("bytes ");
+        sb.append(getFirst(size));
+        sb.append('-');
+        sb.append(getLast(size));
+        sb.append("/");
+        sb.append(size);
+        return sb.toString();
+    }
+
+    /* ------------------------------------------------------------ */
+    public static String to416HeaderRangeString(long size)
+    {
+        StringBuilder sb = new StringBuilder(40);
+        sb.append("bytes */");
+        sb.append(size);
+        return sb.toString();
+    }
+
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public String toString()
+    {
+        StringBuilder sb = new StringBuilder(60);
+        sb.append(Long.toString(first));
+        sb.append(":");
+        sb.append(Long.toString(last));
+        return sb.toString();
+    }
+
+
+}
+
+
+
diff --git a/lib/jetty/org/eclipse/jetty/server/Iso88591HttpWriter.java b/lib/jetty/org/eclipse/jetty/server/Iso88591HttpWriter.java
new file mode 100644 (file)
index 0000000..b4cdf8f
--- /dev/null
@@ -0,0 +1,75 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server;
+
+import java.io.IOException;
+
+/**
+ */
+public class Iso88591HttpWriter extends HttpWriter
+{
+    /* ------------------------------------------------------------ */
+    public Iso88591HttpWriter(HttpOutput out)
+    {
+        super(out);
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public void write (char[] s,int offset, int length) throws IOException
+    {
+        HttpOutput out = _out;
+        if (length==0 && out.isAllContentWritten())
+        {
+            close();
+            return;
+        }
+
+        if (length==1)
+        {
+            int c=s[offset];
+            out.write(c<256?c:'?');
+            return;
+        }
+        
+        while (length > 0)
+        {
+            _bytes.reset();
+            int chars = length>MAX_OUTPUT_CHARS?MAX_OUTPUT_CHARS:length;
+
+            byte[] buffer=_bytes.getBuf();
+            int bytes=_bytes.getCount();
+
+            if (chars>buffer.length-bytes)
+                chars=buffer.length-bytes;
+
+            for (int i = 0; i < chars; i++)
+            {
+                int c = s[offset+i];
+                buffer[bytes++]=(byte)(c<256?c:'?');
+            }
+            if (bytes>=0)
+                _bytes.setCount(bytes);
+
+            _bytes.writeTo(out);
+            length-=chars;
+            offset+=chars;
+        }
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/server/LocalConnector.java b/lib/jetty/org/eclipse/jetty/server/LocalConnector.java
new file mode 100644 (file)
index 0000000..5796226
--- /dev/null
@@ -0,0 +1,265 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executor;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.TimeUnit;
+
+import org.eclipse.jetty.io.ByteArrayEndPoint;
+import org.eclipse.jetty.io.ByteBufferPool;
+import org.eclipse.jetty.io.Connection;
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.ssl.SslContextFactory;
+import org.eclipse.jetty.util.thread.Scheduler;
+
+public class LocalConnector extends AbstractConnector
+{
+    private final BlockingQueue<LocalEndPoint> _connects = new LinkedBlockingQueue<>();
+
+
+    public LocalConnector(Server server, Executor executor, Scheduler scheduler, ByteBufferPool pool, int acceptors, ConnectionFactory... factories)
+    {
+        super(server,executor,scheduler,pool,acceptors,factories);
+        setIdleTimeout(30000);
+    }
+
+    public LocalConnector(Server server)
+    {
+        this(server, null, null, null, -1, new HttpConnectionFactory());
+    }
+
+    public LocalConnector(Server server, SslContextFactory sslContextFactory)
+    {
+        this(server, null, null, null, -1,AbstractConnectionFactory.getFactories(sslContextFactory,new HttpConnectionFactory()));
+    }
+
+    public LocalConnector(Server server, ConnectionFactory connectionFactory)
+    {
+        this(server, null, null, null, -1, connectionFactory);
+    }
+
+    public LocalConnector(Server server, ConnectionFactory connectionFactory, SslContextFactory sslContextFactory)
+    {
+        this(server, null, null, null, -1, AbstractConnectionFactory.getFactories(sslContextFactory,connectionFactory));
+    }
+
+    @Override
+    public Object getTransport()
+    {
+        return this;
+    }
+
+    /** Sends requests and get responses based on thread activity.
+     * Returns all the responses received once the thread activity has
+     * returned to the level it was before the requests.
+     * <p>
+     * This methods waits until the connection is closed or
+     * is idle for 1s before returning the responses.
+     * @param requests the requests
+     * @return the responses
+     * @throws Exception if the requests fail
+     */
+    public String getResponses(String requests) throws Exception
+    {
+        return getResponses(requests, 5, TimeUnit.SECONDS);
+    }
+
+    /** Sends requests and get responses based on thread activity.
+     * Returns all the responses received once the thread activity has
+     * returned to the level it was before the requests.
+     * <p>
+     * This methods waits until the connection is closed or
+     * an idle period before returning the responses.
+     * @param requests the requests
+     * @param idleFor The time the response stream must be idle for before returning
+     * @param units The units of idleFor
+     * @return the responses
+     * @throws Exception if the requests fail
+     */
+    public String getResponses(String requests,long idleFor,TimeUnit units) throws Exception
+    {
+        ByteBuffer result = getResponses(BufferUtil.toBuffer(requests,StandardCharsets.UTF_8),idleFor,units);
+        return result==null?null:BufferUtil.toString(result,StandardCharsets.UTF_8);
+    }
+
+    /** Sends requests and get's responses based on thread activity.
+     * Returns all the responses received once the thread activity has
+     * returned to the level it was before the requests.
+     * <p>
+     * This methods waits until the connection is closed or
+     * is idle for 1s before returning the responses.
+     * @param requestsBuffer the requests
+     * @return the responses
+     * @throws Exception if the requests fail
+     */
+    public ByteBuffer getResponses(ByteBuffer requestsBuffer) throws Exception
+    {
+        return getResponses(requestsBuffer, 5, TimeUnit.SECONDS);
+    }
+
+    /** Sends requests and get's responses based on thread activity.
+     * Returns all the responses received once the thread activity has
+     * returned to the level it was before the requests.
+     * <p>
+     * This methods waits until the connection is closed or
+     * an idle period before returning the responses.
+     * @param requestsBuffer the requests
+     * @param idleFor The time the response stream must be idle for before returning
+     * @param units The units of idleFor
+     * @return the responses
+     * @throws Exception if the requests fail
+     */
+    public ByteBuffer getResponses(ByteBuffer requestsBuffer,long idleFor,TimeUnit units) throws Exception
+    {
+        LOG.debug("requests {}", BufferUtil.toUTF8String(requestsBuffer));
+        LocalEndPoint endp = executeRequest(requestsBuffer);
+        endp.waitUntilClosedOrIdleFor(idleFor,units);
+        ByteBuffer responses = endp.takeOutput();
+        endp.getConnection().close();
+        LOG.debug("responses {}", BufferUtil.toUTF8String(responses));
+        return responses;
+    }
+
+    /**
+     * Execute a request and return the EndPoint through which
+     * responses can be received.
+     * @param rawRequest the request
+     * @return the local endpoint
+     */
+    public LocalEndPoint executeRequest(String rawRequest)
+    {
+        return executeRequest(BufferUtil.toBuffer(rawRequest, StandardCharsets.UTF_8));
+    }
+
+    private LocalEndPoint executeRequest(ByteBuffer rawRequest)
+    {
+        LocalEndPoint endp = new LocalEndPoint();
+        endp.setInput(rawRequest);
+        _connects.add(endp);
+        return endp;
+    }
+
+    @Override
+    protected void accept(int acceptorID) throws IOException, InterruptedException
+    {
+        LOG.debug("accepting {}", acceptorID);
+        LocalEndPoint endPoint = _connects.take();
+        endPoint.onOpen();
+        onEndPointOpened(endPoint);
+
+        Connection connection = getDefaultConnectionFactory().newConnection(this, endPoint);
+        endPoint.setConnection(connection);
+
+        connection.onOpen();
+    }
+
+    public class LocalEndPoint extends ByteArrayEndPoint
+    {
+        private final CountDownLatch _closed = new CountDownLatch(1);
+
+        public LocalEndPoint()
+        {
+            super(getScheduler(), LocalConnector.this.getIdleTimeout());
+            setGrowOutput(true);
+        }
+
+        public void addInput(String s)
+        {
+            // TODO this is a busy wait
+            while(getIn()==null || BufferUtil.hasContent(getIn()))
+                Thread.yield();
+            setInput(BufferUtil.toBuffer(s, StandardCharsets.UTF_8));
+        }
+
+        @Override
+        public void close()
+        {
+            boolean wasOpen=isOpen();
+            super.close();
+            if (wasOpen)
+            {
+//                connectionClosed(getConnection());
+                getConnection().onClose();
+                onClose();
+            }
+        }
+
+        @Override
+        public void onClose()
+        {
+            LocalConnector.this.onEndPointClosed(this);
+            super.onClose();
+            _closed.countDown();
+        }
+
+        @Override
+        public void shutdownOutput()
+        {
+            super.shutdownOutput();
+            close();
+        }
+
+        public void waitUntilClosed()
+        {
+            while (isOpen())
+            {
+                try
+                {
+                    if (!_closed.await(10,TimeUnit.SECONDS))
+                        break;
+                }
+                catch(Exception e)
+                {
+                    LOG.warn(e);
+                }
+            }
+        }
+
+        public void waitUntilClosedOrIdleFor(long idleFor,TimeUnit units)
+        {
+            Thread.yield();
+            int size=getOutput().remaining();
+            while (isOpen())
+            {
+                try
+                {
+                    if (!_closed.await(idleFor,units))
+                    {
+                        if (size==getOutput().remaining())
+                        {
+                            LOG.debug("idle for {} {}",idleFor,units);
+                            return;
+                        }
+                        size=getOutput().remaining();
+                    }
+                }
+                catch(Exception e)
+                {
+                    LOG.warn(e);
+                }
+            }
+        }
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/server/LowResourceMonitor.java b/lib/jetty/org/eclipse/jetty/server/LowResourceMonitor.java
new file mode 100644 (file)
index 0000000..1aad488
--- /dev/null
@@ -0,0 +1,350 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.concurrent.Executor;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.util.annotation.ManagedAttribute;
+import org.eclipse.jetty.util.annotation.ManagedObject;
+import org.eclipse.jetty.util.annotation.Name;
+import org.eclipse.jetty.util.component.AbstractLifeCycle;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.thread.ScheduledExecutorScheduler;
+import org.eclipse.jetty.util.thread.Scheduler;
+import org.eclipse.jetty.util.thread.ThreadPool;
+
+
+/* ------------------------------------------------------------ */
+/** A monitor for low resources
+ * <p>An instance of this class will monitor all the connectors of a server (or a set of connectors
+ * configured with {@link #setMonitoredConnectors(Collection)}) for a low resources state.
+ * Low resources can be detected by:<ul>
+ * <li>{@link ThreadPool#isLowOnThreads()} if {@link Connector#getExecutor()} is
+ * an instance of {@link ThreadPool} and {@link #setMonitorThreads(boolean)} is true.<li>
+ * <li>If {@link #setMaxMemory(long)} is non zero then low resources is detected if the JVMs
+ * {@link Runtime} instance has {@link Runtime#totalMemory()} minus {@link Runtime#freeMemory()}
+ * greater than {@link #getMaxMemory()}</li>
+ * <li>If {@link #setMaxConnections(int)} is non zero then low resources is dected if the total number
+ * of connections exceeds {@link #getMaxConnections()}</li>
+ * </ul>
+ * </p>
+ * <p>Once low resources state is detected, the cause is logged and all existing connections returned
+ * by {@link Connector#getConnectedEndPoints()} have {@link EndPoint#setIdleTimeout(long)} set
+ * to {@link #getLowResourcesIdleTimeout()}.  New connections are not affected, however if the low
+ * resources state persists for more than {@link #getMaxLowResourcesTime()}, then the
+ * {@link #getLowResourcesIdleTimeout()} to all connections again.  Once the low resources state is
+ * cleared, the idle timeout is reset to the connector default given by {@link Connector#getIdleTimeout()}.
+ * </p>
+ */
+@ManagedObject ("Monitor for low resource conditions and activate a low resource mode if detected")
+public class LowResourceMonitor extends AbstractLifeCycle
+{
+    private static final Logger LOG = Log.getLogger(LowResourceMonitor.class);
+    private final Server _server;
+    private Scheduler _scheduler;
+    private Connector[] _monitoredConnectors;
+    private int _period=1000;
+    private int _maxConnections;
+    private long _maxMemory;
+    private int _lowResourcesIdleTimeout=1000;
+    private int _maxLowResourcesTime=0;
+    private boolean _monitorThreads=true;
+    private final AtomicBoolean _low = new AtomicBoolean();
+    private String _cause;
+    private String _reasons;
+    private long _lowStarted;
+
+
+    private final Runnable _monitor = new Runnable()
+    {
+        @Override
+        public void run()
+        {
+            if (isRunning())
+            {
+                monitor();
+                _scheduler.schedule(_monitor,_period,TimeUnit.MILLISECONDS);
+            }
+        }
+    };
+
+    public LowResourceMonitor(@Name("server") Server server)
+    {
+        _server=server;
+    }
+
+    @ManagedAttribute("Are the monitored connectors low on resources?")
+    public boolean isLowOnResources()
+    {
+        return _low.get();
+    }
+
+    @ManagedAttribute("The reason(s) the monitored connectors are low on resources")
+    public String getLowResourcesReasons()
+    {
+        return _reasons;
+    }
+
+    @ManagedAttribute("Get the timestamp in ms since epoch that low resources state started")
+    public long getLowResourcesStarted()
+    {
+        return _lowStarted;
+    }
+
+    @ManagedAttribute("The monitored connectors. If null then all server connectors are monitored")
+    public Collection<Connector> getMonitoredConnectors()
+    {
+        if (_monitoredConnectors==null)
+            return Collections.emptyList();
+        return Arrays.asList(_monitoredConnectors);
+    }
+
+    /**
+     * @param monitoredConnectors The collections of Connectors that should be monitored for low resources.
+     */
+    public void setMonitoredConnectors(Collection<Connector> monitoredConnectors)
+    {
+        if (monitoredConnectors==null || monitoredConnectors.size()==0)
+            _monitoredConnectors=null;
+        else
+            _monitoredConnectors = monitoredConnectors.toArray(new Connector[monitoredConnectors.size()]);
+    }
+
+    @ManagedAttribute("The monitor period in ms")
+    public int getPeriod()
+    {
+        return _period;
+    }
+
+    /**
+     * @param periodMS The period in ms to monitor for low resources
+     */
+    public void setPeriod(int periodMS)
+    {
+        _period = periodMS;
+    }
+
+    @ManagedAttribute("True if low available threads status is monitored")
+    public boolean getMonitorThreads()
+    {
+        return _monitorThreads;
+    }
+
+    /**
+     * @param monitorThreads If true, check connectors executors to see if they are
+     * {@link ThreadPool} instances that are low on threads.
+     */
+    public void setMonitorThreads(boolean monitorThreads)
+    {
+        _monitorThreads = monitorThreads;
+    }
+
+    @ManagedAttribute("The maximum connections allowed for the monitored connectors before low resource handling is activated")
+    public int getMaxConnections()
+    {
+        return _maxConnections;
+    }
+
+    /**
+     * @param maxConnections The maximum connections before low resources state is triggered
+     */
+    public void setMaxConnections(int maxConnections)
+    {
+        _maxConnections = maxConnections;
+    }
+
+    @ManagedAttribute("The maximum memory (in bytes) that can be used before low resources is triggered.  Memory used is calculated as (totalMemory-freeMemory).")
+    public long getMaxMemory()
+    {
+        return _maxMemory;
+    }
+
+    /**
+     * @param maxMemoryBytes The maximum memory in bytes in use before low resources is triggered.
+     */
+    public void setMaxMemory(long maxMemoryBytes)
+    {
+        _maxMemory = maxMemoryBytes;
+    }
+
+    @ManagedAttribute("The idletimeout in ms to apply to all existing connections when low resources is detected")
+    public int getLowResourcesIdleTimeout()
+    {
+        return _lowResourcesIdleTimeout;
+    }
+
+    /**
+     * @param lowResourcesIdleTimeoutMS The timeout in ms to apply to EndPoints when in the low resources state.
+     */
+    public void setLowResourcesIdleTimeout(int lowResourcesIdleTimeoutMS)
+    {
+        _lowResourcesIdleTimeout = lowResourcesIdleTimeoutMS;
+    }
+
+    @ManagedAttribute("The maximum time in ms that low resources condition can persist before lowResourcesIdleTimeout is applied to new connections as well as existing connections")
+    public int getMaxLowResourcesTime()
+    {
+        return _maxLowResourcesTime;
+    }
+
+    /**
+     * @param maxLowResourcesTimeMS The time in milliseconds that a low resource state can persist before the low resource idle timeout is reapplied to all connections
+     */
+    public void setMaxLowResourcesTime(int maxLowResourcesTimeMS)
+    {
+        _maxLowResourcesTime = maxLowResourcesTimeMS;
+    }
+
+    @Override
+    protected void doStart() throws Exception
+    {
+        _scheduler = _server.getBean(Scheduler.class);
+
+        if (_scheduler==null)
+        {
+            _scheduler=new LRMScheduler();
+            _scheduler.start();
+        }
+        super.doStart();
+
+        _scheduler.schedule(_monitor,_period,TimeUnit.MILLISECONDS);
+    }
+
+    @Override
+    protected void doStop() throws Exception
+    {
+        if (_scheduler instanceof LRMScheduler)
+            _scheduler.stop();
+        super.doStop();
+    }
+
+    protected Connector[] getMonitoredOrServerConnectors()
+    {
+        if (_monitoredConnectors!=null && _monitoredConnectors.length>0)
+            return _monitoredConnectors;
+        return _server.getConnectors();
+    }
+
+    protected void monitor()
+    {
+        String reasons=null;
+        String cause="";
+        int connections=0;
+
+        for(Connector connector : getMonitoredOrServerConnectors())
+        {
+            connections+=connector.getConnectedEndPoints().size();
+
+            Executor executor = connector.getExecutor();
+            if (executor instanceof ThreadPool)
+            {
+                ThreadPool threadpool=(ThreadPool) executor;
+                if (_monitorThreads && threadpool.isLowOnThreads())
+                {
+                    reasons=low(reasons,"Low on threads: "+threadpool);
+                    cause+="T";
+                }
+            }
+        }
+
+        if (_maxConnections>0 && connections>_maxConnections)
+        {
+            reasons=low(reasons,"Max Connections exceeded: "+connections+">"+_maxConnections);
+            cause+="C";
+        }
+
+        long memory=Runtime.getRuntime().totalMemory()-Runtime.getRuntime().freeMemory();
+        if (_maxMemory>0 && memory>_maxMemory)
+        {
+            reasons=low(reasons,"Max memory exceeded: "+memory+">"+_maxMemory);
+            cause+="M";
+        }
+
+
+        if (reasons!=null)
+        {
+            // Log the reasons if there is any change in the cause
+            if (!cause.equals(_cause))
+            {
+                LOG.warn("Low Resources: {}",reasons);
+                _cause=cause;
+            }
+
+            // Enter low resources state?
+            if (_low.compareAndSet(false,true))
+            {
+                _reasons=reasons;
+                _lowStarted=System.currentTimeMillis();
+                setLowResources();
+            }
+
+            // Too long in low resources state?
+            if (_maxLowResourcesTime>0 && (System.currentTimeMillis()-_lowStarted)>_maxLowResourcesTime)
+                setLowResources();
+        }
+        else
+        {
+            if (_low.compareAndSet(true,false))
+            {
+                LOG.info("Low Resources cleared");
+                _reasons=null;
+                _lowStarted=0;
+                _cause=null;
+                clearLowResources();
+            }
+        }
+    }
+
+    protected void setLowResources()
+    {
+        for(Connector connector : getMonitoredOrServerConnectors())
+        {
+            for (EndPoint endPoint : connector.getConnectedEndPoints())
+                endPoint.setIdleTimeout(_lowResourcesIdleTimeout);
+        }
+    }
+
+    protected void clearLowResources()
+    {
+        for(Connector connector : getMonitoredOrServerConnectors())
+        {
+            for (EndPoint endPoint : connector.getConnectedEndPoints())
+                endPoint.setIdleTimeout(connector.getIdleTimeout());
+        }
+    }
+
+    private String low(String reasons, String newReason)
+    {
+        if (reasons==null)
+            return newReason;
+        return reasons+", "+newReason;
+    }
+
+
+    private static class LRMScheduler extends ScheduledExecutorScheduler
+    {
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/server/NCSARequestLog.java b/lib/jetty/org/eclipse/jetty/server/NCSARequestLog.java
new file mode 100644 (file)
index 0000000..2c2286e
--- /dev/null
@@ -0,0 +1,281 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.Writer;
+import java.util.TimeZone;
+
+import org.eclipse.jetty.util.RolloverFileOutputStream;
+import org.eclipse.jetty.util.StringUtil;
+import org.eclipse.jetty.util.annotation.ManagedAttribute;
+import org.eclipse.jetty.util.annotation.ManagedObject;
+
+/**
+ * This {@link RequestLog} implementation outputs logs in the pseudo-standard
+ * NCSA common log format. Configuration options allow a choice between the
+ * standard Common Log Format (as used in the 3 log format) and the Combined Log
+ * Format (single log format). This log format can be output by most web
+ * servers, and almost all web log analysis software can understand these
+ * formats.
+ */
+@ManagedObject("NCSA standard format request log")
+public class NCSARequestLog extends AbstractNCSARequestLog implements RequestLog
+{
+    private String _filename;
+    private boolean _append;
+    private int _retainDays;
+    private boolean _closeOut;
+    private String _filenameDateFormat = null;
+    private transient OutputStream _out;
+    private transient OutputStream _fileOut;
+    private transient Writer _writer;
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Create request log object with default settings.
+     */
+    public NCSARequestLog()
+    {
+        setExtended(true);
+        _append = true;
+        _retainDays = 31;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Create request log object with specified output file name.
+     *
+     * @param filename the file name for the request log.
+     *                 This may be in the format expected
+     *                 by {@link RolloverFileOutputStream}
+     */
+    public NCSARequestLog(String filename)
+    {
+        setExtended(true);
+        _append = true;
+        _retainDays = 31;
+        setFilename(filename);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Set the output file name of the request log.
+     * The file name may be in the format expected by
+     * {@link RolloverFileOutputStream}.
+     *
+     * @param filename file name of the request log
+     *
+     */
+    public void setFilename(String filename)
+    {
+        if (filename != null)
+        {
+            filename = filename.trim();
+            if (filename.length() == 0)
+                filename = null;
+        }
+        _filename = filename;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Retrieve the output file name of the request log.
+     *
+     * @return file name of the request log
+     */
+    @ManagedAttribute("file of log")
+    public String getFilename()
+    {
+        return _filename;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * Retrieve the file name of the request log with the expanded
+     * date wildcard if the output is written to the disk using
+     * {@link RolloverFileOutputStream}.
+     *
+     * @return file name of the request log, or null if not applicable
+     */
+    public String getDatedFilename()
+    {
+        if (_fileOut instanceof RolloverFileOutputStream)
+            return ((RolloverFileOutputStream)_fileOut).getDatedFilename();
+        return null;
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    protected boolean isEnabled()
+    {
+        return (_fileOut != null);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Set the number of days before rotated log files are deleted.
+     *
+     * @param retainDays number of days to keep a log file
+     */
+    public void setRetainDays(int retainDays)
+    {
+        _retainDays = retainDays;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Retrieve the number of days before rotated log files are deleted.
+     *
+     * @return number of days to keep a log file
+     */
+    @ManagedAttribute("number of days that log files are kept")
+    public int getRetainDays()
+    {
+        return _retainDays;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Set append to log flag.
+     *
+     * @param append true - request log file will be appended after restart,
+     *               false - request log file will be overwritten after restart
+     */
+    public void setAppend(boolean append)
+    {
+        _append = append;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Retrieve append to log flag.
+     *
+     * @return value of the flag
+     */
+    @ManagedAttribute("existing log files are appends to the new one")
+    public boolean isAppend()
+    {
+        return _append;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Set the log file name date format.
+     * @see RolloverFileOutputStream#RolloverFileOutputStream(String, boolean, int, TimeZone, String, String)
+     *
+     * @param logFileDateFormat format string that is passed to {@link RolloverFileOutputStream}
+     */
+    public void setFilenameDateFormat(String logFileDateFormat)
+    {
+        _filenameDateFormat = logFileDateFormat;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Retrieve the file name date format string.
+     *
+     * @return the log File Date Format
+     */
+    public String getFilenameDateFormat()
+    {
+        return _filenameDateFormat;
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public void write(String requestEntry) throws IOException
+    {
+        synchronized(this)
+        {
+            if (_writer==null)
+                return;
+            _writer.write(requestEntry);
+            _writer.write(StringUtil.__LINE_SEPARATOR);
+            _writer.flush();
+        }
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * Set up request logging and open log file.
+     *
+     * @see org.eclipse.jetty.util.component.AbstractLifeCycle#doStart()
+     */
+    @Override
+    protected synchronized void doStart() throws Exception
+    {
+        if (_filename != null)
+        {
+            _fileOut = new RolloverFileOutputStream(_filename,_append,_retainDays,TimeZone.getTimeZone(getLogTimeZone()),_filenameDateFormat,null);
+            _closeOut = true;
+            LOG.info("Opened " + getDatedFilename());
+        }
+        else
+            _fileOut = System.err;
+
+        _out = _fileOut;
+
+        synchronized(this)
+        {
+            _writer = new OutputStreamWriter(_out);
+        }
+        super.doStart();
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Close the log file and perform cleanup.
+     *
+     * @see org.eclipse.jetty.util.component.AbstractLifeCycle#doStop()
+     */
+    @Override
+    protected void doStop() throws Exception
+    {
+        synchronized (this)
+        {
+            super.doStop();
+            try
+            {
+                if (_writer != null)
+                    _writer.flush();
+            }
+            catch (IOException e)
+            {
+                LOG.ignore(e);
+            }
+            if (_out != null && _closeOut)
+                try
+                {
+                    _out.close();
+                }
+                catch (IOException e)
+                {
+                    LOG.ignore(e);
+                }
+
+            _out = null;
+            _fileOut = null;
+            _closeOut = false;
+            _writer = null;
+        }
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/server/NegotiatingServerConnection.java b/lib/jetty/org/eclipse/jetty/server/NegotiatingServerConnection.java
new file mode 100644 (file)
index 0000000..3d01069
--- /dev/null
@@ -0,0 +1,163 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server;
+
+import java.io.IOException;
+import java.util.List;
+import javax.net.ssl.SSLEngine;
+import javax.net.ssl.SSLEngineResult;
+
+import org.eclipse.jetty.io.AbstractConnection;
+import org.eclipse.jetty.io.Connection;
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.server.ConnectionFactory;
+import org.eclipse.jetty.server.Connector;
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+public abstract class NegotiatingServerConnection extends AbstractConnection
+{
+    private static final Logger LOG = Log.getLogger(NegotiatingServerConnection.class);
+
+    private final Connector connector;
+    private final SSLEngine engine;
+    private final List<String> protocols;
+    private final String defaultProtocol;
+    private String protocol; // No need to be volatile: it is modified and read by the same thread
+
+    protected NegotiatingServerConnection(Connector connector, EndPoint endPoint, SSLEngine engine, List<String> protocols, String defaultProtocol)
+    {
+        super(endPoint, connector.getExecutor());
+        this.connector = connector;
+        this.protocols = protocols;
+        this.defaultProtocol = defaultProtocol;
+        this.engine = engine;
+    }
+
+    protected List<String> getProtocols()
+    {
+        return protocols;
+    }
+
+    protected String getDefaultProtocol()
+    {
+        return defaultProtocol;
+    }
+
+    protected SSLEngine getSSLEngine()
+    {
+        return engine;
+    }
+
+    protected String getProtocol()
+    {
+        return protocol;
+    }
+
+    protected void setProtocol(String protocol)
+    {
+        this.protocol = protocol;
+    }
+
+    @Override
+    public void onOpen()
+    {
+        super.onOpen();
+        fillInterested();
+    }
+
+    @Override
+    public void onFillable()
+    {
+        int filled = fill();
+
+        if (filled == 0)
+        {
+            if (protocol == null)
+            {
+                if (engine.getHandshakeStatus() == SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING)
+                {
+                    // Here the SSL handshake is finished, but the protocol has not been negotiated.
+                    LOG.debug("{} could not negotiate protocol, SSLEngine: {}", this, engine);
+                    close();
+                }
+                else
+                {
+                    // Here the SSL handshake is not finished yet but we filled 0 bytes,
+                    // so we need to read more.
+                    fillInterested();
+                }
+            }
+            else
+            {
+                ConnectionFactory connectionFactory = connector.getConnectionFactory(protocol);
+                if (connectionFactory == null)
+                {
+                    LOG.debug("{} application selected protocol '{}', but no correspondent {} has been configured",
+                            this, protocol, ConnectionFactory.class.getName());
+                    close();
+                }
+                else
+                {
+                    EndPoint endPoint = getEndPoint();
+                    Connection oldConnection = endPoint.getConnection();
+                    Connection newConnection = connectionFactory.newConnection(connector, endPoint);
+                    LOG.debug("{} switching from {} to {}", this, oldConnection, newConnection);
+                    oldConnection.onClose();
+                    endPoint.setConnection(newConnection);
+                    getEndPoint().getConnection().onOpen();
+                }
+            }
+        }
+        else if (filled < 0)
+        {
+            // Something went bad, we need to close.
+            LOG.debug("{} closing on client close", this);
+            close();
+        }
+        else
+        {
+            // Must never happen, since we fill using an empty buffer
+            throw new IllegalStateException();
+        }
+    }
+
+    private int fill()
+    {
+        try
+        {
+            return getEndPoint().fill(BufferUtil.EMPTY_BUFFER);
+        }
+        catch (IOException x)
+        {
+            LOG.debug(x);
+            close();
+            return -1;
+        }
+    }
+
+    @Override
+    public void close()
+    {
+        // Gentler close for SSL.
+        getEndPoint().shutdownOutput();
+        super.close();
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/server/NegotiatingServerConnectionFactory.java b/lib/jetty/org/eclipse/jetty/server/NegotiatingServerConnectionFactory.java
new file mode 100644 (file)
index 0000000..f826d71
--- /dev/null
@@ -0,0 +1,103 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server;
+
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.List;
+import javax.net.ssl.SSLEngine;
+
+import org.eclipse.jetty.io.AbstractConnection;
+import org.eclipse.jetty.io.Connection;
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.io.ssl.SslConnection;
+import org.eclipse.jetty.server.AbstractConnectionFactory;
+import org.eclipse.jetty.server.Connector;
+
+public abstract class NegotiatingServerConnectionFactory extends AbstractConnectionFactory
+{
+    private final List<String> protocols;
+    private String defaultProtocol;
+
+    public NegotiatingServerConnectionFactory(String protocol, String... protocols)
+    {
+        super(protocol);
+        this.protocols = Arrays.asList(protocols);
+    }
+
+    public String getDefaultProtocol()
+    {
+        return defaultProtocol;
+    }
+
+    public void setDefaultProtocol(String defaultProtocol)
+    {
+        this.defaultProtocol = defaultProtocol;
+    }
+
+    public List<String> getProtocols()
+    {
+        return protocols;
+    }
+
+    @Override
+    public Connection newConnection(Connector connector, EndPoint endPoint)
+    {
+        List<String> protocols = this.protocols;
+        if (protocols.isEmpty())
+        {
+            protocols = connector.getProtocols();
+            Iterator<String> i = protocols.iterator();
+            while (i.hasNext())
+            {
+                String protocol = i.next();
+                String prefix = "ssl-";
+                if (protocol.regionMatches(true, 0, prefix, 0, prefix.length()) || protocol.equalsIgnoreCase("alpn"))
+                {
+                    i.remove();
+                }
+            }
+        }
+
+        String dft = defaultProtocol;
+        if (dft == null && !protocols.isEmpty())
+            dft = protocols.get(0);
+
+        SSLEngine engine = null;
+        EndPoint ep = endPoint;
+        while (engine == null && ep != null)
+        {
+            // TODO make more generic
+            if (ep instanceof SslConnection.DecryptedEndPoint)
+                engine = ((SslConnection.DecryptedEndPoint)ep).getSslConnection().getSSLEngine();
+            else
+                ep = null;
+        }
+
+        return configure(newServerConnection(connector, endPoint, engine, protocols, dft), connector, endPoint);
+    }
+
+    protected abstract AbstractConnection newServerConnection(Connector connector, EndPoint endPoint, SSLEngine engine, List<String> protocols, String defaultProtocol);
+
+    @Override
+    public String toString()
+    {
+        return String.format("%s@%x{%s,%s,%s}", getClass().getSimpleName(), hashCode(), getProtocol(), getDefaultProtocol(), getProtocols());
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/server/NetworkConnector.java b/lib/jetty/org/eclipse/jetty/server/NetworkConnector.java
new file mode 100644 (file)
index 0000000..58fdc98
--- /dev/null
@@ -0,0 +1,72 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server;
+
+import java.io.Closeable;
+import java.io.IOException;
+
+/**
+ * <p>A {@link Connector} for TCP/IP network connectors</p>
+ */
+public interface NetworkConnector extends Connector, Closeable
+{
+    /**
+     * <p>Performs the activities needed to open the network communication
+     * (for example, to start accepting incoming network connections).</p>
+     *
+     * @throws IOException if this connector cannot be opened
+     * @see #close()
+     */
+    void open() throws IOException;
+
+    /**
+     * <p>Performs the activities needed to close the network communication
+     * (for example, to stop accepting network connections).</p>
+     * Once a connector has been closed, it cannot be opened again without first
+     * calling {@link #stop()} and it will not be active again until a subsequent call to {@link #start()}
+     */
+    @Override
+    void close();
+
+    /* ------------------------------------------------------------ */
+    /**
+     * A Connector may be opened and not started (to reserve a port)
+     * or closed and running (to allow graceful shutdown of existing connections)
+     * @return True if the connector is Open.
+     */
+    boolean isOpen();
+    
+    /**
+     * @return The hostname representing the interface to which
+     * this connector will bind, or null for all interfaces.
+     */
+    String getHost();
+
+    /**
+     * @return The configured port for the connector or 0 if any available
+     * port may be used.
+     */
+    int getPort();
+
+    /**
+     * @return The actual port the connector is listening on, or
+     * -1 if it has not been opened, or -2 if it has been closed.
+     */
+    int getLocalPort();
+}
diff --git a/lib/jetty/org/eclipse/jetty/server/NetworkTrafficServerConnector.java b/lib/jetty/org/eclipse/jetty/server/NetworkTrafficServerConnector.java
new file mode 100644 (file)
index 0000000..34de615
--- /dev/null
@@ -0,0 +1,92 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server;
+
+import java.io.IOException;
+import java.nio.channels.SelectionKey;
+import java.nio.channels.SocketChannel;
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.Executor;
+
+import org.eclipse.jetty.io.ByteBufferPool;
+import org.eclipse.jetty.io.NetworkTrafficListener;
+import org.eclipse.jetty.io.NetworkTrafficSelectChannelEndPoint;
+import org.eclipse.jetty.io.SelectChannelEndPoint;
+import org.eclipse.jetty.io.SelectorManager;
+import org.eclipse.jetty.util.ssl.SslContextFactory;
+import org.eclipse.jetty.util.thread.Scheduler;
+
+/**
+ * <p>A specialized version of {@link ServerConnector} that supports {@link NetworkTrafficListener}s.</p>
+ * <p>{@link NetworkTrafficListener}s can be added and removed dynamically before and after this connector has
+ * been started without causing {@link java.util.ConcurrentModificationException}s.</p>
+ */
+public class NetworkTrafficServerConnector extends ServerConnector
+{
+    private final List<NetworkTrafficListener> listeners = new CopyOnWriteArrayList<>();
+
+    public NetworkTrafficServerConnector(Server server)
+    {
+        this(server, null, null, null, 0, 0, new HttpConnectionFactory());
+    }
+
+    public NetworkTrafficServerConnector(Server server, ConnectionFactory connectionFactory, SslContextFactory sslContextFactory)
+    {
+        super(server, sslContextFactory, connectionFactory);
+    }
+
+    public NetworkTrafficServerConnector(Server server, ConnectionFactory connectionFactory)
+    {
+        super(server, connectionFactory);
+    }
+
+    public NetworkTrafficServerConnector(Server server, Executor executor, Scheduler scheduler, ByteBufferPool pool, int acceptors, int selectors, ConnectionFactory... factories)
+    {
+        super(server, executor, scheduler, pool, acceptors, selectors, factories);
+    }
+
+    public NetworkTrafficServerConnector(Server server, SslContextFactory sslContextFactory)
+    {
+        super(server, sslContextFactory);
+    }
+
+    /**
+     * @param listener the listener to add
+     */
+    public void addNetworkTrafficListener(NetworkTrafficListener listener)
+    {
+        listeners.add(listener);
+    }
+
+    /**
+     * @param listener the listener to remove
+     */
+    public void removeNetworkTrafficListener(NetworkTrafficListener listener)
+    {
+        listeners.remove(listener);
+    }
+
+    @Override
+    protected SelectChannelEndPoint newEndPoint(SocketChannel channel, SelectorManager.ManagedSelector selectSet, SelectionKey key) throws IOException
+    {
+        NetworkTrafficSelectChannelEndPoint endPoint = new NetworkTrafficSelectChannelEndPoint(channel, selectSet, key, getScheduler(), getIdleTimeout(), listeners);
+        return endPoint;
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/server/QueuedHttpInput.java b/lib/jetty/org/eclipse/jetty/server/QueuedHttpInput.java
new file mode 100644 (file)
index 0000000..881fbfa
--- /dev/null
@@ -0,0 +1,143 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server;
+
+import java.io.IOException;
+import java.io.InterruptedIOException;
+
+import org.eclipse.jetty.util.ArrayQueue;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/**
+ * {@link QueuedHttpInput} holds a queue of items passed to it by calls to {@link #content(Object)}.
+ * <p/>
+ * {@link QueuedHttpInput} stores the items directly; if the items contain byte buffers, it does not copy them
+ * but simply holds references to the item, thus the caller must organize for those buffers to valid while
+ * held by this class.
+ * <p/>
+ * To assist the caller, subclasses may override methods {@link #onAsyncRead()}, {@link #onContentConsumed(Object)}
+ * that can be implemented so that the caller will know when buffers are queued and consumed.
+ */
+public abstract class QueuedHttpInput<T> extends HttpInput<T>
+{
+    private final static Logger LOG = Log.getLogger(QueuedHttpInput.class);
+
+    private final ArrayQueue<T> _inputQ = new ArrayQueue<>(lock());
+
+    public QueuedHttpInput()
+    {
+    }
+
+    public void content(T item)
+    {
+        // The buffer is not copied here.  This relies on the caller not recycling the buffer
+        // until the it is consumed.  The onContentConsumed and onAllContentConsumed() callbacks are
+        // the signals to the caller that the buffers can be recycled.
+
+        synchronized (lock())
+        {
+            boolean wasEmpty = _inputQ.isEmpty();
+            _inputQ.add(item);
+            LOG.debug("{} queued {}", this, item);
+            if (wasEmpty)
+            {
+                if (!onAsyncRead())
+                    lock().notify();
+            }
+        }
+    }
+
+    public void recycle()
+    {
+        synchronized (lock())
+        {
+            T item = _inputQ.pollUnsafe();
+            while (item != null)
+            {
+                onContentConsumed(item);
+                item = _inputQ.pollUnsafe();
+            }
+            super.recycle();
+        }
+    }
+
+    @Override
+    protected T nextContent()
+    {
+        synchronized (lock())
+        {
+            // Items are removed only when they are fully consumed.
+            T item = _inputQ.peekUnsafe();
+            // Skip consumed items at the head of the queue.
+            while (item != null && remaining(item) == 0)
+            {
+                _inputQ.pollUnsafe();
+                onContentConsumed(item);
+                LOG.debug("{} consumed {}", this, item);
+                item = _inputQ.peekUnsafe();
+            }
+            return item;
+        }
+    }
+
+    protected void blockForContent() throws IOException
+    {
+        synchronized (lock())
+        {
+            while (_inputQ.isEmpty() && !isFinished() && !isEOF())
+            {
+                try
+                {
+                    LOG.debug("{} waiting for content", this);
+                    lock().wait();
+                }
+                catch (InterruptedException e)
+                {
+                    throw (IOException)new InterruptedIOException().initCause(e);
+                }
+            }
+        }
+    }
+
+    /**
+     * Callback that signals that the given content has been consumed.
+     *
+     * @param item the consumed content
+     */
+    protected abstract void onContentConsumed(T item);
+
+    public void earlyEOF()
+    {
+        synchronized (lock())
+        {
+            super.earlyEOF();
+            lock().notify();
+        }
+    }
+
+    public void messageComplete()
+    {
+        synchronized (lock())
+        {
+            super.messageComplete();
+            lock().notify();
+        }
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/server/QuietServletException.java b/lib/jetty/org/eclipse/jetty/server/QuietServletException.java
new file mode 100644 (file)
index 0000000..5221d63
--- /dev/null
@@ -0,0 +1,53 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server;
+
+import javax.servlet.ServletException;
+
+
+/* ------------------------------------------------------------ */
+/** A ServletException that is logged less verbosely than
+ * a normal ServletException.
+ * <p>
+ * Used for container generated exceptions that need only a message rather
+ * than a stack trace.
+ * </p>
+ */
+public class QuietServletException extends ServletException
+{
+    public QuietServletException()
+    {
+        super();
+    }
+
+    public QuietServletException(String message, Throwable rootCause)
+    {
+        super(message,rootCause);
+    }
+
+    public QuietServletException(String message)
+    {
+        super(message);
+    }
+
+    public QuietServletException(Throwable rootCause)
+    {
+        super(rootCause);
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/server/Request.java b/lib/jetty/org/eclipse/jetty/server/Request.java
new file mode 100644 (file)
index 0000000..77c80ab
--- /dev/null
@@ -0,0 +1,2265 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server;
+
+import java.io.BufferedReader;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.UnsupportedEncodingException;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+import java.nio.charset.UnsupportedCharsetException;
+import java.security.Principal;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.EventListener;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import javax.servlet.AsyncContext;
+import javax.servlet.AsyncListener;
+import javax.servlet.DispatcherType;
+import javax.servlet.MultipartConfigElement;
+import javax.servlet.RequestDispatcher;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+import javax.servlet.ServletInputStream;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletRequestAttributeEvent;
+import javax.servlet.ServletRequestAttributeListener;
+import javax.servlet.ServletRequestEvent;
+import javax.servlet.ServletRequestListener;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpSession;
+import javax.servlet.http.HttpUpgradeHandler;
+import javax.servlet.http.Part;
+
+import org.eclipse.jetty.http.HttpCookie;
+import org.eclipse.jetty.http.HttpFields;
+import org.eclipse.jetty.http.HttpHeader;
+import org.eclipse.jetty.http.HttpMethod;
+import org.eclipse.jetty.http.HttpStatus;
+import org.eclipse.jetty.http.HttpURI;
+import org.eclipse.jetty.http.HttpVersion;
+import org.eclipse.jetty.http.MimeTypes;
+import org.eclipse.jetty.server.handler.ContextHandler;
+import org.eclipse.jetty.server.handler.ContextHandler.Context;
+import org.eclipse.jetty.server.session.AbstractSession;
+import org.eclipse.jetty.util.Attributes;
+import org.eclipse.jetty.util.AttributesMap;
+import org.eclipse.jetty.util.IO;
+import org.eclipse.jetty.util.MultiException;
+import org.eclipse.jetty.util.MultiMap;
+import org.eclipse.jetty.util.MultiPartInputStreamParser;
+import org.eclipse.jetty.util.StringUtil;
+import org.eclipse.jetty.util.URIUtil;
+import org.eclipse.jetty.util.UrlEncoded;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/* ------------------------------------------------------------ */
+/**
+ * Jetty Request.
+ * <p>
+ * Implements {@link javax.servlet.http.HttpServletRequest} from the <code>javax.servlet.http</code> package.
+ * </p>
+ * <p>
+ * The standard interface of mostly getters, is extended with setters so that the request is mutable by the handlers that it is passed to. This allows the
+ * request object to be as lightweight as possible and not actually implement any significant behavior. For example
+ * <ul>
+ *
+ * <li>The {@link Request#getContextPath()} method will return null, until the request has been passed to a {@link ContextHandler} which matches the
+ * {@link Request#getPathInfo()} with a context path and calls {@link Request#setContextPath(String)} as a result.</li>
+ *
+ * <li>the HTTP session methods will all return null sessions until such time as a request has been passed to a
+ * {@link org.eclipse.jetty.server.session.SessionHandler} which checks for session cookies and enables the ability to create new sessions.</li>
+ *
+ * <li>The {@link Request#getServletPath()} method will return null until the request has been passed to a <code>org.eclipse.jetty.servlet.ServletHandler</code>
+ * and the pathInfo matched against the servlet URL patterns and {@link Request#setServletPath(String)} called as a result.</li>
+ * </ul>
+ *
+ * A request instance is created for each connection accepted by the server and recycled for each HTTP request received via that connection.
+ * An effort is made to avoid reparsing headers and cookies that are likely to be the same for requests from the same connection.
+ *
+ * <p>
+ * The form content that a request can process is limited to protect from Denial of Service attacks. The size in bytes is limited by
+ * {@link ContextHandler#getMaxFormContentSize()} or if there is no context then the "org.eclipse.jetty.server.Request.maxFormContentSize" {@link Server}
+ * attribute. The number of parameters keys is limited by {@link ContextHandler#getMaxFormKeys()} or if there is no context then the
+ * "org.eclipse.jetty.server.Request.maxFormKeys" {@link Server} attribute.
+ *
+ *
+ */
+public class Request implements HttpServletRequest
+{
+    public static final String __MULTIPART_CONFIG_ELEMENT = "org.eclipse.jetty.multipartConfig";
+    public static final String __MULTIPART_INPUT_STREAM = "org.eclipse.jetty.multiPartInputStream";
+    public static final String __MULTIPART_CONTEXT = "org.eclipse.jetty.multiPartContext";
+
+    private static final Logger LOG = Log.getLogger(Request.class);
+    private static final Collection<Locale> __defaultLocale = Collections.singleton(Locale.getDefault());
+    private static final int __NONE = 0, _STREAM = 1, __READER = 2;
+
+    private final HttpChannel<?> _channel;
+    private final HttpFields _fields=new HttpFields();
+    private final List<ServletRequestAttributeListener>  _requestAttributeListeners=new ArrayList<>();
+    private final HttpInput<?> _input;
+    
+    public static class MultiPartCleanerListener implements ServletRequestListener
+    {
+        @Override
+        public void requestDestroyed(ServletRequestEvent sre)
+        {
+            //Clean up any tmp files created by MultiPartInputStream
+            MultiPartInputStreamParser mpis = (MultiPartInputStreamParser)sre.getServletRequest().getAttribute(__MULTIPART_INPUT_STREAM);
+            if (mpis != null)
+            {
+                ContextHandler.Context context = (ContextHandler.Context)sre.getServletRequest().getAttribute(__MULTIPART_CONTEXT);
+
+                //Only do the cleanup if we are exiting from the context in which a servlet parsed the multipart files
+                if (context == sre.getServletContext())
+                {
+                    try
+                    {
+                        mpis.deleteParts();
+                    }
+                    catch (MultiException e)
+                    {
+                        sre.getServletContext().log("Errors deleting multipart tmp files", e);
+                    }
+                }
+            }
+        }
+
+        @Override
+        public void requestInitialized(ServletRequestEvent sre)
+        {
+            //nothing to do, multipart config set up by ServletHolder.handle()
+        }
+        
+    }
+    
+    
+
+    private boolean _secure;
+    private boolean _asyncSupported = true;
+    private boolean _newContext;
+    private boolean _cookiesExtracted = false;
+    private boolean _handled = false;
+    private boolean _paramsExtracted;
+    private boolean _requestedSessionIdFromCookie = false;
+    private volatile Attributes _attributes;
+    private Authentication _authentication;
+    private String _characterEncoding;
+    private ContextHandler.Context _context;
+    private String _contextPath;
+    private CookieCutter _cookies;
+    private DispatcherType _dispatcherType;
+    private int _inputState = __NONE;
+    private HttpMethod _httpMethod;
+    private String _httpMethodString;
+    private MultiMap<String> _queryParameters;
+    private MultiMap<String> _contentParameters;
+    private MultiMap<String> _parameters;
+    private String _pathInfo;
+    private int _port;
+    private HttpVersion _httpVersion = HttpVersion.HTTP_1_1;
+    private String _queryEncoding;
+    private String _queryString;
+    private BufferedReader _reader;
+    private String _readerEncoding;
+    private InetSocketAddress _remote;
+    private String _requestedSessionId;
+    private String _requestURI;
+    private Map<Object, HttpSession> _savedNewSessions;
+    private String _scheme = URIUtil.HTTP;
+    private UserIdentity.Scope _scope;
+    private String _serverName;
+    private String _servletPath;
+    private HttpSession _session;
+    private SessionManager _sessionManager;
+    private long _timeStamp;
+    private HttpURI _uri;
+    private MultiPartInputStreamParser _multiPartInputStream; //if the request is a multi-part mime
+    private AsyncContextState _async;
+    
+    /* ------------------------------------------------------------ */
+    public Request(HttpChannel<?> channel, HttpInput<?> input)
+    {
+        _channel = channel;
+        _input = input;
+    }
+
+    /* ------------------------------------------------------------ */
+    public HttpFields getHttpFields()
+    {
+        return _fields;
+    }
+
+    /* ------------------------------------------------------------ */
+    public HttpInput<?> getHttpInput()
+    {
+        return _input;
+    }
+
+    /* ------------------------------------------------------------ */
+    public void addEventListener(final EventListener listener)
+    {
+        if (listener instanceof ServletRequestAttributeListener)
+            _requestAttributeListeners.add((ServletRequestAttributeListener)listener);
+        if (listener instanceof AsyncListener)
+            throw new IllegalArgumentException(listener.getClass().toString());
+    }
+
+    public void extractParameters()
+    {
+        if (_paramsExtracted)
+            return;
+
+        _paramsExtracted = true;
+
+        // Extract query string parameters; these may be replaced by a forward()
+        // and may have already been extracted by mergeQueryParameters().
+        if (_queryParameters == null)
+            _queryParameters = extractQueryParameters();
+
+        // Extract content parameters; these cannot be replaced by a forward()
+        // once extracted and may have already been extracted by getParts() or
+        // by a processing happening after a form-based authentication.
+        if (_contentParameters == null)
+            _contentParameters = extractContentParameters();
+
+        _parameters = restoreParameters();
+    }
+
+    private MultiMap<String> extractQueryParameters()
+    {
+        MultiMap<String> result = new MultiMap<>();
+        if (_uri != null && _uri.hasQuery())
+        {
+            if (_queryEncoding == null)
+            {
+                _uri.decodeQueryTo(result);
+            }
+            else
+            {
+                try
+                {
+                    _uri.decodeQueryTo(result, _queryEncoding);
+                }
+                catch (UnsupportedEncodingException e)
+                {
+                    if (LOG.isDebugEnabled())
+                        LOG.warn(e);
+                    else
+                        LOG.warn(e.toString());
+                }
+            }
+        }
+        return result;
+    }
+
+    private MultiMap<String> extractContentParameters()
+    {
+        MultiMap<String> result = new MultiMap<>();
+
+        String contentType = getContentType();
+        if (contentType != null && !contentType.isEmpty())
+        {
+            contentType = HttpFields.valueParameters(contentType, null);
+            int contentLength = getContentLength();
+            if (contentLength != 0)
+            {
+                if (MimeTypes.Type.FORM_ENCODED.is(contentType) && _inputState == __NONE &&
+                        (HttpMethod.POST.is(getMethod()) || HttpMethod.PUT.is(getMethod())))
+                {
+                    extractFormParameters(result);
+                }
+                else if (contentType.startsWith("multipart/form-data") &&
+                        getAttribute(__MULTIPART_CONFIG_ELEMENT) != null &&
+                        _multiPartInputStream == null)
+                {
+                    extractMultipartParameters(result);
+                }
+            }
+        }
+
+        return result;
+    }
+
+    public void extractFormParameters(MultiMap<String> params)
+    {
+        try
+        {
+            int maxFormContentSize = -1;
+            int maxFormKeys = -1;
+
+            if (_context != null)
+            {
+                maxFormContentSize = _context.getContextHandler().getMaxFormContentSize();
+                maxFormKeys = _context.getContextHandler().getMaxFormKeys();
+            }
+
+            if (maxFormContentSize < 0)
+            {
+                Object obj = _channel.getServer().getAttribute("org.eclipse.jetty.server.Request.maxFormContentSize");
+                if (obj == null)
+                    maxFormContentSize = 200000;
+                else if (obj instanceof Number)
+                {
+                    Number size = (Number)obj;
+                    maxFormContentSize = size.intValue();
+                }
+                else if (obj instanceof String)
+                {
+                    maxFormContentSize = Integer.valueOf((String)obj);
+                }
+            }
+
+            if (maxFormKeys < 0)
+            {
+                Object obj = _channel.getServer().getAttribute("org.eclipse.jetty.server.Request.maxFormKeys");
+                if (obj == null)
+                    maxFormKeys = 1000;
+                else if (obj instanceof Number)
+                {
+                    Number keys = (Number)obj;
+                    maxFormKeys = keys.intValue();
+                }
+                else if (obj instanceof String)
+                {
+                    maxFormKeys = Integer.valueOf((String)obj);
+                }
+            }
+
+            int contentLength = getContentLength();
+            if (contentLength > maxFormContentSize && maxFormContentSize > 0)
+            {
+                throw new IllegalStateException("Form too large: " + contentLength + " > " + maxFormContentSize);
+            }
+            InputStream in = getInputStream();
+            if (_input.isAsync())
+                throw new IllegalStateException("Cannot extract parameters with async IO");
+
+            UrlEncoded.decodeTo(in,params,getCharacterEncoding(),contentLength<0?maxFormContentSize:-1,maxFormKeys);
+        }
+        catch (IOException e)
+        {
+            if (LOG.isDebugEnabled())
+                LOG.warn(e);
+            else
+                LOG.warn(e.toString());
+        }
+    }
+
+    private void extractMultipartParameters(MultiMap<String> result)
+    {
+        try
+        {
+            getParts(result);
+        }
+        catch (IOException | ServletException e)
+        {
+            if (LOG.isDebugEnabled())
+                LOG.warn(e);
+            else
+                LOG.warn(e.toString());
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public AsyncContext getAsyncContext()
+    {
+        HttpChannelState state = getHttpChannelState();
+        if (_async==null || state.isInitial() && !state.isAsync())
+            throw new IllegalStateException(state.getStatusString());
+        
+        return _async;
+    }
+
+    /* ------------------------------------------------------------ */
+    public HttpChannelState getHttpChannelState()
+    {
+        return _channel.getState();
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Get Request Attribute.
+     * <p>Also supports jetty specific attributes to gain access to Jetty APIs:
+     * <dl>
+     * <dt>org.eclipse.jetty.server.Server</dt><dd>The Jetty Server instance</dd>
+     * <dt>org.eclipse.jetty.server.HttpChannel</dt><dd>The HttpChannel for this request</dd>
+     * <dt>org.eclipse.jetty.server.HttpConnection</dt><dd>The HttpConnection or null if another transport is used</dd>
+     * </dl>
+     * While these attributes may look like security problems, they are exposing nothing that is not already
+     * available via reflection from a Request instance.
+     * </p>
+     * @see javax.servlet.ServletRequest#getAttribute(java.lang.String)
+     */
+    @Override
+    public Object getAttribute(String name)
+    {
+        if (name.startsWith("org.eclipse.jetty"))
+        {
+            if ("org.eclipse.jetty.server.Server".equals(name))
+                return _channel.getServer();
+            if ("org.eclipse.jetty.server.HttpChannel".equals(name))
+                return _channel;
+            if ("org.eclipse.jetty.server.HttpConnection".equals(name) &&
+                _channel.getHttpTransport() instanceof HttpConnection)
+                return _channel.getHttpTransport();
+        }
+        return (_attributes == null)?null:_attributes.getAttribute(name);
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.ServletRequest#getAttributeNames()
+     */
+    @Override
+    public Enumeration<String> getAttributeNames()
+    {
+        if (_attributes == null)
+            return Collections.enumeration(Collections.<String>emptyList());
+
+        return AttributesMap.getAttributeNamesCopy(_attributes);
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     */
+    public Attributes getAttributes()
+    {
+        if (_attributes == null)
+            _attributes = new AttributesMap();
+        return _attributes;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Get the authentication.
+     *
+     * @return the authentication
+     */
+    public Authentication getAuthentication()
+    {
+        return _authentication;
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.http.HttpServletRequest#getAuthType()
+     */
+    @Override
+    public String getAuthType()
+    {
+        if (_authentication instanceof Authentication.Deferred)
+            setAuthentication(((Authentication.Deferred)_authentication).authenticate(this));
+
+        if (_authentication instanceof Authentication.User)
+            return ((Authentication.User)_authentication).getAuthMethod();
+        return null;
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.ServletRequest#getCharacterEncoding()
+     */
+    @Override
+    public String getCharacterEncoding()
+    {
+        return _characterEncoding;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Returns the connection.
+     */
+    public HttpChannel<?> getHttpChannel()
+    {
+        return _channel;
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.ServletRequest#getContentLength()
+     */
+    @Override
+    public int getContentLength()
+    {
+        return (int)_fields.getLongField(HttpHeader.CONTENT_LENGTH.toString());
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.ServletRequest.getContentLengthLong()
+     */
+    @Override
+    public long getContentLengthLong()
+    {
+        return _fields.getLongField(HttpHeader.CONTENT_LENGTH.toString());
+    }
+
+    /* ------------------------------------------------------------ */
+    public long getContentRead()
+    {
+        return _input.getContentRead();
+    }
+    
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.ServletRequest#getContentType()
+     */
+    @Override
+    public String getContentType()
+    {
+        return _fields.getStringField(HttpHeader.CONTENT_TYPE);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return The current {@link Context context} used for this request, or <code>null</code> if {@link #setContext} has not yet been called.
+     */
+    public Context getContext()
+    {
+        return _context;
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.http.HttpServletRequest#getContextPath()
+     */
+    @Override
+    public String getContextPath()
+    {
+        return _contextPath;
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.http.HttpServletRequest#getCookies()
+     */
+    @Override
+    public Cookie[] getCookies()
+    {
+        if (_cookiesExtracted)
+        {
+            if (_cookies == null || _cookies.getCookies().length == 0)
+                return null;
+            
+            return _cookies.getCookies();
+        }
+
+        _cookiesExtracted = true;
+
+        Enumeration<?> enm = _fields.getValues(HttpHeader.COOKIE.toString());
+
+        // Handle no cookies
+        if (enm != null)
+        {
+            if (_cookies == null)
+                _cookies = new CookieCutter();
+
+            while (enm.hasMoreElements())
+            {
+                String c = (String)enm.nextElement();
+                _cookies.addCookieField(c);
+            }
+        }
+
+        //Javadoc for Request.getCookies() stipulates null for no cookies
+        if (_cookies == null || _cookies.getCookies().length == 0)
+            return null;
+        
+        return _cookies.getCookies();
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.http.HttpServletRequest#getDateHeader(java.lang.String)
+     */
+    @Override
+    public long getDateHeader(String name)
+    {
+        return _fields.getDateField(name);
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public DispatcherType getDispatcherType()
+    {
+        return _dispatcherType;
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.http.HttpServletRequest#getHeader(java.lang.String)
+     */
+    @Override
+    public String getHeader(String name)
+    {
+        return _fields.getStringField(name);
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.http.HttpServletRequest#getHeaderNames()
+     */
+    @Override
+    public Enumeration<String> getHeaderNames()
+    {
+        return _fields.getFieldNames();
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.http.HttpServletRequest#getHeaders(java.lang.String)
+     */
+    @Override
+    public Enumeration<String> getHeaders(String name)
+    {
+        Enumeration<String> e = _fields.getValues(name);
+        if (e == null)
+            return Collections.enumeration(Collections.<String>emptyList());
+        return e;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Returns the inputState.
+     */
+    public int getInputState()
+    {
+        return _inputState;
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.ServletRequest#getInputStream()
+     */
+    @Override
+    public ServletInputStream getInputStream() throws IOException
+    {
+        if (_inputState != __NONE && _inputState != _STREAM)
+            throw new IllegalStateException("READER");
+        _inputState = _STREAM;
+
+        if (_channel.isExpecting100Continue())
+            _channel.continue100(_input.available());
+
+        return _input;
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.http.HttpServletRequest#getIntHeader(java.lang.String)
+     */
+    @Override
+    public int getIntHeader(String name)
+    {
+        return (int)_fields.getLongField(name);
+    }
+
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.ServletRequest#getLocale()
+     */
+    @Override
+    public Locale getLocale()
+    {
+        Enumeration<String> enm = _fields.getValues(HttpHeader.ACCEPT_LANGUAGE.toString(),HttpFields.__separators);
+
+        // handle no locale
+        if (enm == null || !enm.hasMoreElements())
+            return Locale.getDefault();
+
+        // sort the list in quality order
+        List<?> acceptLanguage = HttpFields.qualityList(enm);
+        if (acceptLanguage.size() == 0)
+            return Locale.getDefault();
+
+        int size = acceptLanguage.size();
+
+        if (size > 0)
+        {
+            String language = (String)acceptLanguage.get(0);
+            language = HttpFields.valueParameters(language,null);
+            String country = "";
+            int dash = language.indexOf('-');
+            if (dash > -1)
+            {
+                country = language.substring(dash + 1).trim();
+                language = language.substring(0,dash).trim();
+            }
+            return new Locale(language,country);
+        }
+
+        return Locale.getDefault();
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.ServletRequest#getLocales()
+     */
+    @Override
+    public Enumeration<Locale> getLocales()
+    {
+
+        Enumeration<String> enm = _fields.getValues(HttpHeader.ACCEPT_LANGUAGE.toString(),HttpFields.__separators);
+
+        // handle no locale
+        if (enm == null || !enm.hasMoreElements())
+            return Collections.enumeration(__defaultLocale);
+
+        // sort the list in quality order
+        List<String> acceptLanguage = HttpFields.qualityList(enm);
+
+        if (acceptLanguage.size() == 0)
+            return Collections.enumeration(__defaultLocale);
+
+        List<Locale> langs = new ArrayList<>();
+
+        // convert to locals
+        for (String language : acceptLanguage)
+        {
+            language = HttpFields.valueParameters(language, null);
+            String country = "";
+            int dash = language.indexOf('-');
+            if (dash > -1)
+            {
+                country = language.substring(dash + 1).trim();
+                language = language.substring(0, dash).trim();
+            }
+            langs.add(new Locale(language, country));
+        }
+
+        if (langs.size() == 0)
+            return Collections.enumeration(__defaultLocale);
+
+        return Collections.enumeration(langs);
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.ServletRequest#getLocalAddr()
+     */
+    @Override
+    public String getLocalAddr()
+    {
+        InetSocketAddress local=_channel.getLocalAddress();
+        if (local==null)
+            return "";
+        InetAddress address = local.getAddress();
+        if (address==null)
+            return local.getHostString();
+        return address.getHostAddress();
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.ServletRequest#getLocalName()
+     */
+    @Override
+    public String getLocalName()
+    {
+        InetSocketAddress local=_channel.getLocalAddress();
+        return local.getHostString();
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.ServletRequest#getLocalPort()
+     */
+    @Override
+    public int getLocalPort()
+    {
+        InetSocketAddress local=_channel.getLocalAddress();
+        return local.getPort();
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.http.HttpServletRequest#getMethod()
+     */
+    @Override
+    public String getMethod()
+    {
+        return _httpMethodString;
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.ServletRequest#getParameter(java.lang.String)
+     */
+    @Override
+    public String getParameter(String name)
+    {
+        if (!_paramsExtracted)
+            extractParameters();
+        if (_parameters == null)
+            _parameters = restoreParameters();
+        return _parameters.getValue(name,0);
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.ServletRequest#getParameterMap()
+     */
+    @Override
+    public Map<String, String[]> getParameterMap()
+    {
+        if (!_paramsExtracted)
+            extractParameters();
+        if (_parameters == null)
+            _parameters = restoreParameters();
+        return Collections.unmodifiableMap(_parameters.toStringArrayMap());
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.ServletRequest#getParameterNames()
+     */
+    @Override
+    public Enumeration<String> getParameterNames()
+    {
+        if (!_paramsExtracted)
+            extractParameters();
+        if (_parameters == null)
+            _parameters = restoreParameters();
+        return Collections.enumeration(_parameters.keySet());
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.ServletRequest#getParameterValues(java.lang.String)
+     */
+    @Override
+    public String[] getParameterValues(String name)
+    {
+        if (!_paramsExtracted)
+            extractParameters();
+        if (_parameters == null)
+            _parameters = restoreParameters();
+        List<String> vals = _parameters.getValues(name);
+        if (vals == null)
+            return null;
+        return vals.toArray(new String[vals.size()]);
+    }
+
+    private MultiMap<String> restoreParameters()
+    {
+        MultiMap<String> result = new MultiMap<>();
+        if (_queryParameters == null)
+            _queryParameters = extractQueryParameters();
+        result.addAllValues(_queryParameters);
+        result.addAllValues(_contentParameters);
+        return result;
+    }
+
+    public MultiMap<String> getQueryParameters()
+    {
+        return _queryParameters;
+    }
+
+    public void setQueryParameters(MultiMap<String> queryParameters)
+    {
+        _queryParameters = queryParameters;
+    }
+
+    public void setContentParameters(MultiMap<String> contentParameters)
+    {
+        _contentParameters = contentParameters;
+    }
+
+    public void resetParameters()
+    {
+        _parameters = null;
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.http.HttpServletRequest#getPathInfo()
+     */
+    @Override
+    public String getPathInfo()
+    {
+        return _pathInfo;
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.http.HttpServletRequest#getPathTranslated()
+     */
+    @Override
+    public String getPathTranslated()
+    {
+        if (_pathInfo == null || _context == null)
+            return null;
+        return _context.getRealPath(_pathInfo);
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.ServletRequest#getProtocol()
+     */
+    @Override
+    public String getProtocol()
+    {
+        return _httpVersion.toString();
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.ServletRequest#getProtocol()
+     */
+    public HttpVersion getHttpVersion()
+    {
+        return _httpVersion;
+    }
+
+    /* ------------------------------------------------------------ */
+    public String getQueryEncoding()
+    {
+        return _queryEncoding;
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.http.HttpServletRequest#getQueryString()
+     */
+    @Override
+    public String getQueryString()
+    {
+        if (_queryString == null && _uri != null)
+        {
+            if (_queryEncoding == null)
+                _queryString = _uri.getQuery();
+            else
+                _queryString = _uri.getQuery(_queryEncoding);
+        }
+        return _queryString;
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.ServletRequest#getReader()
+     */
+    @Override
+    public BufferedReader getReader() throws IOException
+    {
+        if (_inputState != __NONE && _inputState != __READER)
+            throw new IllegalStateException("STREAMED");
+
+        if (_inputState == __READER)
+            return _reader;
+
+        String encoding = getCharacterEncoding();
+        if (encoding == null)
+            encoding = StringUtil.__ISO_8859_1;
+
+        if (_reader == null || !encoding.equalsIgnoreCase(_readerEncoding))
+        {
+            final ServletInputStream in = getInputStream();
+            _readerEncoding = encoding;
+            _reader = new BufferedReader(new InputStreamReader(in,encoding))
+            {
+                @Override
+                public void close() throws IOException
+                {
+                    in.close();
+                }
+            };
+        }
+        _inputState = __READER;
+        return _reader;
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.ServletRequest#getRealPath(java.lang.String)
+     */
+    @Override
+    public String getRealPath(String path)
+    {
+        if (_context == null)
+            return null;
+        return _context.getRealPath(path);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Access the underlying Remote {@link InetSocketAddress} for this request.
+     * 
+     * @return the remote {@link InetSocketAddress} for this request, or null if the request has no remote (see {@link ServletRequest#getRemoteAddr()} for
+     *         conditions that result in no remote address)
+     */
+    public InetSocketAddress getRemoteInetSocketAddress()
+    {
+        InetSocketAddress remote = _remote;
+        if (remote == null)
+            remote = _channel.getRemoteAddress();
+
+        return remote;
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.ServletRequest#getRemoteAddr()
+     */
+    @Override
+    public String getRemoteAddr()
+    {
+        InetSocketAddress remote=_remote;
+        if (remote==null)
+            remote=_channel.getRemoteAddress();
+        
+        if (remote==null)
+            return "";
+        
+        InetAddress address = remote.getAddress();
+        if (address==null)
+            return remote.getHostString();
+        
+        return address.getHostAddress();
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.ServletRequest#getRemoteHost()
+     */
+    @Override
+    public String getRemoteHost()
+    {
+        InetSocketAddress remote=_remote;
+        if (remote==null)
+            remote=_channel.getRemoteAddress();
+        return remote==null?"":remote.getHostString();
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.ServletRequest#getRemotePort()
+     */
+    @Override
+    public int getRemotePort()
+    {
+        InetSocketAddress remote=_remote;
+        if (remote==null)
+            remote=_channel.getRemoteAddress();
+        return remote==null?0:remote.getPort();
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.http.HttpServletRequest#getRemoteUser()
+     */
+    @Override
+    public String getRemoteUser()
+    {
+        Principal p = getUserPrincipal();
+        if (p == null)
+            return null;
+        return p.getName();
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.ServletRequest#getRequestDispatcher(java.lang.String)
+     */
+    @Override
+    public RequestDispatcher getRequestDispatcher(String path)
+    {
+        if (path == null || _context == null)
+            return null;
+
+        // handle relative path
+        if (!path.startsWith("/"))
+        {
+            String relTo = URIUtil.addPaths(_servletPath,_pathInfo);
+            int slash = relTo.lastIndexOf("/");
+            if (slash > 1)
+                relTo = relTo.substring(0,slash + 1);
+            else
+                relTo = "/";
+            path = URIUtil.addPaths(relTo,path);
+        }
+
+        return _context.getRequestDispatcher(path);
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.http.HttpServletRequest#getRequestedSessionId()
+     */
+    @Override
+    public String getRequestedSessionId()
+    {
+        return _requestedSessionId;
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.http.HttpServletRequest#getRequestURI()
+     */
+    @Override
+    public String getRequestURI()
+    {
+        if (_requestURI == null && _uri != null)
+            _requestURI = _uri.getPathAndParam();
+        return _requestURI;
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.http.HttpServletRequest#getRequestURL()
+     */
+    @Override
+    public StringBuffer getRequestURL()
+    {
+        final StringBuffer url = new StringBuffer(128);
+        URIUtil.appendSchemeHostPort(url,getScheme(),getServerName(),getServerPort());
+        url.append(getRequestURI());
+        return url;
+    }
+
+    /* ------------------------------------------------------------ */
+    public Response getResponse()
+    {
+        return _channel.getResponse();
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Reconstructs the URL the client used to make the request. The returned URL contains a protocol, server name, port number, and, but it does not include a
+     * path.
+     * <p>
+     * Because this method returns a <code>StringBuffer</code>, not a string, you can modify the URL easily, for example, to append path and query parameters.
+     *
+     * This method is useful for creating redirect messages and for reporting errors.
+     *
+     * @return "scheme://host:port"
+     */
+    public StringBuilder getRootURL()
+    {
+        StringBuilder url = new StringBuilder(128);
+        URIUtil.appendSchemeHostPort(url,getScheme(),getServerName(),getServerPort());
+        return url;
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.ServletRequest#getScheme()
+     */
+    @Override
+    public String getScheme()
+    {
+        return _scheme;
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.ServletRequest#getServerName()
+     */
+    @Override
+    public String getServerName()
+    {
+        // Return already determined host
+        if (_serverName != null)
+            return _serverName;
+
+        if (_uri == null)
+            throw new IllegalStateException("No uri");
+
+        // Return host from absolute URI
+        _serverName = _uri.getHost();
+        if (_serverName != null)
+        {
+            _port = _uri.getPort();
+            return _serverName;
+        }
+
+        // Return host from header field
+        String hostPort = _fields.getStringField(HttpHeader.HOST);
+        
+        _port=0;
+        if (hostPort != null)
+        {
+            int len=hostPort.length();
+            loop: for (int i = len; i-- > 0;)
+            {
+                char c2 = (char)(0xff & hostPort.charAt(i));
+                switch (c2)
+                {
+                    case ']':
+                        break loop;
+
+                    case ':':
+                        try
+                        {
+                            len=i;
+                            _port = StringUtil.toInt(hostPort.substring(i+1));
+                        }
+                        catch (NumberFormatException e)
+                        {
+                            LOG.warn(e);
+                            _serverName=hostPort;
+                            _port=0;
+                            return _serverName;
+                        }
+                        break loop;
+                }
+            }
+            if (hostPort.charAt(0)=='[')
+            {
+                if (hostPort.charAt(len-1)!=']') 
+                {
+                    LOG.warn("Bad IPv6 "+hostPort);
+                    _serverName=hostPort;
+                    _port=0;
+                    return _serverName;
+                }
+                _serverName = hostPort.substring(1,len-1);
+            }
+            else if (len==hostPort.length())
+                _serverName=hostPort;
+            else
+                _serverName = hostPort.substring(0,len);
+
+            return _serverName;
+        }
+
+        // Return host from connection
+        if (_channel != null)
+        {
+            _serverName = getLocalName();
+            _port = getLocalPort();
+            if (_serverName != null && !StringUtil.ALL_INTERFACES.equals(_serverName))
+                return _serverName;
+        }
+
+        // Return the local host
+        try
+        {
+            _serverName = InetAddress.getLocalHost().getHostAddress();
+        }
+        catch (java.net.UnknownHostException e)
+        {
+            LOG.ignore(e);
+        }
+        return _serverName;
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.ServletRequest#getServerPort()
+     */
+    @Override
+    public int getServerPort()
+    {
+        if (_port <= 0)
+        {
+            if (_serverName == null)
+                getServerName();
+
+            if (_port <= 0)
+            {
+                if (_serverName != null && _uri != null)
+                    _port = _uri.getPort();
+                else
+                {
+                    InetSocketAddress local = _channel.getLocalAddress();
+                    _port = local == null?0:local.getPort();
+                }
+            }
+        }
+
+        if (_port <= 0)
+        {
+            if (getScheme().equalsIgnoreCase(URIUtil.HTTPS))
+                return 443;
+            return 80;
+        }
+        return _port;
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public ServletContext getServletContext()
+    {
+        return _context;
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     */
+    public String getServletName()
+    {
+        if (_scope != null)
+            return _scope.getName();
+        return null;
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.http.HttpServletRequest#getServletPath()
+     */
+    @Override
+    public String getServletPath()
+    {
+        if (_servletPath == null)
+            _servletPath = "";
+        return _servletPath;
+    }
+
+    /* ------------------------------------------------------------ */
+    public ServletResponse getServletResponse()
+    {
+        return _channel.getResponse();
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * Add @override when 3.1 api is available
+     */
+    public String changeSessionId()
+    {
+        HttpSession session = getSession(false);
+        if (session == null)
+            throw new IllegalStateException("No session");
+
+        if (session instanceof AbstractSession)
+        {
+            AbstractSession abstractSession =  ((AbstractSession)session);
+            abstractSession.renewId(this);
+            if (getRemoteUser() != null)
+                abstractSession.setAttribute(AbstractSession.SESSION_KNOWN_ONLY_TO_AUTHENTICATED, Boolean.TRUE);
+            if (abstractSession.isIdChanged())
+                _channel.getResponse().addCookie(_sessionManager.getSessionCookie(abstractSession, getContextPath(), isSecure()));
+        }
+
+        return session.getId();
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.http.HttpServletRequest#getSession()
+     */
+    @Override
+    public HttpSession getSession()
+    {
+        return getSession(true);
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.http.HttpServletRequest#getSession(boolean)
+     */
+    @Override
+    public HttpSession getSession(boolean create)
+    {
+        if (_session != null)
+        {
+            if (_sessionManager != null && !_sessionManager.isValid(_session))
+                _session = null;
+            else
+                return _session;
+        }
+
+        if (!create)
+            return null;
+        
+        if (getResponse().isCommitted())
+            throw new IllegalStateException("Response is committed");
+
+        if (_sessionManager == null)
+            throw new IllegalStateException("No SessionManager");
+
+        _session = _sessionManager.newHttpSession(this);
+        HttpCookie cookie = _sessionManager.getSessionCookie(_session,getContextPath(),isSecure());
+        if (cookie != null)
+            _channel.getResponse().addCookie(cookie);
+
+        return _session;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Returns the sessionManager.
+     */
+    public SessionManager getSessionManager()
+    {
+        return _sessionManager;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Get Request TimeStamp
+     *
+     * @return The time that the request was received.
+     */
+    public long getTimeStamp()
+    {
+        return _timeStamp;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Returns the uri.
+     */
+    public HttpURI getUri()
+    {
+        return _uri;
+    }
+
+    /* ------------------------------------------------------------ */
+    public UserIdentity getUserIdentity()
+    {
+        if (_authentication instanceof Authentication.Deferred)
+            setAuthentication(((Authentication.Deferred)_authentication).authenticate(this));
+
+        if (_authentication instanceof Authentication.User)
+            return ((Authentication.User)_authentication).getUserIdentity();
+        return null;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return The resolved user Identity, which may be null if the {@link Authentication} is not {@link Authentication.User} (eg.
+     *         {@link Authentication.Deferred}).
+     */
+    public UserIdentity getResolvedUserIdentity()
+    {
+        if (_authentication instanceof Authentication.User)
+            return ((Authentication.User)_authentication).getUserIdentity();
+        return null;
+    }
+
+    /* ------------------------------------------------------------ */
+    public UserIdentity.Scope getUserIdentityScope()
+    {
+        return _scope;
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.http.HttpServletRequest#getUserPrincipal()
+     */
+    @Override
+    public Principal getUserPrincipal()
+    {
+        if (_authentication instanceof Authentication.Deferred)
+            setAuthentication(((Authentication.Deferred)_authentication).authenticate(this));
+
+        if (_authentication instanceof Authentication.User)
+        {
+            UserIdentity user = ((Authentication.User)_authentication).getUserIdentity();
+            return user.getUserPrincipal();
+        }
+        
+        return null;
+    }
+
+
+    /* ------------------------------------------------------------ */
+    public boolean isHandled()
+    {
+        return _handled;
+    }
+
+    @Override
+    public boolean isAsyncStarted()
+    {
+       return getHttpChannelState().isAsyncStarted();
+    }
+
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public boolean isAsyncSupported()
+    {
+        return _asyncSupported;
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.http.HttpServletRequest#isRequestedSessionIdFromCookie()
+     */
+    @Override
+    public boolean isRequestedSessionIdFromCookie()
+    {
+        return _requestedSessionId != null && _requestedSessionIdFromCookie;
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.http.HttpServletRequest#isRequestedSessionIdFromUrl()
+     */
+    @Override
+    public boolean isRequestedSessionIdFromUrl()
+    {
+        return _requestedSessionId != null && !_requestedSessionIdFromCookie;
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.http.HttpServletRequest#isRequestedSessionIdFromURL()
+     */
+    @Override
+    public boolean isRequestedSessionIdFromURL()
+    {
+        return _requestedSessionId != null && !_requestedSessionIdFromCookie;
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.http.HttpServletRequest#isRequestedSessionIdValid()
+     */
+    @Override
+    public boolean isRequestedSessionIdValid()
+    {
+        if (_requestedSessionId == null)
+            return false;
+
+        HttpSession session = getSession(false);
+        return (session != null && _sessionManager.getSessionIdManager().getClusterId(_requestedSessionId).equals(_sessionManager.getClusterId(session)));
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.ServletRequest#isSecure()
+     */
+    @Override
+    public boolean isSecure()
+    {
+        return _secure;
+    }
+
+    /* ------------------------------------------------------------ */
+    public void setSecure(boolean secure)
+    {
+        _secure=secure;
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.http.HttpServletRequest#isUserInRole(java.lang.String)
+     */
+    @Override
+    public boolean isUserInRole(String role)
+    {
+        if (_authentication instanceof Authentication.Deferred)
+            setAuthentication(((Authentication.Deferred)_authentication).authenticate(this));
+
+        if (_authentication instanceof Authentication.User)
+            return ((Authentication.User)_authentication).isUserInRole(_scope,role);
+        return false;
+    }
+
+    /* ------------------------------------------------------------ */
+    public HttpSession recoverNewSession(Object key)
+    {
+        if (_savedNewSessions == null)
+            return null;
+        return _savedNewSessions.get(key);
+    }
+
+    /* ------------------------------------------------------------ */
+    protected void recycle()
+    {
+        if (_context != null)
+            throw new IllegalStateException("Request in context!");
+        
+        if (_inputState == __READER)
+        {
+            try
+            {
+                int r = _reader.read();
+                while (r != -1)
+                    r = _reader.read();
+            }
+            catch (Exception e)
+            {
+                LOG.ignore(e);
+                _reader = null;
+            }
+        }
+
+        _dispatcherType=null;
+        setAuthentication(Authentication.NOT_CHECKED);
+        getHttpChannelState().recycle();
+        if (_async!=null)
+            _async.reset();
+        _async=null;
+        _asyncSupported = true;
+        _handled = false;
+        if (_attributes != null)
+            _attributes.clearAttributes();
+        _characterEncoding = null;
+        _contextPath = null;
+        if (_cookies != null)
+            _cookies.reset();
+        _cookiesExtracted = false;
+        _context = null;
+        _newContext=false;
+        _serverName = null;
+        _httpMethod=null;
+        _httpMethodString = null;
+        _pathInfo = null;
+        _port = 0;
+        _httpVersion = HttpVersion.HTTP_1_1;
+        _queryEncoding = null;
+        _queryString = null;
+        _requestedSessionId = null;
+        _requestedSessionIdFromCookie = false;
+        _secure=false;
+        _session = null;
+        _sessionManager = null;
+        _requestURI = null;
+        _scope = null;
+        _scheme = URIUtil.HTTP;
+        _servletPath = null;
+        _timeStamp = 0;
+        _uri = null;
+        _queryParameters = null;
+        _contentParameters = null;
+        _parameters = null;
+        _paramsExtracted = false;
+        _inputState = __NONE;
+
+        if (_savedNewSessions != null)
+            _savedNewSessions.clear();
+        _savedNewSessions=null;
+        _multiPartInputStream = null;
+        _remote=null;
+        _fields.clear();
+        _input.recycle();
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.ServletRequest#removeAttribute(java.lang.String)
+     */
+    @Override
+    public void removeAttribute(String name)
+    {
+        Object old_value = _attributes == null?null:_attributes.getAttribute(name);
+
+        if (_attributes != null)
+            _attributes.removeAttribute(name);
+
+        if (old_value != null && !_requestAttributeListeners.isEmpty())
+        {
+            final ServletRequestAttributeEvent event = new ServletRequestAttributeEvent(_context,this,name,old_value);
+            for (ServletRequestAttributeListener listener : _requestAttributeListeners)
+                listener.attributeRemoved(event);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    public void removeEventListener(final EventListener listener)
+    {
+        _requestAttributeListeners.remove(listener);
+    }
+
+    /* ------------------------------------------------------------ */
+    public void saveNewSession(Object key, HttpSession session)
+    {
+        if (_savedNewSessions == null)
+            _savedNewSessions = new HashMap<>();
+        _savedNewSessions.put(key,session);
+    }
+
+    /* ------------------------------------------------------------ */
+    public void setAsyncSupported(boolean supported)
+    {
+        _asyncSupported = supported;
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * Set a request attribute. if the attribute name is "org.eclipse.jetty.server.server.Request.queryEncoding" then the value is also passed in a call to
+     * {@link #setQueryEncoding}.
+     *
+     * @see javax.servlet.ServletRequest#setAttribute(java.lang.String, java.lang.Object)
+     */
+    @Override
+    public void setAttribute(String name, Object value)
+    {
+        Object old_value = _attributes == null?null:_attributes.getAttribute(name);
+
+        if ("org.eclipse.jetty.server.Request.queryEncoding".equals(name))
+            setQueryEncoding(value == null?null:value.toString());
+        else if ("org.eclipse.jetty.server.sendContent".equals(name))
+            LOG.warn("Deprecated: org.eclipse.jetty.server.sendContent");
+        
+        if (_attributes == null)
+            _attributes = new AttributesMap();
+        _attributes.setAttribute(name,value);
+
+        if (!_requestAttributeListeners.isEmpty())
+        {
+            final ServletRequestAttributeEvent event = new ServletRequestAttributeEvent(_context,this,name,old_value == null?value:old_value);
+            for (ServletRequestAttributeListener l : _requestAttributeListeners)
+            {
+                if (old_value == null)
+                    l.attributeAdded(event);
+                else if (value == null)
+                    l.attributeRemoved(event);
+                else
+                    l.attributeReplaced(event);
+            }
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     */
+    public void setAttributes(Attributes attributes)
+    {
+        _attributes = attributes;
+    }
+
+    /* ------------------------------------------------------------ */
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Set the authentication.
+     *
+     * @param authentication
+     *            the authentication to set
+     */
+    public void setAuthentication(Authentication authentication)
+    {
+        _authentication = authentication;
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.ServletRequest#setCharacterEncoding(java.lang.String)
+     */
+    @Override
+    public void setCharacterEncoding(String encoding) throws UnsupportedEncodingException
+    {
+        if (_inputState != __NONE)
+            return;
+
+        _characterEncoding = encoding;
+
+        // check encoding is supported
+        if (!StringUtil.isUTF8(encoding))
+        {
+            try
+            {
+                Charset.forName(encoding);
+            }
+            catch (UnsupportedCharsetException e)
+            {
+                throw new UnsupportedEncodingException(e.getMessage());
+            }
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.ServletRequest#setCharacterEncoding(java.lang.String)
+     */
+    public void setCharacterEncodingUnchecked(String encoding)
+    {
+        _characterEncoding = encoding;
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.ServletRequest#getContentType()
+     */
+    public void setContentType(String contentType)
+    {
+        _fields.put(HttpHeader.CONTENT_TYPE,contentType);
+
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Set request context
+     *
+     * @param context
+     *            context object
+     */
+    public void setContext(Context context)
+    {
+        _newContext = _context != context;
+        _context = context;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return True if this is the first call of {@link #takeNewContext()} since the last
+     *         {@link #setContext(org.eclipse.jetty.server.handler.ContextHandler.Context)} call.
+     */
+    public boolean takeNewContext()
+    {
+        boolean nc = _newContext;
+        _newContext = false;
+        return nc;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Sets the "context path" for this request
+     *
+     * @see HttpServletRequest#getContextPath()
+     */
+    public void setContextPath(String contextPath)
+    {
+        _contextPath = contextPath;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param cookies
+     *            The cookies to set.
+     */
+    public void setCookies(Cookie[] cookies)
+    {
+        if (_cookies == null)
+            _cookies = new CookieCutter();
+        _cookies.setCookies(cookies);
+    }
+
+    /* ------------------------------------------------------------ */
+    public void setDispatcherType(DispatcherType type)
+    {
+        _dispatcherType = type;
+    }
+
+    /* ------------------------------------------------------------ */
+    public void setHandled(boolean h)
+    {
+        _handled = h;
+        Response r=getResponse();
+        if (_handled && r.getStatus()==0)
+            r.setStatus(200);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param method
+     *            The method to set.
+     */
+    public void setMethod(HttpMethod httpMethod, String method)
+    {
+        _httpMethod=httpMethod;
+        _httpMethodString = method;
+    }
+
+    /* ------------------------------------------------------------ */
+    public boolean isHead()
+    {
+        return HttpMethod.HEAD==_httpMethod;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param pathInfo
+     *            The pathInfo to set.
+     */
+    public void setPathInfo(String pathInfo)
+    {
+        _pathInfo = pathInfo;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param version
+     *            The protocol to set.
+     */
+    public void setHttpVersion(HttpVersion version)
+    {
+        _httpVersion = version;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Set the character encoding used for the query string. This call will effect the return of getQueryString and getParamaters. It must be called before any
+     * getParameter methods.
+     *
+     * The request attribute "org.eclipse.jetty.server.server.Request.queryEncoding" may be set as an alternate method of calling setQueryEncoding.
+     *
+     * @param queryEncoding
+     */
+    public void setQueryEncoding(String queryEncoding)
+    {
+        _queryEncoding = queryEncoding;
+        _queryString = null;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param queryString
+     *            The queryString to set.
+     */
+    public void setQueryString(String queryString)
+    {
+        _queryString = queryString;
+        _queryEncoding = null; //assume utf-8
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param addr
+     *            The address to set.
+     */
+    public void setRemoteAddr(InetSocketAddress addr)
+    {
+        _remote = addr;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * @param requestedSessionId
+     *            The requestedSessionId to set.
+     */
+    public void setRequestedSessionId(String requestedSessionId)
+    {
+        _requestedSessionId = requestedSessionId;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param requestedSessionIdCookie
+     *            The requestedSessionIdCookie to set.
+     */
+    public void setRequestedSessionIdFromCookie(boolean requestedSessionIdCookie)
+    {
+        _requestedSessionIdFromCookie = requestedSessionIdCookie;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param requestURI
+     *            The requestURI to set.
+     */
+    public void setRequestURI(String requestURI)
+    {
+        _requestURI = requestURI;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param scheme
+     *            The scheme to set.
+     */
+    public void setScheme(String scheme)
+    {
+        _scheme = scheme;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param host
+     *            The host to set.
+     */
+    public void setServerName(String host)
+    {
+        _serverName = host;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param port
+     *            The port to set.
+     */
+    public void setServerPort(int port)
+    {
+        _port = port;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param servletPath
+     *            The servletPath to set.
+     */
+    public void setServletPath(String servletPath)
+    {
+        _servletPath = servletPath;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param session
+     *            The session to set.
+     */
+    public void setSession(HttpSession session)
+    {
+        _session = session;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param sessionManager
+     *            The sessionManager to set.
+     */
+    public void setSessionManager(SessionManager sessionManager)
+    {
+        _sessionManager = sessionManager;
+    }
+
+    /* ------------------------------------------------------------ */
+    public void setTimeStamp(long ts)
+    {
+        _timeStamp = ts;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param uri
+     *            The uri to set.
+     */
+    public void setUri(HttpURI uri)
+    {
+        _uri = uri;
+    }
+
+    /* ------------------------------------------------------------ */
+    public void setUserIdentityScope(UserIdentity.Scope scope)
+    {
+        _scope = scope;
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public AsyncContext startAsync() throws IllegalStateException
+    {
+        if (!_asyncSupported)
+            throw new IllegalStateException("!asyncSupported");
+        HttpChannelState state = getHttpChannelState();
+        if (_async==null)
+            _async=new AsyncContextState(state);
+        AsyncContextEvent event = new AsyncContextEvent(_context,_async,state,this,this,getResponse());
+        state.startAsync(event);
+        return _async;
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public AsyncContext startAsync(ServletRequest servletRequest, ServletResponse servletResponse) throws IllegalStateException
+    {
+        if (!_asyncSupported)
+            throw new IllegalStateException("!asyncSupported");
+        HttpChannelState state = getHttpChannelState();
+        if (_async==null)
+            _async=new AsyncContextState(state);
+        AsyncContextEvent event = new AsyncContextEvent(_context,_async,state,this,servletRequest,servletResponse);
+        event.setDispatchContext(getServletContext());
+        event.setDispatchPath(URIUtil.addPaths(getServletPath(),getPathInfo()));
+        state.startAsync(event);
+        return _async;
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public String toString()
+    {
+        return (_handled?"[":"(") + getMethod() + " " + _uri + (_handled?"]@":")@") + hashCode() + " " + super.toString();
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public boolean authenticate(HttpServletResponse response) throws IOException, ServletException
+    {
+        if (_authentication instanceof Authentication.Deferred)
+        {
+            setAuthentication(((Authentication.Deferred)_authentication).authenticate(this,response));
+            return !(_authentication instanceof Authentication.ResponseSent);
+        }
+        response.sendError(HttpStatus.UNAUTHORIZED_401);
+        return false;
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public Part getPart(String name) throws IOException, ServletException
+    {
+        getParts();
+
+        return _multiPartInputStream.getPart(name);
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public Collection<Part> getParts() throws IOException, ServletException
+    {
+        if (getContentType() == null || !getContentType().startsWith("multipart/form-data"))
+            throw new ServletException("Content-Type != multipart/form-data");
+        return getParts(null);
+    }
+
+    private Collection<Part> getParts(MultiMap<String> params) throws IOException, ServletException
+    {
+        if (_multiPartInputStream == null)
+            _multiPartInputStream = (MultiPartInputStreamParser)getAttribute(__MULTIPART_INPUT_STREAM);
+
+        if (_multiPartInputStream == null)
+        {
+            MultipartConfigElement config = (MultipartConfigElement)getAttribute(__MULTIPART_CONFIG_ELEMENT);
+            
+            if (config == null)
+                throw new IllegalStateException("No multipart config for servlet");
+            
+            _multiPartInputStream = new MultiPartInputStreamParser(getInputStream(),
+                                                             getContentType(), config, 
+                                                             (_context != null?(File)_context.getAttribute("javax.servlet.context.tempdir"):null));
+            
+            setAttribute(__MULTIPART_INPUT_STREAM, _multiPartInputStream);
+            setAttribute(__MULTIPART_CONTEXT, _context);
+            Collection<Part> parts = _multiPartInputStream.getParts(); //causes parsing
+            ByteArrayOutputStream os = null;
+            for (Part p:parts)
+            {
+                MultiPartInputStreamParser.MultiPart mp = (MultiPartInputStreamParser.MultiPart)p;
+                if (mp.getContentDispositionFilename() == null)
+                {
+                    // Servlet Spec 3.0 pg 23, parts without filename must be put into params.
+                    String charset = null;
+                    if (mp.getContentType() != null)
+                        charset = MimeTypes.getCharsetFromContentType(mp.getContentType());
+
+                    try (InputStream is = mp.getInputStream())
+                    {
+                        if (os == null)
+                            os = new ByteArrayOutputStream();
+                        IO.copy(is, os);
+                        String content=new String(os.toByteArray(),charset==null?StandardCharsets.UTF_8:Charset.forName(charset));
+                        if (_contentParameters == null)
+                            _contentParameters = params == null ? new MultiMap<String>() : params;
+                        _contentParameters.add(mp.getName(), content);
+                    }
+                    os.reset();
+                }
+            }
+        }
+
+        return _multiPartInputStream.getParts();
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public void login(String username, String password) throws ServletException
+    {
+        if (_authentication instanceof Authentication.Deferred)
+        {
+            _authentication=((Authentication.Deferred)_authentication).login(username,password,this);
+            if (_authentication == null)
+                throw new Authentication.Failed("Authentication failed for username '"+username+"'");
+        }
+        else
+        {
+            throw new Authentication.Failed("Authenticated failed for username '"+username+"'. Already authenticated as "+_authentication);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public void logout() throws ServletException
+    {
+        if (_authentication instanceof Authentication.User)
+            ((Authentication.User)_authentication).logout();
+        _authentication=Authentication.UNAUTHENTICATED;
+    }
+
+    public void mergeQueryParameters(String newQuery, boolean updateQueryString)
+    {
+        MultiMap<String> newQueryParams = new MultiMap<>();
+        // Have to assume ENCODING because we can't know otherwise.
+        UrlEncoded.decodeTo(newQuery, newQueryParams, UrlEncoded.ENCODING, -1);
+
+        MultiMap<String> oldQueryParams = _queryParameters;
+        if (oldQueryParams == null && _queryString != null)
+        {
+            oldQueryParams = new MultiMap<>();
+            UrlEncoded.decodeTo(_queryString, oldQueryParams, getQueryEncoding(), -1);
+        }
+
+        MultiMap<String> mergedQueryParams = newQueryParams;
+        if (oldQueryParams != null)
+        {
+            // Parameters values are accumulated.
+            mergedQueryParams = new MultiMap<>(newQueryParams);
+            mergedQueryParams.addAllValues(oldQueryParams);
+        }
+
+        setQueryParameters(mergedQueryParams);
+        resetParameters();
+
+        if (updateQueryString)
+        {
+            // Build the new merged query string, parameters in the
+            // new query string hide parameters in the old query string.
+            StringBuilder mergedQuery = new StringBuilder(newQuery);
+            for (Map.Entry<String, List<String>> entry : mergedQueryParams.entrySet())
+            {
+                if (newQueryParams.containsKey(entry.getKey()))
+                    continue;
+                for (String value : entry.getValue())
+                    mergedQuery.append("&").append(entry.getKey()).append("=").append(value);
+            }
+
+            setQueryString(mergedQuery.toString());
+        }
+    }
+
+    /**
+     * @see javax.servlet.http.HttpServletRequest#upgrade(java.lang.Class)
+     */
+    @Override
+    public <T extends HttpUpgradeHandler> T upgrade(Class<T> handlerClass) throws IOException, ServletException
+    {
+        if (getContext() == null)
+            throw new ServletException ("Unable to instantiate "+handlerClass);
+
+        try
+        {
+            //Instantiate an instance and inject it
+            T h = getContext().createInstance(handlerClass);
+            
+            //TODO handle the rest of the upgrade process
+            
+            return h;
+        }
+        catch (Exception e)
+        {
+            if (e instanceof ServletException)
+                throw (ServletException)e;
+            throw new ServletException(e);
+        }
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/server/RequestLog.java b/lib/jetty/org/eclipse/jetty/server/RequestLog.java
new file mode 100644 (file)
index 0000000..0cb1a53
--- /dev/null
@@ -0,0 +1,30 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server; 
+
+import org.eclipse.jetty.util.component.LifeCycle;
+
+/** 
+ * A <code>RequestLog</code> can be attached to a {@link org.eclipse.jetty.server.handler.RequestLogHandler} to enable 
+ * logging of requests/responses.
+ */
+public interface RequestLog extends LifeCycle
+{
+    public void log(Request request, Response response);
+}
diff --git a/lib/jetty/org/eclipse/jetty/server/ResourceCache.java b/lib/jetty/org/eclipse/jetty/server/ResourceCache.java
new file mode 100644 (file)
index 0000000..e39c4a6
--- /dev/null
@@ -0,0 +1,511 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.ByteBuffer;
+import java.nio.channels.ReadableByteChannel;
+import java.util.Comparator;
+import java.util.SortedSet;
+import java.util.TreeSet;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicReference;
+
+import org.eclipse.jetty.http.DateGenerator;
+import org.eclipse.jetty.http.HttpContent;
+import org.eclipse.jetty.http.HttpContent.ResourceAsHttpContent;
+import org.eclipse.jetty.http.MimeTypes;
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.resource.Resource;
+import org.eclipse.jetty.util.resource.ResourceFactory;
+
+
+/* ------------------------------------------------------------ */
+/** 
+ * 
+ */
+public class ResourceCache
+{
+    private static final Logger LOG = Log.getLogger(ResourceCache.class);
+
+    private final ConcurrentMap<String,Content> _cache;
+    private final AtomicInteger _cachedSize;
+    private final AtomicInteger _cachedFiles;
+    private final ResourceFactory _factory;
+    private final ResourceCache _parent;
+    private final MimeTypes _mimeTypes;
+    private final boolean _etagSupported;
+    private final boolean  _useFileMappedBuffer;
+    
+    private int _maxCachedFileSize =4*1024*1024;
+    private int _maxCachedFiles=2048;
+    private int _maxCacheSize =32*1024*1024;
+    
+    /* ------------------------------------------------------------ */
+    /** Constructor.
+     * @param mimeTypes Mimetype to use for meta data
+     */
+    public ResourceCache(ResourceCache parent, ResourceFactory factory, MimeTypes mimeTypes,boolean useFileMappedBuffer,boolean etags)
+    {
+        _factory = factory;
+        _cache=new ConcurrentHashMap<String,Content>();
+        _cachedSize=new AtomicInteger();
+        _cachedFiles=new AtomicInteger();
+        _mimeTypes=mimeTypes;
+        _parent=parent;
+        _useFileMappedBuffer=useFileMappedBuffer;
+        _etagSupported=etags;
+    }
+
+    /* ------------------------------------------------------------ */
+    public int getCachedSize()
+    {
+        return _cachedSize.get();
+    }
+    
+    /* ------------------------------------------------------------ */
+    public int getCachedFiles()
+    {
+        return _cachedFiles.get();
+    }
+    
+    /* ------------------------------------------------------------ */
+    public int getMaxCachedFileSize()
+    {
+        return _maxCachedFileSize;
+    }
+
+    /* ------------------------------------------------------------ */
+    public void setMaxCachedFileSize(int maxCachedFileSize)
+    {
+        _maxCachedFileSize = maxCachedFileSize;
+        shrinkCache();
+    }
+
+    /* ------------------------------------------------------------ */
+    public int getMaxCacheSize()
+    {
+        return _maxCacheSize;
+    }
+
+    /* ------------------------------------------------------------ */
+    public void setMaxCacheSize(int maxCacheSize)
+    {
+        _maxCacheSize = maxCacheSize;
+        shrinkCache();
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Returns the maxCachedFiles.
+     */
+    public int getMaxCachedFiles()
+    {
+        return _maxCachedFiles;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * @param maxCachedFiles The maxCachedFiles to set.
+     */
+    public void setMaxCachedFiles(int maxCachedFiles)
+    {
+        _maxCachedFiles = maxCachedFiles;
+        shrinkCache();
+    }
+
+    /* ------------------------------------------------------------ */
+    public boolean isUseFileMappedBuffer()
+    {
+        return _useFileMappedBuffer;
+    }
+
+    /* ------------------------------------------------------------ */
+    public void flushCache()
+    {
+        if (_cache!=null)
+        {
+            while (_cache.size()>0)
+            {
+                for (String path : _cache.keySet())
+                {
+                    Content content = _cache.remove(path);
+                    if (content!=null)
+                        content.invalidate();
+                }
+            }
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Get a Entry from the cache.
+     * Get either a valid entry object or create a new one if possible.
+     *
+     * @param pathInContext The key into the cache
+     * @return The entry matching <code>pathInContext</code>, or a new entry 
+     * if no matching entry was found. If the content exists but is not cachable, 
+     * then a {@link ResourceAsHttpContent} instance is return. If 
+     * the resource does not exist, then null is returned.
+     * @throws IOException Problem loading the resource
+     */
+    public HttpContent lookup(String pathInContext)
+        throws IOException
+    {
+        // Is the content in this cache?
+        Content content =_cache.get(pathInContext);
+        if (content!=null && (content).isValid())
+            return content;
+       
+        // try loading the content from our factory.
+        Resource resource=_factory.getResource(pathInContext);
+        HttpContent loaded = load(pathInContext,resource);
+        if (loaded!=null)
+            return loaded;
+        
+        // Is the content in the parent cache?
+        if (_parent!=null)
+        {
+            HttpContent httpContent=_parent.lookup(pathInContext);
+            if (httpContent!=null)
+                return httpContent;
+        }
+        
+        return null;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * @param resource
+     * @return True if the resource is cacheable. The default implementation tests the cache sizes.
+     */
+    protected boolean isCacheable(Resource resource)
+    {
+        long len = resource.length();
+
+        // Will it fit in the cache?
+        return  (len>0 && len<_maxCachedFileSize && len<_maxCacheSize);
+    }
+    
+    /* ------------------------------------------------------------ */
+    private HttpContent load(String pathInContext, Resource resource)
+        throws IOException
+    {
+        Content content=null;
+        
+        if (resource==null || !resource.exists())
+            return null;
+        
+        // Will it fit in the cache?
+        if (!resource.isDirectory() && isCacheable(resource))
+        {   
+            // Create the Content (to increment the cache sizes before adding the content 
+            content = new Content(pathInContext,resource);
+
+            // reduce the cache to an acceptable size.
+            shrinkCache();
+
+            // Add it to the cache.
+            Content added = _cache.putIfAbsent(pathInContext,content);
+            if (added!=null)
+            {
+                content.invalidate();
+                content=added;
+            }
+
+            return content;
+        }
+        
+        return new HttpContent.ResourceAsHttpContent(resource,_mimeTypes.getMimeByExtension(resource.toString()),getMaxCachedFileSize(),_etagSupported);
+        
+    }
+    
+    /* ------------------------------------------------------------ */
+    private void shrinkCache()
+    {
+        // While we need to shrink
+        while (_cache.size()>0 && (_cachedFiles.get()>_maxCachedFiles || _cachedSize.get()>_maxCacheSize))
+        {
+            // Scan the entire cache and generate an ordered list by last accessed time.
+            SortedSet<Content> sorted= new TreeSet<Content>(
+                    new Comparator<Content>()
+                    {
+                        public int compare(Content c1, Content c2)
+                        {
+                            if (c1._lastAccessed<c2._lastAccessed)
+                                return -1;
+                            
+                            if (c1._lastAccessed>c2._lastAccessed)
+                                return 1;
+
+                            if (c1._length<c2._length)
+                                return -1;
+                            
+                            return c1._key.compareTo(c2._key);
+                        }
+                    });
+            for (Content content : _cache.values())
+                sorted.add(content);
+            
+            // Invalidate least recently used first
+            for (Content content : sorted)
+            {
+                if (_cachedFiles.get()<=_maxCachedFiles && _cachedSize.get()<=_maxCacheSize)
+                    break;
+                if (content==_cache.remove(content.getKey()))
+                    content.invalidate();
+            }
+        }
+    }
+    
+    /* ------------------------------------------------------------ */
+    protected ByteBuffer getIndirectBuffer(Resource resource)
+    {
+        try
+        {
+            return BufferUtil.toBuffer(resource,true);
+        }
+        catch(IOException|IllegalArgumentException e)
+        {
+            LOG.warn(e);
+            return null;
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    protected ByteBuffer getDirectBuffer(Resource resource)
+    {
+        try
+        {
+            if (_useFileMappedBuffer && resource.getFile()!=null) 
+                return BufferUtil.toMappedBuffer(resource.getFile());
+            
+            return BufferUtil.toBuffer(resource,true);
+        }
+        catch(IOException|IllegalArgumentException e)
+        {
+            LOG.warn(e);
+            return null;
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public String toString()
+    {
+        return "ResourceCache["+_parent+","+_factory+"]@"+hashCode();
+    }
+    
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    /** MetaData associated with a context Resource.
+     */
+    public class Content implements HttpContent
+    {
+        final Resource _resource;
+        final int _length;
+        final String _key;
+        final long _lastModified;
+        final ByteBuffer _lastModifiedBytes;
+        final ByteBuffer _contentType;
+        final String _etag;
+        
+        volatile long _lastAccessed;
+        AtomicReference<ByteBuffer> _indirectBuffer=new AtomicReference<ByteBuffer>();
+        AtomicReference<ByteBuffer> _directBuffer=new AtomicReference<ByteBuffer>();
+
+        /* ------------------------------------------------------------ */
+        Content(String pathInContext,Resource resource)
+        {
+            _key=pathInContext;
+            _resource=resource;
+
+            String mimeType = _mimeTypes.getMimeByExtension(_resource.toString());
+            _contentType=(mimeType==null?null:BufferUtil.toBuffer(mimeType));
+            boolean exists=resource.exists();
+            _lastModified=exists?resource.lastModified():-1;
+            _lastModifiedBytes=_lastModified<0?null:BufferUtil.toBuffer(DateGenerator.formatDate(_lastModified));
+            
+            _length=exists?(int)resource.length():0;
+            _cachedSize.addAndGet(_length);
+            _cachedFiles.incrementAndGet();
+            _lastAccessed=System.currentTimeMillis();
+            
+            _etag=ResourceCache.this._etagSupported?resource.getWeakETag():null;
+        }
+
+
+        /* ------------------------------------------------------------ */
+        public String getKey()
+        {
+            return _key;
+        }
+
+        /* ------------------------------------------------------------ */
+        public boolean isCached()
+        {
+            return _key!=null;
+        }
+        
+        /* ------------------------------------------------------------ */
+        public boolean isMiss()
+        {
+            return false;
+        }
+
+        /* ------------------------------------------------------------ */
+        @Override
+        public Resource getResource()
+        {
+            return _resource;
+        }
+
+        /* ------------------------------------------------------------ */
+        @Override
+        public String getETag()
+        {
+            return _etag;
+        }
+        
+        /* ------------------------------------------------------------ */
+        boolean isValid()
+        {
+            if (_lastModified==_resource.lastModified() && _length==_resource.length())
+            {
+                _lastAccessed=System.currentTimeMillis();
+                return true;
+            }
+
+            if (this==_cache.remove(_key))
+                invalidate();
+            return false;
+        }
+
+        /* ------------------------------------------------------------ */
+        protected void invalidate()
+        {
+            // Invalidate it
+            _cachedSize.addAndGet(-_length);
+            _cachedFiles.decrementAndGet();
+            _resource.close(); 
+        }
+
+        /* ------------------------------------------------------------ */
+        @Override
+        public String getLastModified()
+        {
+            return BufferUtil.toString(_lastModifiedBytes);
+        }
+
+        /* ------------------------------------------------------------ */
+        @Override
+        public String getContentType()
+        {
+            return BufferUtil.toString(_contentType);
+        }
+
+        /* ------------------------------------------------------------ */
+        @Override
+        public void release()
+        {
+            // don't release while cached. Release when invalidated.
+        }
+
+        /* ------------------------------------------------------------ */
+        @Override
+        public ByteBuffer getIndirectBuffer()
+        {
+            ByteBuffer buffer = _indirectBuffer.get();
+            if (buffer==null)
+            {
+                ByteBuffer buffer2=ResourceCache.this.getIndirectBuffer(_resource);
+                
+                if (buffer2==null)
+                    LOG.warn("Could not load "+this);
+                else if (_indirectBuffer.compareAndSet(null,buffer2))
+                    buffer=buffer2;
+                else
+                    buffer=_indirectBuffer.get();
+            }
+            if (buffer==null)
+                return null;
+            return buffer.slice();
+        }
+        
+
+        /* ------------------------------------------------------------ */
+        @Override
+        public ByteBuffer getDirectBuffer()
+        {
+            ByteBuffer buffer = _directBuffer.get();
+            if (buffer==null)
+            {
+                ByteBuffer buffer2=ResourceCache.this.getDirectBuffer(_resource);
+
+                if (buffer2==null)
+                    LOG.warn("Could not load "+this);
+                else if (_directBuffer.compareAndSet(null,buffer2))
+                    buffer=buffer2;
+                else
+                    buffer=_directBuffer.get();
+            }
+            if (buffer==null)
+                return null;
+            return buffer.asReadOnlyBuffer();
+        }
+        
+        /* ------------------------------------------------------------ */
+        @Override
+        public long getContentLength()
+        {
+            return _length;
+        }
+
+        /* ------------------------------------------------------------ */
+        @Override
+        public InputStream getInputStream() throws IOException
+        {
+            ByteBuffer indirect = getIndirectBuffer();
+            if (indirect!=null && indirect.hasArray())
+                return new ByteArrayInputStream(indirect.array(),indirect.arrayOffset()+indirect.position(),indirect.remaining());
+           
+            return _resource.getInputStream();
+        }   
+        
+        /* ------------------------------------------------------------ */
+        @Override
+        public ReadableByteChannel getReadableByteChannel() throws IOException
+        {
+            return _resource.getReadableByteChannel();
+        }
+
+
+        /* ------------------------------------------------------------ */
+        @Override
+        public String toString()
+        {
+            return String.format("%s %s %d %s %s",_resource,_resource.exists(),_resource.lastModified(),_contentType,_lastModifiedBytes);
+        }   
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/server/Response.java b/lib/jetty/org/eclipse/jetty/server/Response.java
new file mode 100644 (file)
index 0000000..0408c88
--- /dev/null
@@ -0,0 +1,1384 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server;
+
+import static org.eclipse.jetty.util.QuotedStringTokenizer.isQuoted;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.nio.channels.IllegalSelectorException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.Iterator;
+import java.util.Locale;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import javax.servlet.RequestDispatcher;
+import javax.servlet.ServletOutputStream;
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpSession;
+
+import org.eclipse.jetty.http.DateGenerator;
+import org.eclipse.jetty.http.HttpContent;
+import org.eclipse.jetty.http.HttpCookie;
+import org.eclipse.jetty.http.HttpField;
+import org.eclipse.jetty.http.HttpFields;
+import org.eclipse.jetty.http.HttpGenerator;
+import org.eclipse.jetty.http.HttpGenerator.ResponseInfo;
+import org.eclipse.jetty.http.HttpHeader;
+import org.eclipse.jetty.http.HttpHeaderValue;
+import org.eclipse.jetty.http.HttpScheme;
+import org.eclipse.jetty.http.HttpStatus;
+import org.eclipse.jetty.http.HttpURI;
+import org.eclipse.jetty.http.HttpVersion;
+import org.eclipse.jetty.http.MimeTypes;
+import org.eclipse.jetty.io.RuntimeIOException;
+import org.eclipse.jetty.server.handler.ErrorHandler;
+import org.eclipse.jetty.util.ByteArrayISO8859Writer;
+import org.eclipse.jetty.util.QuotedStringTokenizer;
+import org.eclipse.jetty.util.StringUtil;
+import org.eclipse.jetty.util.URIUtil;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/**
+ * <p>{@link Response} provides the implementation for {@link HttpServletResponse}.</p>
+ */
+public class Response implements HttpServletResponse
+{
+    private static final Logger LOG = Log.getLogger(Response.class);    
+    private static final String __COOKIE_DELIM="\",;\\ \t";
+    private final static String __01Jan1970_COOKIE = DateGenerator.formatCookieDate(0).trim();
+    private final static int __MIN_BUFFER_SIZE = 1;
+    
+
+    // Cookie building buffer. Reduce garbage for cookie using applications
+    private static final ThreadLocal<StringBuilder> __cookieBuilder = new ThreadLocal<StringBuilder>()
+    {
+       @Override
+       protected StringBuilder initialValue()
+       {
+          return new StringBuilder(128);
+       }
+    };
+
+    /* ------------------------------------------------------------ */
+    public static Response getResponse(HttpServletResponse response)
+    {
+        if (response instanceof Response)
+            return (Response)response;
+        return HttpChannel.getCurrentHttpChannel().getResponse();
+    }
+    
+    
+    public enum OutputType
+    {
+        NONE, STREAM, WRITER
+    }
+
+    /**
+     * If a header name starts with this string,  the header (stripped of the prefix)
+     * can be set during include using only {@link #setHeader(String, String)} or
+     * {@link #addHeader(String, String)}.
+     */
+    public final static String SET_INCLUDE_HEADER_PREFIX = "org.eclipse.jetty.server.include.";
+
+    /**
+     * If this string is found within the comment of a cookie added with {@link #addCookie(Cookie)}, then the cookie
+     * will be set as HTTP ONLY.
+     */
+    public final static String HTTP_ONLY_COMMENT = "__HTTP_ONLY__";
+
+    private final HttpChannel<?> _channel;
+    private final HttpFields _fields = new HttpFields();
+    private final AtomicInteger _include = new AtomicInteger();
+    private HttpOutput _out;
+    private int _status = HttpStatus.NOT_SET_000;
+    private String _reason;
+    private Locale _locale;
+    private MimeTypes.Type _mimeType;
+    private String _characterEncoding;
+    private boolean _explicitEncoding;
+    private String _contentType;
+    private OutputType _outputType = OutputType.NONE;
+    private ResponseWriter _writer;
+    private long _contentLength = -1;
+    
+
+    public Response(HttpChannel<?> channel, HttpOutput out)
+    {
+        _channel = channel;
+        _out = out;
+    }
+
+    protected HttpChannel<?> getHttpChannel()
+    {
+        return _channel;
+    }
+
+    protected void recycle()
+    {
+        _status = HttpStatus.NOT_SET_000;
+        _reason = null;
+        _locale = null;
+        _mimeType = null;
+        _characterEncoding = null;
+        _contentType = null;
+        _outputType = OutputType.NONE;
+        _contentLength = -1;
+        _out.reset();
+        _fields.clear();
+        _explicitEncoding=false;
+    }
+
+    public void setHeaders(HttpContent httpContent)
+    {
+        Response response = _channel.getResponse();
+        String contentType = httpContent.getContentType();
+        if (contentType != null && !response.getHttpFields().containsKey(HttpHeader.CONTENT_TYPE.asString()))
+            setContentType(contentType);
+        
+        if (httpContent.getContentLength() > 0)
+            setLongContentLength(httpContent.getContentLength());
+
+        String lm = httpContent.getLastModified();
+        if (lm != null)
+            response.getHttpFields().put(HttpHeader.LAST_MODIFIED, lm);
+        else if (httpContent.getResource() != null)
+        {
+            long lml = httpContent.getResource().lastModified();
+            if (lml != -1)
+                response.getHttpFields().putDateField(HttpHeader.LAST_MODIFIED, lml);
+        }
+
+        String etag=httpContent.getETag();
+        if (etag!=null)
+            response.getHttpFields().put(HttpHeader.ETAG,etag);
+    }
+    
+    public HttpOutput getHttpOutput()
+    {
+        return _out;
+    }
+    
+    public void setHttpOutput(HttpOutput out)
+    {
+        _out=out;
+    }
+
+    public boolean isIncluding()
+    {
+        return _include.get() > 0;
+    }
+
+    public void include()
+    {
+        _include.incrementAndGet();
+    }
+
+    public void included()
+    {
+        _include.decrementAndGet();
+        if (_outputType == OutputType.WRITER)
+        {
+            _writer.reopen();
+        }
+        _out.reopen();
+    }
+
+    public void addCookie(HttpCookie cookie)
+    {
+        addSetCookie(
+                cookie.getName(),
+                cookie.getValue(),
+                cookie.getDomain(),
+                cookie.getPath(),
+                cookie.getMaxAge(),
+                cookie.getComment(),
+                cookie.isSecure(),
+                cookie.isHttpOnly(),
+                cookie.getVersion());;
+    }
+
+    @Override
+    public void addCookie(Cookie cookie)
+    {
+        String comment = cookie.getComment();
+        boolean httpOnly = false;
+
+        if (comment != null)
+        {
+            int i = comment.indexOf(HTTP_ONLY_COMMENT);
+            if (i >= 0)
+            {
+                httpOnly = true;
+                comment = comment.replace(HTTP_ONLY_COMMENT, "").trim();
+                if (comment.length() == 0)
+                    comment = null;
+            }
+        }
+        addSetCookie(cookie.getName(),
+                cookie.getValue(),
+                cookie.getDomain(),
+                cookie.getPath(),
+                cookie.getMaxAge(),
+                comment,
+                cookie.getSecure(),
+                httpOnly || cookie.isHttpOnly(),
+                cookie.getVersion());
+    }
+
+
+    /**
+     * Format a set cookie value
+     *
+     * @param name the name
+     * @param value the value
+     * @param domain the domain
+     * @param path the path
+     * @param maxAge the maximum age
+     * @param comment the comment (only present on versions > 0)
+     * @param isSecure true if secure cookie
+     * @param isHttpOnly true if for http only
+     * @param version version of cookie logic to use (0 == default behavior)
+     */
+    public void addSetCookie(
+            final String name,
+            final String value,
+            final String domain,
+            final String path,
+            final long maxAge,
+            final String comment,
+            final boolean isSecure,
+            final boolean isHttpOnly,
+            int version)
+    {
+        // Check arguments
+        if (name == null || name.length() == 0)
+            throw new IllegalArgumentException("Bad cookie name");
+
+        // Format value and params
+        StringBuilder buf = __cookieBuilder.get();
+        buf.setLength(0);
+        
+        // Name is checked for legality by servlet spec, but can also be passed directly so check again for quoting
+        boolean quote_name=isQuoteNeededForCookie(name);
+        quoteOnlyOrAppend(buf,name,quote_name);
+        
+        buf.append('=');
+        
+        // Remember name= part to look for other matching set-cookie
+        String name_equals=buf.toString();
+
+        // Append the value
+        boolean quote_value=isQuoteNeededForCookie(value);
+        quoteOnlyOrAppend(buf,value,quote_value);
+
+        // Look for domain and path fields and check if they need to be quoted
+        boolean has_domain = domain!=null && domain.length()>0;
+        boolean quote_domain = has_domain && isQuoteNeededForCookie(domain);
+        boolean has_path = path!=null && path.length()>0;
+        boolean quote_path = has_path && isQuoteNeededForCookie(path);
+        
+        // Upgrade the version if we have a comment or we need to quote value/path/domain or if they were already quoted
+        if (version==0 && ( comment!=null || quote_name || quote_value || quote_domain || quote_path || isQuoted(name) || isQuoted(value) || isQuoted(path) || isQuoted(domain)))
+            version=1;
+
+        // Append version
+        if (version==1)
+            buf.append (";Version=1");
+        else if (version>1)
+            buf.append (";Version=").append(version);
+        
+        // Append path
+        if (has_path)
+        {
+            buf.append(";Path=");
+            quoteOnlyOrAppend(buf,path,quote_path);
+        }
+        
+        // Append domain
+        if (has_domain)
+        {
+            buf.append(";Domain=");
+            quoteOnlyOrAppend(buf,domain,quote_domain);
+        }
+
+        // Handle max-age and/or expires
+        if (maxAge >= 0)
+        {
+            // Always use expires
+            // This is required as some browser (M$ this means you!) don't handle max-age even with v1 cookies
+            buf.append(";Expires=");
+            if (maxAge == 0)
+                buf.append(__01Jan1970_COOKIE);
+            else
+                DateGenerator.formatCookieDate(buf, System.currentTimeMillis() + 1000L * maxAge);
+            
+            // for v1 cookies, also send max-age
+            if (version>=1)
+            {
+                buf.append(";Max-Age=");
+                buf.append(maxAge);
+            }
+        }
+
+        // add the other fields
+        if (isSecure)
+            buf.append(";Secure");
+        if (isHttpOnly)
+            buf.append(";HttpOnly");
+        if (comment != null)
+        {
+            buf.append(";Comment=");
+            quoteOnlyOrAppend(buf,comment,isQuoteNeededForCookie(comment));
+        }
+
+        // remove any existing set-cookie fields of same name
+        Iterator<HttpField> i=_fields.iterator();
+        while (i.hasNext())
+        {
+            HttpField field=i.next();
+            if (field.getHeader()==HttpHeader.SET_COOKIE)
+            {
+                String val = field.getValue();
+                if (val!=null && val.startsWith(name_equals))
+                {
+                    //existing cookie has same name, does it also match domain and path?
+                    if (((!has_domain && !val.contains("Domain")) || (has_domain && val.contains(domain))) &&
+                        ((!has_path && !val.contains("Path")) || (has_path && val.contains(path))))
+                    {
+                        i.remove();
+                    }
+                }
+            }
+        }
+        
+        // add the set cookie
+        _fields.add(HttpHeader.SET_COOKIE.toString(), buf.toString());
+
+        // Expire responses with set-cookie headers so they do not get cached.
+        _fields.put(HttpHeader.EXPIRES.toString(), DateGenerator.__01Jan1970);
+    }
+
+
+    /* ------------------------------------------------------------ */
+    /** Does a cookie value need to be quoted?
+     * @param s value string
+     * @return true if quoted;
+     * @throws IllegalArgumentException If there a control characters in the string
+     */
+    private static boolean isQuoteNeededForCookie(String s)
+    {
+        if (s==null || s.length()==0)
+            return true;
+        
+        if (QuotedStringTokenizer.isQuoted(s))
+            return false;
+
+        for (int i=0;i<s.length();i++)
+        {
+            char c = s.charAt(i);
+            if (__COOKIE_DELIM.indexOf(c)>=0)
+                return true;
+            
+            if (c<0x20 || c>=0x7f)
+                throw new IllegalArgumentException("Illegal character in cookie value");
+        }
+
+        return false;
+    }
+    
+    
+    private static void quoteOnlyOrAppend(StringBuilder buf, String s, boolean quote)
+    {
+        if (quote)
+            QuotedStringTokenizer.quoteOnly(buf,s);
+        else
+            buf.append(s);
+    }
+    
+    @Override
+    public boolean containsHeader(String name)
+    {
+        return _fields.containsKey(name);
+    }
+
+    @Override
+    public String encodeURL(String url)
+    {
+        final Request request = _channel.getRequest();
+        SessionManager sessionManager = request.getSessionManager();
+        if (sessionManager == null)
+            return url;
+
+        HttpURI uri = null;
+        if (sessionManager.isCheckingRemoteSessionIdEncoding() && URIUtil.hasScheme(url))
+        {
+            uri = new HttpURI(url);
+            String path = uri.getPath();
+            path = (path == null ? "" : path);
+            int port = uri.getPort();
+            if (port < 0)
+                port = HttpScheme.HTTPS.asString().equalsIgnoreCase(uri.getScheme()) ? 443 : 80;
+            if (!request.getServerName().equalsIgnoreCase(uri.getHost()) ||
+                    request.getServerPort() != port ||
+                    !path.startsWith(request.getContextPath())) //TODO the root context path is "", with which every non null string starts
+                return url;
+        }
+
+        String sessionURLPrefix = sessionManager.getSessionIdPathParameterNamePrefix();
+        if (sessionURLPrefix == null)
+            return url;
+
+        if (url == null)
+            return null;
+
+        // should not encode if cookies in evidence
+        if ((sessionManager.isUsingCookies() && request.isRequestedSessionIdFromCookie()) || !sessionManager.isUsingURLs()) 
+        {
+            int prefix = url.indexOf(sessionURLPrefix);
+            if (prefix != -1)
+            {
+                int suffix = url.indexOf("?", prefix);
+                if (suffix < 0)
+                    suffix = url.indexOf("#", prefix);
+
+                if (suffix <= prefix)
+                    return url.substring(0, prefix);
+                return url.substring(0, prefix) + url.substring(suffix);
+            }
+            return url;
+        }
+
+        // get session;
+        HttpSession session = request.getSession(false);
+
+        // no session
+        if (session == null)
+            return url;
+
+        // invalid session
+        if (!sessionManager.isValid(session))
+            return url;
+
+        String id = sessionManager.getNodeId(session);
+
+        if (uri == null)
+            uri = new HttpURI(url);
+
+
+        // Already encoded
+        int prefix = url.indexOf(sessionURLPrefix);
+        if (prefix != -1)
+        {
+            int suffix = url.indexOf("?", prefix);
+            if (suffix < 0)
+                suffix = url.indexOf("#", prefix);
+
+            if (suffix <= prefix)
+                return url.substring(0, prefix + sessionURLPrefix.length()) + id;
+            return url.substring(0, prefix + sessionURLPrefix.length()) + id +
+                    url.substring(suffix);
+        }
+
+        // edit the session
+        int suffix = url.indexOf('?');
+        if (suffix < 0)
+            suffix = url.indexOf('#');
+        if (suffix < 0)
+        {
+            return url +
+                    ((HttpScheme.HTTPS.is(uri.getScheme()) || HttpScheme.HTTP.is(uri.getScheme())) && uri.getPath() == null ? "/" : "") + //if no path, insert the root path
+                    sessionURLPrefix + id;
+        }
+
+
+        return url.substring(0, suffix) +
+                ((HttpScheme.HTTPS.is(uri.getScheme()) || HttpScheme.HTTP.is(uri.getScheme())) && uri.getPath() == null ? "/" : "") + //if no path so insert the root path
+                sessionURLPrefix + id + url.substring(suffix);
+    }
+
+    @Override
+    public String encodeRedirectURL(String url)
+    {
+        return encodeURL(url);
+    }
+
+    @Override
+    @Deprecated
+    public String encodeUrl(String url)
+    {
+        return encodeURL(url);
+    }
+
+    @Override
+    @Deprecated
+    public String encodeRedirectUrl(String url)
+    {
+        return encodeRedirectURL(url);
+    }
+
+    @Override
+    public void sendError(int sc) throws IOException
+    {
+        if (sc == 102)
+            sendProcessing();
+        else
+            sendError(sc, null);
+    }
+
+    @Override
+    public void sendError(int code, String message) throws IOException
+    {
+        if (isIncluding())
+            return;
+
+        if (isCommitted())
+            LOG.warn("Committed before "+code+" "+message);
+
+        resetBuffer();
+        _characterEncoding=null;
+        setHeader(HttpHeader.EXPIRES,null);
+        setHeader(HttpHeader.LAST_MODIFIED,null);
+        setHeader(HttpHeader.CACHE_CONTROL,null);
+        setHeader(HttpHeader.CONTENT_TYPE,null);
+        setHeader(HttpHeader.CONTENT_LENGTH,null);
+
+        _outputType = OutputType.NONE;
+        setStatus(code);
+        _reason=message;
+
+        Request request = _channel.getRequest();
+        Throwable cause = (Throwable)request.getAttribute(Dispatcher.ERROR_EXCEPTION);
+        if (message==null)
+            message=cause==null?HttpStatus.getMessage(code):cause.toString();
+
+        // If we are allowed to have a body
+        if (code!=SC_NO_CONTENT &&
+            code!=SC_NOT_MODIFIED &&
+            code!=SC_PARTIAL_CONTENT &&
+            code>=SC_OK)
+        {
+            ErrorHandler error_handler = ErrorHandler.getErrorHandler(_channel.getServer(),request.getContext()==null?null:request.getContext().getContextHandler());
+            if (error_handler!=null)
+            {
+                request.setAttribute(RequestDispatcher.ERROR_STATUS_CODE,new Integer(code));
+                request.setAttribute(RequestDispatcher.ERROR_MESSAGE, message);
+                request.setAttribute(RequestDispatcher.ERROR_REQUEST_URI, request.getRequestURI());
+                request.setAttribute(RequestDispatcher.ERROR_SERVLET_NAME,request.getServletName());
+                error_handler.handle(null,_channel.getRequest(),_channel.getRequest(),this );
+            }
+            else
+            {
+                setHeader(HttpHeader.CACHE_CONTROL, "must-revalidate,no-cache,no-store");
+                setContentType(MimeTypes.Type.TEXT_HTML_8859_1.toString());
+                try (ByteArrayISO8859Writer writer= new ByteArrayISO8859Writer(2048);)
+                {
+                    if (message != null)
+                    {
+                        message= StringUtil.replace(message, "&", "&amp;");
+                        message= StringUtil.replace(message, "<", "&lt;");
+                        message= StringUtil.replace(message, ">", "&gt;");
+                    }
+                    String uri= request.getRequestURI();
+                    if (uri!=null)
+                    {
+                        uri= StringUtil.replace(uri, "&", "&amp;");
+                        uri= StringUtil.replace(uri, "<", "&lt;");
+                        uri= StringUtil.replace(uri, ">", "&gt;");
+                    }
+
+                    writer.write("<html>\n<head>\n<meta http-equiv=\"Content-Type\" content=\"text/html;charset=ISO-8859-1\"/>\n");
+                    writer.write("<title>Error ");
+                    writer.write(Integer.toString(code));
+                    writer.write(' ');
+                    if (message==null)
+                        writer.write(message);
+                    writer.write("</title>\n</head>\n<body>\n<h2>HTTP ERROR: ");
+                    writer.write(Integer.toString(code));
+                    writer.write("</h2>\n<p>Problem accessing ");
+                    writer.write(uri);
+                    writer.write(". Reason:\n<pre>    ");
+                    writer.write(message);
+                    writer.write("</pre>");
+                    writer.write("</p>\n<hr /><i><small>Powered by Jetty://</small></i>");
+                    writer.write("\n</body>\n</html>\n");
+
+                    writer.flush();
+                    setContentLength(writer.size());
+                    try (ServletOutputStream outputStream = getOutputStream())
+                    {
+                        writer.writeTo(outputStream);
+                        writer.destroy();
+                    }
+                }
+            }
+        }
+        else if (code!=SC_PARTIAL_CONTENT)
+        {
+            // TODO work out why this is required?
+            _channel.getRequest().getHttpFields().remove(HttpHeader.CONTENT_TYPE);
+            _channel.getRequest().getHttpFields().remove(HttpHeader.CONTENT_LENGTH);
+            _characterEncoding=null;
+            _mimeType=null;
+        }
+
+        closeOutput();
+    }
+
+    /**
+     * Sends a 102-Processing response.
+     * If the connection is a HTTP connection, the version is 1.1 and the
+     * request has a Expect header starting with 102, then a 102 response is
+     * sent. This indicates that the request still be processed and real response
+     * can still be sent.   This method is called by sendError if it is passed 102.
+     * @see javax.servlet.http.HttpServletResponse#sendError(int)
+     */
+    public void sendProcessing() throws IOException
+    {
+        if (_channel.isExpecting102Processing() && !isCommitted())
+        {
+            _channel.sendResponse(HttpGenerator.PROGRESS_102_INFO, null, true);
+        }
+    }
+    
+    /**
+     * Sends a response with one of the 300 series redirection codes.
+     * @param code
+     * @param location
+     * @throws IOException
+     */
+    public void sendRedirect(int code, String location) throws IOException
+    {
+        if ((code < HttpServletResponse.SC_MULTIPLE_CHOICES) || (code >= HttpServletResponse.SC_BAD_REQUEST))
+            throw new IllegalArgumentException("Not a 3xx redirect code");
+        
+        if (isIncluding())
+            return;
+
+        if (location == null)
+            throw new IllegalArgumentException();
+
+        if (!URIUtil.hasScheme(location))
+        {
+            StringBuilder buf = _channel.getRequest().getRootURL();
+            if (location.startsWith("//"))
+            {
+                buf.delete(0, buf.length());
+                buf.append(_channel.getRequest().getScheme());
+                buf.append(":");
+                buf.append(location);
+            }
+            else if (location.startsWith("/"))
+                buf.append(location);
+            else
+            {
+                String path = _channel.getRequest().getRequestURI();
+                String parent = (path.endsWith("/")) ? path : URIUtil.parentPath(path);
+                location = URIUtil.addPaths(parent, location);
+                if (location == null)
+                    throw new IllegalStateException("path cannot be above root");
+                if (!location.startsWith("/"))
+                    buf.append('/');
+                buf.append(location);
+            }
+
+            location = buf.toString();
+            HttpURI uri = new HttpURI(location);
+            String path = uri.getDecodedPath();
+            String canonical = URIUtil.canonicalPath(path);
+            if (canonical == null)
+                throw new IllegalArgumentException();
+            if (!canonical.equals(path))
+            {
+                buf = _channel.getRequest().getRootURL();
+                buf.append(URIUtil.encodePath(canonical));
+                String param=uri.getParam();
+                if (param!=null)
+                {
+                    buf.append(';');
+                    buf.append(param);
+                }
+                String query=uri.getQuery();
+                if (query!=null)
+                {
+                    buf.append('?');
+                    buf.append(query);
+                }
+                String fragment=uri.getFragment();
+                if (fragment!=null)
+                {
+                    buf.append('#');
+                    buf.append(fragment);
+                }
+                location = buf.toString();
+            }
+        }
+
+        resetBuffer();
+        setHeader(HttpHeader.LOCATION, location);
+        setStatus(code);
+        closeOutput();
+    }
+
+    @Override
+    public void sendRedirect(String location) throws IOException
+    {
+        sendRedirect(HttpServletResponse.SC_MOVED_TEMPORARILY, location);
+    }
+
+    @Override
+    public void setDateHeader(String name, long date)
+    {
+        if (!isIncluding())
+            _fields.putDateField(name, date);
+    }
+
+    @Override
+    public void addDateHeader(String name, long date)
+    {
+        if (!isIncluding())
+            _fields.addDateField(name, date);
+    }
+
+    public void setHeader(HttpHeader name, String value)
+    {
+        if (HttpHeader.CONTENT_TYPE == name)
+            setContentType(value);
+        else
+        {
+            if (isIncluding())
+                return;
+
+            _fields.put(name, value);
+
+            if (HttpHeader.CONTENT_LENGTH == name)
+            {
+                if (value == null)
+                    _contentLength = -1l;
+                else
+                    _contentLength = Long.parseLong(value);
+            }
+        }
+    }
+
+    @Override
+    public void setHeader(String name, String value)
+    {
+        if (HttpHeader.CONTENT_TYPE.is(name))
+            setContentType(value);
+        else
+        {
+            if (isIncluding())
+            {
+                if (name.startsWith(SET_INCLUDE_HEADER_PREFIX))
+                    name = name.substring(SET_INCLUDE_HEADER_PREFIX.length());
+                else
+                    return;
+            }
+            _fields.put(name, value);
+            if (HttpHeader.CONTENT_LENGTH.is(name))
+            {
+                if (value == null)
+                    _contentLength = -1l;
+                else
+                    _contentLength = Long.parseLong(value);
+            }
+        }
+    }
+
+    @Override
+    public Collection<String> getHeaderNames()
+    {
+        final HttpFields fields = _fields;
+        return fields.getFieldNamesCollection();
+    }
+
+    @Override
+    public String getHeader(String name)
+    {
+        return _fields.getStringField(name);
+    }
+
+    @Override
+    public Collection<String> getHeaders(String name)
+    {
+        final HttpFields fields = _fields;
+        Collection<String> i = fields.getValuesList(name);
+        if (i == null)
+            return Collections.emptyList();
+        return i;
+    }
+
+    @Override
+    public void addHeader(String name, String value)
+    {
+        if (isIncluding())
+        {
+            if (name.startsWith(SET_INCLUDE_HEADER_PREFIX))
+                name = name.substring(SET_INCLUDE_HEADER_PREFIX.length());
+            else
+                return;
+        }
+
+        if (HttpHeader.CONTENT_TYPE.is(name))
+        {
+            setContentType(value);
+            return;
+        }
+        
+        if (HttpHeader.CONTENT_LENGTH.is(name))
+        {
+            setHeader(name,value);
+            return;
+        }
+        
+        _fields.add(name, value);
+    }
+
+    @Override
+    public void setIntHeader(String name, int value)
+    {
+        if (!isIncluding())
+        {
+            _fields.putLongField(name, value);
+            if (HttpHeader.CONTENT_LENGTH.is(name))
+                _contentLength = value;
+        }
+    }
+
+    @Override
+    public void addIntHeader(String name, int value)
+    {
+        if (!isIncluding())
+        {
+            _fields.add(name, Integer.toString(value));
+            if (HttpHeader.CONTENT_LENGTH.is(name))
+                _contentLength = value;
+        }
+    }
+
+    @Override
+    public void setStatus(int sc)
+    {
+        if (sc <= 0)
+            throw new IllegalArgumentException();
+        if (!isIncluding())
+        {
+            _status = sc;
+            _reason = null;
+        }
+    }
+
+    @Override
+    @Deprecated
+    public void setStatus(int sc, String sm)
+    {
+        setStatusWithReason(sc,sm);
+    }
+    
+    public void setStatusWithReason(int sc, String sm)
+    {
+        if (sc <= 0)
+            throw new IllegalArgumentException();
+        if (!isIncluding())
+        {
+            _status = sc;
+            _reason = sm;
+        }
+    }
+
+    @Override
+    public String getCharacterEncoding()
+    {
+        if (_characterEncoding == null)
+            _characterEncoding = StringUtil.__ISO_8859_1;
+        return _characterEncoding;
+    }
+
+    @Override
+    public String getContentType()
+    {
+        return _contentType;
+    }
+
+    @Override
+    public ServletOutputStream getOutputStream() throws IOException
+    {
+        if (_outputType == OutputType.WRITER)
+            throw new IllegalStateException("WRITER");
+        _outputType = OutputType.STREAM;
+        return _out;
+    }
+
+    public boolean isWriting()
+    {
+        return _outputType == OutputType.WRITER;
+    }
+
+    @Override
+    public PrintWriter getWriter() throws IOException
+    {
+        if (_outputType == OutputType.STREAM)
+            throw new IllegalStateException("STREAM");
+
+        if (_outputType == OutputType.NONE)
+        {
+            /* get encoding from Content-Type header */
+            String encoding = _characterEncoding;
+            if (encoding == null)
+            {
+                if (_mimeType!=null && _mimeType.isCharsetAssumed())
+                    encoding=_mimeType.getCharset().toString();
+                else
+                {
+                    encoding = MimeTypes.inferCharsetFromContentType(_contentType);
+                    if (encoding == null)
+                        encoding = StringUtil.__ISO_8859_1;
+                    setCharacterEncoding(encoding,false);
+                }
+            }
+            
+            if (_writer != null && _writer.isFor(encoding))
+                _writer.reopen();
+            else
+            {
+                if (StringUtil.__ISO_8859_1.equalsIgnoreCase(encoding))
+                    _writer = new ResponseWriter(new Iso88591HttpWriter(_out),encoding);
+                else if (StringUtil.__UTF8.equalsIgnoreCase(encoding))
+                    _writer = new ResponseWriter(new Utf8HttpWriter(_out),encoding);
+                else
+                    _writer = new ResponseWriter(new EncodingHttpWriter(_out, encoding),encoding);
+            }
+            
+            // Set the output type at the end, because setCharacterEncoding() checks for it
+            _outputType = OutputType.WRITER;
+        }
+        return _writer;
+    }
+
+    @Override
+    public void setContentLength(int len)
+    {
+        // Protect from setting after committed as default handling
+        // of a servlet HEAD request ALWAYS sets _content length, even
+        // if the getHandling committed the response!
+        if (isCommitted() || isIncluding())
+            return;
+
+        _contentLength = len;
+        if (_contentLength > 0)
+        {
+            long written = _out.getWritten();
+            if (written > len)
+                throw new IllegalArgumentException("setContentLength(" + len + ") when already written " + written);
+            
+            _fields.putLongField(HttpHeader.CONTENT_LENGTH, len);
+            if (isAllContentWritten(written))
+            {
+                try
+                {
+                    closeOutput();
+                }
+                catch(IOException e)
+                {
+                    throw new RuntimeIOException(e);
+                }
+            }
+        }
+        else if (_contentLength==0)
+        {
+            long written = _out.getWritten();
+            if (written > 0)
+                throw new IllegalArgumentException("setContentLength(0) when already written " + written);
+            _fields.put(HttpHeader.CONTENT_LENGTH, "0");
+        }
+        else
+            _fields.remove(HttpHeader.CONTENT_LENGTH);
+    }
+    
+    public long getContentLength()
+    {
+        return _contentLength;
+    }
+
+    public boolean isAllContentWritten(long written)
+    {
+        return (_contentLength >= 0 && written >= _contentLength);
+    }
+
+    public void closeOutput() throws IOException
+    {
+        switch (_outputType)
+        {
+            case WRITER:
+                _writer.close();
+                if (!_out.isClosed())
+                    _out.close();
+                break;
+            case STREAM:
+                getOutputStream().close();
+                break;
+            default:
+                _out.close();
+        }
+    }
+
+    public long getLongContentLength()
+    {
+        return _contentLength;
+    }
+
+    public void setLongContentLength(long len)
+    {
+        // Protect from setting after committed as default handling
+        // of a servlet HEAD request ALWAYS sets _content length, even
+        // if the getHandling committed the response!
+        if (isCommitted() || isIncluding())
+            return;
+        _contentLength = len;
+        _fields.putLongField(HttpHeader.CONTENT_LENGTH.toString(), len);
+    }
+    
+    @Override
+    public void setContentLengthLong(long length)
+    {
+        setLongContentLength(length);
+    }
+
+    @Override
+    public void setCharacterEncoding(String encoding)
+    {
+        setCharacterEncoding(encoding,true);
+    }
+    
+    private void setCharacterEncoding(String encoding, boolean explicit)
+    {
+        if (isIncluding() || isWriting())
+            return;
+
+        if (_outputType == OutputType.NONE && !isCommitted())
+        {
+            if (encoding == null)
+            {
+                _explicitEncoding=false;
+                
+                // Clear any encoding.
+                if (_characterEncoding != null)
+                {
+                    _characterEncoding = null;
+                    
+                    if (_mimeType!=null)
+                    {
+                        _mimeType=_mimeType.getBaseType();
+                        _contentType=_mimeType.asString();
+                        _fields.put(_mimeType.getContentTypeField());
+                    }
+                    else if (_contentType != null)
+                    {
+                        _contentType = MimeTypes.getContentTypeWithoutCharset(_contentType);
+                        _fields.put(HttpHeader.CONTENT_TYPE, _contentType);
+                    }
+                }
+            }
+            else
+            {
+                // No, so just add this one to the mimetype
+                _explicitEncoding = explicit;
+                _characterEncoding = HttpGenerator.__STRICT?encoding:StringUtil.normalizeCharset(encoding);
+                if (_mimeType!=null)
+                {
+                    _contentType=_mimeType.getBaseType().asString()+ "; charset=" + _characterEncoding;
+                    _mimeType = MimeTypes.CACHE.get(_contentType);
+                    if (_mimeType==null || HttpGenerator.__STRICT)
+                        _fields.put(HttpHeader.CONTENT_TYPE, _contentType);
+                    else
+                        _fields.put(_mimeType.getContentTypeField());
+                }
+                else if (_contentType != null)
+                {
+                    _contentType = MimeTypes.getContentTypeWithoutCharset(_contentType) + "; charset=" + _characterEncoding;
+                    _fields.put(HttpHeader.CONTENT_TYPE, _contentType);
+                }
+            }
+        }
+    }
+
+    @Override
+    public void setContentType(String contentType)
+    {
+        if (isCommitted() || isIncluding())
+            return;
+
+        if (contentType == null)
+        {
+            if (isWriting() && _characterEncoding != null)
+                throw new IllegalSelectorException();
+
+            if (_locale == null)
+                _characterEncoding = null;
+            _mimeType = null;
+            _contentType = null;
+            _fields.remove(HttpHeader.CONTENT_TYPE);
+        }
+        else
+        {
+            _contentType = contentType;
+            _mimeType = MimeTypes.CACHE.get(contentType);
+            
+            String charset;
+            if (_mimeType!=null && _mimeType.getCharset()!=null && !_mimeType.isCharsetAssumed())
+                charset=_mimeType.getCharset().toString();
+            else
+                charset = MimeTypes.getCharsetFromContentType(contentType);
+
+            if (charset == null)
+            {
+                if (_characterEncoding != null)
+                {
+                    _contentType = contentType + "; charset=" + _characterEncoding;
+                    _mimeType = null;
+                }
+            }
+            else if (isWriting() && !charset.equals(_characterEncoding))
+            {
+                // too late to change the character encoding;
+                _mimeType = null;
+                _contentType = MimeTypes.getContentTypeWithoutCharset(_contentType);
+                if (_characterEncoding != null)
+                    _contentType = _contentType + "; charset=" + _characterEncoding;
+            }
+            else
+            {
+                _characterEncoding = charset;
+                _explicitEncoding = true;
+            }
+
+            if (HttpGenerator.__STRICT || _mimeType==null)
+                _fields.put(HttpHeader.CONTENT_TYPE, _contentType);
+            else
+            {
+                _contentType=_mimeType.asString();
+                _fields.put(_mimeType.getContentTypeField());
+            }
+        }
+        
+    }
+
+    @Override
+    public void setBufferSize(int size)
+    {
+        if (isCommitted() || getContentCount() > 0)
+            throw new IllegalStateException("Committed or content written");
+        if (size <= 0)
+            size = __MIN_BUFFER_SIZE;
+        _out.setBufferSize(size);
+    }
+
+    @Override
+    public int getBufferSize()
+    {
+        return _out.getBufferSize();
+    }
+
+    @Override
+    public void flushBuffer() throws IOException
+    {
+        if (!_out.isClosed())
+            _out.flush();
+    }
+
+    @Override
+    public void reset()
+    {
+        resetForForward();
+        _status = 200;
+        _reason = null;
+        _contentLength = -1;
+        _fields.clear();
+
+        String connection = _channel.getRequest().getHttpFields().getStringField(HttpHeader.CONNECTION);
+        if (connection != null)
+        {
+            String[] values = connection.split(",");
+            for (int i = 0; values != null && i < values.length; i++)
+            {
+                HttpHeaderValue cb = HttpHeaderValue.CACHE.get(values[0].trim());
+
+                if (cb != null)
+                {
+                    switch (cb)
+                    {
+                        case CLOSE:
+                            _fields.put(HttpHeader.CONNECTION, HttpHeaderValue.CLOSE.toString());
+                            break;
+
+                        case KEEP_ALIVE:
+                            if (HttpVersion.HTTP_1_0.is(_channel.getRequest().getProtocol()))
+                                _fields.put(HttpHeader.CONNECTION, HttpHeaderValue.KEEP_ALIVE.toString());
+                            break;
+                        case TE:
+                            _fields.put(HttpHeader.CONNECTION, HttpHeaderValue.TE.toString());
+                            break;
+                        default:
+                    }
+                }
+            }
+        }
+    }
+
+    public void reset(boolean preserveCookies)
+    { 
+        if (!preserveCookies)
+            reset();
+        else
+        {
+            ArrayList<String> cookieValues = new ArrayList<String>(5);
+            Enumeration<String> vals = _fields.getValues(HttpHeader.SET_COOKIE.asString());
+            while (vals.hasMoreElements())
+                cookieValues.add(vals.nextElement());
+            reset();
+            for (String v:cookieValues)
+                _fields.add(HttpHeader.SET_COOKIE, v);
+        }
+    }
+
+    public void resetForForward()
+    {
+        resetBuffer();
+        _outputType = OutputType.NONE;
+    }
+
+    @Override
+    public void resetBuffer()
+    {
+        if (isCommitted())
+            throw new IllegalStateException("Committed");
+
+        switch (_outputType)
+        {
+            case STREAM:
+            case WRITER:
+                _out.reset();
+                break;
+            default:
+        }
+
+        _out.resetBuffer();
+    }
+
+    protected ResponseInfo newResponseInfo()
+    {
+        if (_status == HttpStatus.NOT_SET_000)
+            _status = HttpStatus.OK_200;
+        return new ResponseInfo(_channel.getRequest().getHttpVersion(), _fields, getLongContentLength(), getStatus(), getReason(), _channel.getRequest().isHead());
+    }
+
+    @Override
+    public boolean isCommitted()
+    {
+        return _channel.isCommitted();
+    }
+
+    @Override
+    public void setLocale(Locale locale)
+    {
+        if (locale == null || isCommitted() || isIncluding())
+            return;
+
+        _locale = locale;
+        _fields.put(HttpHeader.CONTENT_LANGUAGE, locale.toString().replace('_', '-'));
+
+        if (_outputType != OutputType.NONE)
+            return;
+
+        if (_channel.getRequest().getContext() == null)
+            return;
+
+        String charset = _channel.getRequest().getContext().getContextHandler().getLocaleEncoding(locale);
+
+        if (charset != null && charset.length() > 0 && !_explicitEncoding)
+            setCharacterEncoding(charset,false);
+    }
+
+    @Override
+    public Locale getLocale()
+    {
+        if (_locale == null)
+            return Locale.getDefault();
+        return _locale;
+    }
+
+    @Override
+    public int getStatus()
+    {
+        return _status;
+    }
+
+    public String getReason()
+    {
+        return _reason;
+    }
+
+    public HttpFields getHttpFields()
+    {
+        return _fields;
+    }
+
+    public long getContentCount()
+    {
+        return _out.getWritten();
+    }
+
+    @Override
+    public String toString()
+    {
+        return String.format("%s %d %s%n%s", _channel.getRequest().getHttpVersion(), _status, _reason == null ? "" : _reason, _fields);
+    }
+    
+
+    private static class ResponseWriter extends PrintWriter
+    {
+        private final String _encoding;
+        private final HttpWriter _httpWriter;
+        
+        public ResponseWriter(HttpWriter httpWriter,String encoding)
+        {
+            super(httpWriter);
+            _httpWriter=httpWriter;
+            _encoding=encoding;
+        }
+
+        public boolean isFor(String encoding)
+        {
+            return _encoding.equalsIgnoreCase(encoding);
+        }
+        
+        protected void reopen()
+        {
+            super.clearError();
+            out=_httpWriter;
+        }
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/server/SecureRequestCustomizer.java b/lib/jetty/org/eclipse/jetty/server/SecureRequestCustomizer.java
new file mode 100644 (file)
index 0000000..b017461
--- /dev/null
@@ -0,0 +1,168 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server;
+
+import java.security.cert.X509Certificate;
+
+import javax.net.ssl.SSLEngine;
+import javax.net.ssl.SSLSession;
+
+import org.eclipse.jetty.http.HttpScheme;
+import org.eclipse.jetty.io.ssl.SslConnection;
+import org.eclipse.jetty.io.ssl.SslConnection.DecryptedEndPoint;
+import org.eclipse.jetty.util.TypeUtil;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.ssl.SslContextFactory;
+
+public class SecureRequestCustomizer implements HttpConfiguration.Customizer
+{
+    private static final Logger LOG = Log.getLogger(SecureRequestCustomizer.class);
+    
+    /**
+     * The name of the SSLSession attribute that will contain any cached information.
+     */
+    public static final String CACHED_INFO_ATTR = CachedInfo.class.getName();
+
+
+    @Override
+    public void customize(Connector connector, HttpConfiguration channelConfig, Request request)
+    {
+        if (request.getHttpChannel().getEndPoint() instanceof DecryptedEndPoint)
+        {
+            request.setScheme(HttpScheme.HTTPS.asString());
+            request.setSecure(true);
+            SslConnection.DecryptedEndPoint ssl_endp = (DecryptedEndPoint)request.getHttpChannel().getEndPoint();
+            SslConnection sslConnection = ssl_endp.getSslConnection();
+            SSLEngine sslEngine=sslConnection.getSSLEngine();
+            customize(sslEngine,request);
+        }
+
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * Allow the Listener a chance to customise the request. before the server
+     * does its stuff. <br>
+     * This allows the required attributes to be set for SSL requests. <br>
+     * The requirements of the Servlet specs are:
+     * <ul>
+     * <li> an attribute named "javax.servlet.request.ssl_session_id" of type
+     * String (since Servlet Spec 3.0).</li>
+     * <li> an attribute named "javax.servlet.request.cipher_suite" of type
+     * String.</li>
+     * <li> an attribute named "javax.servlet.request.key_size" of type Integer.</li>
+     * <li> an attribute named "javax.servlet.request.X509Certificate" of type
+     * java.security.cert.X509Certificate[]. This is an array of objects of type
+     * X509Certificate, the order of this array is defined as being in ascending
+     * order of trust. The first certificate in the chain is the one set by the
+     * client, the next is the one used to authenticate the first, and so on.
+     * </li>
+     * </ul>
+     *
+     * @param request
+     *                HttpRequest to be customised.
+     */
+    public void customize(SSLEngine sslEngine, Request request)
+    {
+        request.setScheme(HttpScheme.HTTPS.asString());
+        SSLSession sslSession = sslEngine.getSession();
+
+        try
+        {
+            String cipherSuite=sslSession.getCipherSuite();
+            Integer keySize;
+            X509Certificate[] certs;
+            String idStr;
+
+            CachedInfo cachedInfo=(CachedInfo)sslSession.getValue(CACHED_INFO_ATTR);
+            if (cachedInfo!=null)
+            {
+                keySize=cachedInfo.getKeySize();
+                certs=cachedInfo.getCerts();
+                idStr=cachedInfo.getIdStr();
+            }
+            else 
+            {
+                keySize=new Integer(SslContextFactory.deduceKeyLength(cipherSuite));
+                certs=SslContextFactory.getCertChain(sslSession);
+                byte[] bytes = sslSession.getId();
+                idStr = TypeUtil.toHexString(bytes);
+                cachedInfo=new CachedInfo(keySize,certs,idStr);
+                sslSession.putValue(CACHED_INFO_ATTR,cachedInfo);
+            }
+
+            if (certs!=null)
+                request.setAttribute("javax.servlet.request.X509Certificate",certs);
+
+            request.setAttribute("javax.servlet.request.cipher_suite",cipherSuite);
+            request.setAttribute("javax.servlet.request.key_size",keySize);
+            request.setAttribute("javax.servlet.request.ssl_session_id", idStr);
+        }
+        catch (Exception e)
+        {
+            LOG.warn(Log.EXCEPTION,e);
+        }
+    }
+
+    @Override
+    public String toString()
+    {
+        return String.format("%s@%x",this.getClass().getSimpleName(),hashCode());
+    }
+    
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    /**
+     * Simple bundle of information that is cached in the SSLSession. Stores the
+     * effective keySize and the client certificate chain.
+     */
+    private static class CachedInfo
+    {
+        private final X509Certificate[] _certs;
+        private final Integer _keySize;
+        private final String _idStr;
+
+        CachedInfo(Integer keySize, X509Certificate[] certs,String idStr)
+        {
+            this._keySize=keySize;
+            this._certs=certs;
+            this._idStr=idStr;
+        }
+
+        X509Certificate[] getCerts()
+        {
+            return _certs;
+        }
+
+        Integer getKeySize()
+        {
+            return _keySize;
+        }
+
+        String getIdStr()
+        {
+            return _idStr;
+        }
+    }
+
+
+
+}
diff --git a/lib/jetty/org/eclipse/jetty/server/Server.java b/lib/jetty/org/eclipse/jetty/server/Server.java
new file mode 100644 (file)
index 0000000..b1437d6
--- /dev/null
@@ -0,0 +1,680 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server;
+
+import java.io.IOException;
+import java.lang.management.ManagementFactory;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Date;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.http.DateGenerator;
+import org.eclipse.jetty.http.HttpField;
+import org.eclipse.jetty.http.HttpGenerator;
+import org.eclipse.jetty.http.HttpHeader;
+import org.eclipse.jetty.http.HttpMethod;
+import org.eclipse.jetty.http.HttpStatus;
+import org.eclipse.jetty.http.HttpURI;
+import org.eclipse.jetty.server.handler.ContextHandler;
+import org.eclipse.jetty.server.handler.HandlerWrapper;
+import org.eclipse.jetty.util.Attributes;
+import org.eclipse.jetty.util.AttributesMap;
+import org.eclipse.jetty.util.Jetty;
+import org.eclipse.jetty.util.MultiException;
+import org.eclipse.jetty.util.URIUtil;
+import org.eclipse.jetty.util.annotation.ManagedAttribute;
+import org.eclipse.jetty.util.annotation.ManagedObject;
+import org.eclipse.jetty.util.annotation.Name;
+import org.eclipse.jetty.util.component.Graceful;
+import org.eclipse.jetty.util.component.LifeCycle;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.thread.QueuedThreadPool;
+import org.eclipse.jetty.util.thread.ShutdownThread;
+import org.eclipse.jetty.util.thread.ThreadPool;
+import org.eclipse.jetty.util.thread.ThreadPool.SizedThreadPool;
+
+/* ------------------------------------------------------------ */
+/** Jetty HTTP Servlet Server.
+ * This class is the main class for the Jetty HTTP Servlet server.
+ * It aggregates Connectors (HTTP request receivers) and request Handlers.
+ * The server is itself a handler and a ThreadPool.  Connectors use the ThreadPool methods
+ * to run jobs that will eventually call the handle method.
+ */
+@ManagedObject(value="Jetty HTTP Servlet server")
+public class Server extends HandlerWrapper implements Attributes
+{
+    private static final Logger LOG = Log.getLogger(Server.class);
+
+    private final AttributesMap _attributes = new AttributesMap();
+    private final ThreadPool _threadPool;
+    private final List<Connector> _connectors = new CopyOnWriteArrayList<>();
+    private SessionIdManager _sessionIdManager;
+    private boolean _stopAtShutdown;
+    private boolean _dumpAfterStart=false;
+    private boolean _dumpBeforeStop=false;
+    
+    private volatile DateField _dateField;
+    
+    /* ------------------------------------------------------------ */
+    public Server()
+    {
+        this((ThreadPool)null);
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Convenience constructor
+     * Creates server and a {@link ServerConnector} at the passed port.
+     * @param port The port of a network HTTP connector (or 0 for a randomly allocated port).
+     * @see NetworkConnector#getLocalPort()
+     */
+    public Server(@Name("port")int port)
+    {
+        this((ThreadPool)null);
+        ServerConnector connector=new ServerConnector(this);
+        connector.setPort(port);
+        setConnectors(new Connector[]{connector});
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Convenience constructor
+     * Creates server and a {@link ServerConnector} at the passed address.
+     */
+    public Server(@Name("address")InetSocketAddress addr)
+    {
+        this((ThreadPool)null);
+        ServerConnector connector=new ServerConnector(this);
+        connector.setHost(addr.getHostName());
+        connector.setPort(addr.getPort());
+        setConnectors(new Connector[]{connector});
+    }
+
+
+
+    /* ------------------------------------------------------------ */
+    public Server(@Name("threadpool") ThreadPool pool)
+    {
+        _threadPool=pool!=null?pool:new QueuedThreadPool();
+        addBean(_threadPool);
+        setServer(this);
+    }
+
+
+    /* ------------------------------------------------------------ */
+    @ManagedAttribute("version of this server")
+    public static String getVersion()
+    {
+        return Jetty.VERSION;
+    }
+
+    /* ------------------------------------------------------------ */
+    public boolean getStopAtShutdown()
+    {
+        return _stopAtShutdown;
+    }
+
+    /* ------------------------------------------------------------ */
+    public void setStopAtShutdown(boolean stop)
+    {
+        //if we now want to stop
+        if (stop)
+        {
+            //and we weren't stopping before
+            if (!_stopAtShutdown)
+            {
+                //only register to stop if we're already started (otherwise we'll do it in doStart())
+                if (isStarted())
+                    ShutdownThread.register(this);
+            }
+        }
+        else
+            ShutdownThread.deregister(this);
+
+        _stopAtShutdown=stop;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Returns the connectors.
+     */
+    @ManagedAttribute(value="connectors for this server", readonly=true)
+    public Connector[] getConnectors()
+    {
+        List<Connector> connectors = new ArrayList<>(_connectors);
+        return connectors.toArray(new Connector[connectors.size()]);
+    }
+
+    /* ------------------------------------------------------------ */
+    public void addConnector(Connector connector)
+    {
+        if (connector.getServer() != this)
+            throw new IllegalArgumentException("Connector " + connector +
+                    " cannot be shared among server " + connector.getServer() + " and server " + this);
+        if (_connectors.add(connector))
+            addBean(connector);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Convenience method which calls {@link #getConnectors()} and {@link #setConnectors(Connector[])} to
+     * remove a connector.
+     * @param connector The connector to remove.
+     */
+    public void removeConnector(Connector connector)
+    {
+        if (_connectors.remove(connector))
+            removeBean(connector);
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Set the connectors for this server.
+     * Each connector has this server set as it's ThreadPool and its Handler.
+     * @param connectors The connectors to set.
+     */
+    public void setConnectors(Connector[] connectors)
+    {
+        if (connectors != null)
+        {
+            for (Connector connector : connectors)
+            {
+                if (connector.getServer() != this)
+                    throw new IllegalArgumentException("Connector " + connector +
+                            " cannot be shared among server " + connector.getServer() + " and server " + this);
+            }
+        }
+
+        Connector[] oldConnectors = getConnectors();
+        updateBeans(oldConnectors, connectors);
+        _connectors.removeAll(Arrays.asList(oldConnectors));
+        if (connectors != null)
+            _connectors.addAll(Arrays.asList(connectors));
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Returns the threadPool.
+     */
+    @ManagedAttribute("the server thread pool")
+    public ThreadPool getThreadPool()
+    {
+        return _threadPool;
+    }
+
+    /**
+     * @return true if {@link #dumpStdErr()} is called after starting
+     */
+    @ManagedAttribute("dump state to stderr after start")
+    public boolean isDumpAfterStart()
+    {
+        return _dumpAfterStart;
+    }
+
+    /**
+     * @param dumpAfterStart true if {@link #dumpStdErr()} is called after starting
+     */
+    public void setDumpAfterStart(boolean dumpAfterStart)
+    {
+        _dumpAfterStart = dumpAfterStart;
+    }
+
+    /**
+     * @return true if {@link #dumpStdErr()} is called before stopping
+     */
+    @ManagedAttribute("dump state to stderr before stop")
+    public boolean isDumpBeforeStop()
+    {
+        return _dumpBeforeStop;
+    }
+
+    /**
+     * @param dumpBeforeStop true if {@link #dumpStdErr()} is called before stopping
+     */
+    public void setDumpBeforeStop(boolean dumpBeforeStop)
+    {
+        _dumpBeforeStop = dumpBeforeStop;
+    }
+
+    /* ------------------------------------------------------------ */
+    public HttpField getDateField()
+    {
+        long now=System.currentTimeMillis();
+        long seconds = now/1000;
+        DateField df = _dateField;
+        
+        if (df==null || df._seconds!=seconds)
+        {
+            synchronized (this) // Trade some contention for less garbage
+            {
+                df = _dateField;
+                if (df==null || df._seconds!=seconds)
+                {
+                    HttpField field=new HttpGenerator.CachedHttpField(HttpHeader.DATE,DateGenerator.formatDate(now));
+                    _dateField=new DateField(seconds,field);
+                    return field;
+                }
+            }
+        }
+        return df._dateField;
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    protected void doStart() throws Exception
+    {
+        if (getStopAtShutdown())
+        {
+            ShutdownThread.register(this);
+        }
+
+        ShutdownMonitor.getInstance().start(); // initialize
+
+        LOG.info("jetty-" + getVersion());
+        HttpGenerator.setJettyVersion(HttpConfiguration.SERVER_VERSION);
+        MultiException mex=new MultiException();
+
+        // check size of thread pool
+        SizedThreadPool pool = getBean(SizedThreadPool.class);
+        int max=pool==null?-1:pool.getMaxThreads();
+        int needed=1;
+        if (mex.size()==0)
+        {
+            for (Connector connector : _connectors)
+            {
+                if (connector instanceof AbstractConnector)
+                    needed+=((AbstractConnector)connector).getAcceptors();
+                if (connector instanceof ServerConnector)
+                    needed+=((ServerConnector)connector).getSelectorManager().getSelectorCount();
+            }
+        }
+
+        if (max>0 && needed>max)
+            throw new IllegalStateException("Insufficient max threads in ThreadPool: max="+max+" < needed="+needed);
+        
+        try
+        {
+            super.doStart();
+        }
+        catch(Throwable e)
+        {
+            mex.add(e);
+        }
+
+        // start connectors last
+        for (Connector connector : _connectors)
+        {
+            try
+            {   
+                connector.start();
+            }
+            catch(Throwable e)
+            {
+                mex.add(e);
+            }
+        }
+        
+        if (isDumpAfterStart())
+            dumpStdErr();
+
+        mex.ifExceptionThrow();
+
+        LOG.info(String.format("Started @%dms",ManagementFactory.getRuntimeMXBean().getUptime()));
+    }
+
+    @Override
+    protected void start(LifeCycle l) throws Exception
+    {
+        // start connectors last
+        if (!(l instanceof Connector))
+            super.start(l);
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    protected void doStop() throws Exception
+    {
+        if (isDumpBeforeStop())
+            dumpStdErr();
+
+        MultiException mex=new MultiException();
+
+        // list if graceful futures
+        List<Future<Void>> futures = new ArrayList<>();
+
+        // First close the network connectors to stop accepting new connections
+        for (Connector connector : _connectors)
+            futures.add(connector.shutdown());
+
+        // Then tell the contexts that we are shutting down
+        
+        Handler[] gracefuls = getChildHandlersByClass(Graceful.class);
+        for (Handler graceful : gracefuls)
+            futures.add(((Graceful)graceful).shutdown());
+
+        // Shall we gracefully wait for zero connections?
+        long stopTimeout = getStopTimeout();
+        if (stopTimeout>0)
+        {
+            long stop_by=System.currentTimeMillis()+stopTimeout;
+            LOG.debug("Graceful shutdown {} by ",this,new Date(stop_by));
+
+            // Wait for shutdowns
+            for (Future<Void> future: futures)
+            {
+                try
+                {
+                    if (!future.isDone())
+                        future.get(Math.max(1L,stop_by-System.currentTimeMillis()),TimeUnit.MILLISECONDS);
+                }
+                catch (Exception e)
+                {
+                    mex.add(e.getCause());
+                }
+            }
+        }
+
+        // Cancel any shutdowns not done
+        for (Future<Void> future: futures)
+            if (!future.isDone())
+                future.cancel(true);
+
+        // Now stop the connectors (this will close existing connections)
+        for (Connector connector : _connectors)
+        {
+            try
+            {
+                connector.stop();
+            }
+            catch (Throwable e)
+            {
+                mex.add(e);
+            }
+        }
+
+        // And finally stop everything else
+        try
+        {
+            super.doStop();
+        }
+        catch (Throwable e)
+        {
+            mex.add(e);
+        }
+
+        if (getStopAtShutdown())
+            ShutdownThread.deregister(this);
+
+        mex.ifExceptionThrow();
+
+    }
+
+    /* ------------------------------------------------------------ */
+    /* Handle a request from a connection.
+     * Called to handle a request on the connection when either the header has been received,
+     * or after the entire request has been received (for short requests of known length), or
+     * on the dispatch of an async request.
+     */
+    public void handle(HttpChannel<?> connection) throws IOException, ServletException
+    {
+        final String target=connection.getRequest().getPathInfo();
+        final Request request=connection.getRequest();
+        final Response response=connection.getResponse();
+
+        if (LOG.isDebugEnabled())
+            LOG.debug(request.getDispatcherType()+" "+target+" on "+connection);
+
+        if ("*".equals(target))
+        {
+            handleOptions(request,response);
+            if (!request.isHandled())
+                handle(target, request, request, response);
+        }
+        else
+            handle(target, request, request, response);
+
+        if (LOG.isDebugEnabled())
+            LOG.debug("RESPONSE "+target+"  "+connection.getResponse().getStatus()+" handled="+request.isHandled());
+    }
+
+    /* ------------------------------------------------------------ */
+    /* Handle Options request to server
+     */
+    protected void handleOptions(Request request,Response response) throws IOException
+    {
+        if (!HttpMethod.OPTIONS.is(request.getMethod()))
+            response.sendError(HttpStatus.BAD_REQUEST_400);
+        request.setHandled(true);
+        response.setStatus(200);
+        response.getHttpFields().put(HttpHeader.ALLOW,"GET,POST,HEAD,OPTIONS");
+        response.setContentLength(0);
+        response.closeOutput();
+    }
+
+    /* ------------------------------------------------------------ */
+    /* Handle a request from a connection.
+     * Called to handle a request on the connection when either the header has been received,
+     * or after the entire request has been received (for short requests of known length), or
+     * on the dispatch of an async request.
+     */
+    public void handleAsync(HttpChannel<?> connection) throws IOException, ServletException
+    {
+        final HttpChannelState state = connection.getRequest().getHttpChannelState();
+        final AsyncContextEvent event = state.getAsyncContextEvent();
+
+        final Request baseRequest=connection.getRequest();
+        final String path=event.getPath();
+
+        if (path!=null)
+        {
+            // this is a dispatch with a path
+            ServletContext context=event.getServletContext();
+            HttpURI uri = new HttpURI(context==null?path:URIUtil.addPaths(context.getContextPath(),path));
+            baseRequest.setUri(uri);
+            baseRequest.setRequestURI(null);
+            baseRequest.setPathInfo(baseRequest.getRequestURI());
+            if (uri.getQuery()!=null)
+                baseRequest.mergeQueryParameters(uri.getQuery(), true); //we have to assume dispatch path and query are UTF8
+        }
+
+        final String target=baseRequest.getPathInfo();
+        final HttpServletRequest request=(HttpServletRequest)event.getSuppliedRequest();
+        final HttpServletResponse response=(HttpServletResponse)event.getSuppliedResponse();
+
+        if (LOG.isDebugEnabled())
+        {
+            LOG.debug(request.getDispatcherType()+" "+target+" on "+connection);
+            handle(target, baseRequest, request, response);
+            LOG.debug("RESPONSE "+target+"  "+connection.getResponse().getStatus());
+        }
+        else
+            handle(target, baseRequest, request, response);
+
+    }
+
+    /* ------------------------------------------------------------ */
+    public void join() throws InterruptedException
+    {
+        getThreadPool().join();
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Returns the sessionIdManager.
+     */
+    public SessionIdManager getSessionIdManager()
+    {
+        return _sessionIdManager;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param sessionIdManager The sessionIdManager to set.
+     */
+    public void setSessionIdManager(SessionIdManager sessionIdManager)
+    {
+        updateBean(_sessionIdManager,sessionIdManager);
+        _sessionIdManager=sessionIdManager;
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see org.eclipse.util.AttributesMap#clearAttributes()
+     */
+    @Override
+    public void clearAttributes()
+    {
+        Enumeration<String> names = _attributes.getAttributeNames();
+        while (names.hasMoreElements())
+            removeBean(_attributes.getAttribute(names.nextElement()));
+        _attributes.clearAttributes();
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see org.eclipse.util.AttributesMap#getAttribute(java.lang.String)
+     */
+    @Override
+    public Object getAttribute(String name)
+    {
+        return _attributes.getAttribute(name);
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see org.eclipse.util.AttributesMap#getAttributeNames()
+     */
+    @Override
+    public Enumeration<String> getAttributeNames()
+    {
+        return AttributesMap.getAttributeNamesCopy(_attributes);
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see org.eclipse.util.AttributesMap#removeAttribute(java.lang.String)
+     */
+    @Override
+    public void removeAttribute(String name)
+    {
+        Object bean=_attributes.getAttribute(name);
+        if (bean!=null)
+            removeBean(bean);
+        _attributes.removeAttribute(name);
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see org.eclipse.util.AttributesMap#setAttribute(java.lang.String, java.lang.Object)
+     */
+    @Override
+    public void setAttribute(String name, Object attribute)
+    {
+        addBean(attribute);
+        _attributes.setAttribute(name, attribute);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return The URI of the first {@link NetworkConnector} and first {@link ContextHandler}, or null
+     */
+    public URI getURI()
+    {
+        NetworkConnector connector=null;
+        for (Connector c: _connectors)
+        {
+            if (c instanceof NetworkConnector)
+            {
+                connector=(NetworkConnector)c;
+                break;
+            }
+        }
+
+        if (connector==null)
+            return null;
+
+        ContextHandler context = getChildHandlerByClass(ContextHandler.class);
+
+        try
+        {
+            String scheme=connector.getDefaultConnectionFactory().getProtocol().startsWith("SSL-")?"https":"http";
+
+            String host=connector.getHost();
+            if (context!=null && context.getVirtualHosts()!=null && context.getVirtualHosts().length>0)
+                host=context.getVirtualHosts()[0];
+            if (host==null)
+                host=InetAddress.getLocalHost().getHostAddress();
+
+            String path=context==null?null:context.getContextPath();
+            if (path==null)
+                path="/";
+            return new URI(scheme,null,host,connector.getLocalPort(),path,null,null);
+        }
+        catch(Exception e)
+        {
+            LOG.warn(e);
+            return null;
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public String toString()
+    {
+        return this.getClass().getName()+"@"+Integer.toHexString(hashCode());
+    }
+
+    @Override
+    public void dump(Appendable out,String indent) throws IOException
+    {
+        dumpBeans(out,indent,Collections.singleton(new ClassLoaderDump(this.getClass().getClassLoader())));
+    }
+
+    /* ------------------------------------------------------------ */
+    public static void main(String...args) throws Exception
+    {
+        System.err.println(getVersion());
+    }
+
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    private static class DateField
+    {
+        final long _seconds;
+        final HttpField _dateField;
+        public DateField(long seconds, HttpField dateField)
+        {
+            super();
+            _seconds = seconds;
+            _dateField = dateField;
+        }
+        
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/server/ServerConnector.java b/lib/jetty/org/eclipse/jetty/server/ServerConnector.java
new file mode 100644 (file)
index 0000000..a7d5fe0
--- /dev/null
@@ -0,0 +1,483 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server;
+
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.net.SocketException;
+import java.nio.channels.Channel;
+import java.nio.channels.SelectionKey;
+import java.nio.channels.Selector;
+import java.nio.channels.ServerSocketChannel;
+import java.nio.channels.SocketChannel;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Future;
+
+import org.eclipse.jetty.io.ByteBufferPool;
+import org.eclipse.jetty.io.Connection;
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.io.SelectChannelEndPoint;
+import org.eclipse.jetty.io.SelectorManager;
+import org.eclipse.jetty.io.SelectorManager.ManagedSelector;
+import org.eclipse.jetty.util.Callback;
+import org.eclipse.jetty.util.annotation.ManagedAttribute;
+import org.eclipse.jetty.util.annotation.ManagedObject;
+import org.eclipse.jetty.util.annotation.Name;
+import org.eclipse.jetty.util.ssl.SslContextFactory;
+import org.eclipse.jetty.util.thread.Scheduler;
+
+/**
+ * This {@link Connector} implementation is the primary connector for the
+ * Jetty server over TCP/IP.  By the use of various {@link ConnectionFactory} instances it is able
+ * to accept connections for HTTP, SPDY and WebSocket, either directly or over SSL.
+ * <p>
+ * The connector is a fully asynchronous NIO based implementation that by default will
+ * use all the commons services (eg {@link Executor}, {@link Scheduler})  of the
+ * passed {@link Server} instance, but all services may also be constructor injected
+ * into the connector so that it may operate with dedicated or otherwise shared services.
+ * <p>
+ * <h2>Connection Factories</h2>
+ * Various convenience constructors are provided to assist with common configurations of
+ * ConnectionFactories, whose generic use is described in {@link AbstractConnector}.
+ * If no connection factories are passed, then the connector will
+ * default to use a {@link HttpConnectionFactory}.  If an non null {@link SslContextFactory}
+ * instance is passed, then this used to instantiate a {@link SslConnectionFactory} which is
+ * prepended to the other passed or default factories.
+ * <p>
+ * <h2>Selectors</h2>
+ * The connector will use the {@link Executor} service to execute a number of Selector Tasks,
+ * which are implemented to each use a NIO {@link Selector} instance to asynchronously
+ * schedule a set of accepted connections.  It is the selector thread that will call the
+ * {@link Callback} instances passed in the {@link EndPoint#fillInterested(Callback)} or
+ * {@link EndPoint#write(Callback, java.nio.ByteBuffer...)} methods.  It is expected
+ * that these callbacks may do some non-blocking IO work, but will always dispatch to the
+ * {@link Executor} service any blocking, long running or application tasks.
+ * <p>
+ * The default number of selectors is equal to the number of processors available to the JVM,
+ * which should allow optimal performance even if all the connections used are performing
+ * significant non-blocking work in the callback tasks.
+ *
+ */
+@ManagedObject("HTTP connector using NIO ByteChannels and Selectors")
+public class ServerConnector extends AbstractNetworkConnector
+{
+    private final SelectorManager _manager;
+    private volatile ServerSocketChannel _acceptChannel;
+    private volatile boolean _inheritChannel = false;
+    private volatile int _localPort = -1;
+    private volatile int _acceptQueueSize = 0;
+    private volatile boolean _reuseAddress = true;
+    private volatile int _lingerTime = -1;
+
+
+    /* ------------------------------------------------------------ */
+    /** HTTP Server Connection.
+     * <p>Construct a ServerConnector with a private instance of {@link HttpConnectionFactory} as the only factory.</p>
+     * @param server The {@link Server} this connector will accept connection for. 
+     */
+    public ServerConnector(
+        @Name("server") Server server)
+    {
+        this(server,null,null,null,-1,-1,new HttpConnectionFactory());
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** HTTP Server Connection.
+     * <p>Construct a ServerConnector with a private instance of {@link HttpConnectionFactory} as the only factory.</p>
+     * @param server The {@link Server} this connector will accept connection for. 
+     * @param acceptors 
+     *          the number of acceptor threads to use, or -1 for a default value. Acceptors accept new TCP/IP connections.  If 0, then 
+     *          the selector threads are used to accept connections.
+     * @param selectors
+     *          the number of selector threads, or -1 for a default value. Selectors notice and schedule established connection that can make IO progress.
+     */
+    public ServerConnector(
+        @Name("server") Server server,
+        @Name("acceptors") int acceptors,
+        @Name("selectors") int selectors)
+    {
+        this(server,null,null,null,acceptors,selectors,new HttpConnectionFactory());
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Generic Server Connection with default configuration.
+     * <p>Construct a Server Connector with the passed Connection factories.</p>
+     * @param server The {@link Server} this connector will accept connection for. 
+     * @param factories Zero or more {@link ConnectionFactory} instances used to create and configure connections.
+     */
+    public ServerConnector(
+        @Name("server") Server server,
+        @Name("factories") ConnectionFactory... factories)
+    {
+        this(server,null,null,null,-1,-1,factories);
+    }
+
+    /* ------------------------------------------------------------ */
+    /** HTTP Server Connection.
+     * <p>Construct a ServerConnector with a private instance of {@link HttpConnectionFactory} as the primary protocol</p>.
+     * @param server The {@link Server} this connector will accept connection for. 
+     * @param sslContextFactory If non null, then a {@link SslConnectionFactory} is instantiated and prepended to the 
+     * list of HTTP Connection Factory.
+     */
+    public ServerConnector(
+        @Name("server") Server server,
+        @Name("sslContextFactory") SslContextFactory sslContextFactory)
+    {
+        this(server,null,null,null,-1,-1,AbstractConnectionFactory.getFactories(sslContextFactory,new HttpConnectionFactory()));
+    }
+
+    /* ------------------------------------------------------------ */
+    /** HTTP Server Connection.
+     * <p>Construct a ServerConnector with a private instance of {@link HttpConnectionFactory} as the primary protocol</p>.
+     * @param server The {@link Server} this connector will accept connection for. 
+     * @param sslContextFactory If non null, then a {@link SslConnectionFactory} is instantiated and prepended to the 
+     * list of HTTP Connection Factory.
+     * @param acceptors 
+     *          the number of acceptor threads to use, or -1 for a default value. Acceptors accept new TCP/IP connections.  If 0, then 
+     *          the selector threads are used to accept connections.
+     * @param selectors
+     *          the number of selector threads, or -1 for a default value. Selectors notice and schedule established connection that can make IO progress.
+     */
+    public ServerConnector(
+        @Name("server") Server server,
+        @Name("acceptors") int acceptors,
+        @Name("selectors") int selectors,
+        @Name("sslContextFactory") SslContextFactory sslContextFactory)
+    {
+        this(server,null,null,null,acceptors,selectors,AbstractConnectionFactory.getFactories(sslContextFactory,new HttpConnectionFactory()));
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Generic SSL Server Connection.
+     * @param server The {@link Server} this connector will accept connection for. 
+     * @param sslContextFactory If non null, then a {@link SslConnectionFactory} is instantiated and prepended to the 
+     * list of ConnectionFactories, with the first factory being the default protocol for the SslConnectionFactory.
+     * @param factories Zero or more {@link ConnectionFactory} instances used to create and configure connections.
+     */
+    public ServerConnector(
+        @Name("server") Server server,
+        @Name("sslContextFactory") SslContextFactory sslContextFactory,
+        @Name("factories") ConnectionFactory... factories)
+    {
+        this(server,null,null,null,-1,-1,AbstractConnectionFactory.getFactories(sslContextFactory,factories));
+    }
+
+    /** Generic Server Connection.
+     * @param server    
+     *          The server this connector will be accept connection for.  
+     * @param executor  
+     *          An executor used to run tasks for handling requests, acceptors and selectors. I
+     *          If null then use the servers executor
+     * @param scheduler 
+     *          A scheduler used to schedule timeouts. If null then use the servers scheduler
+     * @param bufferPool
+     *          A ByteBuffer pool used to allocate buffers.  If null then create a private pool with default configuration.
+     * @param acceptors 
+     *          the number of acceptor threads to use, or -1 for a default value. Acceptors accept new TCP/IP connections.  If 0, then 
+     *          the selector threads are used to accept connections.
+     * @param selectors
+     *          the number of selector threads, or -1 for a default value. Selectors notice and schedule established connection that can make IO progress.
+     * @param factories 
+     *          Zero or more {@link ConnectionFactory} instances used to create and configure connections.
+     */
+    public ServerConnector(
+        @Name("server") Server server,
+        @Name("executor") Executor executor,
+        @Name("scheduler") Scheduler scheduler,
+        @Name("bufferPool") ByteBufferPool bufferPool,
+        @Name("acceptors") int acceptors,
+        @Name("selectors") int selectors,
+        @Name("factories") ConnectionFactory... factories)
+    {
+        super(server,executor,scheduler,bufferPool,acceptors,factories);
+        _manager = new ServerConnectorManager(getExecutor(), getScheduler(), selectors > 0 ? selectors : Runtime.getRuntime().availableProcessors());
+        addBean(_manager, true);
+    }
+
+    @Override
+    protected void doStart() throws Exception
+    {
+        super.doStart();
+
+        if (getAcceptors()==0)
+        {
+            _acceptChannel.configureBlocking(false);
+            _manager.acceptor(_acceptChannel);
+        }
+    }
+
+    @Override
+    public boolean isOpen()
+    {
+        ServerSocketChannel channel = _acceptChannel;
+        return channel!=null && channel.isOpen();
+    }
+
+    /**
+     * @return whether this connector uses a channel inherited from the JVM.
+     * @see System#inheritedChannel()
+     */
+    public boolean isInheritChannel()
+    {
+        return _inheritChannel;
+    }
+
+    /**
+     * <p>Sets whether this connector uses a channel inherited from the JVM.</p>
+     * <p>If true, the connector first tries to inherit from a channel provided by the system.
+     * If there is no inherited channel available, or if the inherited channel is not usable,
+     * then it will fall back using {@link ServerSocketChannel}.</p>
+     * <p>Use it with xinetd/inetd, to launch an instance of Jetty on demand. The port
+     * used to access pages on the Jetty instance is the same as the port used to
+     * launch Jetty.</p>
+     *
+     * @param inheritChannel whether this connector uses a channel inherited from the JVM.
+     */
+    public void setInheritChannel(boolean inheritChannel)
+    {
+        _inheritChannel = inheritChannel;
+    }
+
+    @Override
+    public void open() throws IOException
+    {
+        if (_acceptChannel == null)
+        {
+            ServerSocketChannel serverChannel = null;
+            if (isInheritChannel())
+            {
+                Channel channel = System.inheritedChannel();
+                if (channel instanceof ServerSocketChannel)
+                    serverChannel = (ServerSocketChannel)channel;
+                else
+                    LOG.warn("Unable to use System.inheritedChannel() [{}]. Trying a new ServerSocketChannel at {}:{}", channel, getHost(), getPort());
+            }
+
+            if (serverChannel == null)
+            {
+                serverChannel = ServerSocketChannel.open();
+
+                InetSocketAddress bindAddress = getHost() == null ? new InetSocketAddress(getPort()) : new InetSocketAddress(getHost(), getPort());
+                serverChannel.socket().bind(bindAddress, getAcceptQueueSize());
+                serverChannel.socket().setReuseAddress(getReuseAddress());
+
+                _localPort = serverChannel.socket().getLocalPort();
+                if (_localPort <= 0)
+                    throw new IOException("Server channel not bound");
+
+                addBean(serverChannel);
+            }
+
+            serverChannel.configureBlocking(true);
+            addBean(serverChannel);
+
+            _acceptChannel = serverChannel;
+        }
+    }
+
+    @Override
+    public Future<Void> shutdown()
+    {
+        // TODO shutdown all the connections
+        return super.shutdown();
+    }
+
+    @Override
+    public void close()
+    {
+        ServerSocketChannel serverChannel = _acceptChannel;
+        _acceptChannel = null;
+
+        if (serverChannel != null)
+        {
+            removeBean(serverChannel);
+
+            // If the interrupt did not close it, we should close it
+            if (serverChannel.isOpen())
+            {
+                try
+                {
+                    serverChannel.close();
+                }
+                catch (IOException e)
+                {
+                    LOG.warn(e);
+                }
+            }
+        }
+        // super.close();
+        _localPort = -2;
+    }
+
+    @Override
+    public void accept(int acceptorID) throws IOException
+    {
+        ServerSocketChannel serverChannel = _acceptChannel;
+        if (serverChannel != null && serverChannel.isOpen())
+        {
+            SocketChannel channel = serverChannel.accept();
+            accepted(channel);
+        }
+    }
+    
+    private void accepted(SocketChannel channel) throws IOException
+    {
+        channel.configureBlocking(false);
+        Socket socket = channel.socket();
+        configure(socket);
+        _manager.accept(channel);
+    }
+
+    protected void configure(Socket socket)
+    {
+        try
+        {
+            socket.setTcpNoDelay(true);
+            if (_lingerTime >= 0)
+                socket.setSoLinger(true, _lingerTime / 1000);
+            else
+                socket.setSoLinger(false, 0);
+        }
+        catch (SocketException e)
+        {
+            LOG.ignore(e);
+        }
+    }
+
+    public SelectorManager getSelectorManager()
+    {
+        return _manager;
+    }
+
+    @Override
+    public Object getTransport()
+    {
+        return _acceptChannel;
+    }
+
+    @Override
+    @ManagedAttribute("local port")
+    public int getLocalPort()
+    {
+        return _localPort;
+    }
+
+    protected SelectChannelEndPoint newEndPoint(SocketChannel channel, ManagedSelector selectSet, SelectionKey key) throws IOException
+    {
+        return new SelectChannelEndPoint(channel, selectSet, key, getScheduler(), getIdleTimeout());
+    }
+
+    /**
+     * @return the linger time
+     * @see Socket#getSoLinger()
+     */
+    @ManagedAttribute("TCP/IP solinger time or -1 to disable")
+    public int getSoLingerTime()
+    {
+        return _lingerTime;
+    }
+
+    /**
+     * @param lingerTime the linger time. Use -1 to disable.
+     * @see Socket#setSoLinger(boolean, int)
+     */
+    public void setSoLingerTime(int lingerTime)
+    {
+        _lingerTime = lingerTime;
+    }
+
+    /**
+     * @return the accept queue size
+     */
+    @ManagedAttribute("Accept Queue size")
+    public int getAcceptQueueSize()
+    {
+        return _acceptQueueSize;
+    }
+
+    /**
+     * @param acceptQueueSize the accept queue size (also known as accept backlog)
+     */
+    public void setAcceptQueueSize(int acceptQueueSize)
+    {
+        _acceptQueueSize = acceptQueueSize;
+    }
+
+    /**
+     * @return whether the server socket reuses addresses
+     * @see ServerSocket#getReuseAddress()
+     */
+    public boolean getReuseAddress()
+    {
+        return _reuseAddress;
+    }
+
+    /**
+     * @param reuseAddress whether the server socket reuses addresses
+     * @see ServerSocket#setReuseAddress(boolean)
+     */
+    public void setReuseAddress(boolean reuseAddress)
+    {
+        _reuseAddress = reuseAddress;
+    }
+
+    private final class ServerConnectorManager extends SelectorManager
+    {
+        private ServerConnectorManager(Executor executor, Scheduler scheduler, int selectors)
+        {
+            super(executor, scheduler, selectors);
+        }
+
+        @Override
+        protected void accepted(SocketChannel channel) throws IOException
+        {
+            ServerConnector.this.accepted(channel);
+        }
+
+        @Override
+        protected SelectChannelEndPoint newEndPoint(SocketChannel channel, ManagedSelector selectSet, SelectionKey selectionKey) throws IOException
+        {
+            return ServerConnector.this.newEndPoint(channel, selectSet, selectionKey);
+        }
+
+        @Override
+        public Connection newConnection(SocketChannel channel, EndPoint endpoint, Object attachment) throws IOException
+        {
+            return getDefaultConnectionFactory().newConnection(ServerConnector.this, endpoint);
+        }
+
+        @Override
+        protected void endPointOpened(EndPoint endpoint)
+        {
+            super.endPointOpened(endpoint);
+            onEndPointOpened(endpoint);
+        }
+
+        @Override
+        protected void endPointClosed(EndPoint endpoint)
+        {
+            onEndPointClosed(endpoint);
+            super.endPointClosed(endpoint);
+        }
+        
+        
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/server/ServletRequestHttpWrapper.java b/lib/jetty/org/eclipse/jetty/server/ServletRequestHttpWrapper.java
new file mode 100644 (file)
index 0000000..11f249e
--- /dev/null
@@ -0,0 +1,238 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+
+package org.eclipse.jetty.server;
+
+import java.io.IOException;
+import java.security.Principal;
+import java.util.Collection;
+import java.util.Enumeration;
+
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletRequestWrapper;
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpSession;
+import javax.servlet.http.HttpUpgradeHandler;
+import javax.servlet.http.Part;
+
+
+/* ------------------------------------------------------------ */
+/** 
+ * ServletRequestHttpWrapper
+ * 
+ * Class to tunnel a ServletRequest via a HttpServletRequest
+ */
+public class ServletRequestHttpWrapper extends ServletRequestWrapper implements HttpServletRequest
+{
+    public ServletRequestHttpWrapper(ServletRequest request)
+    {
+        super(request);
+    }
+
+    public String getAuthType()
+    {
+        return null;
+    }
+
+    public Cookie[] getCookies()
+    {
+        return null;
+    }
+
+    public long getDateHeader(String name)
+    {
+        return 0;
+    }
+
+    public String getHeader(String name)
+    {
+        return null;
+    }
+
+    public Enumeration getHeaders(String name)
+    {
+        return null;
+    }
+
+    public Enumeration getHeaderNames()
+    {
+        return null;
+    }
+
+    public int getIntHeader(String name)
+    {
+        return 0;
+    }
+
+    public String getMethod()
+    {
+        return null;
+    }
+
+    public String getPathInfo()
+    {
+        return null;
+    }
+
+    public String getPathTranslated()
+    {
+        return null;
+    }
+
+    public String getContextPath()
+    {
+        return null;
+    }
+
+    public String getQueryString()
+    {
+        return null;
+    }
+
+    public String getRemoteUser()
+    {
+        return null;
+    }
+
+    public boolean isUserInRole(String role)
+    {
+        return false;
+    }
+
+    public Principal getUserPrincipal()
+    {
+        return null;
+    }
+
+    public String getRequestedSessionId()
+    {
+        return null;
+    }
+
+    public String getRequestURI()
+    {
+        return null;
+    }
+
+    public StringBuffer getRequestURL()
+    {
+        return null;
+    }
+
+    public String getServletPath()
+    {
+        return null;
+    }
+
+    public HttpSession getSession(boolean create)
+    {
+        return null;
+    }
+
+    public HttpSession getSession()
+    {
+        return null;
+    }
+
+    public boolean isRequestedSessionIdValid()
+    {
+        return false;
+    }
+
+    public boolean isRequestedSessionIdFromCookie()
+    {
+        return false;
+    }
+
+    public boolean isRequestedSessionIdFromURL()
+    {
+        return false;
+    }
+
+    public boolean isRequestedSessionIdFromUrl()
+    {
+        return false;
+    }
+
+    /**
+     * @see javax.servlet.http.HttpServletRequest#authenticate(javax.servlet.http.HttpServletResponse)
+     */
+    public boolean authenticate(HttpServletResponse response) throws IOException, ServletException
+    {
+        return false;
+    }
+
+    /**
+     * @see javax.servlet.http.HttpServletRequest#getPart(java.lang.String)
+     */
+    public Part getPart(String name) throws IOException, ServletException
+    {
+        return null;
+    }
+
+    /**
+     * @see javax.servlet.http.HttpServletRequest#getParts()
+     */
+    public Collection<Part> getParts() throws IOException, ServletException
+    {
+        return null;
+    }
+
+    /**
+     * @see javax.servlet.http.HttpServletRequest#login(java.lang.String, java.lang.String)
+     */
+    public void login(String username, String password) throws ServletException
+    {
+
+    }
+
+    /**
+     * @see javax.servlet.http.HttpServletRequest#logout()
+     */
+    public void logout() throws ServletException
+    {
+
+    }
+
+
+    /** 
+     * @see javax.servlet.http.HttpServletRequest#changeSessionId()
+     */
+    @Override
+    public String changeSessionId()
+    {
+        // TODO 3.1 Auto-generated method stub
+        return null;
+    }
+
+    /** 
+     * @see javax.servlet.http.HttpServletRequest#upgrade(java.lang.Class)
+     */
+    @Override
+    public <T extends HttpUpgradeHandler> T upgrade(Class<T> handlerClass) throws IOException, ServletException
+    {
+        // TODO 3.1 Auto-generated method stub
+        return null;
+    }
+
+
+}
diff --git a/lib/jetty/org/eclipse/jetty/server/ServletResponseHttpWrapper.java b/lib/jetty/org/eclipse/jetty/server/ServletResponseHttpWrapper.java
new file mode 100644 (file)
index 0000000..076ab1b
--- /dev/null
@@ -0,0 +1,148 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server;
+
+import java.io.IOException;
+import java.util.Collection;
+
+import javax.servlet.ServletResponse;
+import javax.servlet.ServletResponseWrapper;
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletResponse;
+
+
+/* ------------------------------------------------------------ */
+/** 
+ * ServletResponseHttpWrapper
+ * 
+ * Wrapper to tunnel a ServletResponse via a HttpServletResponse
+ */
+public class ServletResponseHttpWrapper extends ServletResponseWrapper implements HttpServletResponse
+{
+    public ServletResponseHttpWrapper(ServletResponse response)
+    {
+        super(response);
+    }
+
+    public void addCookie(Cookie cookie)
+    {
+    }
+
+    public boolean containsHeader(String name)
+    {
+        return false;
+    }
+
+    public String encodeURL(String url)
+    {
+        return null;
+    }
+
+    public String encodeRedirectURL(String url)
+    {
+        return null;
+    }
+
+    public String encodeUrl(String url)
+    {
+        return null;
+    }
+
+    public String encodeRedirectUrl(String url)
+    {
+        return null;
+    }
+
+    public void sendError(int sc, String msg) throws IOException
+    {
+    }
+
+    public void sendError(int sc) throws IOException
+    {
+    }
+
+    public void sendRedirect(String location) throws IOException
+    {
+    }
+
+    public void setDateHeader(String name, long date)
+    {
+    }
+
+    public void addDateHeader(String name, long date)
+    {
+    }
+
+    public void setHeader(String name, String value)
+    {
+    }
+
+    public void addHeader(String name, String value)
+    {
+    }
+
+    public void setIntHeader(String name, int value)
+    {
+    }
+
+    public void addIntHeader(String name, int value)
+    {
+    }
+
+    public void setStatus(int sc)
+    {
+    }
+
+    public void setStatus(int sc, String sm)
+    {
+    }
+
+    /**
+     * @see javax.servlet.http.HttpServletResponse#getHeader(java.lang.String)
+     */
+    public String getHeader(String name)
+    {
+        return null;
+    }
+
+    /**
+     * @see javax.servlet.http.HttpServletResponse#getHeaderNames()
+     */
+    public Collection<String> getHeaderNames()
+    {
+        return null;
+    }
+
+    /**
+     * @see javax.servlet.http.HttpServletResponse#getHeaders(java.lang.String)
+     */
+    public Collection<String> getHeaders(String name)
+    {
+        return null;
+    }
+
+    /**
+     * @see javax.servlet.http.HttpServletResponse#getStatus()
+     */
+    public int getStatus()
+    {
+        return 0;
+    }
+
+}
diff --git a/lib/jetty/org/eclipse/jetty/server/SessionIdManager.java b/lib/jetty/org/eclipse/jetty/server/SessionIdManager.java
new file mode 100644 (file)
index 0000000..ef7ee62
--- /dev/null
@@ -0,0 +1,94 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpSession;
+
+import org.eclipse.jetty.util.component.LifeCycle;
+
+/** Session ID Manager.
+ * Manages session IDs across multiple contexts.
+ */
+public interface SessionIdManager extends LifeCycle
+{
+    /**
+     * @param id The session ID without any cluster node extension
+     * @return True if the session ID is in use by at least one context.
+     */
+    public boolean idInUse(String id);
+    
+    /**
+     * Add a session to the list of known sessions for a given ID.
+     * @param session The session
+     */
+    public void addSession(HttpSession session);
+    
+    /**
+     * Remove session from the list of known sessions for a given ID.
+     * @param session
+     */
+    public void removeSession(HttpSession session);
+    
+    /**
+     * Call {@link HttpSession#invalidate()} on all known sessions for the given id.
+     * @param id The session ID without any cluster node extension
+     */
+    public void invalidateAll(String id);
+    
+    /**
+     * @param request
+     * @param created
+     * @return the new session id
+     */
+    public String newSessionId(HttpServletRequest request,long created);
+    
+    
+    
+    public String getWorkerName();
+    
+    
+    /* ------------------------------------------------------------ */
+    /** Get a cluster ID from a node ID.
+     * Strip node identifier from a located session ID.
+     * @param nodeId
+     * @return the cluster id
+     */
+    public String getClusterId(String nodeId);
+    
+    /* ------------------------------------------------------------ */
+    /** Get a node ID from a cluster ID and a request
+     * @param clusterId The ID of the session
+     * @param request The request that for the session (or null)
+     * @return The session ID qualified with the node ID.
+     */
+    public String getNodeId(String clusterId,HttpServletRequest request);
+    
+    
+    /* ------------------------------------------------------------ */
+    /** Change the existing session id.
+    * 
+    * @param oldClusterId
+    * @param oldNodeId
+    * @param request
+    */
+    public void renewSessionId(String oldClusterId, String oldNodeId, HttpServletRequest request);    
+
+    
+}
diff --git a/lib/jetty/org/eclipse/jetty/server/SessionManager.java b/lib/jetty/org/eclipse/jetty/server/SessionManager.java
new file mode 100644 (file)
index 0000000..267391b
--- /dev/null
@@ -0,0 +1,317 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server;
+
+import java.util.EventListener;
+import java.util.Set;
+
+import javax.servlet.SessionCookieConfig;
+import javax.servlet.SessionTrackingMode;
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpSession;
+
+import org.eclipse.jetty.http.HttpCookie;
+import org.eclipse.jetty.server.session.SessionHandler;
+import org.eclipse.jetty.util.component.LifeCycle;
+
+/* --------------------------------------------------------------------- */
+/**
+ * Session Manager.
+ * The API required to manage sessions for a servlet context.
+ *
+ */
+
+/* ------------------------------------------------------------ */
+/**
+ */
+public interface SessionManager extends LifeCycle
+{
+    /* ------------------------------------------------------------ */
+    /**
+     * Session cookie name.
+     * Defaults to <code>JSESSIONID</code>, but can be set with the
+     * <code>org.eclipse.jetty.servlet.SessionCookie</code> context init parameter.
+     */
+    public final static String __SessionCookieProperty = "org.eclipse.jetty.servlet.SessionCookie";
+    public final static String __DefaultSessionCookie = "JSESSIONID";
+
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Session id path parameter name.
+     * Defaults to <code>jsessionid</code>, but can be set with the
+     * <code>org.eclipse.jetty.servlet.SessionIdPathParameterName</code> context init parameter.
+     * If set to null or "none" no URL rewriting will be done.
+     */
+    public final static String __SessionIdPathParameterNameProperty = "org.eclipse.jetty.servlet.SessionIdPathParameterName";
+    public final static String __DefaultSessionIdPathParameterName = "jsessionid";
+    public final static String __CheckRemoteSessionEncoding = "org.eclipse.jetty.servlet.CheckingRemoteSessionIdEncoding";
+
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Session Domain.
+     * If this property is set as a ServletContext InitParam, then it is
+     * used as the domain for session cookies. If it is not set, then
+     * no domain is specified for the session cookie.
+     */
+    public final static String __SessionDomainProperty = "org.eclipse.jetty.servlet.SessionDomain";
+    public final static String __DefaultSessionDomain = null;
+
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Session Path.
+     * If this property is set as a ServletContext InitParam, then it is
+     * used as the path for the session cookie.  If it is not set, then
+     * the context path is used as the path for the cookie.
+     */
+    public final static String __SessionPathProperty = "org.eclipse.jetty.servlet.SessionPath";
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Session Max Age.
+     * If this property is set as a ServletContext InitParam, then it is
+     * used as the max age for the session cookie.  If it is not set, then
+     * a max age of -1 is used.
+     */
+    public final static String __MaxAgeProperty = "org.eclipse.jetty.servlet.MaxAge";
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Returns the <code>HttpSession</code> with the given session id
+     *
+     * @param id the session id
+     * @return the <code>HttpSession</code> with the corresponding id or null if no session with the given id exists
+     */
+    public HttpSession getHttpSession(String id);
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Creates a new <code>HttpSession</code>.
+     *
+     * @param request the HttpServletRequest containing the requested session id
+     * @return the new <code>HttpSession</code>
+     */
+    public HttpSession newHttpSession(HttpServletRequest request);
+
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return true if session cookies should be HTTP-only (Microsoft extension)
+     * @see org.eclipse.jetty.http.HttpCookie#isHttpOnly()
+     */
+    public boolean getHttpOnly();
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return the max period of inactivity, after which the session is invalidated, in seconds.
+     * @see #setMaxInactiveInterval(int)
+     */
+    public int getMaxInactiveInterval();
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Sets the max period of inactivity, after which the session is invalidated, in seconds.
+     *
+     * @param seconds the max inactivity period, in seconds.
+     * @see #getMaxInactiveInterval()
+     */
+    public void setMaxInactiveInterval(int seconds);
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Sets the {@link SessionHandler}.
+     *
+     * @param handler the <code>SessionHandler</code> object
+     */
+    public void setSessionHandler(SessionHandler handler);
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Adds an event listener for session-related events.
+     *
+     * @param listener the session event listener to add
+     *                 Individual SessionManagers implementations may accept arbitrary listener types,
+     *                 but they are expected to at least handle HttpSessionActivationListener,
+     *                 HttpSessionAttributeListener, HttpSessionBindingListener and HttpSessionListener.
+     * @see #removeEventListener(EventListener)
+     */
+    public void addEventListener(EventListener listener);
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Removes an event listener for for session-related events.
+     *
+     * @param listener the session event listener to remove
+     * @see #addEventListener(EventListener)
+     */
+    public void removeEventListener(EventListener listener);
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Removes all event listeners for session-related events.
+     *
+     * @see #removeEventListener(EventListener)
+     */
+    public void clearEventListeners();
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Gets a Cookie for a session.
+     *
+     * @param session         the session to which the cookie should refer.
+     * @param contextPath     the context to which the cookie should be linked.
+     *                        The client will only send the cookie value when requesting resources under this path.
+     * @param requestIsSecure whether the client is accessing the server over a secure protocol (i.e. HTTPS).
+     * @return if this <code>SessionManager</code> uses cookies, then this method will return a new
+     *         {@link Cookie cookie object} that should be set on the client in order to link future HTTP requests
+     *         with the <code>session</code>. If cookies are not in use, this method returns <code>null</code>.
+     */
+    public HttpCookie getSessionCookie(HttpSession session, String contextPath, boolean requestIsSecure);
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return the cross context session id manager.
+     * @see #setSessionIdManager(SessionIdManager)
+     */
+    public SessionIdManager getSessionIdManager();
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return the cross context session id manager.
+     * @deprecated use {@link #getSessionIdManager()}
+     */
+    @Deprecated
+    public SessionIdManager getMetaManager();
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Sets the cross context session id manager
+     *
+     * @param idManager the cross context session id manager.
+     * @see #getSessionIdManager()
+     */
+    public void setSessionIdManager(SessionIdManager idManager);
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param session the session to test for validity
+     * @return whether the given session is valid, that is, it has not been invalidated.
+     */
+    public boolean isValid(HttpSession session);
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param session the session object
+     * @return the unique id of the session within the cluster, extended with an optional node id.
+     * @see #getClusterId(HttpSession)
+     */
+    public String getNodeId(HttpSession session);
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param session the session object
+     * @return the unique id of the session within the cluster (without a node id extension)
+     * @see #getNodeId(HttpSession)
+     */
+    public String getClusterId(HttpSession session);
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Called by the {@link SessionHandler} when a session is first accessed by a request.
+     *
+     * @param session the session object
+     * @param secure  whether the request is secure or not
+     * @return the session cookie. If not null, this cookie should be set on the response to either migrate
+     *         the session or to refresh a session cookie that may expire.
+     * @see #complete(HttpSession)
+     */
+    public HttpCookie access(HttpSession session, boolean secure);
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Called by the {@link SessionHandler} when a session is last accessed by a request.
+     *
+     * @param session the session object
+     * @see #access(HttpSession, boolean)
+     */
+    public void complete(HttpSession session);
+
+    /**
+     * Sets the session id URL path parameter name.
+     *
+     * @param parameterName the URL path parameter name for session id URL rewriting (null or "none" for no rewriting).
+     * @see #getSessionIdPathParameterName()
+     * @see #getSessionIdPathParameterNamePrefix()
+     */
+    public void setSessionIdPathParameterName(String parameterName);
+
+    /**
+     * @return the URL path parameter name for session id URL rewriting, by default "jsessionid".
+     * @see #setSessionIdPathParameterName(String)
+     */
+    public String getSessionIdPathParameterName();
+
+    /**
+     * @return a formatted version of {@link #getSessionIdPathParameterName()}, by default
+     *         ";" + sessionIdParameterName + "=", for easier lookup in URL strings.
+     * @see #getSessionIdPathParameterName()
+     */
+    public String getSessionIdPathParameterNamePrefix();
+
+    /**
+     * @return whether the session management is handled via cookies.
+     */
+    public boolean isUsingCookies();
+
+    /**
+     * @return whether the session management is handled via URLs.
+     */
+    public boolean isUsingURLs();
+
+    public Set<SessionTrackingMode> getDefaultSessionTrackingModes();
+
+    public Set<SessionTrackingMode> getEffectiveSessionTrackingModes();
+
+    public void setSessionTrackingModes(Set<SessionTrackingMode> sessionTrackingModes);
+
+    public SessionCookieConfig getSessionCookieConfig();
+
+    /**
+     * @return True if absolute URLs are check for remoteness before being session encoded.
+     */
+    public boolean isCheckingRemoteSessionIdEncoding();
+
+    /**
+     * @param remote True if absolute URLs are check for remoteness before being session encoded.
+     */
+    public void setCheckingRemoteSessionIdEncoding(boolean remote);
+    
+    /* ------------------------------------------------------------ */
+    /** Change the existing session id.
+    * 
+    * @param oldClusterId
+    * @param oldNodeId
+    * @param newClusterId
+    * @param newNodeId
+    */
+    public void renewSessionId(String oldClusterId, String oldNodeId, String newClusterId, String newNodeId);  
+}
diff --git a/lib/jetty/org/eclipse/jetty/server/ShutdownMonitor.java b/lib/jetty/org/eclipse/jetty/server/ShutdownMonitor.java
new file mode 100644 (file)
index 0000000..abc6d61
--- /dev/null
@@ -0,0 +1,411 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server;
+
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.LineNumberReader;
+import java.io.OutputStream;
+import java.net.InetAddress;
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.nio.charset.StandardCharsets;
+import java.util.Properties;
+
+import org.eclipse.jetty.util.thread.ShutdownThread;
+
+/**
+ * Shutdown/Stop Monitor thread.
+ * <p>
+ * This thread listens on the port specified by the STOP.PORT system parameter (defaults to -1 for not listening) for request authenticated with the key given
+ * by the STOP.KEY system parameter (defaults to "eclipse") for admin requests.
+ * <p>
+ * If the stop port is set to zero, then a random port is assigned and the port number is printed to stdout.
+ * <p>
+ * Commands "stop" and "status" are currently supported.
+ */
+public class ShutdownMonitor 
+{
+    // Implementation of safe lazy init, using Initialization on Demand Holder technique.
+    static class Holder
+    {
+        static ShutdownMonitor instance = new ShutdownMonitor();
+    }
+
+    public static ShutdownMonitor getInstance()
+    {
+        return Holder.instance;
+    }
+
+    /**
+     * ShutdownMonitorThread
+     *
+     * Thread for listening to STOP.PORT for command to stop Jetty.
+     * If ShowndownMonitor.exitVm is true, then Sytem.exit will also be
+     * called after the stop.
+     *
+     */
+    public class ShutdownMonitorThread extends Thread
+    {
+
+        public ShutdownMonitorThread ()
+        {
+            setDaemon(true);
+            setName("ShutdownMonitor");
+        }
+        
+        @Override
+        public void run()
+        {
+            if (serverSocket == null)
+            {
+                return;
+            }
+
+            while (serverSocket != null)
+            {
+                Socket socket = null;
+                try
+                {
+                    socket = serverSocket.accept();
+
+                    LineNumberReader lin = new LineNumberReader(new InputStreamReader(socket.getInputStream()));
+                    String receivedKey = lin.readLine();
+                    if (!key.equals(receivedKey))
+                    {
+                        System.err.println("Ignoring command with incorrect key");
+                        continue;
+                    }
+
+                    OutputStream out = socket.getOutputStream();
+
+                    String cmd = lin.readLine();
+                    debug("command=%s",cmd);
+                    if ("stop".equals(cmd))
+                    {
+                        // Graceful Shutdown
+                        debug("Issuing graceful shutdown..");
+                        ShutdownThread.getInstance().run();
+                        
+                        //Stop accepting any more
+                        close(serverSocket);
+                        serverSocket = null;
+                        
+                        //Shutdown input from client
+                        shutdownInput(socket);
+
+                        // Reply to client
+                        debug("Informing client that we are stopped.");
+                        out.write("Stopped\r\n".getBytes(StandardCharsets.UTF_8));
+                        out.flush();
+
+                        // Shutdown Monitor
+                        socket.shutdownOutput();
+                        close(socket);
+                        socket = null;                        
+                        debug("Shutting down monitor");
+
+                        if (exitVm)
+                        {
+                            // Kill JVM
+                            debug("Killing JVM");
+                            System.exit(0);
+                        }
+                    }
+                    else if ("status".equals(cmd))
+                    {
+                        // Reply to client
+                        out.write("OK\r\n".getBytes(StandardCharsets.UTF_8));
+                        out.flush();
+                    }
+                }
+                catch (Exception e)
+                {
+                    debug(e);
+                    System.err.println(e.toString());
+                }
+                finally
+                {
+                    close(socket);
+                    socket = null;
+                }
+            }
+        }
+        
+        public void start()
+        {
+            if (isAlive())
+            {
+                // TODO why are we reentrant here?
+                if (DEBUG)
+                    System.err.printf("ShutdownMonitorThread already started");
+                return; // cannot start it again
+            }
+
+            startListenSocket();
+            
+            if (serverSocket == null)
+            {
+                return;
+            }
+            if (DEBUG)
+                System.err.println("Starting ShutdownMonitorThread");
+            super.start();
+        }
+        
+        private void startListenSocket()
+        {
+            if (port < 0)
+            {            
+                if (DEBUG)
+                    System.err.println("ShutdownMonitor not in use (port < 0): " + port);
+                return;
+            }
+
+            try
+            {
+                serverSocket = new ServerSocket(port,1,InetAddress.getByName("127.0.0.1"));
+                if (port == 0)
+                {
+                    // server assigned port in use
+                    port = serverSocket.getLocalPort();
+                    System.out.printf("STOP.PORT=%d%n",port);
+                }
+
+                if (key == null)
+                {
+                    // create random key
+                    key = Long.toString((long)(Long.MAX_VALUE * Math.random() + this.hashCode() + System.currentTimeMillis()),36);
+                    System.out.printf("STOP.KEY=%s%n",key);
+                }
+            }
+            catch (Exception e)
+            {
+                debug(e);
+                System.err.println("Error binding monitor port " + port + ": " + e.toString());
+                serverSocket = null;
+            }
+            finally
+            {
+                // establish the port and key that are in use
+                debug("STOP.PORT=%d",port);
+                debug("STOP.KEY=%s",key);
+                debug("%s",serverSocket);
+            }
+        }
+
+    }
+    
+    private boolean DEBUG;
+    private int port;
+    private String key;
+    private boolean exitVm;
+    private ServerSocket serverSocket;
+    private ShutdownMonitorThread thread;
+    
+    
+
+    /**
+     * Create a ShutdownMonitor using configuration from the System properties.
+     * <p>
+     * <code>STOP.PORT</code> = the port to listen on (empty, null, or values less than 0 disable the stop ability)<br>
+     * <code>STOP.KEY</code> = the magic key/passphrase to allow the stop (defaults to "eclipse")<br>
+     * <p>
+     * Note: server socket will only listen on localhost, and a successful stop will issue a System.exit() call.
+     */
+    private ShutdownMonitor()
+    {
+        Properties props = System.getProperties();
+
+        this.DEBUG = props.containsKey("DEBUG");
+
+        // Use values passed thru via /jetty-start/
+        this.port = Integer.parseInt(props.getProperty("STOP.PORT","-1"));
+        this.key = props.getProperty("STOP.KEY",null);
+        this.exitVm = true;
+    }
+
+    private void close(ServerSocket server)
+    {
+        if (server == null)
+        {
+            return;
+        }
+
+        try
+        {
+            server.close();
+        }
+        catch (IOException ignore)
+        {
+            debug(ignore);
+        }
+    }
+
+    private void close(Socket socket)
+    {
+        if (socket == null)
+        {
+            return;
+        }
+
+        try
+        {
+            socket.close();
+        }
+        catch (IOException ignore)
+        {
+            debug(ignore);
+        }
+    }
+
+    
+    private void shutdownInput(Socket socket)
+    {
+        if (socket == null)
+            return;
+        
+        try
+        {
+            socket.shutdownInput();
+        }   
+        catch (IOException ignore)
+        {
+            debug(ignore);
+        }
+    }
+    
+    
+    private void debug(String format, Object... args)
+    {
+        if (DEBUG)
+        {
+            System.err.printf("[ShutdownMonitor] " + format + "%n",args);
+        }
+    }
+
+    private void debug(Throwable t)
+    {
+        if (DEBUG)
+        {
+            t.printStackTrace(System.err);
+        }
+    }
+
+    public String getKey()
+    {
+        return key;
+    }
+
+    public int getPort()
+    {
+        return port;
+    }
+
+    public ServerSocket getServerSocket()
+    {
+        return serverSocket;
+    }
+
+    public boolean isExitVm()
+    {
+        return exitVm;
+    }
+
+
+    public void setDebug(boolean flag)
+    {
+        this.DEBUG = flag;
+    }
+
+    public void setExitVm(boolean exitVm)
+    {
+        synchronized (this)
+        {
+            if (thread != null && thread.isAlive())
+            {
+                throw new IllegalStateException("ShutdownMonitorThread already started");
+            }
+            this.exitVm = exitVm;
+        }
+    }
+
+    public void setKey(String key)
+    {
+        synchronized (this)
+        {
+            if (thread != null && thread.isAlive())
+            {
+                throw new IllegalStateException("ShutdownMonitorThread already started");
+            }
+            this.key = key;
+        }
+    }
+
+    public void setPort(int port)
+    {
+        synchronized (this)
+        {
+            if (thread != null && thread.isAlive())
+            {
+                throw new IllegalStateException("ShutdownMonitorThread already started");
+            }
+            this.port = port;
+        }
+    }
+
+    protected void start() throws Exception
+    {
+        ShutdownMonitorThread t = null;
+        synchronized (this)
+        {
+            if (thread != null && thread.isAlive())
+            {
+                // TODO why are we reentrant here?
+                if (DEBUG)
+                    System.err.printf("ShutdownMonitorThread already started");
+                return; // cannot start it again
+            }
+         
+            thread = new ShutdownMonitorThread();
+            t = thread;
+        }
+         
+        if (t != null)
+            t.start();
+    }
+
+
+    protected boolean isAlive ()
+    {
+        boolean result = false;
+        synchronized (this)
+        {
+            result = (thread != null && thread.isAlive());
+        }
+        return result;
+    }
+    
+    @Override
+    public String toString()
+    {
+        return String.format("%s[port=%d]",this.getClass().getName(),port);
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/server/SslConnectionFactory.java b/lib/jetty/org/eclipse/jetty/server/SslConnectionFactory.java
new file mode 100644 (file)
index 0000000..9552f0d
--- /dev/null
@@ -0,0 +1,111 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+
+package org.eclipse.jetty.server;
+
+
+import javax.net.ssl.SSLEngine;
+import javax.net.ssl.SSLSession;
+
+import org.eclipse.jetty.http.HttpVersion;
+import org.eclipse.jetty.io.Connection;
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.io.ssl.SslConnection;
+import org.eclipse.jetty.io.ssl.SslReconfigurator;
+import org.eclipse.jetty.util.annotation.Name;
+import org.eclipse.jetty.util.ssl.SslContextFactory;
+
+public class SslConnectionFactory extends AbstractConnectionFactory implements SslReconfigurator
+{
+    private final SslContextFactory _sslContextFactory;
+    private final String _nextProtocol;
+
+    public SslConnectionFactory()
+    {
+        this(HttpVersion.HTTP_1_1.asString());
+    }
+
+    public SslConnectionFactory(@Name("next") String nextProtocol)
+    {
+        this(null,nextProtocol);
+    }
+
+    public SslConnectionFactory(@Name("sslContextFactory") SslContextFactory factory, @Name("next") String nextProtocol)
+    {
+        super("SSL-"+nextProtocol);
+        _sslContextFactory=factory==null?new SslContextFactory():factory;
+        _nextProtocol=nextProtocol;
+        addBean(_sslContextFactory);
+    }
+
+    public SslContextFactory getSslContextFactory()
+    {
+        return _sslContextFactory;
+    }
+
+    @Override
+    protected void doStart() throws Exception
+    {
+        super.doStart();
+
+        SSLEngine engine = _sslContextFactory.newSSLEngine();
+        engine.setUseClientMode(false);
+        SSLSession session=engine.getSession();
+
+        if (session.getPacketBufferSize()>getInputBufferSize())
+            setInputBufferSize(session.getPacketBufferSize());
+    }
+
+    @Override
+    public Connection newConnection(Connector connector, EndPoint endPoint)
+    {
+        SSLEngine engine = _sslContextFactory.newSSLEngine(endPoint.getRemoteAddress());
+        engine.setUseClientMode(false);
+
+        SslConnection sslConnection = newSslConnection(connector, endPoint, engine);
+        sslConnection.setRenegotiationAllowed(_sslContextFactory.isRenegotiationAllowed());
+        configure(sslConnection, connector, endPoint);
+
+        ConnectionFactory next = connector.getConnectionFactory(_nextProtocol);
+        EndPoint decryptedEndPoint = sslConnection.getDecryptedEndPoint();
+        Connection connection = next.newConnection(connector, decryptedEndPoint);
+        decryptedEndPoint.setConnection(connection);
+
+        return sslConnection;
+    }
+
+    protected SslConnection newSslConnection(Connector connector, EndPoint endPoint, SSLEngine engine)
+    {
+        return new SslConnection(connector.getByteBufferPool(), connector.getExecutor(), endPoint, engine, shouldRestartSSL()?this:null);
+    }
+    
+    public boolean shouldRestartSSL(){
+       return false;
+    }
+    
+    public SSLEngine restartSSL(SSLSession sslSession){
+       throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public String toString()
+    {
+        return String.format("%s@%x{%s}",this.getClass().getSimpleName(),hashCode(),getProtocol());
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/server/UserIdentity.java b/lib/jetty/org/eclipse/jetty/server/UserIdentity.java
new file mode 100644 (file)
index 0000000..4e20331
--- /dev/null
@@ -0,0 +1,117 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server;
+
+import java.security.Principal;
+import java.util.Map;
+
+import javax.security.auth.Subject;
+
+/* ------------------------------------------------------------ */
+/** User object that encapsulates user identity and operations such as run-as-role actions,
+ * checking isUserInRole and getUserPrincipal.
+ *
+ * Implementations of UserIdentity should be immutable so that they may be
+ * cached by Authenticators and LoginServices.
+ *
+ */
+public interface UserIdentity
+{
+    /* ------------------------------------------------------------ */
+    /**
+     * @return The user subject
+     */
+    Subject getSubject();
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return The user principal
+     */
+    Principal getUserPrincipal();
+
+    /* ------------------------------------------------------------ */
+    /** Check if the user is in a role.
+     * This call is used to satisfy authorization calls from
+     * container code which will be using translated role names.
+     * @param role A role name.
+     * @param scope
+     * @return True if the user can act in that role.
+     */
+    boolean isUserInRole(String role, Scope scope);
+
+
+    /* ------------------------------------------------------------ */
+    /**
+     * A UserIdentity Scope.
+     * A scope is the environment in which a User Identity is to
+     * be interpreted. Typically it is set by the target servlet of
+     * a request.
+     */
+    interface Scope
+    {
+        /* ------------------------------------------------------------ */
+        /**
+         * @return The context path that the identity is being considered within
+         */
+        String getContextPath();
+
+        /* ------------------------------------------------------------ */
+        /**
+         * @return The name of the identity context. Typically this is the servlet name.
+         */
+        String getName();
+
+        /* ------------------------------------------------------------ */
+        /**
+         * @return A map of role reference names that converts from names used by application code
+         * to names used by the context deployment.
+         */
+        Map<String,String> getRoleRefMap();
+    }
+
+    /* ------------------------------------------------------------ */
+    public interface UnauthenticatedUserIdentity extends UserIdentity
+    {
+    }
+
+    /* ------------------------------------------------------------ */
+    public static final UserIdentity UNAUTHENTICATED_IDENTITY = new UnauthenticatedUserIdentity()
+    {
+        public Subject getSubject()
+        {
+            return null;
+        }
+
+        public Principal getUserPrincipal()
+        {
+            return null;
+        }
+
+        public boolean isUserInRole(String role, Scope scope)
+        {
+            return false;
+        }
+
+        @Override
+        public String toString()
+        {
+            return "UNAUTHENTICATED";
+        }
+    };
+}
diff --git a/lib/jetty/org/eclipse/jetty/server/Utf8HttpWriter.java b/lib/jetty/org/eclipse/jetty/server/Utf8HttpWriter.java
new file mode 100644 (file)
index 0000000..cbd1adb
--- /dev/null
@@ -0,0 +1,188 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server;
+
+import java.io.IOException;
+
+/** OutputWriter.
+ * A writer that can wrap a {@link HttpOutput} stream and provide
+ * character encodings.
+ *
+ * The UTF-8 encoding is done by this class and no additional
+ * buffers or Writers are used.
+ * The UTF-8 code was inspired by http://javolution.org
+ */
+public class Utf8HttpWriter extends HttpWriter
+{
+    int _surrogate=0;
+
+    /* ------------------------------------------------------------ */
+    public Utf8HttpWriter(HttpOutput out)
+    {
+        super(out);
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public void write (char[] s,int offset, int length) throws IOException
+    {
+        HttpOutput out = _out;
+        if (length==0 && out.isAllContentWritten())
+        {
+            close();
+            return;
+        }
+        
+        while (length > 0)
+        {
+            _bytes.reset();
+            int chars = length>MAX_OUTPUT_CHARS?MAX_OUTPUT_CHARS:length;
+
+            byte[] buffer=_bytes.getBuf();
+            int bytes=_bytes.getCount();
+
+            if (bytes+chars>buffer.length)
+                chars=buffer.length-bytes;
+
+            for (int i = 0; i < chars; i++)
+            {
+                int code = s[offset+i];
+
+                // Do we already have a surrogate?
+                if(_surrogate==0)
+                {
+                    // No - is this char code a surrogate?
+                    if(Character.isHighSurrogate((char)code))
+                    {
+                        _surrogate=code; // UCS-?
+                        continue;
+                    }
+                }
+                // else handle a low surrogate
+                else if(Character.isLowSurrogate((char)code))
+                {
+                    code = Character.toCodePoint((char)_surrogate, (char)code); // UCS-4
+                }
+                // else UCS-2
+                else
+                {
+                    code=_surrogate; // UCS-2
+                    _surrogate=0; // USED
+                    i--;
+                }
+
+                if ((code & 0xffffff80) == 0)
+                {
+                    // 1b
+                    if (bytes>=buffer.length)
+                    {
+                        chars=i;
+                        break;
+                    }
+                    buffer[bytes++]=(byte)(code);
+                }
+                else
+                {
+                    if((code&0xfffff800)==0)
+                    {
+                        // 2b
+                        if (bytes+2>buffer.length)
+                        {
+                            chars=i;
+                            break;
+                        }
+                        buffer[bytes++]=(byte)(0xc0|(code>>6));
+                        buffer[bytes++]=(byte)(0x80|(code&0x3f));
+                    }
+                    else if((code&0xffff0000)==0)
+                    {
+                        // 3b
+                        if (bytes+3>buffer.length)
+                        {
+                            chars=i;
+                            break;
+                        }
+                        buffer[bytes++]=(byte)(0xe0|(code>>12));
+                        buffer[bytes++]=(byte)(0x80|((code>>6)&0x3f));
+                        buffer[bytes++]=(byte)(0x80|(code&0x3f));
+                    }
+                    else if((code&0xff200000)==0)
+                    {
+                        // 4b
+                        if (bytes+4>buffer.length)
+                        {
+                            chars=i;
+                            break;
+                        }
+                        buffer[bytes++]=(byte)(0xf0|(code>>18));
+                        buffer[bytes++]=(byte)(0x80|((code>>12)&0x3f));
+                        buffer[bytes++]=(byte)(0x80|((code>>6)&0x3f));
+                        buffer[bytes++]=(byte)(0x80|(code&0x3f));
+                    }
+                    else if((code&0xf4000000)==0)
+                    {
+                        // 5b
+                        if (bytes+5>buffer.length)
+                        {
+                            chars=i;
+                            break;
+                        }
+                        buffer[bytes++]=(byte)(0xf8|(code>>24));
+                        buffer[bytes++]=(byte)(0x80|((code>>18)&0x3f));
+                        buffer[bytes++]=(byte)(0x80|((code>>12)&0x3f));
+                        buffer[bytes++]=(byte)(0x80|((code>>6)&0x3f));
+                        buffer[bytes++]=(byte)(0x80|(code&0x3f));
+                    }
+                    else if((code&0x80000000)==0)
+                    {
+                        // 6b
+                        if (bytes+6>buffer.length)
+                        {
+                            chars=i;
+                            break;
+                        }
+                        buffer[bytes++]=(byte)(0xfc|(code>>30));
+                        buffer[bytes++]=(byte)(0x80|((code>>24)&0x3f));
+                        buffer[bytes++]=(byte)(0x80|((code>>18)&0x3f));
+                        buffer[bytes++]=(byte)(0x80|((code>>12)&0x3f));
+                        buffer[bytes++]=(byte)(0x80|((code>>6)&0x3f));
+                        buffer[bytes++]=(byte)(0x80|(code&0x3f));
+                    }
+                    else
+                    {
+                        buffer[bytes++]=(byte)('?');
+                    }
+
+                    _surrogate=0; // USED
+
+                    if (bytes==buffer.length)
+                    {
+                        chars=i+1;
+                        break;
+                    }
+                }
+            }
+            _bytes.setCount(bytes);
+
+            _bytes.writeTo(out);
+            length-=chars;
+            offset+=chars;
+        }
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/server/handler/AbstractHandler.java b/lib/jetty/org/eclipse/jetty/server/handler/AbstractHandler.java
new file mode 100644 (file)
index 0000000..3d92512
--- /dev/null
@@ -0,0 +1,108 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server.handler;
+
+
+import java.io.IOException;
+
+import org.eclipse.jetty.server.Handler;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.util.annotation.ManagedObject;
+import org.eclipse.jetty.util.component.ContainerLifeCycle;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+
+/* ------------------------------------------------------------ */
+/** AbstractHandler.
+ */
+@ManagedObject("Jetty Handler")
+public abstract class AbstractHandler extends ContainerLifeCycle implements Handler
+{
+    private static final Logger LOG = Log.getLogger(AbstractHandler.class);
+
+    private Server _server;
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * 
+     */
+    public AbstractHandler()
+    {
+    }
+
+    /* ------------------------------------------------------------ */
+    /* 
+     * @see org.eclipse.thread.LifeCycle#start()
+     */
+    @Override
+    protected void doStart() throws Exception
+    {
+        LOG.debug("starting {}",this);
+        if (_server==null)
+            LOG.warn("No Server set for {}",this);
+        super.doStart();
+    }
+
+    /* ------------------------------------------------------------ */
+    /* 
+     * @see org.eclipse.thread.LifeCycle#stop()
+     */
+    @Override
+    protected void doStop() throws Exception
+    {
+        LOG.debug("stopping {}",this);
+        super.doStop();
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public void setServer(Server server)
+    {
+        if (_server==server)
+            return;
+        if (isStarted())
+            throw new IllegalStateException(STARTED);
+        _server=server;
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public Server getServer()
+    {
+        return _server;
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public void destroy()
+    {
+        if (!isStopped())
+            throw new IllegalStateException("!STOPPED");
+        super.destroy();
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public void dumpThis(Appendable out) throws IOException
+    {
+        out.append(toString()).append(" - ").append(getState()).append('\n');
+    }
+    
+}
diff --git a/lib/jetty/org/eclipse/jetty/server/handler/AbstractHandlerContainer.java b/lib/jetty/org/eclipse/jetty/server/handler/AbstractHandlerContainer.java
new file mode 100644 (file)
index 0000000..0d54e53
--- /dev/null
@@ -0,0 +1,118 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server.handler;
+
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import org.eclipse.jetty.server.Handler;
+import org.eclipse.jetty.server.HandlerContainer;
+
+
+/* ------------------------------------------------------------ */
+/** Abstract Handler Container.
+ * This is the base class for handlers that may contain other handlers.
+ *
+ */
+public abstract class AbstractHandlerContainer extends AbstractHandler implements HandlerContainer
+{
+    /* ------------------------------------------------------------ */
+    public AbstractHandlerContainer()
+    {
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public Handler[] getChildHandlers()
+    {
+        List<Handler> list=new ArrayList<>();
+        expandChildren(list,null);
+        return list.toArray(new Handler[list.size()]);
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public Handler[] getChildHandlersByClass(Class<?> byclass)
+    {
+        List<Handler> list=new ArrayList<>();
+        expandChildren(list,byclass);
+        return list.toArray(new Handler[list.size()]);
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public <T extends Handler> T getChildHandlerByClass(Class<T> byclass)
+    {
+        List<Handler> list=new ArrayList<>();
+        expandChildren(list,byclass);
+        if (list.isEmpty())
+            return null;
+        return (T)list.get(0);
+    }
+
+    /* ------------------------------------------------------------ */
+    protected void expandChildren(List<Handler> list, Class<?> byClass)
+    {
+    }
+
+    /* ------------------------------------------------------------ */
+    protected void expandHandler(Handler handler, List<Handler> list, Class<?> byClass)
+    {
+        if (handler==null)
+            return;
+
+        if (byClass==null || byClass.isAssignableFrom(handler.getClass()))
+            list.add(handler);
+
+        if (handler instanceof AbstractHandlerContainer)
+            ((AbstractHandlerContainer)handler).expandChildren(list, byClass);
+        else if (handler instanceof HandlerContainer)
+        {
+            HandlerContainer container = (HandlerContainer)handler;
+            Handler[] handlers=byClass==null?container.getChildHandlers():container.getChildHandlersByClass(byClass);
+            list.addAll(Arrays.asList(handlers));
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    public static <T extends HandlerContainer> T findContainerOf(HandlerContainer root,Class<T>type, Handler handler)
+    {
+        if (root==null || handler==null)
+            return null;
+
+        Handler[] branches=root.getChildHandlersByClass(type);
+        if (branches!=null)
+        {
+            for (Handler h:branches)
+            {
+                T container = (T)h;
+                Handler[] candidates = container.getChildHandlersByClass(handler.getClass());
+                if (candidates!=null)
+                {
+                    for (Handler c:candidates)
+                        if (c==handler)
+                            return container;
+                }
+            }
+        }
+        return null;
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/server/handler/AllowSymLinkAliasChecker.java b/lib/jetty/org/eclipse/jetty/server/handler/AllowSymLinkAliasChecker.java
new file mode 100644 (file)
index 0000000..c3d8b59
--- /dev/null
@@ -0,0 +1,95 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server.handler;
+
+import java.io.File;
+import java.net.URI;
+import java.nio.file.Files;
+import java.nio.file.Path;
+
+import org.eclipse.jetty.server.handler.ContextHandler.AliasCheck;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.resource.Resource;
+
+
+/* ------------------------------------------------------------ */
+/** Symbolic Link AliasChecker.
+ * <p>An instance of this class can be registered with {@link ContextHandler#addAliasCheck(AliasCheck)}
+ * to check resources that are aliased to other locations.   The checker uses the 
+ * Java {@link Files#readSymbolicLink(Path)} and {@link Path#toRealPath(java.nio.file.LinkOption...)}
+ * APIs to check if a file is aliased with symbolic links.</p>
+ */
+public class AllowSymLinkAliasChecker implements AliasCheck
+{
+    private static final Logger LOG = Log.getLogger(AllowSymLinkAliasChecker.class);
+    
+    @Override
+    public boolean check(String path, Resource resource)
+    {
+        try
+        {
+            File file =resource.getFile();
+            if (file==null)
+                return false;
+            
+            // If the file exists
+            if (file.exists())
+            {
+                // we can use the real path method to check the symlinks resolve to the alias
+                URI real = file.toPath().toRealPath().toUri();
+                if (real.equals(resource.getAlias()))
+                {
+                    LOG.debug("Allow symlink {} --> {}",resource,real);
+                    return true;
+                }
+            }
+            else
+            {
+                // file does not exists, so we have to walk the path and links ourselves.
+                Path p = file.toPath().toAbsolutePath();
+                File d = p.getRoot().toFile();
+                for (Path e:p)
+                {
+                    d=new File(d,e.toString());
+                    
+                    while (d.exists() && Files.isSymbolicLink(d.toPath()))
+                    {
+                        Path link=Files.readSymbolicLink(d.toPath());
+                        if (!link.isAbsolute())
+                            link=link.resolve(d.toPath());
+                        d=link.toFile().getAbsoluteFile().getCanonicalFile();
+                    }
+                }
+                if (resource.getAlias().equals(d.toURI()))
+                {
+                    LOG.debug("Allow symlink {} --> {}",resource,d);
+                    return true;
+                }
+            }
+        }
+        catch(Exception e)
+        {
+            e.printStackTrace();
+            LOG.ignore(e);
+        }
+        return false;
+    }
+
+}
diff --git a/lib/jetty/org/eclipse/jetty/server/handler/ContextHandler.java b/lib/jetty/org/eclipse/jetty/server/handler/ContextHandler.java
new file mode 100644 (file)
index 0000000..3a7d9d3
--- /dev/null
@@ -0,0 +1,2807 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server.handler;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.reflect.Method;
+import java.net.MalformedURLException;
+import java.net.URI;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.security.AccessController;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.EventListener;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.ListIterator;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.Future;
+
+import javax.servlet.DispatcherType;
+import javax.servlet.Filter;
+import javax.servlet.FilterRegistration;
+import javax.servlet.FilterRegistration.Dynamic;
+import javax.servlet.RequestDispatcher;
+import javax.servlet.Servlet;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletContextAttributeEvent;
+import javax.servlet.ServletContextAttributeListener;
+import javax.servlet.ServletContextEvent;
+import javax.servlet.ServletContextListener;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRegistration;
+import javax.servlet.ServletRequestAttributeListener;
+import javax.servlet.ServletRequestEvent;
+import javax.servlet.ServletRequestListener;
+import javax.servlet.SessionCookieConfig;
+import javax.servlet.SessionTrackingMode;
+import javax.servlet.descriptor.JspConfigDescriptor;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.http.MimeTypes;
+import org.eclipse.jetty.server.ClassLoaderDump;
+import org.eclipse.jetty.server.Connector;
+import org.eclipse.jetty.server.Dispatcher;
+import org.eclipse.jetty.server.Handler;
+import org.eclipse.jetty.server.HandlerContainer;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.util.Attributes;
+import org.eclipse.jetty.util.AttributesMap;
+import org.eclipse.jetty.util.FutureCallback;
+import org.eclipse.jetty.util.Loader;
+import org.eclipse.jetty.util.StringUtil;
+import org.eclipse.jetty.util.URIUtil;
+import org.eclipse.jetty.util.annotation.ManagedAttribute;
+import org.eclipse.jetty.util.annotation.ManagedObject;
+import org.eclipse.jetty.util.component.Graceful;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.resource.Resource;
+
+/* ------------------------------------------------------------ */
+/**
+ * ContextHandler.
+ *
+ * This handler wraps a call to handle by setting the context and servlet path, plus setting the context classloader.
+ *
+ * <p>
+ * If the context init parameter "org.eclipse.jetty.server.context.ManagedAttributes" is set to a comma separated list of names, then they are treated as
+ * context attribute names, which if set as attributes are passed to the servers Container so that they may be managed with JMX.
+ * <p>
+ * The maximum size of a form that can be processed by this context is controlled by the system properties org.eclipse.jetty.server.Request.maxFormKeys
+ * and org.eclipse.jetty.server.Request.maxFormContentSize.  These can also be configured with {@link #setMaxFormContentSize(int)} and {@link #setMaxFormKeys(int)}
+ * <p>
+ * This servers executore is made available via a context attributed "org.eclipse.jetty.server.Executor".
+ *
+ * @org.apache.xbean.XBean description="Creates a basic HTTP context"
+ */
+@ManagedObject("URI Context")
+public class ContextHandler extends ScopedHandler implements Attributes, Graceful
+{
+    public final static int SERVLET_MAJOR_VERSION=3;
+    public final static int SERVLET_MINOR_VERSION=0;
+    public static final Class[] SERVLET_LISTENER_TYPES = new Class[] {ServletContextListener.class,
+                                                                      ServletContextAttributeListener.class,
+                                                                      ServletRequestListener.class,
+                                                                      ServletRequestAttributeListener.class};
+
+    public static final int DEFAULT_LISTENER_TYPE_INDEX = 1;
+    public static final int EXTENDED_LISTENER_TYPE_INDEX = 0;
+
+
+    final private static String __unimplmented="Unimplemented - use org.eclipse.jetty.servlet.ServletContextHandler";
+
+    private static final Logger LOG = Log.getLogger(ContextHandler.class);
+
+    private static final ThreadLocal<Context> __context = new ThreadLocal<Context>();
+
+    /**
+     * If a context attribute with this name is set, it is interpreted as a comma separated list of attribute name. Any other context attributes that are set
+     * with a name from this list will result in a call to {@link #setManagedAttribute(String, Object)}, which typically initiates the creation of a JMX MBean
+     * for the attribute value.
+     */
+    public static final String MANAGED_ATTRIBUTES = "org.eclipse.jetty.server.context.ManagedAttributes";
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Get the current ServletContext implementation.
+     *
+     * @return ServletContext implementation
+     */
+    public static Context getCurrentContext()
+    {
+        return __context.get();
+    }
+
+    /* ------------------------------------------------------------ */
+    public static ContextHandler getContextHandler(ServletContext context)
+    {
+        if(context instanceof ContextHandler.Context)
+            return ((ContextHandler.Context)context).getContextHandler();
+        Context c=  getCurrentContext();
+        if (c!=null)
+            return c.getContextHandler();
+        return null;
+    }
+
+
+    protected Context _scontext;
+    private final AttributesMap _attributes;
+    private final Map<String, String> _initParams;
+    private ClassLoader _classLoader;
+    private String _contextPath = "/";
+
+    private String _displayName;
+
+    private Resource _baseResource;
+    private MimeTypes _mimeTypes;
+    private Map<String, String> _localeEncodingMap;
+    private String[] _welcomeFiles;
+    private ErrorHandler _errorHandler;
+    private String[] _vhosts;
+
+    private Logger _logger;
+    private boolean _allowNullPathInfo;
+    private int _maxFormKeys = Integer.getInteger("org.eclipse.jetty.server.Request.maxFormKeys",-1).intValue();
+    private int _maxFormContentSize = Integer.getInteger("org.eclipse.jetty.server.Request.maxFormContentSize",-1).intValue();
+    private boolean _compactPath = false;
+
+    private final List<EventListener> _eventListeners=new CopyOnWriteArrayList<>();
+    private final List<EventListener> _programmaticListeners=new CopyOnWriteArrayList<>();
+    private final List<ServletContextListener> _contextListeners=new CopyOnWriteArrayList<>();
+    private final List<ServletContextAttributeListener> _contextAttributeListeners=new CopyOnWriteArrayList<>();
+    private final List<ServletRequestListener> _requestListeners=new CopyOnWriteArrayList<>();
+    private final List<ServletRequestAttributeListener> _requestAttributeListeners=new CopyOnWriteArrayList<>();
+    private final List<EventListener> _durableListeners = new CopyOnWriteArrayList<>();
+    private Map<String, Object> _managedAttributes;
+    private String[] _protectedTargets;
+    private final CopyOnWriteArrayList<AliasCheck> _aliasChecks = new CopyOnWriteArrayList<ContextHandler.AliasCheck>();
+
+    public enum Availability { UNAVAILABLE,STARTING,AVAILABLE,SHUTDOWN,};
+    private volatile Availability _availability;
+
+    /* ------------------------------------------------------------ */
+    /**
+     *
+     */
+    public ContextHandler()
+    {
+        super();
+        _scontext = new Context();
+        _attributes = new AttributesMap();
+        _initParams = new HashMap<String, String>();
+        addAliasCheck(new ApproveNonExistentDirectoryAliases());
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     *
+     */
+    protected ContextHandler(Context context)
+    {
+        super();
+        _scontext = context;
+        _attributes = new AttributesMap();
+        _initParams = new HashMap<String, String>();
+        addAliasCheck(new ApproveNonExistentDirectoryAliases());
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     *
+     */
+    public ContextHandler(String contextPath)
+    {
+        this();
+        setContextPath(contextPath);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     *
+     */
+    public ContextHandler(HandlerContainer parent, String contextPath)
+    {
+        this();
+        setContextPath(contextPath);
+        if (parent instanceof HandlerWrapper)
+            ((HandlerWrapper)parent).setHandler(this);
+        else if (parent instanceof HandlerCollection)
+            ((HandlerCollection)parent).addHandler(this);
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public void dump(Appendable out, String indent) throws IOException
+    {
+        dumpBeans(out,indent,
+            Collections.singletonList(new ClassLoaderDump(getClassLoader())),
+            _initParams.entrySet(),
+            _attributes.getAttributeEntrySet(),
+            _scontext.getAttributeEntrySet());
+    }
+
+    /* ------------------------------------------------------------ */
+    public Context getServletContext()
+    {
+        return _scontext;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return the allowNullPathInfo true if /context is not redirected to /context/
+     */
+    @ManagedAttribute("Checks if the /context is not redirected to /context/")
+    public boolean getAllowNullPathInfo()
+    {
+        return _allowNullPathInfo;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param allowNullPathInfo
+     *            true if /context is not redirected to /context/
+     */
+    public void setAllowNullPathInfo(boolean allowNullPathInfo)
+    {
+        _allowNullPathInfo = allowNullPathInfo;
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public void setServer(Server server)
+    {
+        super.setServer(server);
+        if (_errorHandler != null)
+            _errorHandler.setServer(server);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Set the virtual hosts for the context. Only requests that have a matching host header or fully qualified URL will be passed to that context with a
+     * virtual host name. A context with no virtual host names or a null virtual host name is available to all requests that are not served by a context with a
+     * matching virtual host name.
+     *
+     * @param vhosts
+     *            Array of virtual hosts that this context responds to. A null host name or null/empty array means any hostname is acceptable. Host names may be
+     *            String representation of IP addresses. Host names may start with '*.' to wildcard one level of names.  Hosts may start with '@', in which case they
+     *            will match the {@link Connector#getName()} for the request.
+     */
+    public void setVirtualHosts(String[] vhosts)
+    {
+        if (vhosts == null)
+        {
+            _vhosts = vhosts;
+        }
+        else
+        {
+            _vhosts = new String[vhosts.length];
+            for (int i = 0; i < vhosts.length; i++)
+                _vhosts[i] = normalizeHostname(vhosts[i]);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Either set virtual hosts or add to an existing set of virtual hosts.
+     *
+     * @param virtualHosts
+     *            Array of virtual hosts that this context responds to. A null host name or null/empty array means any hostname is acceptable. Host names may be
+     *            String representation of IP addresses. Host names may start with '*.' to wildcard one level of names. Host names may start with '@', in which case they
+     *            will match the {@link Connector#getName()} for the request.
+     */
+    public void addVirtualHosts(String[] virtualHosts)
+    {
+        if (virtualHosts == null)  // since this is add, we don't null the old ones
+        {
+            return;
+        }
+        else
+        {
+            List<String> currentVirtualHosts = null;
+            if (_vhosts != null)
+            {
+                currentVirtualHosts = new ArrayList<String>(Arrays.asList(_vhosts));
+            }
+            else
+            {
+                currentVirtualHosts = new ArrayList<String>();
+            }
+
+            for (int i = 0; i < virtualHosts.length; i++)
+            {
+                String normVhost = normalizeHostname(virtualHosts[i]);
+                if (!currentVirtualHosts.contains(normVhost))
+                {
+                    currentVirtualHosts.add(normVhost);
+                }
+            }
+            _vhosts = currentVirtualHosts.toArray(new String[0]);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Removes an array of virtual host entries, if this removes all entries the _vhosts will be set to null
+     *
+     *  @param virtualHosts
+     *            Array of virtual hosts that this context responds to. A null host name or null/empty array means any hostname is acceptable. Host names may be
+     *            String representation of IP addresses. Host names may start with '*.' to wildcard one level of names.
+     */
+    public void removeVirtualHosts(String[] virtualHosts)
+    {
+        if (virtualHosts == null)
+        {
+            return; // do nothing
+        }
+        else if ( _vhosts == null || _vhosts.length == 0)
+        {
+            return; // do nothing
+        }
+        else
+        {
+            List<String> existingVirtualHosts = new ArrayList<String>(Arrays.asList(_vhosts));
+
+            for (int i = 0; i < virtualHosts.length; i++)
+            {
+                String toRemoveVirtualHost = normalizeHostname(virtualHosts[i]);
+                if (existingVirtualHosts.contains(toRemoveVirtualHost))
+                {
+                    existingVirtualHosts.remove(toRemoveVirtualHost);
+                }
+            }
+
+            if (existingVirtualHosts.isEmpty())
+            {
+                _vhosts = null; // if we ended up removing them all, just null out _vhosts
+            }
+            else
+            {
+                _vhosts = existingVirtualHosts.toArray(new String[0]);
+            }
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Get the virtual hosts for the context. Only requests that have a matching host header or fully qualified URL will be passed to that context with a
+     * virtual host name. A context with no virtual host names or a null virtual host name is available to all requests that are not served by a context with a
+     * matching virtual host name.
+     *
+     * @return Array of virtual hosts that this context responds to. A null host name or empty array means any hostname is acceptable. Host names may be String
+     *         representation of IP addresses. Host names may start with '*.' to wildcard one level of names.
+     */
+    @ManagedAttribute(value="Virtual hosts accepted by the context", readonly=true)
+    public String[] getVirtualHosts()
+    {
+        return _vhosts;
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.ServletContext#getAttribute(java.lang.String)
+     */
+    @Override
+    public Object getAttribute(String name)
+    {
+        return _attributes.getAttribute(name);
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.ServletContext#getAttributeNames()
+     */
+    @Override
+    public Enumeration<String> getAttributeNames()
+    {
+        return AttributesMap.getAttributeNamesCopy(_attributes);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Returns the attributes.
+     */
+    public Attributes getAttributes()
+    {
+        return _attributes;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Returns the classLoader.
+     */
+    public ClassLoader getClassLoader()
+    {
+        return _classLoader;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Make best effort to extract a file classpath from the context classloader
+     *
+     * @return Returns the classLoader.
+     */
+    @ManagedAttribute("The file classpath")
+    public String getClassPath()
+    {
+        if (_classLoader == null || !(_classLoader instanceof URLClassLoader))
+            return null;
+        URLClassLoader loader = (URLClassLoader)_classLoader;
+        URL[] urls = loader.getURLs();
+        StringBuilder classpath = new StringBuilder();
+        for (int i = 0; i < urls.length; i++)
+        {
+            try
+            {
+                Resource resource = newResource(urls[i]);
+                File file = resource.getFile();
+                if (file != null && file.exists())
+                {
+                    if (classpath.length() > 0)
+                        classpath.append(File.pathSeparatorChar);
+                    classpath.append(file.getAbsolutePath());
+                }
+            }
+            catch (IOException e)
+            {
+                LOG.debug(e);
+            }
+        }
+        if (classpath.length() == 0)
+            return null;
+        return classpath.toString();
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Returns the _contextPath.
+     */
+    @ManagedAttribute("True if URLs are compacted to replace the multiple '/'s with a single '/'")
+    public String getContextPath()
+    {
+        return _contextPath;
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.ServletContext#getInitParameter(java.lang.String)
+     */
+    public String getInitParameter(String name)
+    {
+        return _initParams.get(name);
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     */
+    public String setInitParameter(String name, String value)
+    {
+        return _initParams.put(name,value);
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.ServletContext#getInitParameterNames()
+     */
+    @SuppressWarnings("rawtypes")
+    public Enumeration getInitParameterNames()
+    {
+        return Collections.enumeration(_initParams.keySet());
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Returns the initParams.
+     */
+    @ManagedAttribute("Initial Parameter map for the context")
+    public Map<String, String> getInitParams()
+    {
+        return _initParams;
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.ServletContext#getServletContextName()
+     */
+    @ManagedAttribute(value="Display name of the Context", readonly=true)
+    public String getDisplayName()
+    {
+        return _displayName;
+    }
+
+    /* ------------------------------------------------------------ */
+    public EventListener[] getEventListeners()
+    {
+        return _eventListeners.toArray(new EventListener[_eventListeners.size()]);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Set the context event listeners.
+     *
+     * @param eventListeners
+     *            the event listeners
+     * @see ServletContextListener
+     * @see ServletContextAttributeListener
+     * @see ServletRequestListener
+     * @see ServletRequestAttributeListener
+     */
+    public void setEventListeners(EventListener[] eventListeners)
+    {
+        _contextListeners.clear();
+        _contextAttributeListeners.clear();
+        _requestListeners.clear();
+        _requestAttributeListeners.clear();
+        _eventListeners.clear();
+
+        if (eventListeners!=null)
+            for (EventListener listener : eventListeners)
+                addEventListener(listener);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Add a context event listeners.
+     *
+     * @see ServletContextListener
+     * @see ServletContextAttributeListener
+     * @see ServletRequestListener
+     * @see ServletRequestAttributeListener
+     */
+    public void addEventListener(EventListener listener)
+    {
+        _eventListeners.add(listener);
+
+        if (!(isStarted() || isStarting()))
+            _durableListeners.add(listener);
+
+        if (listener instanceof ServletContextListener)
+            _contextListeners.add((ServletContextListener)listener);
+
+        if (listener instanceof ServletContextAttributeListener)
+            _contextAttributeListeners.add((ServletContextAttributeListener)listener);
+
+        if (listener instanceof ServletRequestListener)
+            _requestListeners.add((ServletRequestListener)listener);
+
+        if (listener instanceof ServletRequestAttributeListener)
+            _requestAttributeListeners.add((ServletRequestAttributeListener)listener);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Remove a context event listeners.
+     *
+     * @see ServletContextListener
+     * @see ServletContextAttributeListener
+     * @see ServletRequestListener
+     * @see ServletRequestAttributeListener
+     */
+    public void removeEventListener(EventListener listener)
+    {
+        _eventListeners.remove(listener);
+
+        if (listener instanceof ServletContextListener)
+            _contextListeners.remove(listener);
+
+        if (listener instanceof ServletContextAttributeListener)
+            _contextAttributeListeners.remove(listener);
+
+        if (listener instanceof ServletRequestListener)
+            _requestListeners.remove(listener);
+
+        if (listener instanceof ServletRequestAttributeListener)
+            _requestAttributeListeners.remove(listener);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Apply any necessary restrictions on a programmatic added listener.
+     *
+     * @param listener
+     */
+    protected void addProgrammaticListener (EventListener listener)
+    {
+        _programmaticListeners.add(listener);
+    }
+
+    /* ------------------------------------------------------------ */
+    protected boolean isProgrammaticListener(EventListener listener)
+    {
+        return _programmaticListeners.contains(listener);
+    }
+
+
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return true if this context is accepting new requests
+     */
+    @ManagedAttribute("true for graceful shutdown, which allows existing requests to complete")
+    public boolean isShutdown()
+    {
+        switch(_availability)
+        {
+            case SHUTDOWN:
+                return true;
+            default:
+                return false;
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Set shutdown status. This field allows for graceful shutdown of a context. A started context may be put into non accepting state so that existing
+     * requests can complete, but no new requests are accepted.
+     *
+     */
+    @Override
+    public Future<Void> shutdown()
+    {
+        _availability = isRunning() ? Availability.SHUTDOWN : Availability.UNAVAILABLE;
+        return new FutureCallback(true);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return false if this context is unavailable (sends 503)
+     */
+    public boolean isAvailable()
+    {
+        return _availability==Availability.AVAILABLE;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Set Available status.
+     */
+    public void setAvailable(boolean available)
+    {
+        synchronized (this)
+        {
+            if (available && isRunning())
+                _availability = Availability.AVAILABLE;
+            else if (!available || !isRunning())
+                _availability = Availability.UNAVAILABLE;
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    public Logger getLogger()
+    {
+        return _logger;
+    }
+
+    /* ------------------------------------------------------------ */
+    public void setLogger(Logger logger)
+    {
+        _logger = logger;
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see org.eclipse.thread.AbstractLifeCycle#doStart()
+     */
+    @Override
+    protected void doStart() throws Exception
+    {
+        _availability = Availability.STARTING;
+
+        if (_contextPath == null)
+            throw new IllegalStateException("Null contextPath");
+
+        _logger = Log.getLogger(getDisplayName() == null?getContextPath():getDisplayName());
+        ClassLoader old_classloader = null;
+        Thread current_thread = null;
+        Context old_context = null;
+
+        _attributes.setAttribute("org.eclipse.jetty.server.Executor",getServer().getThreadPool());
+        
+        try
+        {
+            // Set the classloader
+            if (_classLoader != null)
+            {
+                current_thread = Thread.currentThread();
+                old_classloader = current_thread.getContextClassLoader();
+                current_thread.setContextClassLoader(_classLoader);
+            }
+
+            if (_mimeTypes == null)
+                _mimeTypes = new MimeTypes();
+
+            old_context = __context.get();
+            __context.set(_scontext);
+
+            // defers the calling of super.doStart()
+            startContext();
+
+            _availability = Availability.AVAILABLE;
+            LOG.info("Started {}", this);
+        }
+        finally
+        {
+            __context.set(old_context);
+
+            // reset the classloader
+            if (_classLoader != null && current_thread!=null)
+                current_thread.setContextClassLoader(old_classloader);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Extensible startContext. this method is called from {@link ContextHandler#doStart()} instead of a call to super.doStart(). This allows derived classes to
+     * insert additional handling (Eg configuration) before the call to super.doStart by this method will start contained handlers.
+     *
+     * @see org.eclipse.jetty.server.handler.ContextHandler.Context
+     */
+    protected void startContext() throws Exception
+    {
+        String managedAttributes = _initParams.get(MANAGED_ATTRIBUTES);
+        if (managedAttributes != null)
+        {
+            _managedAttributes = new HashMap<String, Object>();
+            String[] attributes = managedAttributes.split(",");
+            for (String attribute : attributes)
+                _managedAttributes.put(attribute,null);
+
+            Enumeration<String> e = _scontext.getAttributeNames();
+            while (e.hasMoreElements())
+            {
+                String name = e.nextElement();
+                Object value = _scontext.getAttribute(name);
+                checkManagedAttribute(name,value);
+            }
+        }
+
+        super.doStart();
+
+        // Call context listeners
+        if (!_contextListeners.isEmpty())
+        {
+            ServletContextEvent event = new ServletContextEvent(_scontext);
+            for (ServletContextListener listener:_contextListeners)
+                callContextInitialized(listener, event);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    protected void callContextInitialized (ServletContextListener l, ServletContextEvent e)
+    {
+        LOG.debug("contextInitialized: {}->{}",e,l);
+        l.contextInitialized(e);
+    }
+
+    /* ------------------------------------------------------------ */
+    protected void callContextDestroyed (ServletContextListener l, ServletContextEvent e)
+    {
+        LOG.debug("contextDestroyed: {}->{}",e,l);
+        l.contextDestroyed(e);
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see org.eclipse.thread.AbstractLifeCycle#doStop()
+     */
+    @Override
+    protected void doStop() throws Exception
+    {
+        _availability = Availability.UNAVAILABLE;
+
+        ClassLoader old_classloader = null;
+        Thread current_thread = null;
+
+        Context old_context = __context.get();
+        __context.set(_scontext);
+        try
+        {
+            // Set the classloader
+            if (_classLoader != null)
+            {
+                current_thread = Thread.currentThread();
+                old_classloader = current_thread.getContextClassLoader();
+                current_thread.setContextClassLoader(_classLoader);
+            }
+
+            super.doStop();
+
+            // Context listeners
+            if (!_contextListeners.isEmpty())
+            {
+                ServletContextEvent event = new ServletContextEvent(_scontext);
+                for (int i = _contextListeners.size(); i-->0;)
+                    callContextDestroyed(_contextListeners.get(i),event);
+            }
+
+            //retain only durable listeners
+            setEventListeners(_durableListeners.toArray(new EventListener[_durableListeners.size()]));
+            _durableListeners.clear();
+
+            if (_errorHandler != null)
+                _errorHandler.stop();
+
+            Enumeration<String> e = _scontext.getAttributeNames();
+            while (e.hasMoreElements())
+            {
+                String name = e.nextElement();
+                checkManagedAttribute(name,null);
+            }
+
+            for (EventListener l : _programmaticListeners)
+                removeEventListener(l);
+            _programmaticListeners.clear();
+        }
+        finally
+        {
+            LOG.info("Stopped {}", this);
+            __context.set(old_context);
+            // reset the classloader
+            if (_classLoader != null && current_thread!=null)
+                current_thread.setContextClassLoader(old_classloader);
+        }
+
+        _scontext.clearAttributes();
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see org.eclipse.jetty.server.Handler#handle(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
+     */
+    public boolean checkContext(final String target, final Request baseRequest, final HttpServletResponse response) throws IOException
+    {
+        DispatcherType dispatch = baseRequest.getDispatcherType();
+
+        switch (_availability)
+        {
+            case SHUTDOWN:
+            case UNAVAILABLE:
+                baseRequest.setHandled(true);
+                response.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE);
+                return false;
+            default:
+                if ((DispatcherType.REQUEST.equals(dispatch) && baseRequest.isHandled()))
+                    return false;
+        }
+
+        // Check the vhosts
+        if (_vhosts != null && _vhosts.length > 0)
+        {
+            String vhost = normalizeHostname(baseRequest.getServerName());
+
+            boolean match = false;
+            boolean connectorName = false;
+            boolean connectorMatch = false;
+
+            for (String contextVhost:_vhosts)
+            {
+                if (contextVhost == null || contextVhost.length()==0)
+                    continue;
+                char c=contextVhost.charAt(0);
+                switch (c)
+                {
+                    case '*':
+                        if (contextVhost.startsWith("*."))
+                            // wildcard only at the beginning, and only for one additional subdomain level
+                            match = match || contextVhost.regionMatches(true,2,vhost,vhost.indexOf(".") + 1,contextVhost.length() - 2);
+                        break;
+                    case '@':
+                        connectorName=true;
+                        String name=baseRequest.getHttpChannel().getConnector().getName();
+                        boolean m=name!=null && contextVhost.length()==name.length()+1 && contextVhost.endsWith(name);
+                        match = match || m;
+                        connectorMatch = connectorMatch || m;
+                        break;
+                    default:
+                        match = match || contextVhost.equalsIgnoreCase(vhost);
+                }
+
+            }
+            if (!match || connectorName && !connectorMatch)
+                return false;
+        }
+
+        // Are we not the root context?
+        if (_contextPath.length() > 1)
+        {
+            // reject requests that are not for us
+            if (!target.startsWith(_contextPath))
+                return false;
+            if (target.length() > _contextPath.length() && target.charAt(_contextPath.length()) != '/')
+                return false;
+
+            // redirect null path infos
+            if (!_allowNullPathInfo && _contextPath.length() == target.length())
+            {
+                // context request must end with /
+                baseRequest.setHandled(true);
+                if (baseRequest.getQueryString() != null)
+                    response.sendRedirect(URIUtil.addPaths(baseRequest.getRequestURI(),URIUtil.SLASH) + "?" + baseRequest.getQueryString());
+                else
+                    response.sendRedirect(URIUtil.addPaths(baseRequest.getRequestURI(),URIUtil.SLASH));
+                return false;
+            }
+        }
+
+        return true;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.server.handler.ScopedHandler#doScope(java.lang.String, org.eclipse.jetty.server.Request, javax.servlet.http.HttpServletRequest,
+     *      javax.servlet.http.HttpServletResponse)
+     */
+    @Override
+    public void doScope(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+    {
+        if (LOG.isDebugEnabled())
+            LOG.debug("scope {}|{}|{} @ {}",baseRequest.getContextPath(),baseRequest.getServletPath(),baseRequest.getPathInfo(),this);
+
+        Context old_context = null;
+        String old_context_path = null;
+        String old_servlet_path = null;
+        String old_path_info = null;
+        ClassLoader old_classloader = null;
+        Thread current_thread = null;
+        String pathInfo = target;
+
+        DispatcherType dispatch = baseRequest.getDispatcherType();
+
+        old_context = baseRequest.getContext();
+
+        // Are we already in this context?
+        if (old_context != _scontext)
+        {
+            // check the target.
+            if (DispatcherType.REQUEST.equals(dispatch) ||
+                DispatcherType.ASYNC.equals(dispatch) ||
+                DispatcherType.ERROR.equals(dispatch) && baseRequest.getHttpChannelState().isAsync())
+            {
+                if (_compactPath)
+                    target = URIUtil.compactPath(target);
+                if (!checkContext(target,baseRequest,response))
+                    return;
+
+                if (target.length() > _contextPath.length())
+                {
+                    if (_contextPath.length() > 1)
+                        target = target.substring(_contextPath.length());
+                    pathInfo = target;
+                }
+                else if (_contextPath.length() == 1)
+                {
+                    target = URIUtil.SLASH;
+                    pathInfo = URIUtil.SLASH;
+                }
+                else
+                {
+                    target = URIUtil.SLASH;
+                    pathInfo = null;
+                }
+            }
+
+            // Set the classloader
+            if (_classLoader != null)
+            {
+                current_thread = Thread.currentThread();
+                old_classloader = current_thread.getContextClassLoader();
+                current_thread.setContextClassLoader(_classLoader);
+            }
+        }
+
+        try
+        {
+            old_context_path = baseRequest.getContextPath();
+            old_servlet_path = baseRequest.getServletPath();
+            old_path_info = baseRequest.getPathInfo();
+
+            // Update the paths
+            baseRequest.setContext(_scontext);
+            __context.set(_scontext);
+            if (!DispatcherType.INCLUDE.equals(dispatch) && target.startsWith("/"))
+            {
+                if (_contextPath.length() == 1)
+                    baseRequest.setContextPath("");
+                else
+                    baseRequest.setContextPath(_contextPath);
+                baseRequest.setServletPath(null);
+                baseRequest.setPathInfo(pathInfo);
+            }
+
+            if (LOG.isDebugEnabled())
+                LOG.debug("context={}|{}|{} @ {}",baseRequest.getContextPath(),baseRequest.getServletPath(), baseRequest.getPathInfo(),this);
+
+            // start manual inline of nextScope(target,baseRequest,request,response);
+            if (never())
+                nextScope(target,baseRequest,request,response);
+            else if (_nextScope != null)
+                _nextScope.doScope(target,baseRequest,request,response);
+            else if (_outerScope != null)
+                _outerScope.doHandle(target,baseRequest,request,response);
+            else
+                doHandle(target,baseRequest,request,response);
+            // end manual inline (pathentic attempt to reduce stack depth)
+        }
+        finally
+        {
+            if (old_context != _scontext)
+            {
+                // reset the classloader
+                if (_classLoader != null && current_thread!=null)
+                {
+                    current_thread.setContextClassLoader(old_classloader);
+                }
+
+                // reset the context and servlet path.
+                baseRequest.setContext(old_context);
+                __context.set(old_context);
+                baseRequest.setContextPath(old_context_path);
+                baseRequest.setServletPath(old_servlet_path);
+                baseRequest.setPathInfo(old_path_info);
+            }
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.server.handler.ScopedHandler#doHandle(java.lang.String, org.eclipse.jetty.server.Request, javax.servlet.http.HttpServletRequest,
+     *      javax.servlet.http.HttpServletResponse)
+     */
+    @Override
+    public void doHandle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+    {
+        final DispatcherType dispatch = baseRequest.getDispatcherType();
+        final boolean new_context = baseRequest.takeNewContext();
+        try
+        {
+            if (new_context)
+            {
+                // Handle the REALLY SILLY request events!
+                if (!_requestAttributeListeners.isEmpty())
+                    for (ServletRequestAttributeListener l :_requestAttributeListeners)
+                        baseRequest.addEventListener(l);
+
+                if (!_requestListeners.isEmpty())
+                {
+                    final ServletRequestEvent sre = new ServletRequestEvent(_scontext,request);
+                    for (ServletRequestListener l : _requestListeners)
+                        l.requestInitialized(sre);
+                }
+            }
+
+            if (DispatcherType.REQUEST.equals(dispatch) && isProtectedTarget(target))
+            {
+                response.sendError(HttpServletResponse.SC_NOT_FOUND);
+                baseRequest.setHandled(true);
+                return;
+            }
+
+            // start manual inline of nextHandle(target,baseRequest,request,response);
+            // noinspection ConstantIfStatement
+            if (never())
+                nextHandle(target,baseRequest,request,response);
+            else if (_nextScope != null && _nextScope == _handler)
+                _nextScope.doHandle(target,baseRequest,request,response);
+            else if (_handler != null)
+                _handler.handle(target,baseRequest,request,response);
+            // end manual inline
+        }
+        finally
+        {
+            // Handle more REALLY SILLY request events!
+            if (new_context)
+            {
+                if (!_requestListeners.isEmpty())
+                {
+                    final ServletRequestEvent sre = new ServletRequestEvent(_scontext,request);
+                    for (int i=_requestListeners.size();i-->0;)
+                        _requestListeners.get(i).requestDestroyed(sre);
+                }
+
+                if (!_requestAttributeListeners.isEmpty())
+                {
+                    ListIterator<ServletRequestAttributeListener> iter = _requestAttributeListeners.listIterator(_requestAttributeListeners.size());
+                    for (int i=_requestAttributeListeners.size();i-->0;)
+                        baseRequest.removeEventListener(_requestAttributeListeners.get(i));
+                }
+            }
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * Handle a runnable in this context
+     */
+    public void handle(Runnable runnable)
+    {
+        ClassLoader old_classloader = null;
+        Thread current_thread = null;
+        Context old_context = null;
+        try
+        {
+            old_context = __context.get();
+            __context.set(_scontext);
+
+            // Set the classloader
+            if (_classLoader != null)
+            {
+                current_thread = Thread.currentThread();
+                old_classloader = current_thread.getContextClassLoader();
+                current_thread.setContextClassLoader(_classLoader);
+            }
+
+            runnable.run();
+        }
+        finally
+        {
+            __context.set(old_context);
+            if (old_classloader != null && current_thread!=null)
+            {
+                current_thread.setContextClassLoader(old_classloader);
+            }
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Check the target. Called by {@link #handle(String, Request, HttpServletRequest, HttpServletResponse)} when a target within a context is determined. If
+     * the target is protected, 404 is returned.
+     */
+    /* ------------------------------------------------------------ */
+    public boolean isProtectedTarget(String target)
+    {
+        if (target == null || _protectedTargets == null)
+            return false;
+
+        while (target.startsWith("//"))
+            target=URIUtil.compactPath(target);
+
+        for (int i=0; i<_protectedTargets.length; i++)
+        {
+            String t=_protectedTargets[i];
+            if (StringUtil.startsWithIgnoreCase(target,t))
+            {
+                if (target.length()==t.length())
+                    return true;
+                
+                // Check that the target prefix really is a path segment, thus
+                // it can end with /, a query, a target or a parameter
+                char c=target.charAt(t.length());
+                if (c=='/'||c=='?'||c=='#'||c==';')
+                    return true;
+            }
+        }
+        return false;
+    }
+
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param targets Array of URL prefix. Each prefix is in the form /path and will match
+     * either /path exactly or /path/anything
+     */
+    public void setProtectedTargets (String[] targets)
+    {
+        if (targets == null)
+        {
+            _protectedTargets = null;
+            return;
+        }
+        
+        _protectedTargets = Arrays.copyOf(targets, targets.length);
+    }
+
+    /* ------------------------------------------------------------ */
+    public String[] getProtectedTargets()
+    {
+        if (_protectedTargets == null)
+            return null;
+
+        return Arrays.copyOf(_protectedTargets, _protectedTargets.length);
+    }
+
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.ServletContext#removeAttribute(java.lang.String)
+     */
+    @Override
+    public void removeAttribute(String name)
+    {
+        checkManagedAttribute(name,null);
+        _attributes.removeAttribute(name);
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * Set a context attribute. Attributes set via this API cannot be overriden by the ServletContext.setAttribute API. Their lifecycle spans the stop/start of
+     * a context. No attribute listener events are triggered by this API.
+     *
+     * @see javax.servlet.ServletContext#setAttribute(java.lang.String, java.lang.Object)
+     */
+    @Override
+    public void setAttribute( String name, Object value)
+    {
+        checkManagedAttribute(name,value);
+        _attributes.setAttribute(name,value);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param attributes
+     *            The attributes to set.
+     */
+    public void setAttributes(Attributes attributes)
+    {
+        _attributes.clearAttributes();
+        _attributes.addAll(attributes);
+        Enumeration<String> e = _attributes.getAttributeNames();
+        while (e.hasMoreElements())
+        {
+            String name = e.nextElement();
+            checkManagedAttribute(name,attributes.getAttribute(name));
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public void clearAttributes()
+    {
+        Enumeration<String> e = _attributes.getAttributeNames();
+        while (e.hasMoreElements())
+        {
+            String name = e.nextElement();
+            checkManagedAttribute(name,null);
+        }
+        _attributes.clearAttributes();
+    }
+
+    /* ------------------------------------------------------------ */
+    public void checkManagedAttribute(String name, Object value)
+    {
+        if (_managedAttributes != null && _managedAttributes.containsKey(name))
+        {
+            setManagedAttribute(name,value);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    public void setManagedAttribute(String name, Object value)
+    {
+        Object old = _managedAttributes.put(name,value);
+        updateBean(old,value);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param classLoader
+     *            The classLoader to set.
+     */
+    public void setClassLoader(ClassLoader classLoader)
+    {
+        _classLoader = classLoader;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param contextPath
+     *            The _contextPath to set.
+     */
+    public void setContextPath(String contextPath)
+    {
+        if (contextPath == null)
+            throw new IllegalArgumentException("null contextPath");
+
+        if (contextPath.endsWith("/*"))
+        {
+            LOG.warn(this+" contextPath ends with /*");
+            contextPath=contextPath.substring(0,contextPath.length()-2);
+        }
+        else if (contextPath.length()>1 && contextPath.endsWith("/"))
+        {
+            LOG.warn(this+" contextPath ends with /");
+            contextPath=contextPath.substring(0,contextPath.length()-1);
+        }
+
+        if (contextPath.length()==0)
+        {
+            LOG.warn("Empty contextPath");
+            contextPath="/";
+        }
+
+        _contextPath = contextPath;
+
+        if (getServer() != null && (getServer().isStarting() || getServer().isStarted()))
+        {
+            Handler[] contextCollections = getServer().getChildHandlersByClass(ContextHandlerCollection.class);
+            for (int h = 0; contextCollections != null && h < contextCollections.length; h++)
+                ((ContextHandlerCollection)contextCollections[h]).mapContexts();
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param servletContextName
+     *            The servletContextName to set.
+     */
+    public void setDisplayName(String servletContextName)
+    {
+        _displayName = servletContextName;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Returns the resourceBase.
+     */
+    public Resource getBaseResource()
+    {
+        if (_baseResource == null)
+            return null;
+        return _baseResource;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Returns the base resource as a string.
+     */
+    @ManagedAttribute("document root for context")
+    public String getResourceBase()
+    {
+        if (_baseResource == null)
+            return null;
+        return _baseResource.toString();
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Set the base resource for this context.
+     * @param base The resource used as the base for all static content of this context.
+     * @see #setResourceBase(String)
+     */
+    public void setBaseResource(Resource base)
+    {
+        _baseResource = base;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** 
+     * Set the base resource for this context.
+     * @param resourceBase A string representing the base resource for the context. Any string accepted 
+     * by {@link Resource#newResource(String)} may be passed and the call is equivalent to 
+     * <code>setBaseResource(newResource(resourceBase));</code>
+     */
+    public void setResourceBase(String resourceBase)
+    {
+        try
+        {
+            setBaseResource(newResource(resourceBase));
+        }
+        catch (Exception e)
+        {
+            LOG.warn(e.toString());
+            LOG.debug(e);
+            throw new IllegalArgumentException(resourceBase);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Returns the mimeTypes.
+     */
+    public MimeTypes getMimeTypes()
+    {
+        if (_mimeTypes == null)
+            _mimeTypes = new MimeTypes();
+        return _mimeTypes;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param mimeTypes
+     *            The mimeTypes to set.
+     */
+    public void setMimeTypes(MimeTypes mimeTypes)
+    {
+        _mimeTypes = mimeTypes;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     */
+    public void setWelcomeFiles(String[] files)
+    {
+        _welcomeFiles = files;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return The names of the files which the server should consider to be welcome files in this context.
+     * @see <a href="http://jcp.org/aboutJava/communityprocess/final/jsr154/index.html">The Servlet Specification</a>
+     * @see #setWelcomeFiles
+     */
+    @ManagedAttribute(value="Partial URIs of directory welcome files", readonly=true)
+    public String[] getWelcomeFiles()
+    {
+        return _welcomeFiles;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Returns the errorHandler.
+     */
+    @ManagedAttribute("The error handler to use for the context")
+    public ErrorHandler getErrorHandler()
+    {
+        return _errorHandler;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param errorHandler
+     *            The errorHandler to set.
+     */
+    public void setErrorHandler(ErrorHandler errorHandler)
+    {
+        if (errorHandler != null)
+            errorHandler.setServer(getServer());
+        updateBean(_errorHandler,errorHandler);
+        _errorHandler = errorHandler;
+    }
+
+    /* ------------------------------------------------------------ */
+    @ManagedAttribute("The maximum content size")
+    public int getMaxFormContentSize()
+    {
+        return _maxFormContentSize;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Set the maximum size of a form post, to protect against DOS attacks from large forms.
+     * @param maxSize
+     */
+    public void setMaxFormContentSize(int maxSize)
+    {
+        _maxFormContentSize = maxSize;
+    }
+
+    /* ------------------------------------------------------------ */
+    public int getMaxFormKeys()
+    {
+        return _maxFormKeys;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Set the maximum number of form Keys to protect against DOS attack from crafted hash keys.
+     * @param max
+     */
+    public void setMaxFormKeys(int max)
+    {
+        _maxFormKeys = max;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return True if URLs are compacted to replace multiple '/'s with a single '/'
+     */
+    public boolean isCompactPath()
+    {
+        return _compactPath;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param compactPath
+     *            True if URLs are compacted to replace multiple '/'s with a single '/'
+     */
+    public void setCompactPath(boolean compactPath)
+    {
+        _compactPath = compactPath;
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public String toString()
+    {
+        String[] vhosts = getVirtualHosts();
+
+        StringBuilder b = new StringBuilder();
+
+        Package pkg = getClass().getPackage();
+        if (pkg != null)
+        {
+            String p = pkg.getName();
+            if (p != null && p.length() > 0)
+            {
+                String[] ss = p.split("\\.");
+                for (String s : ss)
+                    b.append(s.charAt(0)).append('.');
+            }
+        }
+        b.append(getClass().getSimpleName()).append('@').append(Integer.toString(hashCode(),16));
+        b.append('{').append(getContextPath()).append(',').append(getBaseResource()).append(',').append(_availability);
+
+        if (vhosts != null && vhosts.length > 0)
+            b.append(',').append(vhosts[0]);
+        b.append('}');
+
+        return b.toString();
+    }
+
+    /* ------------------------------------------------------------ */
+    public synchronized Class<?> loadClass(String className) throws ClassNotFoundException
+    {
+        if (className == null)
+            return null;
+
+        if (_classLoader == null)
+            return Loader.loadClass(this.getClass(),className);
+
+        return _classLoader.loadClass(className);
+    }
+
+    /* ------------------------------------------------------------ */
+    public void addLocaleEncoding(String locale, String encoding)
+    {
+        if (_localeEncodingMap == null)
+            _localeEncodingMap = new HashMap<String, String>();
+        _localeEncodingMap.put(locale,encoding);
+    }
+
+    /* ------------------------------------------------------------ */
+    public String getLocaleEncoding(String locale)
+    {
+        if (_localeEncodingMap == null)
+            return null;
+        String encoding = _localeEncodingMap.get(locale);
+        return encoding;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Get the character encoding for a locale. The full locale name is first looked up in the map of encodings. If no encoding is found, then the locale
+     * language is looked up.
+     *
+     * @param locale
+     *            a <code>Locale</code> value
+     * @return a <code>String</code> representing the character encoding for the locale or null if none found.
+     */
+    public String getLocaleEncoding(Locale locale)
+    {
+        if (_localeEncodingMap == null)
+            return null;
+        String encoding = _localeEncodingMap.get(locale.toString());
+        if (encoding == null)
+            encoding = _localeEncodingMap.get(locale.getLanguage());
+        return encoding;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** 
+     * Get all of the locale encodings
+     * 
+     * @return a map of all the locale encodings: key is name of the locale and value is the char encoding
+     */
+    public Map<String,String> getLocaleEncodings()
+    {
+        if (_localeEncodingMap == null)
+            return null;
+        return Collections.unmodifiableMap(_localeEncodingMap);
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     */
+    public Resource getResource(String path) throws MalformedURLException
+    {
+        if (path == null || !path.startsWith(URIUtil.SLASH))
+            throw new MalformedURLException(path);
+
+        if (_baseResource == null)
+            return null;
+
+        try
+        {
+            path = URIUtil.canonicalPath(path);
+            Resource resource = _baseResource.addPath(path);
+            
+            if (checkAlias(path,resource))
+                return resource;
+            return null;
+        }
+        catch (Exception e)
+        {
+            LOG.ignore(e);
+        }
+
+        return null;
+    }
+
+    /* ------------------------------------------------------------ */
+    public boolean checkAlias(String path, Resource resource)
+    {
+        // Is the resource aliased?
+            if (resource.getAlias() != null)
+        {
+            if (LOG.isDebugEnabled())
+                LOG.debug("Aliased resource: " + resource + "~=" + resource.getAlias());
+
+            // alias checks
+            for (Iterator<AliasCheck> i=_aliasChecks.iterator();i.hasNext();)
+            {
+                AliasCheck check = i.next();
+                if (check.check(path,resource))
+                {
+                    if (LOG.isDebugEnabled())
+                        LOG.debug("Aliased resource: " + resource + " approved by " + check);
+                    return true;
+                }
+            }
+            return false;
+        }
+        return true;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * Convert URL to Resource wrapper for {@link Resource#newResource(URL)} enables extensions to provide alternate resource implementations.
+     */
+    public Resource newResource(URL url) throws IOException
+    {
+        return Resource.newResource(url);
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * Convert URL to Resource wrapper for {@link Resource#newResource(URL)} enables extensions to provide alternate resource implementations.
+     */
+    public Resource newResource(URI uri) throws IOException
+    {
+        return Resource.newResource(uri);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Convert a URL or path to a Resource. The default implementation is a wrapper for {@link Resource#newResource(String)}.
+     *
+     * @param urlOrPath
+     *            The URL or path to convert
+     * @return The Resource for the URL/path
+     * @throws IOException
+     *             The Resource could not be created.
+     */
+    public Resource newResource(String urlOrPath) throws IOException
+    {
+        return Resource.newResource(urlOrPath);
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     */
+    public Set<String> getResourcePaths(String path)
+    {
+        try
+        {
+            path = URIUtil.canonicalPath(path);
+            Resource resource = getResource(path);
+
+            if (resource != null && resource.exists())
+            {
+                if (!path.endsWith(URIUtil.SLASH))
+                    path = path + URIUtil.SLASH;
+
+                String[] l = resource.list();
+                if (l != null)
+                {
+                    HashSet<String> set = new HashSet<String>();
+                    for (int i = 0; i < l.length; i++)
+                        set.add(path + l[i]);
+                    return set;
+                }
+            }
+        }
+        catch (Exception e)
+        {
+            LOG.ignore(e);
+        }
+        return Collections.emptySet();
+    }
+
+    /* ------------------------------------------------------------ */
+    private String normalizeHostname(String host)
+    {
+        if (host == null)
+            return null;
+
+        if (host.endsWith("."))
+            return host.substring(0,host.length() - 1);
+
+        return host;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Add an AliasCheck instance to possibly permit aliased resources
+     * @param check The alias checker
+     */
+    public void addAliasCheck(AliasCheck check)
+    {
+        _aliasChecks.add(check);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Mutable list of Alias checks
+     */
+    public List<AliasCheck> getAliasChecks()
+    {
+        return _aliasChecks;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * @param checks list of AliasCheck instances
+     */
+    public void setAliasChecks(List<AliasCheck> checks)
+    {
+        _aliasChecks.clear();
+        _aliasChecks.addAll(checks);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Context.
+     * <p>
+     * A partial implementation of {@link javax.servlet.ServletContext}. A complete implementation is provided by the derived {@link ContextHandler}.
+     * </p>
+     *
+     *
+     */
+    public class Context extends NoContext
+    {
+        protected boolean _enabled = true; //whether or not the dynamic API is enabled for callers
+        protected boolean _extendedListenerTypes = false;
+
+
+        /* ------------------------------------------------------------ */
+        protected Context()
+        {
+        }
+
+        /* ------------------------------------------------------------ */
+        public ContextHandler getContextHandler()
+        {
+            return ContextHandler.this;
+        }
+
+        /* ------------------------------------------------------------ */
+        /*
+         * @see javax.servlet.ServletContext#getContext(java.lang.String)
+         */
+        @Override
+        public ServletContext getContext(String uripath)
+        {
+            List<ContextHandler> contexts = new ArrayList<ContextHandler>();
+            Handler[] handlers = getServer().getChildHandlersByClass(ContextHandler.class);
+            String matched_path = null;
+
+            for (Handler handler : handlers)
+            {
+                if (handler == null)
+                    continue;
+                ContextHandler ch = (ContextHandler)handler;
+                String context_path = ch.getContextPath();
+
+                if (uripath.equals(context_path) || (uripath.startsWith(context_path) && uripath.charAt(context_path.length()) == '/')
+                        || "/".equals(context_path))
+                {
+                    // look first for vhost matching context only
+                    if (getVirtualHosts() != null && getVirtualHosts().length > 0)
+                    {
+                        if (ch.getVirtualHosts() != null && ch.getVirtualHosts().length > 0)
+                        {
+                            for (String h1 : getVirtualHosts())
+                                for (String h2 : ch.getVirtualHosts())
+                                    if (h1.equals(h2))
+                                    {
+                                        if (matched_path == null || context_path.length() > matched_path.length())
+                                        {
+                                            contexts.clear();
+                                            matched_path = context_path;
+                                        }
+
+                                        if (matched_path.equals(context_path))
+                                            contexts.add(ch);
+                                    }
+                        }
+                    }
+                    else
+                    {
+                        if (matched_path == null || context_path.length() > matched_path.length())
+                        {
+                            contexts.clear();
+                            matched_path = context_path;
+                        }
+
+                        if (matched_path.equals(context_path))
+                            contexts.add(ch);
+                    }
+                }
+            }
+
+            if (contexts.size() > 0)
+                return contexts.get(0)._scontext;
+
+            // try again ignoring virtual hosts
+            matched_path = null;
+            for (Handler handler : handlers)
+            {
+                if (handler == null)
+                    continue;
+                ContextHandler ch = (ContextHandler)handler;
+                String context_path = ch.getContextPath();
+
+                if (uripath.equals(context_path) || (uripath.startsWith(context_path) && uripath.charAt(context_path.length()) == '/')
+                        || "/".equals(context_path))
+                {
+                    if (matched_path == null || context_path.length() > matched_path.length())
+                    {
+                        contexts.clear();
+                        matched_path = context_path;
+                    }
+
+                    if (matched_path.equals(context_path))
+                        contexts.add(ch);
+                }
+            }
+
+            if (contexts.size() > 0)
+                return contexts.get(0)._scontext;
+            return null;
+        }
+
+        /* ------------------------------------------------------------ */
+        /*
+         * @see javax.servlet.ServletContext#getMimeType(java.lang.String)
+         */
+        @Override
+        public String getMimeType(String file)
+        {
+            if (_mimeTypes == null)
+                return null;
+            return _mimeTypes.getMimeByExtension(file);
+        }
+
+        /* ------------------------------------------------------------ */
+        /*
+         * @see javax.servlet.ServletContext#getRequestDispatcher(java.lang.String)
+         */
+        @Override
+        public RequestDispatcher getRequestDispatcher(String uriInContext)
+        {
+            if (uriInContext == null)
+                return null;
+
+            if (!uriInContext.startsWith("/"))
+                return null;
+
+            try
+            {
+                String query = null;
+                int q = 0;
+                if ((q = uriInContext.indexOf('?')) > 0)
+                {
+                    query = uriInContext.substring(q + 1);
+                    uriInContext = uriInContext.substring(0,q);
+                }
+
+                String pathInContext = URIUtil.canonicalPath(URIUtil.decodePath(uriInContext));
+                if (pathInContext!=null)
+                {
+                    String uri = URIUtil.addPaths(getContextPath(),uriInContext);
+                    ContextHandler context = ContextHandler.this;
+                    return new Dispatcher(context,uri,pathInContext,query);
+                }
+            }
+            catch (Exception e)
+            {
+                LOG.ignore(e);
+            }
+            return null;
+        }
+
+        /* ------------------------------------------------------------ */
+        /*
+         * @see javax.servlet.ServletContext#getRealPath(java.lang.String)
+         */
+        @Override
+        public String getRealPath(String path)
+        {
+            if (path == null)
+                return null;
+            if (path.length() == 0)
+                path = URIUtil.SLASH;
+            else if (path.charAt(0) != '/')
+                path = URIUtil.SLASH + path;
+
+            try
+            {
+                Resource resource = ContextHandler.this.getResource(path);
+                if (resource != null)
+                {
+                    File file = resource.getFile();
+                    if (file != null)
+                        return file.getCanonicalPath();
+                }
+            }
+            catch (Exception e)
+            {
+                LOG.ignore(e);
+            }
+
+            return null;
+        }
+
+        /* ------------------------------------------------------------ */
+        @Override
+        public URL getResource(String path) throws MalformedURLException
+        {
+            Resource resource = ContextHandler.this.getResource(path);
+            if (resource != null && resource.exists())
+                return resource.getURL();
+            return null;
+        }
+
+        /* ------------------------------------------------------------ */
+        /*
+         * @see javax.servlet.ServletContext#getResourceAsStream(java.lang.String)
+         */
+        @Override
+        public InputStream getResourceAsStream(String path)
+        {
+            try
+            {
+                URL url = getResource(path);
+                if (url == null)
+                    return null;
+                Resource r = Resource.newResource(url);
+                return r.getInputStream();
+            }
+            catch (Exception e)
+            {
+                LOG.ignore(e);
+                return null;
+            }
+        }
+
+        /* ------------------------------------------------------------ */
+        /*
+         * @see javax.servlet.ServletContext#getResourcePaths(java.lang.String)
+         */
+        @Override
+        public Set<String> getResourcePaths(String path)
+        {
+            return ContextHandler.this.getResourcePaths(path);
+        }
+
+        /* ------------------------------------------------------------ */
+        /*
+         * @see javax.servlet.ServletContext#log(java.lang.Exception, java.lang.String)
+         */
+        @Override
+        public void log(Exception exception, String msg)
+        {
+            _logger.warn(msg,exception);
+        }
+
+        /* ------------------------------------------------------------ */
+        /*
+         * @see javax.servlet.ServletContext#log(java.lang.String)
+         */
+        @Override
+        public void log(String msg)
+        {
+            _logger.info(msg);
+        }
+
+        /* ------------------------------------------------------------ */
+        /*
+         * @see javax.servlet.ServletContext#log(java.lang.String, java.lang.Throwable)
+         */
+        @Override
+        public void log(String message, Throwable throwable)
+        {
+            _logger.warn(message,throwable);
+        }
+
+        /* ------------------------------------------------------------ */
+        /*
+         * @see javax.servlet.ServletContext#getInitParameter(java.lang.String)
+         */
+        @Override
+        public String getInitParameter(String name)
+        {
+            return ContextHandler.this.getInitParameter(name);
+        }
+
+        /* ------------------------------------------------------------ */
+        /*
+         * @see javax.servlet.ServletContext#getInitParameterNames()
+         */
+        @SuppressWarnings("unchecked")
+        @Override
+        public Enumeration<String> getInitParameterNames()
+        {
+            return ContextHandler.this.getInitParameterNames();
+        }
+
+        /* ------------------------------------------------------------ */
+        /*
+         * @see javax.servlet.ServletContext#getAttribute(java.lang.String)
+         */
+        @Override
+        public synchronized Object getAttribute(String name)
+        {
+            Object o = ContextHandler.this.getAttribute(name);
+            if (o == null)
+                o = super.getAttribute(name);
+            return o;
+        }
+
+        /* ------------------------------------------------------------ */
+        /*
+         * @see javax.servlet.ServletContext#getAttributeNames()
+         */
+        @Override
+        public synchronized Enumeration<String> getAttributeNames()
+        {
+            HashSet<String> set = new HashSet<String>();
+            Enumeration<String> e = super.getAttributeNames();
+            while (e.hasMoreElements())
+                set.add(e.nextElement());
+            e = _attributes.getAttributeNames();
+            while (e.hasMoreElements())
+                set.add(e.nextElement());
+
+            return Collections.enumeration(set);
+        }
+
+        /* ------------------------------------------------------------ */
+        /*
+         * @see javax.servlet.ServletContext#setAttribute(java.lang.String, java.lang.Object)
+         */
+        @Override
+        public synchronized void setAttribute(String name, Object value)
+        {
+            checkManagedAttribute(name,value);
+            Object old_value = super.getAttribute(name);
+
+            if (value == null)
+                super.removeAttribute(name);
+            else
+                super.setAttribute(name,value);
+
+            if (!_contextAttributeListeners.isEmpty())
+            {
+                ServletContextAttributeEvent event = new ServletContextAttributeEvent(_scontext,name,old_value == null?value:old_value);
+
+                for (ServletContextAttributeListener l : _contextAttributeListeners)
+                {
+                    if (old_value == null)
+                        l.attributeAdded(event);
+                    else if (value == null)
+                        l.attributeRemoved(event);
+                    else
+                        l.attributeReplaced(event);
+                }
+            }
+        }
+
+        /* ------------------------------------------------------------ */
+        /*
+         * @see javax.servlet.ServletContext#removeAttribute(java.lang.String)
+         */
+        @Override
+        public synchronized void removeAttribute(String name)
+        {
+            checkManagedAttribute(name,null);
+
+            Object old_value = super.getAttribute(name);
+            super.removeAttribute(name);
+            if (old_value != null &&!_contextAttributeListeners.isEmpty())
+            {
+                ServletContextAttributeEvent event = new ServletContextAttributeEvent(_scontext,name,old_value);
+
+                for (ServletContextAttributeListener l : _contextAttributeListeners)
+                    l.attributeRemoved(event);
+            }
+        }
+
+        /* ------------------------------------------------------------ */
+        /*
+         * @see javax.servlet.ServletContext#getServletContextName()
+         */
+        @Override
+        public String getServletContextName()
+        {
+            String name = ContextHandler.this.getDisplayName();
+            if (name == null)
+                name = ContextHandler.this.getContextPath();
+            return name;
+        }
+
+        /* ------------------------------------------------------------ */
+        @Override
+        public String getContextPath()
+        {
+            if ((_contextPath != null) && _contextPath.equals(URIUtil.SLASH))
+                return "";
+
+            return _contextPath;
+        }
+
+        /* ------------------------------------------------------------ */
+        @Override
+        public String toString()
+        {
+            return "ServletContext@" + ContextHandler.this.toString();
+        }
+
+        /* ------------------------------------------------------------ */
+        @Override
+        public boolean setInitParameter(String name, String value)
+        {
+            if (ContextHandler.this.getInitParameter(name) != null)
+                return false;
+            ContextHandler.this.getInitParams().put(name,value);
+            return true;
+        }
+
+        @Override
+        public void addListener(String className)
+        {
+            if (!_enabled)
+                throw new UnsupportedOperationException();
+
+            try
+            {
+                Class<? extends EventListener> clazz = (Class<? extends EventListener>) (_classLoader==null?Loader.loadClass(ContextHandler.class,className):_classLoader.loadClass(className));
+                addListener(clazz);
+            }
+            catch (ClassNotFoundException e)
+            {
+                throw new IllegalArgumentException(e);
+            }
+        }
+
+        @Override
+        public <T extends EventListener> void addListener(T t)
+        {
+            if (!_enabled)
+                throw new UnsupportedOperationException();
+
+            checkListener(t.getClass());
+
+            ContextHandler.this.addEventListener(t);
+            ContextHandler.this.addProgrammaticListener(t);
+        }
+
+        @Override
+        public void addListener(Class<? extends EventListener> listenerClass)
+        {
+            if (!_enabled)
+                throw new UnsupportedOperationException();
+
+            try
+            {
+                EventListener e = createListener(listenerClass);
+                addListener(e);
+            }
+            catch (ServletException e)
+            {
+                throw new IllegalArgumentException(e);
+            }
+        }
+
+        @Override
+        public <T extends EventListener> T createListener(Class<T> clazz) throws ServletException
+        {
+            try
+            {
+                return createInstance(clazz);
+            }
+            catch (Exception e)
+            {
+                throw new ServletException(e);
+            }
+        }
+
+
+        public void checkListener (Class<? extends EventListener> listener) throws IllegalStateException
+        {
+            boolean ok = false;
+            int startIndex = (isExtendedListenerTypes()?EXTENDED_LISTENER_TYPE_INDEX:DEFAULT_LISTENER_TYPE_INDEX);
+            for (int i=startIndex;i<SERVLET_LISTENER_TYPES.length;i++)
+            {
+                if (SERVLET_LISTENER_TYPES[i].isAssignableFrom(listener))
+                {
+                    ok = true;
+                    break;
+                }
+            }
+            if (!ok)
+                throw new IllegalArgumentException("Inappropriate listener class "+listener.getName());
+        }
+
+        public void setExtendedListenerTypes (boolean extended)
+        {
+           _extendedListenerTypes = extended;
+        }
+
+       public boolean isExtendedListenerTypes()
+       {
+           return _extendedListenerTypes;
+       }
+
+
+       @Override
+       public ClassLoader getClassLoader()
+       {
+           if (!_enabled)
+               throw new UnsupportedOperationException();
+           
+           //no security manager just return the classloader
+           if (System.getSecurityManager() == null)
+               return _classLoader;
+           else
+           {
+               //check to see if the classloader of the caller is the same as the context
+               //classloader, or a parent of it
+               try
+               {
+                   Class reflect = Loader.loadClass(getClass(), "sun.reflect.Reflection");
+                   Method getCallerClass = reflect.getMethod("getCallerClass", Integer.TYPE);
+                   Class caller = (Class)getCallerClass.invoke(null, 2);
+
+                   boolean ok = false;
+                   ClassLoader callerLoader = caller.getClassLoader();
+                   while (!ok && callerLoader != null)
+                   {
+                       if (callerLoader == _classLoader) 
+                           ok = true;
+                       else
+                           callerLoader = callerLoader.getParent();    
+                   }
+
+                   if (ok)
+                       return _classLoader;
+               }
+               catch (Exception e)      
+               {
+                   LOG.warn("Unable to check classloader of caller",e);
+               }
+              
+               AccessController.checkPermission(new RuntimePermission("getClassLoader"));
+               return _classLoader;
+           }
+        }
+
+        @Override
+        public JspConfigDescriptor getJspConfigDescriptor()
+        {
+            LOG.warn(__unimplmented);
+            return null;
+        }
+
+        public void setJspConfigDescriptor(JspConfigDescriptor d)
+        {
+
+        }
+
+        @Override
+        public void declareRoles(String... roleNames)
+        {
+            if (!isStarting())
+                throw new IllegalStateException ();
+            if (!_enabled)
+                throw new UnsupportedOperationException();
+        }
+
+        public void setEnabled(boolean enabled)
+        {
+            _enabled = enabled;
+        }
+
+        public boolean isEnabled()
+        {
+            return _enabled;
+        }
+
+
+
+        public <T> T createInstance (Class<T> clazz) throws Exception
+        {
+            T o = clazz.newInstance();
+            return o;
+        }
+    }
+
+
+    public static class NoContext extends AttributesMap implements ServletContext
+    {
+        private int _effectiveMajorVersion = SERVLET_MAJOR_VERSION;
+        private int _effectiveMinorVersion = SERVLET_MINOR_VERSION;
+
+        /* ------------------------------------------------------------ */
+        public NoContext()
+        {
+        }
+
+        @Override
+        public ServletContext getContext(String uripath)
+        {
+            return null;
+        }
+
+        @Override
+        public int getMajorVersion()
+        {
+            return SERVLET_MAJOR_VERSION;
+        }
+
+        @Override
+        public String getMimeType(String file)
+        {
+            return null;
+        }
+
+        @Override
+        public int getMinorVersion()
+        {
+            return SERVLET_MINOR_VERSION;
+        }
+
+        @Override
+        public RequestDispatcher getNamedDispatcher(String name)
+        {
+            return null;
+        }
+
+        @Override
+        public RequestDispatcher getRequestDispatcher(String uriInContext)
+        {
+            return null;
+        }
+
+        @Override
+        public String getRealPath(String path)
+        {
+            return null;
+        }
+
+        @Override
+        public URL getResource(String path) throws MalformedURLException
+        {
+            return null;
+        }
+
+        @Override
+        public InputStream getResourceAsStream(String path)
+        {
+            return null;
+        }
+
+        @Override
+        public Set<String> getResourcePaths(String path)
+        {
+            return null;
+        }
+
+        @Override
+        public String getServerInfo()
+        {
+            return "jetty/" + Server.getVersion();
+        }
+
+        @Override
+        @Deprecated
+        public Servlet getServlet(String name) throws ServletException
+        {
+            return null;
+        }
+
+        @SuppressWarnings("unchecked")
+        @Override
+        @Deprecated
+        public Enumeration<String> getServletNames()
+        {
+            return Collections.enumeration(Collections.EMPTY_LIST);
+        }
+
+        @SuppressWarnings("unchecked")
+        @Override
+        @Deprecated
+        public Enumeration<Servlet> getServlets()
+        {
+            return Collections.enumeration(Collections.EMPTY_LIST);
+        }
+
+        @Override
+        public void log(Exception exception, String msg)
+        {
+            LOG.warn(msg,exception);
+        }
+
+        @Override
+        public void log(String msg)
+        {
+            LOG.info(msg);
+        }
+
+        @Override
+        public void log(String message, Throwable throwable)
+        {
+            LOG.warn(message,throwable);
+        }
+
+        @Override
+        public String getInitParameter(String name)
+        {
+            return null;
+        }
+
+        @SuppressWarnings("unchecked")
+        @Override
+        public Enumeration<String> getInitParameterNames()
+        {
+            return Collections.enumeration(Collections.EMPTY_LIST);
+        }
+
+
+        @Override
+        public String getServletContextName()
+        {
+            return "No Context";
+        }
+
+        @Override
+        public String getContextPath()
+        {
+            return null;
+        }
+
+
+        @Override
+        public boolean setInitParameter(String name, String value)
+        {
+            return false;
+        }
+
+        @Override
+        public Dynamic addFilter(String filterName, Class<? extends Filter> filterClass)
+        {
+            LOG.warn(__unimplmented);
+            return null;
+        }
+
+        @Override
+        public Dynamic addFilter(String filterName, Filter filter)
+        {
+            LOG.warn(__unimplmented);
+            return null;
+        }
+
+        @Override
+        public Dynamic addFilter(String filterName, String className)
+        {
+            LOG.warn(__unimplmented);
+            return null;
+        }
+
+        @Override
+        public javax.servlet.ServletRegistration.Dynamic addServlet(String servletName, Class<? extends Servlet> servletClass)
+        {
+            LOG.warn(__unimplmented);
+            return null;
+        }
+
+        @Override
+        public javax.servlet.ServletRegistration.Dynamic addServlet(String servletName, Servlet servlet)
+        {
+            LOG.warn(__unimplmented);
+            return null;
+        }
+
+        @Override
+        public javax.servlet.ServletRegistration.Dynamic addServlet(String servletName, String className)
+        {
+            LOG.warn(__unimplmented);
+            return null;
+        }
+
+        @Override
+        public <T extends Filter> T createFilter(Class<T> c) throws ServletException
+        {
+            LOG.warn(__unimplmented);
+            return null;
+        }
+
+        @Override
+        public <T extends Servlet> T createServlet(Class<T> c) throws ServletException
+        {
+            LOG.warn(__unimplmented);
+            return null;
+        }
+
+        @Override
+        public Set<SessionTrackingMode> getDefaultSessionTrackingModes()
+        {
+            LOG.warn(__unimplmented);
+            return null;
+        }
+
+        @Override
+        public Set<SessionTrackingMode> getEffectiveSessionTrackingModes()
+        {
+            LOG.warn(__unimplmented);
+            return null;
+        }
+
+        @Override
+        public FilterRegistration getFilterRegistration(String filterName)
+        {
+            LOG.warn(__unimplmented);
+            return null;
+        }
+
+        @Override
+        public Map<String, ? extends FilterRegistration> getFilterRegistrations()
+        {
+            LOG.warn(__unimplmented);
+            return null;
+        }
+
+        @Override
+        public ServletRegistration getServletRegistration(String servletName)
+        {
+            LOG.warn(__unimplmented);
+            return null;
+        }
+
+        @Override
+        public Map<String, ? extends ServletRegistration> getServletRegistrations()
+        {
+            LOG.warn(__unimplmented);
+            return null;
+        }
+
+        @Override
+        public SessionCookieConfig getSessionCookieConfig()
+        {
+            LOG.warn(__unimplmented);
+            return null;
+        }
+
+        @Override
+        public void setSessionTrackingModes(Set<SessionTrackingMode> sessionTrackingModes)
+        {
+            LOG.warn(__unimplmented);
+        }
+
+        @Override
+        public void addListener(String className)
+        {
+            LOG.warn(__unimplmented);
+        }
+
+        @Override
+        public <T extends EventListener> void addListener(T t)
+        {
+            LOG.warn(__unimplmented);
+        }
+
+        @Override
+        public void addListener(Class<? extends EventListener> listenerClass)
+        {
+            LOG.warn(__unimplmented);
+        }
+
+        @Override
+        public <T extends EventListener> T createListener(Class<T> clazz) throws ServletException
+        {
+            try
+            {
+                return clazz.newInstance();
+            }
+            catch (InstantiationException e)
+            {
+                throw new ServletException(e);
+            }
+            catch (IllegalAccessException e)
+            {
+                throw new ServletException(e);
+            }
+        }
+
+        @Override
+        public ClassLoader getClassLoader()
+        {
+            AccessController.checkPermission(new RuntimePermission("getClassLoader"));
+            return ContextHandler.class.getClassLoader();
+        }
+
+        @Override
+        public int getEffectiveMajorVersion()
+        {
+            return _effectiveMajorVersion;
+        }
+
+        @Override
+        public int getEffectiveMinorVersion()
+        {
+            return _effectiveMinorVersion;
+        }
+
+        public void setEffectiveMajorVersion (int v)
+        {
+            _effectiveMajorVersion = v;
+        }
+
+        public void setEffectiveMinorVersion (int v)
+        {
+            _effectiveMinorVersion = v;
+        }
+
+        @Override
+        public JspConfigDescriptor getJspConfigDescriptor()
+        {
+            LOG.warn(__unimplmented);
+            return null;
+        }
+
+        @Override
+        public void declareRoles(String... roleNames)
+        {
+            LOG.warn(__unimplmented);
+        }
+
+        /**
+         * @see javax.servlet.ServletContext#getVirtualServerName()
+         */
+        @Override
+        public String getVirtualServerName()
+        {
+            // TODO 3.1 Auto-generated method stub
+            return null;
+        }
+    }
+
+
+    /* ------------------------------------------------------------ */
+    /** Interface to check aliases
+     */
+    public interface AliasCheck
+    {
+        /* ------------------------------------------------------------ */
+        /** Check an alias
+         * @param path The path the aliased resource was created for
+         * @param resource The aliased resourced
+         * @return True if the resource is OK to be served.
+         */
+        boolean check(String path, Resource resource);
+    }
+
+
+    /* ------------------------------------------------------------ */
+    /** Approve all aliases.
+     */
+    public static class ApproveAliases implements AliasCheck
+    {
+        @Override
+        public boolean check(String path, Resource resource)
+        {
+            return true;
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Approve Aliases with same suffix.
+     * Eg. a symbolic link from /foobar.html to /somewhere/wibble.html would be
+     * approved because both the resource and alias end with ".html".
+     */
+    @Deprecated
+    public static class ApproveSameSuffixAliases implements AliasCheck
+    {
+        {
+            LOG.warn("ApproveSameSuffixAlias is not safe for production");
+        }
+        
+        @Override
+        public boolean check(String path, Resource resource)
+        {
+            int dot = path.lastIndexOf('.');
+            if (dot<0)
+                return false;
+            String suffix=path.substring(dot);
+            return resource.toString().endsWith(suffix);
+        }
+    }
+
+
+    /* ------------------------------------------------------------ */
+    /** Approve Aliases with a path prefix.
+     * Eg. a symbolic link from /dirA/foobar.html to /dirB/foobar.html would be
+     * approved because both the resource and alias end with "/foobar.html".
+     */
+    @Deprecated
+    public static class ApprovePathPrefixAliases implements AliasCheck
+    {
+        {
+            LOG.warn("ApprovePathPrefixAliases is not safe for production");
+        }
+        
+        @Override
+        public boolean check(String path, Resource resource)
+        {
+            int slash = path.lastIndexOf('/');
+            if (slash<0 || slash==path.length()-1)
+                return false;
+            String suffix=path.substring(slash);
+            return resource.toString().endsWith(suffix);
+        }
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** Approve Aliases of a non existent directory.
+     * If a directory "/foobar/" does not exist, then the resource is
+     * aliased to "/foobar".  Accept such aliases.
+     */
+    public static class ApproveNonExistentDirectoryAliases implements AliasCheck
+    {
+        @Override
+        public boolean check(String path, Resource resource)
+        {
+            if (resource.exists())
+                return false;
+            
+            String a=resource.getAlias().toString();
+            String r=resource.getURL().toString();
+            
+            if (a.length()>r.length())
+                return a.startsWith(r) && a.length()==r.length()+1 && a.endsWith("/");
+            if (a.length()<r.length())
+                return r.startsWith(a) && r.length()==a.length()+1 && r.endsWith("/");
+            
+            return a.equals(r); 
+        }
+    }
+
+}
diff --git a/lib/jetty/org/eclipse/jetty/server/handler/ContextHandlerCollection.java b/lib/jetty/org/eclipse/jetty/server/handler/ContextHandlerCollection.java
new file mode 100644 (file)
index 0000000..4ade364
--- /dev/null
@@ -0,0 +1,267 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server.handler;
+
+import java.io.IOException;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.server.Handler;
+import org.eclipse.jetty.server.HandlerContainer;
+import org.eclipse.jetty.server.HttpChannelState;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.util.ArrayTernaryTrie;
+import org.eclipse.jetty.util.ArrayUtil;
+import org.eclipse.jetty.util.Trie;
+import org.eclipse.jetty.util.annotation.ManagedObject;
+import org.eclipse.jetty.util.annotation.ManagedOperation;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/* ------------------------------------------------------------ */
+/** ContextHandlerCollection.
+ *
+ * This {@link org.eclipse.jetty.server.handler.HandlerCollection} is creates a
+ * {@link org.eclipse.jetty.http.PathMap} to it's contained handlers based
+ * on the context path and virtual hosts of any contained {@link org.eclipse.jetty.server.handler.ContextHandler}s.
+ * The contexts do not need to be directly contained, only children of the contained handlers.
+ * Multiple contexts may have the same context path and they are called in order until one
+ * handles the request.
+ *
+ */
+@ManagedObject("Context Handler Collection")
+public class ContextHandlerCollection extends HandlerCollection
+{
+    private static final Logger LOG = Log.getLogger(ContextHandlerCollection.class);
+
+    private volatile Trie<ContextHandler[]> _contexts;
+    private Class<? extends ContextHandler> _contextClass = ContextHandler.class;
+
+    /* ------------------------------------------------------------ */
+    public ContextHandlerCollection()
+    {
+        super(true);
+    }
+
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Remap the context paths.
+     */
+    @ManagedOperation("update the mapping of context path to context")
+    public void mapContexts()
+    {
+        int capacity=512;
+        
+        // Loop until we have a big enough trie to hold all the context paths
+        Trie<ContextHandler[]> trie;
+        loop: while(true)
+        {
+            trie=new ArrayTernaryTrie<>(false,capacity);
+
+            Handler[] branches = getHandlers();
+
+            // loop over each group of contexts
+            for (int b=0;branches!=null && b<branches.length;b++)
+            {
+                Handler[] handlers=null;
+
+                if (branches[b] instanceof ContextHandler)
+                {
+                    handlers = new Handler[]{ branches[b] };
+                }
+                else if (branches[b] instanceof HandlerContainer)
+                {
+                    handlers = ((HandlerContainer)branches[b]).getChildHandlersByClass(ContextHandler.class);
+                }
+                else
+                    continue;
+
+                // for each context handler in a group
+                for (int i=0;handlers!=null && i<handlers.length;i++)
+                {
+                    ContextHandler handler=(ContextHandler)handlers[i];
+                    String contextPath=handler.getContextPath().substring(1);
+                    ContextHandler[] contexts=trie.get(contextPath);
+                    
+                    if (!trie.put(contextPath,ArrayUtil.addToArray(contexts,handler,ContextHandler.class)))
+                    {
+                        capacity+=512;
+                        continue loop;
+                    }
+                }
+            }
+            
+            break;
+        }
+        
+        // Sort the contexts so those with virtual hosts are considered before those without
+        for (String ctx : trie.keySet())
+        {
+            ContextHandler[] contexts=trie.get(ctx);
+            ContextHandler[] sorted=new ContextHandler[contexts.length];
+            int i=0;
+            for (ContextHandler handler:contexts)
+                if (handler.getVirtualHosts()!=null && handler.getVirtualHosts().length>0)
+                    sorted[i++]=handler;
+            for (ContextHandler handler:contexts)
+                if (handler.getVirtualHosts()==null || handler.getVirtualHosts().length==0)
+                    sorted[i++]=handler;
+            trie.put(ctx,sorted);
+        }
+
+        //for (String ctx : trie.keySet())
+        //    System.err.printf("'%s'->%s%n",ctx,Arrays.asList(trie.get(ctx)));
+        _contexts=trie;
+    }
+
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see org.eclipse.jetty.server.server.handler.HandlerCollection#setHandlers(org.eclipse.jetty.server.server.Handler[])
+     */
+    @Override
+    public void setHandlers(Handler[] handlers)
+    {
+        super.setHandlers(handlers);
+        if (isStarted())
+            mapContexts();
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    protected void doStart() throws Exception
+    {
+        mapContexts();
+        super.doStart();
+    }
+
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see org.eclipse.jetty.server.server.Handler#handle(java.lang.String, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, int)
+     */
+    @Override
+    public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+    {
+        Handler[] handlers = getHandlers();
+        if (handlers==null || handlers.length==0)
+           return;
+
+       HttpChannelState async = baseRequest.getHttpChannelState();
+       if (async.isAsync())
+       {
+           ContextHandler context=async.getContextHandler();
+           if (context!=null)
+           {
+               context.handle(target,baseRequest,request, response);
+               return;
+           }
+       }
+
+       // data structure which maps a request to a context; first-best match wins
+       // { context path => [ context ] }
+       // }
+       if (target.startsWith("/"))
+       {
+           int limit = target.length()-1;
+
+           while (limit>=0)
+           {
+               // Get best match
+               ContextHandler[] contexts = _contexts.getBest(target,1,limit);
+               if (contexts==null)
+                   break;
+
+               int l=contexts[0].getContextPath().length();
+               if (l==1 || target.length()==l || target.charAt(l)=='/')
+               {
+                   for (ContextHandler handler : contexts)
+                   {
+                       handler.handle(target,baseRequest, request, response);
+                       if (baseRequest.isHandled())
+                           return;
+                   }
+               }
+               
+               limit=l-2;
+           }
+       }
+       else
+       {
+            // This may not work in all circumstances... but then I think it should never be called
+           for (int i=0;i<handlers.length;i++)
+           {
+               handlers[i].handle(target,baseRequest, request, response);
+               if ( baseRequest.isHandled())
+                   return;
+           }
+       }
+    }
+
+
+    /* ------------------------------------------------------------ */
+    /** Add a context handler.
+     * @param contextPath  The context path to add
+     * @return the ContextHandler just added
+     */
+    public ContextHandler addContext(String contextPath,String resourceBase)
+    {
+        try
+        {
+            ContextHandler context = _contextClass.newInstance();
+            context.setContextPath(contextPath);
+            context.setResourceBase(resourceBase);
+            addHandler(context);
+            return context;
+        }
+        catch (Exception e)
+        {
+            LOG.debug(e);
+            throw new Error(e);
+        }
+    }
+
+
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return The class to use to add new Contexts
+     */
+    public Class<?> getContextClass()
+    {
+        return _contextClass;
+    }
+
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param contextClass The class to use to add new Contexts
+     */
+    public void setContextClass(Class<? extends ContextHandler> contextClass)
+    {
+        if (contextClass ==null || !(ContextHandler.class.isAssignableFrom(contextClass)))
+            throw new IllegalArgumentException();
+        _contextClass = contextClass;
+    }
+
+
+}
diff --git a/lib/jetty/org/eclipse/jetty/server/handler/DebugHandler.java b/lib/jetty/org/eclipse/jetty/server/handler/DebugHandler.java
new file mode 100644 (file)
index 0000000..0ed7261
--- /dev/null
@@ -0,0 +1,184 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server.handler;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.PrintStream;
+import java.util.Locale;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.io.Connection;
+import org.eclipse.jetty.server.AbstractConnector;
+import org.eclipse.jetty.server.Connector;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.Response;
+import org.eclipse.jetty.util.DateCache;
+import org.eclipse.jetty.util.RolloverFileOutputStream;
+
+
+/**
+ * Debug Handler.
+ * A lightweight debug handler that can be used in production code.
+ * Details of the request and response are written to an output stream
+ * and the current thread name is updated with information that will link
+ * to the details in that output.
+ */
+public class DebugHandler extends HandlerWrapper implements Connection.Listener
+{
+    private DateCache _date=new DateCache("HH:mm:ss", Locale.US);
+    private OutputStream _out;
+    private PrintStream _print;
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see org.eclipse.jetty.server.Handler#handle(java.lang.String, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, int)
+     */
+    @Override
+    public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response)
+            throws IOException, ServletException
+    {
+        final Response base_response = baseRequest.getResponse();
+        final Thread thread=Thread.currentThread();
+        final String old_name=thread.getName();
+
+        boolean suspend=false;
+        boolean retry=false;
+        String name=(String)request.getAttribute("org.eclipse.jetty.thread.name");
+        if (name==null)
+            name=old_name+":"+baseRequest.getScheme()+"://"+baseRequest.getLocalAddr()+":"+baseRequest.getLocalPort()+baseRequest.getUri();
+        else
+            retry=true;
+
+        String ex=null;
+        try
+        {
+            if (retry)
+                print(name,"RESUME");
+            else
+                print(name,"REQUEST "+baseRequest.getRemoteAddr()+" "+request.getMethod()+" "+baseRequest.getHeader("Cookie")+"; "+baseRequest.getHeader("User-Agent"));
+            thread.setName(name);
+
+            getHandler().handle(target,baseRequest,request,response);
+        }
+        catch(IOException ioe)
+        {
+            ex=ioe.toString();
+            throw ioe;
+        }
+        catch(ServletException se)
+        {
+            ex=se.toString()+":"+se.getCause();
+            throw se;
+        }
+        catch(RuntimeException rte)
+        {
+            ex=rte.toString();
+            throw rte;
+        }
+        catch(Error e)
+        {
+            ex=e.toString();
+            throw e;
+        }
+        finally
+        {
+            thread.setName(old_name);
+            suspend=baseRequest.getHttpChannelState().isSuspended();
+            if (suspend)
+            {
+                request.setAttribute("org.eclipse.jetty.thread.name",name);
+                print(name,"SUSPEND");
+            }
+            else
+                print(name,"RESPONSE "+base_response.getStatus()+(ex==null?"":("/"+ex))+" "+base_response.getContentType());
+        }
+    }
+    
+    private void print(String name,String message)
+    {
+        long now=System.currentTimeMillis();
+        final String d=_date.formatNow(now);
+        final int ms=(int)(now%1000);
+
+        _print.println(d+(ms>99?".":(ms>9?".0":".00"))+ms+":"+name+" "+message);
+    }
+
+    /* (non-Javadoc)
+     * @see org.eclipse.jetty.server.handler.HandlerWrapper#doStart()
+     */
+    @Override
+    protected void doStart() throws Exception
+    {
+        if (_out==null)
+            _out=new RolloverFileOutputStream("./logs/yyyy_mm_dd.debug.log",true);
+        _print=new PrintStream(_out);
+        
+        for (Connector connector : getServer().getConnectors())
+            if (connector instanceof AbstractConnector)
+                ((AbstractConnector)connector).addBean(this,false);
+            
+        super.doStart();
+    }
+
+    /* (non-Javadoc)
+     * @see org.eclipse.jetty.server.handler.HandlerWrapper#doStop()
+     */
+    @Override
+    protected void doStop() throws Exception
+    {
+        super.doStop();
+        _print.close();
+        for (Connector connector : getServer().getConnectors())
+            if (connector instanceof AbstractConnector)
+                ((AbstractConnector)connector).removeBean(this);
+    }
+
+    /**
+     * @return the out
+     */
+    public OutputStream getOutputStream()
+    {
+        return _out;
+    }
+
+    /**
+     * @param out the out to set
+     */
+    public void setOutputStream(OutputStream out)
+    {
+        _out = out;
+    }
+    
+    @Override
+    public void onOpened(Connection connection)
+    {
+        print(Thread.currentThread().getName(),"OPENED "+connection.toString());
+    }
+
+    @Override
+    public void onClosed(Connection connection)
+    {
+        print(Thread.currentThread().getName(),"CLOSED "+connection.toString());
+    }
+
+}
diff --git a/lib/jetty/org/eclipse/jetty/server/handler/DefaultHandler.java b/lib/jetty/org/eclipse/jetty/server/handler/DefaultHandler.java
new file mode 100644 (file)
index 0000000..f71720a
--- /dev/null
@@ -0,0 +1,206 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server.handler;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.net.URL;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.http.HttpHeader;
+import org.eclipse.jetty.http.HttpMethod;
+import org.eclipse.jetty.http.MimeTypes;
+import org.eclipse.jetty.server.Handler;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.util.ByteArrayISO8859Writer;
+import org.eclipse.jetty.util.IO;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.resource.Resource;
+
+
+/* ------------------------------------------------------------ */
+/** Default Handler.
+ *
+ * This handle will deal with unhandled requests in the server.
+ * For requests for favicon.ico, the Jetty icon is served.
+ * For reqests to '/' a 404 with a list of known contexts is served.
+ * For all other requests a normal 404 is served.
+ *
+ *
+ * @org.apache.xbean.XBean
+ */
+public class DefaultHandler extends AbstractHandler
+{
+    private static final Logger LOG = Log.getLogger(DefaultHandler.class);
+
+    final long _faviconModified=(System.currentTimeMillis()/1000)*1000L;
+    byte[] _favicon;
+    boolean _serveIcon=true;
+    boolean _showContexts=true;
+
+    public DefaultHandler()
+    {
+        try
+        {
+            URL fav = this.getClass().getClassLoader().getResource("org/eclipse/jetty/favicon.ico");
+            if (fav!=null)
+            {
+                Resource r = Resource.newResource(fav);
+                _favicon=IO.readBytes(r.getInputStream());
+            }
+        }
+        catch(Exception e)
+        {
+            LOG.warn(e);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see org.eclipse.jetty.server.server.Handler#handle(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, int)
+     */
+    @Override
+    public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+    {
+        if (response.isCommitted() || baseRequest.isHandled())
+            return;
+
+        baseRequest.setHandled(true);
+
+        String method=request.getMethod();
+
+        // little cheat for common request
+        if (_serveIcon && _favicon!=null && HttpMethod.GET.is(method) && request.getRequestURI().equals("/favicon.ico"))
+        {
+            if (request.getDateHeader(HttpHeader.IF_MODIFIED_SINCE.toString())==_faviconModified)
+                response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
+            else
+            {
+                response.setStatus(HttpServletResponse.SC_OK);
+                response.setContentType("image/x-icon");
+                response.setContentLength(_favicon.length);
+                response.setDateHeader(HttpHeader.LAST_MODIFIED.toString(), _faviconModified);
+                response.setHeader(HttpHeader.CACHE_CONTROL.toString(),"max-age=360000,public");
+                response.getOutputStream().write(_favicon);
+            }
+            return;
+        }
+
+
+        if (!_showContexts || !HttpMethod.GET.is(method) || !request.getRequestURI().equals("/"))
+        {
+            response.sendError(HttpServletResponse.SC_NOT_FOUND);
+            return;
+        }
+
+        response.setStatus(HttpServletResponse.SC_NOT_FOUND);
+        response.setContentType(MimeTypes.Type.TEXT_HTML.toString());
+
+        ByteArrayISO8859Writer writer = new ByteArrayISO8859Writer(1500);
+
+        writer.write("<HTML>\n<HEAD>\n<TITLE>Error 404 - Not Found");
+        writer.write("</TITLE>\n<BODY>\n<H2>Error 404 - Not Found.</H2>\n");
+        writer.write("No context on this server matched or handled this request.<BR>");
+        writer.write("Contexts known to this server are: <ul>");
+
+        Server server = getServer();
+        Handler[] handlers = server==null?null:server.getChildHandlersByClass(ContextHandler.class);
+
+        for (int i=0;handlers!=null && i<handlers.length;i++)
+        {
+            ContextHandler context = (ContextHandler)handlers[i];
+            if (context.isRunning())
+            {
+                writer.write("<li><a href=\"");
+                if (context.getVirtualHosts()!=null && context.getVirtualHosts().length>0)
+                    writer.write("http://"+context.getVirtualHosts()[0]+":"+request.getLocalPort());
+                writer.write(context.getContextPath());
+                if (context.getContextPath().length()>1 && context.getContextPath().endsWith("/"))
+                    writer.write("/");
+                writer.write("\">");
+                writer.write(context.getContextPath());
+                if (context.getVirtualHosts()!=null && context.getVirtualHosts().length>0)
+                    writer.write("&nbsp;@&nbsp;"+context.getVirtualHosts()[0]+":"+request.getLocalPort());
+                writer.write("&nbsp;--->&nbsp;");
+                writer.write(context.toString());
+                writer.write("</a></li>\n");
+            }
+            else
+            {
+                writer.write("<li>");
+                writer.write(context.getContextPath());
+                if (context.getVirtualHosts()!=null && context.getVirtualHosts().length>0)
+                    writer.write("&nbsp;@&nbsp;"+context.getVirtualHosts()[0]+":"+request.getLocalPort());
+                writer.write("&nbsp;--->&nbsp;");
+                writer.write(context.toString());
+                if (context.isFailed())
+                    writer.write(" [failed]");
+                if (context.isStopped())
+                    writer.write(" [stopped]");
+                writer.write("</li>\n");
+            }
+        }
+
+        writer.write("</ul><hr>");
+        writer.write("<a href=\"http://eclipse.org/jetty\"><img border=0 src=\"/favicon.ico\"/></a>&nbsp;");
+        writer.write("<a href=\"http://eclipse.org/jetty\">Powered by Jetty:// Java Web Server</a><hr/>\n");
+
+        writer.write("\n</BODY>\n</HTML>\n");
+        writer.flush();
+        response.setContentLength(writer.size());
+        try (OutputStream out=response.getOutputStream())
+        {
+            writer.writeTo(out);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Returns true if the handle can server the jetty favicon.ico
+     */
+    public boolean getServeIcon()
+    {
+        return _serveIcon;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param serveIcon true if the handle can server the jetty favicon.ico
+     */
+    public void setServeIcon(boolean serveIcon)
+    {
+        _serveIcon = serveIcon;
+    }
+
+    public boolean getShowContexts()
+    {
+        return _showContexts;
+    }
+
+    public void setShowContexts(boolean show)
+    {
+        _showContexts = show;
+    }
+
+}
diff --git a/lib/jetty/org/eclipse/jetty/server/handler/ErrorHandler.java b/lib/jetty/org/eclipse/jetty/server/handler/ErrorHandler.java
new file mode 100644 (file)
index 0000000..b1af520
--- /dev/null
@@ -0,0 +1,319 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server.handler;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.io.Writer;
+import java.nio.ByteBuffer;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.http.HttpFields;
+import org.eclipse.jetty.http.HttpHeader;
+import org.eclipse.jetty.http.HttpMethod;
+import org.eclipse.jetty.http.HttpStatus;
+import org.eclipse.jetty.http.MimeTypes;
+import org.eclipse.jetty.server.Dispatcher;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.Response;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.ByteArrayISO8859Writer;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/* ------------------------------------------------------------ */
+/** Handler for Error pages
+ * An ErrorHandler is registered with {@link ContextHandler#setErrorHandler(ErrorHandler)} or
+ * {@link org.eclipse.jetty.server.Server#addBean(Object)}.
+ * It is called by the HttpResponse.sendError method to write a error page via {@link #handle(String, Request, HttpServletRequest, HttpServletResponse)}
+ * or via {@link #badMessageError(int, String, HttpFields)} for bad requests for which a dispatch cannot be done.
+ *
+ */
+public class ErrorHandler extends AbstractHandler
+{    
+    private static final Logger LOG = Log.getLogger(ErrorHandler.class);
+    public final static String ERROR_PAGE="org.eclipse.jetty.server.error_page";
+    
+    boolean _showStacks=true;
+    boolean _showMessageInTitle=true;
+    String _cacheControl="must-revalidate,no-cache,no-store";
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see org.eclipse.jetty.server.server.Handler#handle(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, int)
+     */
+    @Override
+    public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException
+    {
+        String method = request.getMethod();
+        if (!HttpMethod.GET.is(method) && !HttpMethod.POST.is(method) && !HttpMethod.HEAD.is(method))
+        {
+            baseRequest.setHandled(true);
+            return;
+        }
+        
+        if (this instanceof ErrorPageMapper)
+        {
+            String error_page=((ErrorPageMapper)this).getErrorPage(request);
+            if (error_page!=null && request.getServletContext()!=null)
+            {
+                String old_error_page=(String)request.getAttribute(ERROR_PAGE);
+                if (old_error_page==null || !old_error_page.equals(error_page))
+                {
+                    request.setAttribute(ERROR_PAGE, error_page);
+
+                    Dispatcher dispatcher = (Dispatcher) request.getServletContext().getRequestDispatcher(error_page);
+                    try
+                    {
+                        if(dispatcher!=null)
+                        {
+                            dispatcher.error(request, response);
+                            return;
+                        }
+                        LOG.warn("No error page "+error_page);
+                    }
+                    catch (ServletException e)
+                    {
+                        LOG.warn(Log.EXCEPTION, e);
+                        return;
+                    }
+                }
+            }
+        }
+        
+        baseRequest.setHandled(true);
+        response.setContentType(MimeTypes.Type.TEXT_HTML_8859_1.asString());    
+        if (_cacheControl!=null)
+            response.setHeader(HttpHeader.CACHE_CONTROL.asString(), _cacheControl);
+        ByteArrayISO8859Writer writer= new ByteArrayISO8859Writer(4096);
+        String reason=(response instanceof Response)?((Response)response).getReason():null;
+        handleErrorPage(request, writer, response.getStatus(), reason);
+        writer.flush();
+        response.setContentLength(writer.size());
+        writer.writeTo(response.getOutputStream());
+        writer.destroy();
+    }
+
+    /* ------------------------------------------------------------ */
+    protected void handleErrorPage(HttpServletRequest request, Writer writer, int code, String message)
+        throws IOException
+    {
+        writeErrorPage(request, writer, code, message, _showStacks);
+    }
+
+    /* ------------------------------------------------------------ */
+    protected void writeErrorPage(HttpServletRequest request, Writer writer, int code, String message, boolean showStacks)
+        throws IOException
+    {
+        if (message == null)
+            message=HttpStatus.getMessage(code);
+
+        writer.write("<html>\n<head>\n");
+        writeErrorPageHead(request,writer,code,message);
+        writer.write("</head>\n<body>");
+        writeErrorPageBody(request,writer,code,message,showStacks);
+        writer.write("\n</body>\n</html>\n");
+    }
+
+    /* ------------------------------------------------------------ */
+    protected void writeErrorPageHead(HttpServletRequest request, Writer writer, int code, String message)
+        throws IOException
+        {
+        writer.write("<meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\"/>\n");
+        writer.write("<title>Error ");
+        writer.write(Integer.toString(code));
+
+        if (_showMessageInTitle)
+        {
+            writer.write(' ');
+            write(writer,message);
+        }
+        writer.write("</title>\n");
+    }
+
+    /* ------------------------------------------------------------ */
+    protected void writeErrorPageBody(HttpServletRequest request, Writer writer, int code, String message, boolean showStacks)
+        throws IOException
+    {
+        String uri= request.getRequestURI();
+
+        writeErrorPageMessage(request,writer,code,message,uri);
+        if (showStacks)
+            writeErrorPageStacks(request,writer);
+        writer.write("<hr><i><small>Powered by Jetty://</small></i><hr/>\n");
+    }
+
+    /* ------------------------------------------------------------ */
+    protected void writeErrorPageMessage(HttpServletRequest request, Writer writer, int code, String message,String uri)
+    throws IOException
+    {
+        writer.write("<h2>HTTP ERROR ");
+        writer.write(Integer.toString(code));
+        writer.write("</h2>\n<p>Problem accessing ");
+        write(writer,uri);
+        writer.write(". Reason:\n<pre>    ");
+        write(writer,message);
+        writer.write("</pre></p>");
+    }
+
+    /* ------------------------------------------------------------ */
+    protected void writeErrorPageStacks(HttpServletRequest request, Writer writer)
+        throws IOException
+    {
+        Throwable th = (Throwable)request.getAttribute("javax.servlet.error.exception");
+        while(th!=null)
+        {
+            writer.write("<h3>Caused by:</h3><pre>");
+            StringWriter sw = new StringWriter();
+            PrintWriter pw = new PrintWriter(sw);
+            th.printStackTrace(pw);
+            pw.flush();
+            write(writer,sw.getBuffer().toString());
+            writer.write("</pre>\n");
+
+            th =th.getCause();
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Bad Message Error body
+     * <p>Generate a error response body to be sent for a bad message.
+     * In this case there is something wrong with the request, so either
+     * a request cannot be built, or it is not safe to build a request.
+     * This method allows for a simple error page body to be returned 
+     * and some response headers to be set.
+     * @param status The error code that will be sent
+     * @param reason The reason for the error code (may be null)
+     * @param fields The header fields that will be sent with the response.
+     * @return The content as a ByteBuffer, or null for no body.
+     */
+    public ByteBuffer badMessageError(int status, String reason, HttpFields fields)
+    {
+        if (reason==null)
+            reason=HttpStatus.getMessage(status);
+        fields.put(HttpHeader.CONTENT_TYPE,MimeTypes.Type.TEXT_HTML_8859_1.asString());
+        return BufferUtil.toBuffer("<h1>Bad Message " + status + "</h1><pre>reason: " + reason + "</pre>");
+    }    
+    
+    /* ------------------------------------------------------------ */
+    /** Get the cacheControl.
+     * @return the cacheControl header to set on error responses.
+     */
+    public String getCacheControl()
+    {
+        return _cacheControl;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Set the cacheControl.
+     * @param cacheControl the cacheControl header to set on error responses.
+     */
+    public void setCacheControl(String cacheControl)
+    {
+        _cacheControl = cacheControl;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return True if stack traces are shown in the error pages
+     */
+    public boolean isShowStacks()
+    {
+        return _showStacks;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param showStacks True if stack traces are shown in the error pages
+     */
+    public void setShowStacks(boolean showStacks)
+    {
+        _showStacks = showStacks;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param showMessageInTitle if true, the error message appears in page title
+     */
+    public void setShowMessageInTitle(boolean showMessageInTitle)
+    {
+        _showMessageInTitle = showMessageInTitle;
+    }
+
+
+    /* ------------------------------------------------------------ */
+    public boolean getShowMessageInTitle()
+    {
+        return _showMessageInTitle;
+    }
+
+    /* ------------------------------------------------------------ */
+    protected void write(Writer writer,String string)
+        throws IOException
+    {
+        if (string==null)
+            return;
+
+        for (int i=0;i<string.length();i++)
+        {
+            char c=string.charAt(i);
+
+            switch(c)
+            {
+                case '&' :
+                    writer.write("&amp;");
+                    break;
+                case '<' :
+                    writer.write("&lt;");
+                    break;
+                case '>' :
+                    writer.write("&gt;");
+                    break;
+
+                default:
+                    if (Character.isISOControl(c) && !Character.isWhitespace(c))
+                        writer.write('?');
+                    else
+                        writer.write(c);
+            }
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    public interface ErrorPageMapper
+    {
+        String getErrorPage(HttpServletRequest request);
+    }
+
+    /* ------------------------------------------------------------ */
+    public static ErrorHandler getErrorHandler(Server server, ContextHandler context)
+    {
+        ErrorHandler error_handler=null;
+        if (context!=null)
+            error_handler=context.getErrorHandler();
+        if (error_handler==null && server!=null)
+            error_handler = server.getBean(ErrorHandler.class);
+        return error_handler;
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/server/handler/HandlerCollection.java b/lib/jetty/org/eclipse/jetty/server/handler/HandlerCollection.java
new file mode 100644 (file)
index 0000000..c118c7e
--- /dev/null
@@ -0,0 +1,188 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server.handler;
+
+import java.io.IOException;
+import java.util.List;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.server.Handler;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.util.ArrayUtil;
+import org.eclipse.jetty.util.MultiException;
+import org.eclipse.jetty.util.annotation.ManagedAttribute;
+import org.eclipse.jetty.util.annotation.ManagedObject;
+
+/* ------------------------------------------------------------ */
+/** A collection of handlers.
+ * <p>
+ * The default implementations  calls all handlers in list order,
+ * regardless of the response status or exceptions. Derived implementation
+ * may alter the order or the conditions of calling the contained
+ * handlers.
+ * <p>
+ *
+ */
+@ManagedObject("Handler of multiple handlers")
+public class HandlerCollection extends AbstractHandlerContainer
+{
+    private final boolean _mutableWhenRunning;
+    private volatile Handler[] _handlers;
+
+    /* ------------------------------------------------------------ */
+    public HandlerCollection()
+    {
+        _mutableWhenRunning=false;
+    }
+
+    /* ------------------------------------------------------------ */
+    public HandlerCollection(boolean mutableWhenRunning)
+    {
+        _mutableWhenRunning=mutableWhenRunning;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Returns the handlers.
+     */
+    @Override
+    @ManagedAttribute(value="Wrapped handlers", readonly=true)
+    public Handler[] getHandlers()
+    {
+        return _handlers;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param handlers The handlers to set.
+     */
+    public void setHandlers(Handler[] handlers)
+    {
+        if (!_mutableWhenRunning && isStarted())
+            throw new IllegalStateException(STARTED);
+
+        if (handlers!=null)
+            for (Handler handler:handlers)
+                if (handler.getServer()!=getServer())
+                    handler.setServer(getServer());
+        Handler[] old=_handlers;;
+        _handlers = handlers;
+        updateBeans(old, handlers);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see Handler#handle(String, Request, HttpServletRequest, HttpServletResponse)
+     */
+    @Override
+    public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response)
+        throws IOException, ServletException
+    {
+        if (_handlers!=null && isStarted())
+        {
+            MultiException mex=null;
+
+            for (int i=0;i<_handlers.length;i++)
+            {
+                try
+                {
+                    _handlers[i].handle(target,baseRequest, request, response);
+                }
+                catch(IOException e)
+                {
+                    throw e;
+                }
+                catch(RuntimeException e)
+                {
+                    throw e;
+                }
+                catch(Exception e)
+                {
+                    if (mex==null)
+                        mex=new MultiException();
+                    mex.add(e);
+                }
+            }
+            if (mex!=null)
+            {
+                if (mex.size()==1)
+                    throw new ServletException(mex.getThrowable(0));
+                else
+                    throw new ServletException(mex);
+            }
+
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public void setServer(Server server)
+    {
+        super.setServer(server);
+        Handler[] handlers=getHandlers();
+        if (handlers!=null)
+            for (Handler h : handlers)
+                h.setServer(server);
+    }
+
+    /* ------------------------------------------------------------ */
+    /* Add a handler.
+     * This implementation adds the passed handler to the end of the existing collection of handlers.
+     * @see org.eclipse.jetty.server.server.HandlerContainer#addHandler(org.eclipse.jetty.server.server.Handler)
+     */
+    public void addHandler(Handler handler)
+    {
+        setHandlers(ArrayUtil.addToArray(getHandlers(), handler, Handler.class));
+    }
+
+    /* ------------------------------------------------------------ */
+    public void removeHandler(Handler handler)
+    {
+        Handler[] handlers = getHandlers();
+
+        if (handlers!=null && handlers.length>0 )
+            setHandlers(ArrayUtil.removeFromArray(handlers, handler));
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    protected void expandChildren(List<Handler> list, Class<?> byClass)
+    {
+        if (getHandlers()!=null)
+            for (Handler h:getHandlers())
+                expandHandler(h, list, byClass);
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public void destroy()
+    {
+        if (!isStopped())
+            throw new IllegalStateException("!STOPPED");
+        Handler[] children=getChildHandlers();
+        setHandlers(null);
+        for (Handler child: children)
+            child.destroy();
+        super.destroy();
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/server/handler/HandlerList.java b/lib/jetty/org/eclipse/jetty/server/handler/HandlerList.java
new file mode 100644 (file)
index 0000000..74320b0
--- /dev/null
@@ -0,0 +1,58 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server.handler;
+
+import java.io.IOException;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.server.Handler;
+import org.eclipse.jetty.server.Request;
+
+/* ------------------------------------------------------------ */
+/** HandlerList.
+ * This extension of {@link HandlerCollection} will call
+ * each contained handler in turn until either an exception is thrown, the response
+ * is committed or a positive response status is set.
+ */
+public class HandlerList extends HandlerCollection
+{
+    /* ------------------------------------------------------------ */
+    /**
+     * @see Handler#handle(String, Request, HttpServletRequest, HttpServletResponse)
+     */
+    @Override
+    public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response)
+        throws IOException, ServletException
+    {
+        Handler[] handlers = getHandlers();
+
+        if (handlers!=null && isStarted())
+        {
+            for (int i=0;i<handlers.length;i++)
+            {
+                handlers[i].handle(target,baseRequest, request, response);
+                if ( baseRequest.isHandled())
+                    return;
+            }
+        }
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/server/handler/HandlerWrapper.java b/lib/jetty/org/eclipse/jetty/server/handler/HandlerWrapper.java
new file mode 100644 (file)
index 0000000..dcbf6ff
--- /dev/null
@@ -0,0 +1,142 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server.handler;
+
+import java.io.IOException;
+import java.util.List;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.server.Handler;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.util.annotation.ManagedAttribute;
+import org.eclipse.jetty.util.annotation.ManagedObject;
+import org.eclipse.jetty.util.component.LifeCycle;
+
+/* ------------------------------------------------------------ */
+/** A <code>HandlerWrapper</code> acts as a {@link Handler} but delegates the {@link Handler#handle handle} method and
+ * {@link LifeCycle life cycle} events to a delegate. This is primarily used to implement the <i>Decorator</i> pattern.
+ *
+ */
+@ManagedObject("Handler wrapping another Handler")
+public class HandlerWrapper extends AbstractHandlerContainer
+{
+    protected Handler _handler;
+
+    /* ------------------------------------------------------------ */
+    /**
+     *
+     */
+    public HandlerWrapper()
+    {
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Returns the handlers.
+     */
+    @ManagedAttribute(value="Wrapped Handler", readonly=true)
+    public Handler getHandler()
+    {
+        return _handler;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Returns the handlers.
+     */
+    @Override
+    public Handler[] getHandlers()
+    {
+        if (_handler==null)
+            return new Handler[0];
+        return new Handler[] {_handler};
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param handler Set the {@link Handler} which should be wrapped.
+     */
+    public void setHandler(Handler handler)
+    {
+        if (isStarted())
+            throw new IllegalStateException(STARTED);
+
+        if (handler!=null)
+            handler.setServer(getServer());
+        
+        Handler old=_handler;
+        _handler=handler;
+        updateBean(old,_handler);
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+    {
+        if (_handler!=null && isStarted())
+        {
+            _handler.handle(target,baseRequest, request, response);
+        }
+    }
+
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public void setServer(Server server)
+    {
+        if (server==getServer())
+            return;
+        
+        if (isStarted())
+            throw new IllegalStateException(STARTED);
+
+        super.setServer(server);
+        Handler h=getHandler();
+        if (h!=null)
+            h.setServer(server);
+    }
+
+
+    /* ------------------------------------------------------------ */
+    @Override
+    protected void expandChildren(List<Handler> list, Class<?> byClass)
+    {
+        expandHandler(_handler,list,byClass);
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public void destroy()
+    {
+        if (!isStopped())
+            throw new IllegalStateException("!STOPPED");
+        Handler child=getHandler();
+        if (child!=null)
+        {
+            setHandler(null);
+            child.destroy();
+        }
+        super.destroy();
+    }
+
+}
diff --git a/lib/jetty/org/eclipse/jetty/server/handler/HotSwapHandler.java b/lib/jetty/org/eclipse/jetty/server/handler/HotSwapHandler.java
new file mode 100644 (file)
index 0000000..162a719
--- /dev/null
@@ -0,0 +1,160 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server.handler;
+
+import java.io.IOException;
+import java.util.List;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.server.Handler;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.Server;
+
+/* ------------------------------------------------------------ */
+/**
+ * A <code>HandlerContainer</code> that allows a hot swap of a wrapped handler.
+ *
+ */
+public class HotSwapHandler extends AbstractHandlerContainer
+{
+    private volatile Handler _handler;
+
+    /* ------------------------------------------------------------ */
+    /**
+     *
+     */
+    public HotSwapHandler()
+    {
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Returns the handlers.
+     */
+    public Handler getHandler()
+    {
+        return _handler;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Returns the handlers.
+     */
+    @Override
+    public Handler[] getHandlers()
+    {
+        return new Handler[]
+        { _handler };
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param handler
+     *            Set the {@link Handler} which should be wrapped.
+     */
+    public void setHandler(Handler handler)
+    {
+        if (handler == null)
+            throw new IllegalArgumentException("Parameter handler is null.");
+        try
+        {
+            updateBean(_handler,handler);
+            _handler=handler;
+            Server server = getServer();
+            handler.setServer(server);
+
+        }
+        catch (Exception e)
+        {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see org.eclipse.thread.AbstractLifeCycle#doStart()
+     */
+    @Override
+    protected void doStart() throws Exception
+    {
+        super.doStart();
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see org.eclipse.thread.AbstractLifeCycle#doStop()
+     */
+    @Override
+    protected void doStop() throws Exception
+    {
+        super.doStop();
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see org.eclipse.jetty.server.server.EventHandler#handle(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
+     */
+    @Override
+    public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+    {
+        if (_handler != null && isStarted())
+        {
+            _handler.handle(target,baseRequest,request,response);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public void setServer(Server server)
+    {
+        if (isRunning())
+            throw new IllegalStateException(RUNNING);
+
+        super.setServer(server);
+
+        Handler h = getHandler();
+        if (h != null)
+            h.setServer(server);
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    protected void expandChildren(List<Handler> list, Class<?> byClass)
+    {
+        expandHandler(_handler,list,byClass);
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public void destroy()
+    {
+        if (!isStopped())
+            throw new IllegalStateException("!STOPPED");
+        Handler child = getHandler();
+        if (child != null)
+        {
+            setHandler(null);
+            child.destroy();
+        }
+        super.destroy();
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/server/handler/IPAccessHandler.java b/lib/jetty/org/eclipse/jetty/server/handler/IPAccessHandler.java
new file mode 100644 (file)
index 0000000..97a3ac8
--- /dev/null
@@ -0,0 +1,385 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server.handler;
+
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.util.Map;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.http.HttpStatus;
+import org.eclipse.jetty.http.PathMap;
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.server.HttpChannel;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.util.IPAddressMap;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+
+/**
+ * IP Access Handler
+ * <p>
+ * Controls access to the wrapped handler by the real remote IP. Control is provided
+ * by white/black lists that include both internet addresses and URIs. This handler
+ * uses the real internet address of the connection, not one reported in the forwarded
+ * for headers, as this cannot be as easily forged.
+ * <p>
+ * Typically, the black/white lists will be used in one of three modes:
+ * <ul>
+ * <li>Blocking a few specific IPs/URLs by specifying several black list entries.
+ * <li>Allowing only some specific IPs/URLs by specifying several white lists entries.
+ * <li>Allowing a general range of IPs/URLs by specifying several general white list
+ * entries, that are then further refined by several specific black list exceptions
+ * </ul>
+ * <p>
+ * By default an empty white list is treated as match all. If there is at least one entry in
+ * the white list, then a request must match a white list entry. Black list entries
+ * are always applied, so that even if an entry matches the white list, a black list
+ * entry will override it.
+ * <p>
+ * <p>
+ * You can change white list policy setting whiteListByPath to true. In this mode a request will be white listed
+ * IF it has a matching URL in the white list, otherwise the black list applies, e.g. in default mode when
+ * whiteListByPath = false and wl = "127.0.0.1|/foo", /bar request from 127.0.0.1 will be blacklisted,
+ * if whiteListByPath=true then not.
+ * </p>
+ * Internet addresses may be specified as absolute address or as a combination of
+ * four octet wildcard specifications (a.b.c.d) that are defined as follows.
+ * </p>
+ * <pre>
+ * nnn - an absolute value (0-255)
+ * mmm-nnn - an inclusive range of absolute values,
+ *           with following shorthand notations:
+ *           nnn- => nnn-255
+ *           -nnn => 0-nnn
+ *           -    => 0-255
+ * a,b,... - a list of wildcard specifications
+ * </pre>
+ * <p>
+ * Internet address specification is separated from the URI pattern using the "|" (pipe)
+ * character. URI patterns follow the servlet specification for simple * prefix and
+ * suffix wild cards (e.g. /, /foo, /foo/bar, /foo/bar/*, *.baz).
+ * <p>
+ * Earlier versions of the handler used internet address prefix wildcard specification
+ * to define a range of the internet addresses (e.g. 127., 10.10., 172.16.1.).
+ * They also used the first "/" character of the URI pattern to separate it from the
+ * internet address. Both of these features have been deprecated in the current version.
+ * <p>
+ * Examples of the entry specifications are:
+ * <ul>
+ * <li>10.10.1.2 - all requests from IP 10.10.1.2
+ * <li>10.10.1.2|/foo/bar - all requests from IP 10.10.1.2 to URI /foo/bar
+ * <li>10.10.1.2|/foo/* - all requests from IP 10.10.1.2 to URIs starting with /foo/
+ * <li>10.10.1.2|*.html - all requests from IP 10.10.1.2 to URIs ending with .html
+ * <li>10.10.0-255.0-255 - all requests from IPs within 10.10.0.0/16 subnet
+ * <li>10.10.0-.-255|/foo/bar - all requests from IPs within 10.10.0.0/16 subnet to URI /foo/bar
+ * <li>10.10.0-3,1,3,7,15|/foo/* - all requests from IPs addresses with last octet equal
+ *                                  to 1,3,7,15 in subnet 10.10.0.0/22 to URIs starting with /foo/
+ * </ul>
+ * <p>
+ * Earlier versions of the handler used internet address prefix wildcard specification
+ * to define a range of the internet addresses (e.g. 127., 10.10., 172.16.1.).
+ * They also used the first "/" character of the URI pattern to separate it from the
+ * internet address. Both of these features have been deprecated in the current version.
+ */
+public class IPAccessHandler extends HandlerWrapper
+{
+    private static final Logger LOG = Log.getLogger(IPAccessHandler.class);
+    // true means nodefault match
+    PathMap<IPAddressMap<Boolean>> _white = new PathMap<IPAddressMap<Boolean>>(true);
+    PathMap<IPAddressMap<Boolean>> _black = new PathMap<IPAddressMap<Boolean>>(true);
+    boolean _whiteListByPath = false;
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Creates new handler object
+     */
+    public IPAccessHandler()
+    {
+        super();
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Creates new handler object and initializes white- and black-list
+     *
+     * @param white array of whitelist entries
+     * @param black array of blacklist entries
+     */
+    public IPAccessHandler(String[] white, String []black)
+    {
+        super();
+
+        if (white != null && white.length > 0)
+            setWhite(white);
+        if (black != null && black.length > 0)
+            setBlack(black);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Add a whitelist entry to an existing handler configuration
+     *
+     * @param entry new whitelist entry
+     */
+    public void addWhite(String entry)
+    {
+        add(entry, _white);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Add a blacklist entry to an existing handler configuration
+     *
+     * @param entry new blacklist entry
+     */
+    public void addBlack(String entry)
+    {
+        add(entry, _black);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Re-initialize the whitelist of existing handler object
+     *
+     * @param entries array of whitelist entries
+     */
+    public void setWhite(String[] entries)
+    {
+        set(entries, _white);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Re-initialize the blacklist of existing handler object
+     *
+     * @param entries array of blacklist entries
+     */
+    public void setBlack(String[] entries)
+    {
+        set(entries, _black);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Re-initialize the mode of path matching
+     *
+     * @param whiteListByPath matching mode
+     */
+    public void setWhiteListByPath(boolean whiteListByPath)
+    {
+        this._whiteListByPath = whiteListByPath;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Checks the incoming request against the whitelist and blacklist
+     *
+     * @see org.eclipse.jetty.server.handler.HandlerWrapper#handle(java.lang.String, org.eclipse.jetty.server.Request, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
+     */
+    @Override
+    public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+    {
+        // Get the real remote IP (not the one set by the forwarded headers (which may be forged))
+        HttpChannel<?> channel = baseRequest.getHttpChannel();
+        if (channel!=null)
+        {
+            EndPoint endp=channel.getEndPoint();
+            if (endp!=null)
+            {
+                InetSocketAddress address = endp.getRemoteAddress();
+                if (address!=null && !isAddrUriAllowed(address.getHostString(),baseRequest.getPathInfo()))
+                {
+                    response.sendError(HttpStatus.FORBIDDEN_403);
+                    baseRequest.setHandled(true);
+                    return;
+                }
+            }
+        }
+
+        getHandler().handle(target,baseRequest, request, response);
+    }
+
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Helper method to parse the new entry and add it to
+     * the specified address pattern map.
+     *
+     * @param entry new entry
+     * @param patternMap target address pattern map
+     */
+    protected void add(String entry, PathMap<IPAddressMap<Boolean>> patternMap)
+    {
+        if (entry != null && entry.length() > 0)
+        {
+            boolean deprecated = false;
+            int idx;
+            if (entry.indexOf('|') > 0 )
+            {
+                idx = entry.indexOf('|');
+            }
+            else
+            {
+                idx = entry.indexOf('/');
+                deprecated = (idx >= 0);
+            }
+
+            String addr = idx > 0 ? entry.substring(0,idx) : entry;
+            String path = idx > 0 ? entry.substring(idx) : "/*";
+
+            if (addr.endsWith("."))
+                deprecated = true;
+            if (path!=null && (path.startsWith("|") || path.startsWith("/*.")))
+                path=path.substring(1);
+
+            IPAddressMap<Boolean> addrMap = patternMap.get(path);
+            if (addrMap == null)
+            {
+                addrMap = new IPAddressMap<Boolean>();
+                patternMap.put(path,addrMap);
+            }
+            if (addr != null && !"".equals(addr))
+                // MUST NOT BE null
+                addrMap.put(addr, true);
+
+            if (deprecated)
+                LOG.debug(toString() +" - deprecated specification syntax: "+entry);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Helper method to process a list of new entries and replace
+     * the content of the specified address pattern map
+     *
+     * @param entries new entries
+     * @param patternMap target address pattern map
+     */
+    protected void set(String[] entries,  PathMap<IPAddressMap<Boolean>> patternMap)
+    {
+        patternMap.clear();
+
+        if (entries != null && entries.length > 0)
+        {
+            for (String addrPath:entries)
+            {
+                add(addrPath, patternMap);
+            }
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Check if specified request is allowed by current IPAccess rules.
+     *
+     * @param addr internet address
+     * @param path context path
+     * @return true if request is allowed
+     *
+     */
+    protected boolean isAddrUriAllowed(String addr, String path)
+    {
+        if (_white.size()>0)
+        {
+            boolean match = false;
+            boolean matchedByPath = false;
+
+            for (Map.Entry<String,IPAddressMap<Boolean>> entry : _white.getMatches(path))
+            {
+                matchedByPath=true;
+                IPAddressMap<Boolean> addrMap = entry.getValue();
+                if ((addrMap!=null && (addrMap.size()==0 || addrMap.match(addr)!=null)))
+                {
+                    match=true;
+                    break;
+                }
+            }
+            
+            if (_whiteListByPath)
+            {
+                if (matchedByPath && !match)
+                    return false;
+            }
+            else
+            {
+                if (!match)
+                    return false;
+            }
+        }
+
+        if (_black.size() > 0)
+        {
+            for (Map.Entry<String,IPAddressMap<Boolean>> entry : _black.getMatches(path))
+            {
+                IPAddressMap<Boolean> addrMap = entry.getValue();
+                if (addrMap!=null && (addrMap.size()==0 || addrMap.match(addr)!=null))
+                    return false;
+            }
+            
+        }
+
+        return true;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Dump the handler configuration
+     */
+    @Override
+    public String dump()
+    {
+        StringBuilder buf = new StringBuilder();
+
+        buf.append(toString());
+        buf.append(" WHITELIST:\n");
+        dump(buf, _white);
+        buf.append(toString());
+        buf.append(" BLACKLIST:\n");
+        dump(buf, _black);
+
+        return buf.toString();
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Dump a pattern map into a StringBuilder buffer
+     *
+     * @param buf buffer
+     * @param patternMap pattern map to dump
+     */
+    protected void dump(StringBuilder buf, PathMap<IPAddressMap<Boolean>> patternMap)
+    {
+        for (String path: patternMap.keySet())
+        {
+            for (String addr: patternMap.get(path).keySet())
+            {
+                buf.append("# ");
+                buf.append(addr);
+                buf.append("|");
+                buf.append(path);
+                buf.append("\n");
+            }
+        }
+    }
+ }
diff --git a/lib/jetty/org/eclipse/jetty/server/handler/IdleTimeoutHandler.java b/lib/jetty/org/eclipse/jetty/server/handler/IdleTimeoutHandler.java
new file mode 100644 (file)
index 0000000..04a90f1
--- /dev/null
@@ -0,0 +1,134 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server.handler;
+
+import java.io.IOException;
+
+import javax.servlet.AsyncEvent;
+import javax.servlet.AsyncListener;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.server.HttpConnection;
+import org.eclipse.jetty.server.Request;
+
+/**
+ * Handler to adjust the idle timeout of requests while dispatched.
+ * Can be applied in jetty.xml with
+ * <pre>
+ *   &lt;Get id='handler' name='Handler'/>
+ *   &lt;Set name='Handler'>
+ *     &lt;New id='idleTimeoutHandler' class='org.eclipse.jetty.server.handler.IdleTimeoutHandler'>
+ *       &lt;Set name='Handler'>&lt;Ref id='handler'/>&lt;/Set>
+ *       &lt;Set name='IdleTimeoutMs'>5000&lt;/Set>
+ *     &lt;/New>
+ *   &lt;/Set>
+ * </pre>
+ */
+public class IdleTimeoutHandler extends HandlerWrapper
+{
+    private long _idleTimeoutMs = 1000;
+    private boolean _applyToAsync = false;
+    
+    public boolean isApplyToAsync()
+    {
+        return _applyToAsync;
+    }
+
+    /**
+     * Should the adjusted idle time be maintained for asynchronous requests
+     * @param applyToAsync true if alternate idle timeout is applied to asynchronous requests
+     */
+    public void setApplyToAsync(boolean applyToAsync)
+    {
+        _applyToAsync = applyToAsync;
+    }
+
+    public long getIdleTimeoutMs()
+    {
+        return _idleTimeoutMs;
+    }
+
+    /**
+     * @param idleTimeoutMs The idle timeout in MS to apply while dispatched or async
+     */
+    public void setIdleTimeoutMs(long idleTimeoutMs)
+    {
+        this._idleTimeoutMs = idleTimeoutMs;
+    }
+    
+   
+    @Override
+    public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+    {
+        HttpConnection connection = HttpConnection.getCurrentConnection();
+        final EndPoint endp = connection==null?null:connection.getEndPoint();
+        
+        final long idle_timeout;
+        if (endp==null)
+            idle_timeout=-1;
+        else
+        {
+            idle_timeout=endp.getIdleTimeout();
+            endp.setIdleTimeout(_idleTimeoutMs);
+        }
+        
+        try
+        {
+            super.handle(target,baseRequest,request,response);
+        }
+        finally
+        {
+            if (endp!=null)
+            {
+                if (_applyToAsync && request.isAsyncStarted())
+                {
+                    request.getAsyncContext().addListener(new AsyncListener()
+                    {
+                        @Override
+                        public void onTimeout(AsyncEvent event) throws IOException
+                        {                            
+                        }
+                        
+                        @Override
+                        public void onStartAsync(AsyncEvent event) throws IOException
+                        {
+                        }
+                        
+                        @Override
+                        public void onError(AsyncEvent event) throws IOException
+                        {
+                            endp.setIdleTimeout(idle_timeout);
+                        }
+                        
+                        @Override
+                        public void onComplete(AsyncEvent event) throws IOException
+                        {
+                            endp.setIdleTimeout(idle_timeout);
+                        }
+                    });
+                }
+                else 
+                    endp.setIdleTimeout(idle_timeout);
+            }
+        }
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/server/handler/MovedContextHandler.java b/lib/jetty/org/eclipse/jetty/server/handler/MovedContextHandler.java
new file mode 100644 (file)
index 0000000..5e9fd85
--- /dev/null
@@ -0,0 +1,154 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server.handler;
+
+import java.io.IOException;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.http.HttpHeader;
+import org.eclipse.jetty.server.HandlerContainer;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.util.URIUtil;
+
+/* ------------------------------------------------------------ */
+/** Moved ContextHandler.
+ * This context can be used to replace a context that has changed
+ * location.  Requests are redirected (either to a fixed URL or to a
+ * new context base).
+ */
+public class MovedContextHandler extends ContextHandler
+{
+    final Redirector _redirector;
+    String _newContextURL;
+    boolean _discardPathInfo;
+    boolean _discardQuery;
+    boolean _permanent;
+    String _expires;
+
+    public MovedContextHandler()
+    {
+        _redirector=new Redirector();
+        setHandler(_redirector);
+        setAllowNullPathInfo(true);
+    }
+
+    public MovedContextHandler(HandlerContainer parent, String contextPath, String newContextURL)
+    {
+        super(parent,contextPath);
+        _newContextURL=newContextURL;
+        _redirector=new Redirector();
+        setHandler(_redirector);
+    }
+
+    public boolean isDiscardPathInfo()
+    {
+        return _discardPathInfo;
+    }
+
+    public void setDiscardPathInfo(boolean discardPathInfo)
+    {
+        _discardPathInfo = discardPathInfo;
+    }
+
+    public String getNewContextURL()
+    {
+        return _newContextURL;
+    }
+
+    public void setNewContextURL(String newContextURL)
+    {
+        _newContextURL = newContextURL;
+    }
+
+    public boolean isPermanent()
+    {
+        return _permanent;
+    }
+
+    public void setPermanent(boolean permanent)
+    {
+        _permanent = permanent;
+    }
+
+    public boolean isDiscardQuery()
+    {
+        return _discardQuery;
+    }
+
+    public void setDiscardQuery(boolean discardQuery)
+    {
+        _discardQuery = discardQuery;
+    }
+
+    private class Redirector extends AbstractHandler
+    {
+        public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+        {
+            if (_newContextURL==null)
+                return;
+
+            String path=_newContextURL;
+            if (!_discardPathInfo && request.getPathInfo()!=null)
+                path=URIUtil.addPaths(path, request.getPathInfo());
+
+            StringBuilder location = URIUtil.hasScheme(path)?new StringBuilder():baseRequest.getRootURL();
+
+            location.append(path);
+            if (!_discardQuery && request.getQueryString()!=null)
+            {
+                location.append('?');
+                String q=request.getQueryString();
+                q=q.replaceAll("\r\n?&=","!");
+                location.append(q);
+            }
+
+            response.setHeader(HttpHeader.LOCATION.asString(),location.toString());
+
+            if (_expires!=null)
+                response.setHeader(HttpHeader.EXPIRES.asString(),_expires);
+
+            response.setStatus(_permanent?HttpServletResponse.SC_MOVED_PERMANENTLY:HttpServletResponse.SC_FOUND);
+            response.setContentLength(0);
+            baseRequest.setHandled(true);
+        }
+
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return the expires header value or null if no expires header
+     */
+    public String getExpires()
+    {
+        return _expires;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param expires the expires header value or null if no expires header
+     */
+    public void setExpires(String expires)
+    {
+        _expires = expires;
+    }
+
+}
diff --git a/lib/jetty/org/eclipse/jetty/server/handler/RequestLogHandler.java b/lib/jetty/org/eclipse/jetty/server/handler/RequestLogHandler.java
new file mode 100644 (file)
index 0000000..706b988
--- /dev/null
@@ -0,0 +1,153 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server.handler;
+
+import java.io.IOException;
+
+import javax.servlet.AsyncEvent;
+import javax.servlet.AsyncListener;
+import javax.servlet.DispatcherType;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.server.AsyncContextState;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.RequestLog;
+import org.eclipse.jetty.server.Response;
+import org.eclipse.jetty.util.component.AbstractLifeCycle;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+
+
+/**
+ * RequestLogHandler.
+ * This handler can be used to wrap an individual context for context logging.
+ *
+ *
+ * @org.apache.xbean.XBean
+ */
+public class RequestLogHandler extends HandlerWrapper
+{
+    private static final Logger LOG = Log.getLogger(RequestLogHandler.class);
+    private RequestLog _requestLog;
+    private final AsyncListener _listener = new AsyncListener()
+    {
+        
+        @Override
+        public void onTimeout(AsyncEvent event) throws IOException
+        {
+            
+        }
+        
+        @Override
+        public void onStartAsync(AsyncEvent event) throws IOException
+        {
+            event.getAsyncContext().addListener(this);
+        }
+        
+        @Override
+        public void onError(AsyncEvent event) throws IOException
+        {
+            
+        }
+        
+        @Override
+        public void onComplete(AsyncEvent event) throws IOException
+        {
+            AsyncContextState context = (AsyncContextState)event.getAsyncContext();
+            Request request=context.getHttpChannelState().getBaseRequest();
+            Response response=request.getResponse();
+            _requestLog.log(request,response);
+        }
+    };
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see org.eclipse.jetty.server.server.Handler#handle(java.lang.String, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, int)
+     */
+    @Override
+    public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response)
+            throws IOException, ServletException
+    {
+        try
+        {
+            super.handle(target, baseRequest, request, response);
+        }
+        finally
+        {
+            if (_requestLog != null && baseRequest.getDispatcherType().equals(DispatcherType.REQUEST))
+            {
+                if (baseRequest.getHttpChannelState().isAsync())
+                {
+                    if (baseRequest.getHttpChannelState().isInitial())
+                        baseRequest.getAsyncContext().addListener(_listener);
+                }
+                else
+                    _requestLog.log(baseRequest, (Response)response);
+            }
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    public void setRequestLog(RequestLog requestLog)
+    {
+        updateBean(_requestLog,requestLog);
+        _requestLog=requestLog;
+    }
+
+    /* ------------------------------------------------------------ */
+    public RequestLog getRequestLog()
+    {
+        return _requestLog;
+    }
+    
+    /* ------------------------------------------------------------ */
+    @Override
+    protected void doStart() throws Exception
+    {
+        if (_requestLog==null)
+        {
+            LOG.warn("!RequestLog");
+            _requestLog=new NullRequestLog();
+        }
+        super.doStart();
+    }
+    
+    /* ------------------------------------------------------------ */
+    @Override
+    protected void doStop() throws Exception
+    {
+        super.doStop();
+        if (_requestLog instanceof NullRequestLog)
+            _requestLog=null;
+    }
+
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    private static class NullRequestLog extends AbstractLifeCycle implements RequestLog
+    {
+        @Override
+        public void log(Request request, Response response)
+        {            
+        }
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/server/handler/ResourceHandler.java b/lib/jetty/org/eclipse/jetty/server/handler/ResourceHandler.java
new file mode 100644 (file)
index 0000000..10d1e05
--- /dev/null
@@ -0,0 +1,613 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server.handler;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.net.MalformedURLException;
+import java.nio.ByteBuffer;
+import java.nio.channels.ReadableByteChannel;
+
+import javax.servlet.AsyncContext;
+import javax.servlet.RequestDispatcher;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.http.HttpFields;
+import org.eclipse.jetty.http.HttpHeader;
+import org.eclipse.jetty.http.HttpMethod;
+import org.eclipse.jetty.http.HttpStatus;
+import org.eclipse.jetty.http.MimeTypes;
+import org.eclipse.jetty.io.WriterOutputStream;
+import org.eclipse.jetty.server.HttpOutput;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.Response;
+import org.eclipse.jetty.server.handler.ContextHandler.Context;
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.Callback;
+import org.eclipse.jetty.util.URIUtil;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.resource.FileResource;
+import org.eclipse.jetty.util.resource.Resource;
+
+
+/* ------------------------------------------------------------ */
+/** Resource Handler.
+ *
+ * This handle will serve static content and handle If-Modified-Since headers.
+ * No caching is done.
+ * Requests for resources that do not exist are let pass (Eg no 404's).
+ *
+ *
+ * @org.apache.xbean.XBean
+ */
+public class ResourceHandler extends HandlerWrapper
+{
+    private static final Logger LOG = Log.getLogger(ResourceHandler.class);
+
+    ContextHandler _context;
+    Resource _baseResource;
+    Resource _defaultStylesheet;
+    Resource _stylesheet;
+    String[] _welcomeFiles={"index.html"};
+    MimeTypes _mimeTypes = new MimeTypes();
+    String _cacheControl;
+    boolean _directory;
+    boolean _etags;
+    int _minMemoryMappedContentLength=-1;
+    int _minAsyncContentLength=0;
+
+    /* ------------------------------------------------------------ */
+    public ResourceHandler()
+    {
+
+    }
+
+    /* ------------------------------------------------------------ */
+    public MimeTypes getMimeTypes()
+    {
+        return _mimeTypes;
+    }
+
+    /* ------------------------------------------------------------ */
+    public void setMimeTypes(MimeTypes mimeTypes)
+    {
+        _mimeTypes = mimeTypes;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Get the directory option.
+     * @return true if directories are listed.
+     */
+    public boolean isDirectoriesListed()
+    {
+        return _directory;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Set the directory.
+     * @param directory true if directories are listed.
+     */
+    public void setDirectoriesListed(boolean directory)
+    {
+        _directory = directory;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Get minimum memory mapped file content length.
+     * @return the minimum size in bytes of a file resource that will
+     * be served using a memory mapped buffer, or -1 (default) for no memory mapped
+     * buffers.
+     */
+    public int getMinMemoryMappedContentLength()
+    {
+        return _minMemoryMappedContentLength;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Set minimum memory mapped file content length.
+     * @param minMemoryMappedFileSize the minimum size in bytes of a file resource that will
+     * be served using a memory mapped buffer, or -1 for no memory mapped
+     * buffers.
+     */
+    public void setMinMemoryMappedContentLength(int minMemoryMappedFileSize)
+    {
+        _minMemoryMappedContentLength = minMemoryMappedFileSize;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Get the minimum content length for async handling.
+     * @return The minimum size in bytes of the content before asynchronous 
+     * handling is used, or -1 for no async handling or 0 (default) for using
+     * {@link HttpServletResponse#getBufferSize()} as the minimum length.
+     */
+    public int getMinAsyncContentLength()
+    {
+        return _minAsyncContentLength;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Set the minimum content length for async handling.
+     * @param minAsyncContentLength The minimum size in bytes of the content before asynchronous 
+     * handling is used, or -1 for no async handling or 0 for using
+     * {@link HttpServletResponse#getBufferSize()} as the minimum length.
+     */
+    public void setMinAsyncContentLength(int minAsyncContentLength)
+    {
+        _minAsyncContentLength = minAsyncContentLength;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return True if ETag processing is done
+     */
+    public boolean isEtags()
+    {
+        return _etags;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param etags True if ETag processing is done
+     */
+    public void setEtags(boolean etags)
+    {
+        _etags = etags;
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public void doStart()
+    throws Exception
+    {
+        Context scontext = ContextHandler.getCurrentContext();
+        _context = (scontext==null?null:scontext.getContextHandler());
+
+        super.doStart();
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Returns the resourceBase.
+     */
+    public Resource getBaseResource()
+    {
+        if (_baseResource==null)
+            return null;
+        return _baseResource;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Returns the base resource as a string.
+     */
+    public String getResourceBase()
+    {
+        if (_baseResource==null)
+            return null;
+        return _baseResource.toString();
+    }
+
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param base The resourceBase to set.
+     */
+    public void setBaseResource(Resource base)
+    {
+        _baseResource=base;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param resourceBase The base resource as a string.
+     */
+    public void setResourceBase(String resourceBase)
+    {
+        try
+        {
+            setBaseResource(Resource.newResource(resourceBase));
+        }
+        catch (Exception e)
+        {
+            LOG.warn(e.toString());
+            LOG.debug(e);
+            throw new IllegalArgumentException(resourceBase);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Returns the stylesheet as a Resource.
+     */
+    public Resource getStylesheet()
+    {
+       if(_stylesheet != null)
+       {
+           return _stylesheet;
+       }
+       else
+       {
+           if(_defaultStylesheet == null)
+           {
+               _defaultStylesheet =  Resource.newResource(this.getClass().getResource("/jetty-dir.css"));
+           }
+           return _defaultStylesheet;
+       }
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param stylesheet The location of the stylesheet to be used as a String.
+     */
+    public void setStylesheet(String stylesheet)
+    {
+        try
+        {
+            _stylesheet = Resource.newResource(stylesheet);
+            if(!_stylesheet.exists())
+            {
+                LOG.warn("unable to find custom stylesheet: " + stylesheet);
+                _stylesheet = null;
+            }
+        }
+       catch(Exception e)
+       {
+           LOG.warn(e.toString());
+           LOG.debug(e);
+           throw new IllegalArgumentException(stylesheet);
+       }
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return the cacheControl header to set on all static content.
+     */
+    public String getCacheControl()
+    {
+        return _cacheControl;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param cacheControl the cacheControl header to set on all static content.
+     */
+    public void setCacheControl(String cacheControl)
+    {
+        _cacheControl=cacheControl;
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     */
+    public Resource getResource(String path) throws MalformedURLException
+    {
+        if (path==null || !path.startsWith("/"))
+            throw new MalformedURLException(path);
+
+        Resource base = _baseResource;
+        if (base==null)
+        {
+            if (_context==null)
+                return null;
+            base=_context.getBaseResource();
+            if (base==null)
+                return null;
+        }
+
+        try
+        {
+            path=URIUtil.canonicalPath(path);
+            return base.addPath(path);
+        }
+        catch(Exception e)
+        {
+            LOG.ignore(e);
+        }
+
+        return null;
+    }
+
+    /* ------------------------------------------------------------ */
+    protected Resource getResource(HttpServletRequest request) throws MalformedURLException
+    {
+        String servletPath;
+        String pathInfo;
+        Boolean included = request.getAttribute(RequestDispatcher.INCLUDE_REQUEST_URI) != null;
+        if (included != null && included.booleanValue())
+        {
+            servletPath = (String)request.getAttribute(RequestDispatcher.INCLUDE_SERVLET_PATH);
+            pathInfo = (String)request.getAttribute(RequestDispatcher.INCLUDE_PATH_INFO);
+
+            if (servletPath == null && pathInfo == null)
+            {
+                servletPath = request.getServletPath();
+                pathInfo = request.getPathInfo();
+            }
+        }
+        else
+        {
+            servletPath = request.getServletPath();
+            pathInfo = request.getPathInfo();
+        }
+
+        String pathInContext=URIUtil.addPaths(servletPath,pathInfo);
+        return getResource(pathInContext);
+    }
+
+
+    /* ------------------------------------------------------------ */
+    public String[] getWelcomeFiles()
+    {
+        return _welcomeFiles;
+    }
+
+    /* ------------------------------------------------------------ */
+    public void setWelcomeFiles(String[] welcomeFiles)
+    {
+        _welcomeFiles=welcomeFiles;
+    }
+
+    /* ------------------------------------------------------------ */
+    protected Resource getWelcome(Resource directory) throws MalformedURLException, IOException
+    {
+        for (int i=0;i<_welcomeFiles.length;i++)
+        {
+            Resource welcome=directory.addPath(_welcomeFiles[i]);
+            if (welcome.exists() && !welcome.isDirectory())
+                return welcome;
+        }
+
+        return null;
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see org.eclipse.jetty.server.Handler#handle(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, int)
+     */
+    @Override
+    public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+    {
+        if (baseRequest.isHandled())
+            return;
+
+        boolean skipContentBody = false;
+
+        if(!HttpMethod.GET.is(request.getMethod()))
+        {
+            if(!HttpMethod.HEAD.is(request.getMethod()))
+            {
+                //try another handler
+                super.handle(target, baseRequest, request, response);
+                return;
+            }
+            skipContentBody = true;
+        }
+
+        Resource resource = getResource(request);
+        // If resource is not found
+        if (resource==null || !resource.exists())
+        {
+            // inject the jetty-dir.css file if it matches
+            if (target.endsWith("/jetty-dir.css"))
+            {
+                resource = getStylesheet();
+                if (resource==null)
+                    return;
+                response.setContentType("text/css");
+            }
+            else
+            {
+                //no resource - try other handlers
+                super.handle(target, baseRequest, request, response);
+                return;
+            }
+        }
+
+        // We are going to serve something
+        baseRequest.setHandled(true);
+
+        // handle directories
+        if (resource.isDirectory())
+        {
+            if (!request.getPathInfo().endsWith(URIUtil.SLASH))
+            {
+                response.sendRedirect(response.encodeRedirectURL(URIUtil.addPaths(request.getRequestURI(),URIUtil.SLASH)));
+                return;
+            }
+
+            Resource welcome=getWelcome(resource);
+            if (welcome!=null && welcome.exists())
+                resource=welcome;
+            else
+            {
+                doDirectory(request,response,resource);
+                baseRequest.setHandled(true);
+                return;
+            }
+        }
+
+        // Handle ETAGS
+        long last_modified=resource.lastModified();
+        String etag=null;
+        if (_etags)
+        {
+            // simple handling of only a single etag
+            String ifnm = request.getHeader(HttpHeader.IF_NONE_MATCH.asString());
+            etag=resource.getWeakETag();
+            if (ifnm!=null && resource!=null && ifnm.equals(etag))
+            {
+                response.setStatus(HttpStatus.NOT_MODIFIED_304);
+                baseRequest.getResponse().getHttpFields().put(HttpHeader.ETAG,etag);
+                return;
+            }
+        }
+        
+        // Handle if modified since 
+        if (last_modified>0)
+        {
+            long if_modified=request.getDateHeader(HttpHeader.IF_MODIFIED_SINCE.asString());
+            if (if_modified>0 && last_modified/1000<=if_modified/1000)
+            {
+                response.setStatus(HttpStatus.NOT_MODIFIED_304);
+                return;
+            }
+        }
+
+        // set the headers
+        String mime=_mimeTypes.getMimeByExtension(resource.toString());
+        if (mime==null)
+            mime=_mimeTypes.getMimeByExtension(request.getPathInfo());
+        doResponseHeaders(response,resource,mime);
+        if (_etags)
+            baseRequest.getResponse().getHttpFields().put(HttpHeader.ETAG,etag);
+        
+        if(skipContentBody)
+            return;
+        
+        
+        // Send the content
+        OutputStream out =null;
+        try {out = response.getOutputStream();}
+        catch(IllegalStateException e) {out = new WriterOutputStream(response.getWriter());}
+
+        // Has the output been wrapped
+        if (!(out instanceof HttpOutput))
+            // Write content via wrapped output
+            resource.writeTo(out,0,resource.length());
+        else
+        {
+            // select async by size
+            int min_async_size=_minAsyncContentLength==0?response.getBufferSize():_minAsyncContentLength;
+            
+            if (request.isAsyncSupported() && 
+                min_async_size>0 &&
+                resource.length()>=min_async_size)
+            {
+                final AsyncContext async = request.startAsync();
+                Callback callback = new Callback()
+                {
+                    @Override
+                    public void succeeded()
+                    {
+                        async.complete();
+                    }
+
+                    @Override
+                    public void failed(Throwable x)
+                    {
+                        LOG.warn(x.toString());
+                        LOG.debug(x);
+                        async.complete();
+                    }   
+                };
+
+                // Can we use a memory mapped file?
+                if (_minMemoryMappedContentLength>0 && 
+                    resource.length()>_minMemoryMappedContentLength &&
+                    resource instanceof FileResource)
+                {
+                    ByteBuffer buffer = BufferUtil.toMappedBuffer(resource.getFile());
+                    ((HttpOutput)out).sendContent(buffer,callback);
+                }
+                else  // Do a blocking write of a channel (if available) or input stream
+                {
+                    // Close of the channel/inputstream is done by the async sendContent
+                    ReadableByteChannel channel= resource.getReadableByteChannel();
+                    if (channel!=null)
+                        ((HttpOutput)out).sendContent(channel,callback);
+                    else
+                        ((HttpOutput)out).sendContent(resource.getInputStream(),callback);
+                }
+            }
+            else
+            {
+                // Can we use a memory mapped file?
+                if (_minMemoryMappedContentLength>0 && 
+                    resource.length()>_minMemoryMappedContentLength &&
+                    resource instanceof FileResource)
+                {
+                    ByteBuffer buffer = BufferUtil.toMappedBuffer(resource.getFile());
+                    ((HttpOutput)out).sendContent(buffer);
+                }
+                else  // Do a blocking write of a channel (if available) or input stream
+                {
+                    ReadableByteChannel channel= resource.getReadableByteChannel();
+                    if (channel!=null)
+                        ((HttpOutput)out).sendContent(channel);
+                    else
+                        ((HttpOutput)out).sendContent(resource.getInputStream());
+                }
+            }
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    protected void doDirectory(HttpServletRequest request,HttpServletResponse response, Resource resource)
+        throws IOException
+    {
+        if (_directory)
+        {
+            String listing = resource.getListHTML(request.getRequestURI(),request.getPathInfo().lastIndexOf("/") > 0);
+            response.setContentType("text/html; charset=UTF-8");
+            response.getWriter().println(listing);
+        }
+        else
+            response.sendError(HttpStatus.FORBIDDEN_403);
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Set the response headers.
+     * This method is called to set the response headers such as content type and content length.
+     * May be extended to add additional headers.
+     * @param response
+     * @param resource
+     * @param mimeType
+     */
+    protected void doResponseHeaders(HttpServletResponse response, Resource resource, String mimeType)
+    {
+        if (mimeType!=null)
+            response.setContentType(mimeType);
+
+        long length=resource.length();
+
+        if (response instanceof Response)
+        {
+            HttpFields fields = ((Response)response).getHttpFields();
+
+            if (length>0)
+                ((Response)response).setLongContentLength(length);
+
+            if (_cacheControl!=null)
+                fields.put(HttpHeader.CACHE_CONTROL,_cacheControl);
+        }
+        else
+        {
+            if (length>Integer.MAX_VALUE)
+                response.setHeader(HttpHeader.CONTENT_LENGTH.asString(),Long.toString(length));
+            else if (length>0)
+                response.setContentLength((int)length);
+
+            if (_cacheControl!=null)
+                response.setHeader(HttpHeader.CACHE_CONTROL.asString(),_cacheControl);
+        }
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/server/handler/ScopedHandler.java b/lib/jetty/org/eclipse/jetty/server/handler/ScopedHandler.java
new file mode 100644 (file)
index 0000000..50998be
--- /dev/null
@@ -0,0 +1,200 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server.handler;
+
+import java.io.IOException;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.server.Request;
+
+
+/* ------------------------------------------------------------ */
+/** ScopedHandler.
+ *
+ * A ScopedHandler is a HandlerWrapper where the wrapped handlers
+ * each define a scope.   
+ * 
+ * <p>When {@link #handle(String, Request, HttpServletRequest, HttpServletResponse)}
+ * is called on the first ScopedHandler in a chain of HandlerWrappers,
+ * the {@link #doScope(String, Request, HttpServletRequest, HttpServletResponse)} method is
+ * called on all contained ScopedHandlers, before the
+ * {@link #doHandle(String, Request, HttpServletRequest, HttpServletResponse)} method
+ * is called on all contained handlers.</p>
+ *
+ * <p>For example if Scoped handlers A, B & C were chained together, then
+ * the calling order would be:</p>
+ * <pre>
+ * A.handle(...)
+ *   A.doScope(...)
+ *     B.doScope(...)
+ *       C.doScope(...)
+ *         A.doHandle(...)
+ *           B.doHandle(...)
+ *              C.doHandle(...)
+ * </pre>
+ *
+ * <p>If non scoped handler X was in the chained A, B, X & C, then
+ * the calling order would be:</p>
+ * <pre>
+ * A.handle(...)
+ *   A.doScope(...)
+ *     B.doScope(...)
+ *       C.doScope(...)
+ *         A.doHandle(...)
+ *           B.doHandle(...)
+ *             X.handle(...)
+ *               C.handle(...)
+ *                 C.doHandle(...)
+ * </pre>
+ *
+ * <p>A typical usage pattern is:</p>
+ * <pre>
+ *     private static class MyHandler extends ScopedHandler
+ *     {
+ *         public void doScope(String target, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+ *         {
+ *             try
+ *             {
+ *                 setUpMyScope();
+ *                 super.doScope(target,request,response);
+ *             }
+ *             finally
+ *             {
+ *                 tearDownMyScope();
+ *             }
+ *         }
+ *
+ *         public void doHandle(String target, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+ *         {
+ *             try
+ *             {
+ *                 doMyHandling();
+ *                 super.doHandle(target,request,response);
+ *             }
+ *             finally
+ *             {
+ *                 cleanupMyHandling();
+ *             }
+ *         }
+ *     }
+ * </pre>
+ */
+public abstract class ScopedHandler extends HandlerWrapper
+{
+    private static final ThreadLocal<ScopedHandler> __outerScope= new ThreadLocal<ScopedHandler>();
+    protected ScopedHandler _outerScope;
+    protected ScopedHandler _nextScope;
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.server.handler.HandlerWrapper#doStart()
+     */
+    @Override
+    protected void doStart() throws Exception
+    {
+        try
+        {
+            _outerScope=__outerScope.get();
+            if (_outerScope==null)
+                __outerScope.set(this);
+
+            super.doStart();
+
+            _nextScope= getChildHandlerByClass(ScopedHandler.class);
+
+        }
+        finally
+        {
+            if (_outerScope==null)
+                __outerScope.set(null);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     */
+    @Override
+    public final void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+    {
+        if (isStarted())
+        {
+            if (_outerScope==null)
+                doScope(target,baseRequest,request, response);
+            else
+                doHandle(target,baseRequest,request, response);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * Scope the handler
+     */
+    public abstract void doScope(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response)
+        throws IOException, ServletException;
+
+    /* ------------------------------------------------------------ */
+    /*
+     * Scope the handler
+     */
+    public final void nextScope(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response)
+        throws IOException, ServletException
+    {
+        // this method has been manually inlined in several locations, but
+        // is called protected by an if(never()), so your IDE can find those
+        // locations if this code is changed.
+        if (_nextScope!=null)
+            _nextScope.doScope(target,baseRequest,request, response);
+        else if (_outerScope!=null)
+            _outerScope.doHandle(target,baseRequest,request, response);
+        else
+            doHandle(target,baseRequest,request, response);
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * Do the handler work within the scope.
+     */
+    public abstract void doHandle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response)
+        throws IOException, ServletException;
+
+    /* ------------------------------------------------------------ */
+    /*
+     * Do the handler work within the scope.
+     */
+    public final void nextHandle(String target, final Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+    {
+        // this method has been manually inlined in several locations, but
+        // is called protected by an if(never()), so your IDE can find those
+        // locations if this code is changed.
+        if (_nextScope!=null && _nextScope==_handler)
+            _nextScope.doHandle(target,baseRequest,request, response);
+        else if (_handler!=null)
+            _handler.handle(target,baseRequest, request, response);
+    }
+
+    /* ------------------------------------------------------------ */
+    protected boolean never()
+    {
+        return false;
+    }
+
+}
diff --git a/lib/jetty/org/eclipse/jetty/server/handler/ShutdownHandler.java b/lib/jetty/org/eclipse/jetty/server/handler/ShutdownHandler.java
new file mode 100644 (file)
index 0000000..e946fb6
--- /dev/null
@@ -0,0 +1,266 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server.handler;
+
+import java.io.IOException;
+import java.net.HttpURLConnection;
+import java.net.InetSocketAddress;
+import java.net.SocketException;
+import java.net.URL;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.server.Connector;
+import org.eclipse.jetty.server.NetworkConnector;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/* ------------------------------------------------------------ */
+/**
+ * A handler that shuts the server down on a valid request. Used to do "soft" restarts from Java. If _exitJvm ist set to true a hard System.exit() call is being
+ * made.
+ *
+ * This handler is a contribution from Johannes Brodwall: https://bugs.eclipse.org/bugs/show_bug.cgi?id=357687
+ *
+ * Usage:
+ *
+ * <pre>
+    Server server = new Server(8080);
+    HandlerList handlers = new HandlerList();
+    handlers.setHandlers(new Handler[]
+    { someOtherHandler, new ShutdownHandler(&quot;secret password&quot;) });
+    server.setHandler(handlers);
+    server.start();
+   </pre>
+ *
+   <pre>
+   public static void attemptShutdown(int port, String shutdownCookie) {
+        try {
+            URL url = new URL("http://localhost:" + port + "/shutdown?token=" + shutdownCookie);
+            HttpURLConnection connection = (HttpURLConnection)url.openConnection();
+            connection.setRequestMethod("POST");
+            connection.getResponseCode();
+            logger.info("Shutting down " + url + ": " + connection.getResponseMessage());
+        } catch (SocketException e) {
+            logger.debug("Not running");
+            // Okay - the server is not running
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+    }
+  </pre>
+ */
+public class ShutdownHandler extends HandlerWrapper
+{
+    private static final Logger LOG = Log.getLogger(ShutdownHandler.class);
+
+    private final String _shutdownToken;
+    private boolean _sendShutdownAtStart;
+    private boolean _exitJvm = false;
+
+
+    /**
+     * Creates a listener that lets the server be shut down remotely (but only from localhost).
+     *
+     * @param server
+     *            the Jetty instance that should be shut down
+     * @param shutdownToken
+     *            a secret password to avoid unauthorized shutdown attempts
+     */
+    @Deprecated
+    public ShutdownHandler(Server server, String shutdownToken)
+    {
+        this(shutdownToken);
+    }
+
+    public ShutdownHandler(String shutdownToken)
+    {
+        this(shutdownToken,false,false);
+    }
+    
+    /**
+     * @param shutdownToken
+     * @param sendShutdownAtStart If true, a shutdown is sent as a HTTP post
+     * during startup, which will shutdown any previously running instances of
+     * this server with an identically configured ShutdownHandler
+     */
+    public ShutdownHandler(String shutdownToken, boolean exitJVM, boolean sendShutdownAtStart)
+    {
+        this._shutdownToken = shutdownToken;
+        setExitJvm(exitJVM);
+        setSendShutdownAtStart(sendShutdownAtStart);
+    }
+    
+
+    public void sendShutdown() throws IOException
+    {
+        URL url = new URL(getServerUrl() + "/shutdown?token=" + _shutdownToken);
+        try
+        {
+            HttpURLConnection connection = (HttpURLConnection)url.openConnection();
+            connection.setRequestMethod("POST");
+            connection.getResponseCode();
+            LOG.info("Shutting down " + url + ": " + connection.getResponseMessage());
+        }
+        catch (SocketException e)
+        {
+            LOG.debug("Not running");
+            // Okay - the server is not running
+        }
+        catch (IOException e)
+        {
+            throw new RuntimeException(e);
+        }
+    }
+
+    private String getServerUrl()
+    {
+        NetworkConnector connector=null;
+        for (Connector c: getServer().getConnectors())
+        {
+            if (c instanceof NetworkConnector)
+            {
+                connector=(NetworkConnector)c;
+                break;
+            }
+        }
+
+        if (connector==null)
+            return "http://localhost";
+
+        return "http://localhost:" + connector.getPort();
+    }
+    
+    
+    @Override
+    protected void doStart() throws Exception
+    {
+        super.doStart();
+        if (_sendShutdownAtStart)
+            sendShutdown();
+    }
+    
+    @Override
+    public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+    {
+        if (!target.equals("/shutdown"))
+        {
+            super.handle(target,baseRequest,request,response);
+            return;
+        }
+
+        if (!request.getMethod().equals("POST"))
+        {
+            response.sendError(HttpServletResponse.SC_BAD_REQUEST);
+            return;
+        }
+        if (!hasCorrectSecurityToken(request))
+        {
+            LOG.warn("Unauthorized tokenless shutdown attempt from " + request.getRemoteAddr());
+            response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
+            return;
+        }
+        if (!requestFromLocalhost(baseRequest))
+        {
+            LOG.warn("Unauthorized non-loopback shutdown attempt from " + request.getRemoteAddr());
+            response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
+            return;
+        }
+
+        LOG.info("Shutting down by request from " + request.getRemoteAddr());
+
+        final Server server=getServer();
+        new Thread()
+        {
+            @Override
+            public void run ()
+            {
+                try
+                {
+                    shutdownServer(server);
+                }
+                catch (InterruptedException e)
+                {
+                    LOG.ignore(e);
+                }
+                catch (Exception e)
+                {
+                    throw new RuntimeException("Shutting down server",e);
+                }
+            }
+        }.start();
+    }
+
+    private boolean requestFromLocalhost(Request request)
+    {
+        InetSocketAddress addr = request.getRemoteInetSocketAddress();
+        if (addr == null)
+        {
+            return false;
+        }
+        return addr.getAddress().isLoopbackAddress();
+    }
+
+    private boolean hasCorrectSecurityToken(HttpServletRequest request)
+    {
+        String tok = request.getParameter("token");
+        LOG.debug("Token: {}", tok);
+        return _shutdownToken.equals(tok);
+    }
+
+    private void shutdownServer(Server server) throws Exception
+    {
+        server.stop();
+
+        if (_exitJvm)
+        {
+            System.exit(0);
+        }
+    }
+
+    public void setExitJvm(boolean exitJvm)
+    {
+        this._exitJvm = exitJvm;
+    }
+
+    public boolean isSendShutdownAtStart()
+    {
+        return _sendShutdownAtStart;
+    }
+
+    public void setSendShutdownAtStart(boolean sendShutdownAtStart)
+    {
+        _sendShutdownAtStart = sendShutdownAtStart;
+    }
+
+    public String getShutdownToken()
+    {
+        return _shutdownToken;
+    }
+
+    public boolean isExitJvm()
+    {
+        return _exitJvm;
+    }
+
+}
diff --git a/lib/jetty/org/eclipse/jetty/server/handler/StatisticsHandler.java b/lib/jetty/org/eclipse/jetty/server/handler/StatisticsHandler.java
new file mode 100644 (file)
index 0000000..98bb413
--- /dev/null
@@ -0,0 +1,571 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server.handler;
+
+import java.io.IOException;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeoutException;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.concurrent.atomic.AtomicReference;
+
+import javax.servlet.AsyncEvent;
+import javax.servlet.AsyncListener;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.server.AsyncContextEvent;
+import org.eclipse.jetty.server.HttpChannelState;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.Response;
+import org.eclipse.jetty.util.FutureCallback;
+import org.eclipse.jetty.util.annotation.ManagedAttribute;
+import org.eclipse.jetty.util.annotation.ManagedObject;
+import org.eclipse.jetty.util.annotation.ManagedOperation;
+import org.eclipse.jetty.util.component.Graceful;
+import org.eclipse.jetty.util.statistic.CounterStatistic;
+import org.eclipse.jetty.util.statistic.SampleStatistic;
+
+@ManagedObject("Request Statistics Gathering")
+public class StatisticsHandler extends HandlerWrapper implements Graceful
+{
+    private final AtomicLong _statsStartedAt = new AtomicLong();
+
+    private final CounterStatistic _requestStats = new CounterStatistic();
+    private final SampleStatistic _requestTimeStats = new SampleStatistic();
+    private final CounterStatistic _dispatchedStats = new CounterStatistic();
+    private final SampleStatistic _dispatchedTimeStats = new SampleStatistic();
+    private final CounterStatistic _asyncWaitStats = new CounterStatistic();
+
+    private final AtomicInteger _asyncDispatches = new AtomicInteger();
+    private final AtomicInteger _expires = new AtomicInteger();
+
+    private final AtomicInteger _responses1xx = new AtomicInteger();
+    private final AtomicInteger _responses2xx = new AtomicInteger();
+    private final AtomicInteger _responses3xx = new AtomicInteger();
+    private final AtomicInteger _responses4xx = new AtomicInteger();
+    private final AtomicInteger _responses5xx = new AtomicInteger();
+    private final AtomicLong _responsesTotalBytes = new AtomicLong();
+
+    private final AtomicReference<FutureCallback> _shutdown=new AtomicReference<>();
+    
+    private final AsyncListener _onCompletion = new AsyncListener()
+    {
+        @Override
+        public void onTimeout(AsyncEvent event) throws IOException
+        {
+            _expires.incrementAndGet();
+        }
+        
+        @Override
+        public void onStartAsync(AsyncEvent event) throws IOException
+        {
+            event.getAsyncContext().addListener(this);
+        }
+        
+        @Override
+        public void onError(AsyncEvent event) throws IOException
+        {
+        }
+
+        @Override
+        public void onComplete(AsyncEvent event) throws IOException
+        {
+            HttpChannelState state = ((AsyncContextEvent)event).getHttpChannelState();
+
+            Request request = state.getBaseRequest();
+            final long elapsed = System.currentTimeMillis()-request.getTimeStamp();
+
+            long d=_requestStats.decrement();
+            _requestTimeStats.set(elapsed);
+
+            updateResponse(request);
+
+            _asyncWaitStats.decrement();
+            
+            // If we have no more dispatches, should we signal shutdown?
+            if (d==0)
+            {
+                FutureCallback shutdown = _shutdown.get();
+                if (shutdown!=null)
+                    shutdown.succeeded();
+            }   
+        }
+    };
+
+    /**
+     * Resets the current request statistics.
+     */
+    @ManagedOperation(value="resets statistics", impact="ACTION")
+    public void statsReset()
+    {
+        _statsStartedAt.set(System.currentTimeMillis());
+
+        _requestStats.reset();
+        _requestTimeStats.reset();
+        _dispatchedStats.reset();
+        _dispatchedTimeStats.reset();
+        _asyncWaitStats.reset();
+
+        _asyncDispatches.set(0);
+        _expires.set(0);
+        _responses1xx.set(0);
+        _responses2xx.set(0);
+        _responses3xx.set(0);
+        _responses4xx.set(0);
+        _responses5xx.set(0);
+        _responsesTotalBytes.set(0L);
+    }
+
+    @Override
+    public void handle(String path, Request request, HttpServletRequest httpRequest, HttpServletResponse httpResponse) throws IOException, ServletException
+    {
+        _dispatchedStats.increment();
+
+        final long start;
+        HttpChannelState state = request.getHttpChannelState();
+        if (state.isInitial())
+        {
+            // new request
+            _requestStats.increment();
+            start = request.getTimeStamp();
+        }
+        else
+        {
+            // resumed request
+            start = System.currentTimeMillis();
+            _asyncDispatches.incrementAndGet();
+        }
+
+        try
+        {
+            super.handle(path, request, httpRequest, httpResponse);
+        }
+        finally
+        {
+            final long now = System.currentTimeMillis();
+            final long dispatched=now-start;
+
+            _dispatchedStats.decrement();
+            _dispatchedTimeStats.set(dispatched);
+
+            if (state.isSuspended())
+            {
+                if (state.isInitial())
+                {
+                    state.addListener(_onCompletion);
+                    _asyncWaitStats.increment();
+                }
+            }
+            else if (state.isInitial())
+            {
+                long d=_requestStats.decrement();
+                _requestTimeStats.set(dispatched);
+                updateResponse(request);
+                
+                // If we have no more dispatches, should we signal shutdown?
+                FutureCallback shutdown = _shutdown.get();
+                if (shutdown!=null)
+                {
+                    httpResponse.flushBuffer();
+                    if (d==0)
+                        shutdown.succeeded();
+                }   
+            }
+            // else onCompletion will handle it.
+        }
+    }
+
+    private void updateResponse(Request request)
+    {
+        Response response = request.getResponse();
+        switch (response.getStatus() / 100)
+        {
+            case 0:
+                if (request.isHandled())
+                    _responses2xx.incrementAndGet();
+                else
+                    _responses4xx.incrementAndGet();
+                break;
+            case 1:
+                _responses1xx.incrementAndGet();
+                break;
+            case 2:
+                _responses2xx.incrementAndGet();
+                break;
+            case 3:
+                _responses3xx.incrementAndGet();
+                break;
+            case 4:
+                _responses4xx.incrementAndGet();
+                break;
+            case 5:
+                _responses5xx.incrementAndGet();
+                break;
+            default:
+                break;
+        }
+        _responsesTotalBytes.addAndGet(response.getContentCount());
+    }
+
+    @Override
+    protected void doStart() throws Exception
+    {
+        _shutdown.set(null);
+        super.doStart();
+        statsReset();
+    }
+    
+
+    @Override
+    protected void doStop() throws Exception
+    {
+        super.doStop();
+        FutureCallback shutdown = _shutdown.get();
+        if (shutdown!=null && !shutdown.isDone())
+            shutdown.failed(new TimeoutException());
+    }
+
+    /**
+     * @return the number of requests handled by this handler
+     * since {@link #statsReset()} was last called, excluding
+     * active requests
+     * @see #getAsyncDispatches()
+     */
+    @ManagedAttribute("number of requests")
+    public int getRequests()
+    {
+        return (int)_requestStats.getTotal();
+    }
+
+    /**
+     * @return the number of requests currently active.
+     * since {@link #statsReset()} was last called.
+     */
+    @ManagedAttribute("number of requests currently active")
+    public int getRequestsActive()
+    {
+        return (int)_requestStats.getCurrent();
+    }
+
+    /**
+     * @return the maximum number of active requests
+     * since {@link #statsReset()} was last called.
+     */
+    @ManagedAttribute("maximum number of active requests")
+    public int getRequestsActiveMax()
+    {
+        return (int)_requestStats.getMax();
+    }
+
+    /**
+     * @return the maximum time (in milliseconds) of request handling
+     * since {@link #statsReset()} was last called.
+     */
+    @ManagedAttribute("maximum time spend handling requests (in ms)")
+    public long getRequestTimeMax()
+    {
+        return _requestTimeStats.getMax();
+    }
+
+    /**
+     * @return the total time (in milliseconds) of requests handling
+     * since {@link #statsReset()} was last called.
+     */
+    @ManagedAttribute("total time spend in all request handling (in ms)")
+    public long getRequestTimeTotal()
+    {
+        return _requestTimeStats.getTotal();
+    }
+
+    /**
+     * @return the mean time (in milliseconds) of request handling
+     * since {@link #statsReset()} was last called.
+     * @see #getRequestTimeTotal()
+     * @see #getRequests()
+     */
+    @ManagedAttribute("mean time spent handling requests (in ms)")
+    public double getRequestTimeMean()
+    {
+        return _requestTimeStats.getMean();
+    }
+
+    /**
+     * @return the standard deviation of time (in milliseconds) of request handling
+     * since {@link #statsReset()} was last called.
+     * @see #getRequestTimeTotal()
+     * @see #getRequests()
+     */
+    @ManagedAttribute("standard deviation for request handling (in ms)")
+    public double getRequestTimeStdDev()
+    {
+        return _requestTimeStats.getStdDev();
+    }
+
+    /**
+     * @return the number of dispatches seen by this handler
+     * since {@link #statsReset()} was last called, excluding
+     * active dispatches
+     */
+    @ManagedAttribute("number of dispatches")
+    public int getDispatched()
+    {
+        return (int)_dispatchedStats.getTotal();
+    }
+
+    /**
+     * @return the number of dispatches currently in this handler
+     * since {@link #statsReset()} was last called, including
+     * resumed requests
+     */
+    @ManagedAttribute("number of dispatches currently active")
+    public int getDispatchedActive()
+    {
+        return (int)_dispatchedStats.getCurrent();
+    }
+
+    /**
+     * @return the max number of dispatches currently in this handler
+     * since {@link #statsReset()} was last called, including
+     * resumed requests
+     */
+    @ManagedAttribute("maximum number of active dispatches being handled")
+    public int getDispatchedActiveMax()
+    {
+        return (int)_dispatchedStats.getMax();
+    }
+
+    /**
+     * @return the maximum time (in milliseconds) of request dispatch
+     * since {@link #statsReset()} was last called.
+     */
+    @ManagedAttribute("maximum time spend in dispatch handling")
+    public long getDispatchedTimeMax()
+    {
+        return _dispatchedTimeStats.getMax();
+    }
+
+    /**
+     * @return the total time (in milliseconds) of requests handling
+     * since {@link #statsReset()} was last called.
+     */
+    @ManagedAttribute("total time spent in dispatch handling (in ms)")
+    public long getDispatchedTimeTotal()
+    {
+        return _dispatchedTimeStats.getTotal();
+    }
+
+    /**
+     * @return the mean time (in milliseconds) of request handling
+     * since {@link #statsReset()} was last called.
+     * @see #getRequestTimeTotal()
+     * @see #getRequests()
+     */
+    @ManagedAttribute("mean time spent in dispatch handling (in ms)")
+    public double getDispatchedTimeMean()
+    {
+        return _dispatchedTimeStats.getMean();
+    }
+
+    /**
+     * @return the standard deviation of time (in milliseconds) of request handling
+     * since {@link #statsReset()} was last called.
+     * @see #getRequestTimeTotal()
+     * @see #getRequests()
+     */
+    @ManagedAttribute("standard deviation for dispatch handling (in ms)")
+    public double getDispatchedTimeStdDev()
+    {
+        return _dispatchedTimeStats.getStdDev();
+    }
+
+    /**
+     * @return the number of requests handled by this handler
+     * since {@link #statsReset()} was last called, including
+     * resumed requests
+     * @see #getAsyncDispatches()
+     */
+    @ManagedAttribute("total number of async requests")
+    public int getAsyncRequests()
+    {
+        return (int)_asyncWaitStats.getTotal();
+    }
+
+    /**
+     * @return the number of requests currently suspended.
+     * since {@link #statsReset()} was last called.
+     */
+    @ManagedAttribute("currently waiting async requests")
+    public int getAsyncRequestsWaiting()
+    {
+        return (int)_asyncWaitStats.getCurrent();
+    }
+
+    /**
+     * @return the maximum number of current suspended requests
+     * since {@link #statsReset()} was last called.
+     */
+    @ManagedAttribute("maximum number of waiting async requests")
+    public int getAsyncRequestsWaitingMax()
+    {
+        return (int)_asyncWaitStats.getMax();
+    }
+
+    /**
+     * @return the number of requests that have been asynchronously dispatched
+     */
+    @ManagedAttribute("number of requested that have been asynchronously dispatched")
+    public int getAsyncDispatches()
+    {
+        return _asyncDispatches.get();
+    }
+
+    /**
+     * @return the number of requests that expired while suspended.
+     * @see #getAsyncDispatches()
+     */
+    @ManagedAttribute("number of async requests requests that have expired")
+    public int getExpires()
+    {
+        return _expires.get();
+    }
+
+    /**
+     * @return the number of responses with a 1xx status returned by this context
+     * since {@link #statsReset()} was last called.
+     */
+    @ManagedAttribute("number of requests with 1xx response status")
+    public int getResponses1xx()
+    {
+        return _responses1xx.get();
+    }
+
+    /**
+     * @return the number of responses with a 2xx status returned by this context
+     * since {@link #statsReset()} was last called.
+     */
+    @ManagedAttribute("number of requests with 2xx response status")
+    public int getResponses2xx()
+    {
+        return _responses2xx.get();
+    }
+
+    /**
+     * @return the number of responses with a 3xx status returned by this context
+     * since {@link #statsReset()} was last called.
+     */
+    @ManagedAttribute("number of requests with 3xx response status")
+    public int getResponses3xx()
+    {
+        return _responses3xx.get();
+    }
+
+    /**
+     * @return the number of responses with a 4xx status returned by this context
+     * since {@link #statsReset()} was last called.
+     */
+    @ManagedAttribute("number of requests with 4xx response status")
+    public int getResponses4xx()
+    {
+        return _responses4xx.get();
+    }
+
+    /**
+     * @return the number of responses with a 5xx status returned by this context
+     * since {@link #statsReset()} was last called.
+     */
+    @ManagedAttribute("number of requests with 5xx response status")
+    public int getResponses5xx()
+    {
+        return _responses5xx.get();
+    }
+
+    /**
+     * @return the milliseconds since the statistics were started with {@link #statsReset()}.
+     */
+    @ManagedAttribute("time in milliseconds stats have been collected for")
+    public long getStatsOnMs()
+    {
+        return System.currentTimeMillis() - _statsStartedAt.get();
+    }
+
+    /**
+     * @return the total bytes of content sent in responses
+     */
+    @ManagedAttribute("total number of bytes across all responses")
+    public long getResponsesBytesTotal()
+    {
+        return _responsesTotalBytes.get();
+    }
+
+    public String toStatsHTML()
+    {
+        StringBuilder sb = new StringBuilder();
+
+        sb.append("<h1>Statistics:</h1>\n");
+        sb.append("Statistics gathering started ").append(getStatsOnMs()).append("ms ago").append("<br />\n");
+
+        sb.append("<h2>Requests:</h2>\n");
+        sb.append("Total requests: ").append(getRequests()).append("<br />\n");
+        sb.append("Active requests: ").append(getRequestsActive()).append("<br />\n");
+        sb.append("Max active requests: ").append(getRequestsActiveMax()).append("<br />\n");
+        sb.append("Total requests time: ").append(getRequestTimeTotal()).append("<br />\n");
+        sb.append("Mean request time: ").append(getRequestTimeMean()).append("<br />\n");
+        sb.append("Max request time: ").append(getRequestTimeMax()).append("<br />\n");
+        sb.append("Request time standard deviation: ").append(getRequestTimeStdDev()).append("<br />\n");
+
+
+        sb.append("<h2>Dispatches:</h2>\n");
+        sb.append("Total dispatched: ").append(getDispatched()).append("<br />\n");
+        sb.append("Active dispatched: ").append(getDispatchedActive()).append("<br />\n");
+        sb.append("Max active dispatched: ").append(getDispatchedActiveMax()).append("<br />\n");
+        sb.append("Total dispatched time: ").append(getDispatchedTimeTotal()).append("<br />\n");
+        sb.append("Mean dispatched time: ").append(getDispatchedTimeMean()).append("<br />\n");
+        sb.append("Max dispatched time: ").append(getDispatchedTimeMax()).append("<br />\n");
+        sb.append("Dispatched time standard deviation: ").append(getDispatchedTimeStdDev()).append("<br />\n");
+
+
+        sb.append("Total requests suspended: ").append(getAsyncRequests()).append("<br />\n");
+        sb.append("Total requests expired: ").append(getExpires()).append("<br />\n");
+        sb.append("Total requests resumed: ").append(getAsyncDispatches()).append("<br />\n");
+
+        sb.append("<h2>Responses:</h2>\n");
+        sb.append("1xx responses: ").append(getResponses1xx()).append("<br />\n");
+        sb.append("2xx responses: ").append(getResponses2xx()).append("<br />\n");
+        sb.append("3xx responses: ").append(getResponses3xx()).append("<br />\n");
+        sb.append("4xx responses: ").append(getResponses4xx()).append("<br />\n");
+        sb.append("5xx responses: ").append(getResponses5xx()).append("<br />\n");
+        sb.append("Bytes sent total: ").append(getResponsesBytesTotal()).append("<br />\n");
+
+        return sb.toString();
+
+    }
+
+    @Override
+    public Future<Void> shutdown()
+    {
+        FutureCallback shutdown=new FutureCallback(false);
+        _shutdown.compareAndSet(null,shutdown);
+        shutdown=_shutdown.get();
+        if (_dispatchedStats.getCurrent()==0)
+            shutdown.succeeded();
+        return shutdown;
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/server/handler/package-info.java b/lib/jetty/org/eclipse/jetty/server/handler/package-info.java
new file mode 100644 (file)
index 0000000..1571d7b
--- /dev/null
@@ -0,0 +1,23 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+/**
+ * Jetty Server : Core Handler API
+ */
+package org.eclipse.jetty.server.handler;
+
diff --git a/lib/jetty/org/eclipse/jetty/server/nio/NetworkTrafficSelectChannelConnector.java b/lib/jetty/org/eclipse/jetty/server/nio/NetworkTrafficSelectChannelConnector.java
new file mode 100644 (file)
index 0000000..f4c5958
--- /dev/null
@@ -0,0 +1,60 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server.nio;
+
+import java.util.concurrent.Executor;
+
+import org.eclipse.jetty.io.ByteBufferPool;
+import org.eclipse.jetty.server.ConnectionFactory;
+import org.eclipse.jetty.server.NetworkTrafficServerConnector;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.util.ssl.SslContextFactory;
+import org.eclipse.jetty.util.thread.Scheduler;
+
+/**
+ * @deprecated use {@link org.eclipse.jetty.server.NetworkTrafficServerConnector} instead.
+ */
+@Deprecated
+public class NetworkTrafficSelectChannelConnector extends NetworkTrafficServerConnector
+{
+    public NetworkTrafficSelectChannelConnector(Server server)
+    {
+        super(server);
+    }
+
+    public NetworkTrafficSelectChannelConnector(Server server, ConnectionFactory connectionFactory, SslContextFactory sslContextFactory)
+    {
+        super(server, connectionFactory, sslContextFactory);
+    }
+
+    public NetworkTrafficSelectChannelConnector(Server server, ConnectionFactory connectionFactory)
+    {
+        super(server, connectionFactory);
+    }
+
+    public NetworkTrafficSelectChannelConnector(Server server, Executor executor, Scheduler scheduler, ByteBufferPool pool, int acceptors, int selectors, ConnectionFactory... factories)
+    {
+        super(server, executor, scheduler, pool, acceptors, selectors, factories);
+    }
+
+    public NetworkTrafficSelectChannelConnector(Server server, SslContextFactory sslContextFactory)
+    {
+        super(server, sslContextFactory);
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/server/nio/package-info.java b/lib/jetty/org/eclipse/jetty/server/nio/package-info.java
new file mode 100644 (file)
index 0000000..8450e18
--- /dev/null
@@ -0,0 +1,23 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+/**
+ * Jetty Server : Core Server Connector
+ */
+package org.eclipse.jetty.server.nio;
+
diff --git a/lib/jetty/org/eclipse/jetty/server/package-info.java b/lib/jetty/org/eclipse/jetty/server/package-info.java
new file mode 100644 (file)
index 0000000..4688304
--- /dev/null
@@ -0,0 +1,23 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+/**
+ * Jetty Server : Core Server API
+ */
+package org.eclipse.jetty.server;
+
diff --git a/lib/jetty/org/eclipse/jetty/server/session/AbstractSession.java b/lib/jetty/org/eclipse/jetty/server/session/AbstractSession.java
new file mode 100644 (file)
index 0000000..809d9d9
--- /dev/null
@@ -0,0 +1,650 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server.session;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import javax.servlet.ServletContext;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpSessionActivationListener;
+import javax.servlet.http.HttpSessionBindingEvent;
+import javax.servlet.http.HttpSessionBindingListener;
+import javax.servlet.http.HttpSessionContext;
+import javax.servlet.http.HttpSessionEvent;
+
+import org.eclipse.jetty.server.SessionManager;
+import org.eclipse.jetty.util.log.Logger;
+
+/**
+ *
+ * <p>
+ * Implements {@link javax.servlet.http.HttpSession} from the <code>javax.servlet</code> package.
+ * </p>
+ *
+ */
+@SuppressWarnings("deprecation")
+public abstract class AbstractSession implements AbstractSessionManager.SessionIf
+{
+    final static Logger LOG = SessionHandler.LOG;
+    public final static String SESSION_KNOWN_ONLY_TO_AUTHENTICATED="org.eclipse.jetty.security.sessionKnownOnlytoAuthenticated";
+    private  String _clusterId; // ID without any node (ie "worker") id appended
+    private  String _nodeId;    // ID of session with node(ie "worker") id appended
+    private final AbstractSessionManager _manager;
+    private boolean _idChanged;
+    private final long _created;
+    private long _cookieSet;
+    private long _accessed;         // the time of the last access
+    private long _lastAccessed;     // the time of the last access excluding this one
+    private boolean _invalid;
+    private boolean _doInvalidate;
+    private long _maxIdleMs;
+    private boolean _newSession;
+    private int _requests;
+
+
+
+    /* ------------------------------------------------------------- */
+    protected AbstractSession(AbstractSessionManager abstractSessionManager, HttpServletRequest request)
+    {
+        _manager = abstractSessionManager;
+
+        _newSession=true;
+        _created=System.currentTimeMillis();
+        _clusterId=_manager._sessionIdManager.newSessionId(request,_created);
+        _nodeId=_manager._sessionIdManager.getNodeId(_clusterId,request);
+        _accessed=_created;
+        _lastAccessed=_created;
+        _requests=1;
+        _maxIdleMs=_manager._dftMaxIdleSecs>0?_manager._dftMaxIdleSecs*1000L:-1;
+        if (LOG.isDebugEnabled())
+            LOG.debug("new session & id "+_nodeId+" "+_clusterId);
+    }
+
+    /* ------------------------------------------------------------- */
+    protected AbstractSession(AbstractSessionManager abstractSessionManager, long created, long accessed, String clusterId)
+    {
+        _manager = abstractSessionManager;
+        _created=created;
+        _clusterId=clusterId;
+        _nodeId=_manager._sessionIdManager.getNodeId(_clusterId,null);
+        _accessed=accessed;
+        _lastAccessed=accessed;
+        _requests=1;
+        _maxIdleMs=_manager._dftMaxIdleSecs>0?_manager._dftMaxIdleSecs*1000L:-1;
+        if (LOG.isDebugEnabled())
+            LOG.debug("new session "+_nodeId+" "+_clusterId);
+    }
+
+    /* ------------------------------------------------------------- */
+    /**
+     * asserts that the session is valid
+     */
+    protected void checkValid() throws IllegalStateException
+    {
+        if (_invalid)
+            throw new IllegalStateException();
+    }
+    
+    /* ------------------------------------------------------------- */
+    /** Check to see if session has expired as at the time given.
+     * @param time
+     * @return
+     */
+    protected boolean checkExpiry(long time)
+    {
+        if (_maxIdleMs>0 && _lastAccessed>0 && _lastAccessed + _maxIdleMs < time)
+            return true;
+        return false;
+    }
+
+    /* ------------------------------------------------------------- */
+    @Override
+    public AbstractSession getSession()
+    {
+        return this;
+    }
+
+    /* ------------------------------------------------------------- */
+    public long getAccessed()
+    {
+        synchronized (this)
+        {
+            return _accessed;
+        }
+    }
+
+    /* ------------------------------------------------------------- */
+    public abstract Map<String,Object> getAttributeMap();
+
+
+    
+
+    /* ------------------------------------------------------------ */
+    public abstract int getAttributes();
+  
+
+
+    /* ------------------------------------------------------------ */
+    public abstract Set<String> getNames();
+  
+
+    /* ------------------------------------------------------------- */
+    public long getCookieSetTime()
+    {
+        return _cookieSet;
+    }
+
+    /* ------------------------------------------------------------- */
+    @Override
+    public long getCreationTime() throws IllegalStateException
+    {
+        checkValid();
+        return _created;
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public String getId() throws IllegalStateException
+    {
+        return _manager._nodeIdInSessionId?_nodeId:_clusterId;
+    }
+
+    /* ------------------------------------------------------------- */
+    public String getNodeId()
+    {
+        return _nodeId;
+    }
+
+    /* ------------------------------------------------------------- */
+    public String getClusterId()
+    {
+        return _clusterId;
+    }
+
+    /* ------------------------------------------------------------- */
+    @Override
+    public long getLastAccessedTime() throws IllegalStateException
+    {
+        checkValid();
+        return _lastAccessed;
+    }
+    
+    /* ------------------------------------------------------------- */
+    public void setLastAccessedTime(long time)
+    {
+        _lastAccessed = time;
+    }
+
+    /* ------------------------------------------------------------- */
+    @Override
+    public int getMaxInactiveInterval()
+    {
+        return (int)(_maxIdleMs/1000);
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.http.HttpSession#getServletContext()
+     */
+    @Override
+    public ServletContext getServletContext()
+    {
+        return _manager._context;
+    }
+
+    /* ------------------------------------------------------------- */
+    @Deprecated
+    @Override
+    public HttpSessionContext getSessionContext() throws IllegalStateException
+    {
+        checkValid();
+        return AbstractSessionManager.__nullSessionContext;
+    }
+
+    /* ------------------------------------------------------------- */
+    /**
+     * @deprecated As of Version 2.2, this method is replaced by
+     *             {@link #getAttribute}
+     */
+    @Deprecated
+    @Override
+    public Object getValue(String name) throws IllegalStateException
+    {
+        return getAttribute(name);
+    }
+
+
+    /* ------------------------------------------------------------ */
+    public void renewId(HttpServletRequest request)
+    {
+        _manager._sessionIdManager.renewSessionId(getClusterId(), getNodeId(), request); 
+        setIdChanged(true);
+    }
+       
+    /* ------------------------------------------------------------- */
+    public SessionManager getSessionManager()
+    {
+        return _manager;
+    }
+
+    /* ------------------------------------------------------------ */
+    protected void setClusterId (String clusterId)
+    {
+        _clusterId = clusterId;
+    }
+    
+    /* ------------------------------------------------------------ */
+    protected void setNodeId (String nodeId)
+    {
+        _nodeId = nodeId;
+    }
+    
+
+    /* ------------------------------------------------------------ */
+    protected boolean access(long time)
+    {
+        synchronized(this)
+        {
+            if (_invalid)
+                return false;
+            _newSession=false;
+            _lastAccessed=_accessed;
+            _accessed=time;
+
+            if (checkExpiry(time))
+            {
+                invalidate();
+                return false;
+            }
+            _requests++;
+            return true;
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    protected void complete()
+    {
+        synchronized(this)
+        {
+            _requests--;
+            if (_doInvalidate && _requests<=0  )
+                doInvalidate();
+        }
+    }
+
+
+    /* ------------------------------------------------------------- */
+    protected void timeout() throws IllegalStateException
+    {
+        // remove session from context and invalidate other sessions with same ID.
+        _manager.removeSession(this,true);
+
+        // Notify listeners and unbind values
+        boolean do_invalidate=false;
+        synchronized (this)
+        {
+            if (!_invalid)
+            {
+                if (_requests<=0)
+                    do_invalidate=true;
+                else
+                    _doInvalidate=true;
+            }
+        }
+        if (do_invalidate)
+            doInvalidate();
+    }
+
+    /* ------------------------------------------------------------- */
+    @Override
+    public void invalidate() throws IllegalStateException
+    {
+        checkValid();
+        // remove session from context and invalidate other sessions with same ID.
+        _manager.removeSession(this,true);
+        doInvalidate();
+    }
+
+    /* ------------------------------------------------------------- */
+    protected void doInvalidate() throws IllegalStateException
+    {
+        try
+        {
+            LOG.debug("invalidate {}",_clusterId);
+            if (isValid())
+                clearAttributes();
+        }
+        finally
+        {
+            synchronized (this)
+            {
+                // mark as invalid
+                _invalid=true;
+            }
+        }
+    }
+
+    /* ------------------------------------------------------------- */
+    public abstract void clearAttributes();
+   
+
+    /* ------------------------------------------------------------- */
+    public boolean isIdChanged()
+    {
+        return _idChanged;
+    }
+
+    /* ------------------------------------------------------------- */
+    @Override
+    public boolean isNew() throws IllegalStateException
+    {
+        checkValid();
+        return _newSession;
+    }
+
+    /* ------------------------------------------------------------- */
+    /**
+     * @deprecated As of Version 2.2, this method is replaced by
+     *             {@link #setAttribute}
+     */
+    @Deprecated
+    @Override
+    public void putValue(java.lang.String name, java.lang.Object value) throws IllegalStateException
+    {
+        changeAttribute(name,value);
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public void removeAttribute(String name)
+    {
+        setAttribute(name,null);
+    }
+
+    /* ------------------------------------------------------------- */
+    /**
+     * @deprecated As of Version 2.2, this method is replaced by
+     *             {@link #removeAttribute}
+     */
+    @Deprecated
+    @Override
+    public void removeValue(java.lang.String name) throws IllegalStateException
+    {
+        removeAttribute(name);
+    }
+    
+    /* ------------------------------------------------------------ */
+    @SuppressWarnings({ "unchecked" })
+    @Override
+    public Enumeration<String> getAttributeNames()
+    {
+        synchronized (this)
+        {
+            checkValid();
+            return doGetAttributeNames();
+        }
+    }
+    
+    /* ------------------------------------------------------------- */
+    /**
+     * @deprecated As of Version 2.2, this method is replaced by
+     *             {@link #getAttributeNames}
+     */
+    @Deprecated
+    @Override
+    public String[] getValueNames() throws IllegalStateException
+    {
+        synchronized(this)
+        {
+            checkValid();
+            Enumeration<String> anames = doGetAttributeNames();
+            if (anames == null)
+                return new String[0];
+            ArrayList<String> names = new ArrayList<String>();
+            while (anames.hasMoreElements())
+                names.add(anames.nextElement());
+            return names.toArray(new String[names.size()]);
+        }
+    }
+    
+
+    /* ------------------------------------------------------------ */
+    public abstract Object doPutOrRemove(String name, Object value);
+
+    /* ------------------------------------------------------------ */
+    public abstract Object doGet(String name);
+    
+    
+    /* ------------------------------------------------------------ */
+    public abstract Enumeration<String> doGetAttributeNames();
+    
+    
+    /* ------------------------------------------------------------ */
+    @Override
+    public Object getAttribute(String name)
+    {
+        synchronized (this)
+        {
+            checkValid();
+            return doGet(name);
+        }
+    }
+   
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public void setAttribute(String name, Object value)
+    {
+        changeAttribute(name,value);
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * @param name
+     * @param value
+     * @deprecated use changeAttribute(String,Object) instead
+     * @return
+     */
+    protected boolean updateAttribute (String name, Object value)
+    {
+        Object old=null;
+        synchronized (this)
+        {
+            checkValid();
+            old=doPutOrRemove(name,value);
+        }
+
+        if (value==null || !value.equals(old))
+        {
+            if (old!=null)
+                unbindValue(name,old);
+            if (value!=null)
+                bindValue(name,value);
+
+            _manager.doSessionAttributeListeners(this,name,old,value);
+            return true;
+        }
+        return false;
+    }
+    
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * Either set (perhaps replace) or remove the value of the attribute
+     * in the session. The appropriate session attribute listeners are
+     * also called.
+     * 
+     * @param name
+     * @param value
+     * @return
+     */
+    protected Object changeAttribute (String name, Object value)
+    {
+        Object old=null;
+        synchronized (this)
+        {
+            checkValid();
+            old=doPutOrRemove(name,value);
+        }
+
+        callSessionAttributeListeners(name, value, old);
+
+        return old;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Call binding and attribute listeners based on the new and old
+     * values of the attribute.
+     * 
+     * @param name name of the attribute
+     * @param newValue  new value of the attribute
+     * @param oldValue previous value of the attribute
+     */
+    protected void callSessionAttributeListeners (String name, Object newValue, Object oldValue)
+    {
+        if (newValue==null || !newValue.equals(oldValue))
+        {
+            if (oldValue!=null)
+                unbindValue(name,oldValue);
+            if (newValue!=null)
+                bindValue(name,newValue);
+
+            _manager.doSessionAttributeListeners(this,name,oldValue,newValue);
+        }
+    }
+  
+
+    /* ------------------------------------------------------------- */
+    public void setIdChanged(boolean changed)
+    {
+        _idChanged=changed;
+    }
+
+    /* ------------------------------------------------------------- */
+    @Override
+    public void setMaxInactiveInterval(int secs)
+    {
+        _maxIdleMs=(long)secs*1000L;
+    }
+
+    /* ------------------------------------------------------------- */
+    @Override
+    public String toString()
+    {
+        return this.getClass().getName()+":"+getId()+"@"+hashCode();
+    }
+
+    /* ------------------------------------------------------------- */
+    /** If value implements HttpSessionBindingListener, call valueBound() */
+    public void bindValue(java.lang.String name, Object value)
+    {
+        if (value!=null&&value instanceof HttpSessionBindingListener)
+            ((HttpSessionBindingListener)value).valueBound(new HttpSessionBindingEvent(this,name));
+    }
+
+    /* ------------------------------------------------------------ */
+    public boolean isValid()
+    {
+        return !_invalid;
+    }
+
+    /* ------------------------------------------------------------- */
+    protected void cookieSet()
+    {
+        synchronized (this)
+        {
+            _cookieSet=_accessed;
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    public int getRequests()
+    {
+        synchronized (this)
+        {
+            return _requests;
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    public void setRequests(int requests)
+    {
+        synchronized (this)
+        {
+            _requests=requests;
+        }
+    }
+
+    /* ------------------------------------------------------------- */
+    /** If value implements HttpSessionBindingListener, call valueUnbound() */
+    public void unbindValue(java.lang.String name, Object value)
+    {
+        if (value!=null&&value instanceof HttpSessionBindingListener)
+            ((HttpSessionBindingListener)value).valueUnbound(new HttpSessionBindingEvent(this,name));
+    }
+
+    /* ------------------------------------------------------------- */
+    public void willPassivate()
+    {
+        synchronized(this)
+        {
+            HttpSessionEvent event = new HttpSessionEvent(this);
+            for (Iterator<Object> iter = getAttributeMap().values().iterator(); iter.hasNext();)
+            {
+                Object value = iter.next();
+                if (value instanceof HttpSessionActivationListener)
+                {
+                    HttpSessionActivationListener listener = (HttpSessionActivationListener) value;
+                    listener.sessionWillPassivate(event);
+                }
+            }
+        }
+    }
+
+    /* ------------------------------------------------------------- */
+    public void didActivate()
+    {
+        synchronized(this)
+        {
+            HttpSessionEvent event = new HttpSessionEvent(this);
+            for (Iterator<Object> iter = getAttributeMap().values().iterator(); iter.hasNext();)
+            {
+                Object value = iter.next();
+                if (value instanceof HttpSessionActivationListener)
+                {
+                    HttpSessionActivationListener listener = (HttpSessionActivationListener) value;
+                    listener.sessionDidActivate(event);
+                }
+            }
+        }
+    }
+
+
+}
diff --git a/lib/jetty/org/eclipse/jetty/server/session/AbstractSessionIdManager.java b/lib/jetty/org/eclipse/jetty/server/session/AbstractSessionIdManager.java
new file mode 100644 (file)
index 0000000..9414408
--- /dev/null
@@ -0,0 +1,279 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server.session;
+
+import java.security.SecureRandom;
+import java.util.Random;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.eclipse.jetty.server.SessionIdManager;
+import org.eclipse.jetty.util.component.AbstractLifeCycle;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+public abstract class AbstractSessionIdManager extends AbstractLifeCycle implements SessionIdManager
+{
+    private static final Logger LOG = Log.getLogger(AbstractSessionIdManager.class);
+
+    private final static String __NEW_SESSION_ID="org.eclipse.jetty.server.newSessionId";
+
+    protected Random _random;
+    protected boolean _weakRandom;
+    protected String _workerName;
+    protected String _workerAttr;
+    protected long _reseed=100000L;
+
+    /* ------------------------------------------------------------ */
+    public AbstractSessionIdManager()
+    {
+    }
+
+    /* ------------------------------------------------------------ */
+    public AbstractSessionIdManager(Random random)
+    {
+        _random=random;
+    }
+
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Get the workname. If set, the workername is dot appended to the session
+     * ID and can be used to assist session affinity in a load balancer.
+     *
+     * @return String or null
+     */
+    @Override
+    public String getWorkerName()
+    {
+        return _workerName;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Set the workname. If set, the workername is dot appended to the session
+     * ID and can be used to assist session affinity in a load balancer.
+     * A worker name starting with $ is used as a request attribute name to
+     * lookup the worker name that can be dynamically set by a request
+     * customiser.
+     *
+     * @param workerName
+     */
+    public void setWorkerName(String workerName)
+    {
+        if (isRunning())
+            throw new IllegalStateException(getState());
+        if (workerName.contains("."))
+            throw new IllegalArgumentException("Name cannot contain '.'");
+        _workerName=workerName;
+    }
+
+    /* ------------------------------------------------------------ */
+    public Random getRandom()
+    {
+        return _random;
+    }
+
+    /* ------------------------------------------------------------ */
+    public synchronized void setRandom(Random random)
+    {
+        _random=random;
+        _weakRandom=false;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return the reseed probability
+     */
+    public long getReseed()
+    {
+        return _reseed;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Set the reseed probability.
+     * @param reseed  If non zero then when a random long modulo the reseed value == 1, the {@link SecureRandom} will be reseeded.
+     */
+    public void setReseed(long reseed)
+    {
+        _reseed = reseed;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Create a new session id if necessary.
+     *
+     * @see org.eclipse.jetty.server.SessionIdManager#newSessionId(javax.servlet.http.HttpServletRequest, long)
+     */
+    @Override
+    public String newSessionId(HttpServletRequest request, long created)
+    {
+        synchronized (this)
+        {
+            if (request==null)
+                return newSessionId(created);
+
+            // A requested session ID can only be used if it is in use already.
+            String requested_id=request.getRequestedSessionId();
+            if (requested_id!=null)
+            {
+                String cluster_id=getClusterId(requested_id);
+                if (idInUse(cluster_id))
+                    return cluster_id;
+            }
+
+            // Else reuse any new session ID already defined for this request.
+            String new_id=(String)request.getAttribute(__NEW_SESSION_ID);
+            if (new_id!=null&&idInUse(new_id))
+                return new_id;
+
+            // pick a new unique ID!
+            String id = newSessionId(request.hashCode());
+
+            request.setAttribute(__NEW_SESSION_ID,id);
+            return id;
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    public String newSessionId(long seedTerm)
+    {
+        // pick a new unique ID!
+        String id=null;
+        while (id==null||id.length()==0||idInUse(id))
+        {
+            long r0=_weakRandom
+                    ?(hashCode()^Runtime.getRuntime().freeMemory()^_random.nextInt()^((seedTerm)<<32))
+                    :_random.nextLong();
+            if (r0<0)
+                r0=-r0;
+                    
+            // random chance to reseed
+            if (_reseed>0 && (r0%_reseed)== 1L)
+            {
+                LOG.debug("Reseeding {}",this);
+                if (_random instanceof SecureRandom)
+                {
+                    SecureRandom secure = (SecureRandom)_random;
+                    secure.setSeed(secure.generateSeed(8));
+                }
+                else
+                {
+                    _random.setSeed(_random.nextLong()^System.currentTimeMillis()^seedTerm^Runtime.getRuntime().freeMemory());
+                }
+            }
+            
+            long r1=_weakRandom
+                ?(hashCode()^Runtime.getRuntime().freeMemory()^_random.nextInt()^((seedTerm)<<32))
+                :_random.nextLong();
+            if (r1<0)
+                r1=-r1;
+            
+            id=Long.toString(r0,36)+Long.toString(r1,36);
+
+            //add in the id of the node to ensure unique id across cluster
+            //NOTE this is different to the node suffix which denotes which node the request was received on
+            if (_workerName!=null)
+                id=_workerName + id;
+    
+        }
+        return id;
+    }
+
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public abstract void renewSessionId(String oldClusterId, String oldNodeId, HttpServletRequest request);
+
+    
+    /* ------------------------------------------------------------ */
+    @Override
+    protected void doStart() throws Exception
+    {
+       initRandom();
+       _workerAttr=(_workerName!=null && _workerName.startsWith("$"))?_workerName.substring(1):null;
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    protected void doStop() throws Exception
+    {
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Set up a random number generator for the sessionids.
+     *
+     * By preference, use a SecureRandom but allow to be injected.
+     */
+    public void initRandom ()
+    {
+        if (_random==null)
+        {
+            try
+            {
+                _random=new SecureRandom();
+            }
+            catch (Exception e)
+            {
+                LOG.warn("Could not generate SecureRandom for session-id randomness",e);
+                _random=new Random();
+                _weakRandom=true;
+            }
+        }
+        else
+            _random.setSeed(_random.nextLong()^System.currentTimeMillis()^hashCode()^Runtime.getRuntime().freeMemory());
+    }
+
+    /** Get the session ID with any worker ID.
+     *
+     * @param clusterId
+     * @param request
+     * @return sessionId plus any worker ID.
+     */
+    @Override
+    public String getNodeId(String clusterId, HttpServletRequest request)
+    {
+        if (_workerName!=null)
+        {
+            if (_workerAttr==null)
+                return clusterId+'.'+_workerName;
+
+            String worker=(String)request.getAttribute(_workerAttr);
+            if (worker!=null)
+                return clusterId+'.'+worker;
+        }
+    
+        return clusterId;
+    }
+
+    /** Get the session ID without any worker ID.
+     *
+     * @param nodeId the node id
+     * @return sessionId without any worker ID.
+     */
+    @Override
+    public String getClusterId(String nodeId)
+    {
+        int dot=nodeId.lastIndexOf('.');
+        return (dot>0)?nodeId.substring(0,dot):nodeId;
+    }
+
+
+}
diff --git a/lib/jetty/org/eclipse/jetty/server/session/AbstractSessionManager.java b/lib/jetty/org/eclipse/jetty/server/session/AbstractSessionManager.java
new file mode 100644 (file)
index 0000000..1f966c5
--- /dev/null
@@ -0,0 +1,1052 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server.session;
+
+import static java.lang.Math.round;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.EventListener;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+import javax.servlet.SessionCookieConfig;
+import javax.servlet.SessionTrackingMode;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpSession;
+import javax.servlet.http.HttpSessionAttributeListener;
+import javax.servlet.http.HttpSessionBindingEvent;
+import javax.servlet.http.HttpSessionContext;
+import javax.servlet.http.HttpSessionEvent;
+import javax.servlet.http.HttpSessionIdListener;
+import javax.servlet.http.HttpSessionListener;
+
+import org.eclipse.jetty.http.HttpCookie;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.SessionIdManager;
+import org.eclipse.jetty.server.SessionManager;
+import org.eclipse.jetty.server.handler.ContextHandler;
+import org.eclipse.jetty.util.annotation.ManagedAttribute;
+import org.eclipse.jetty.util.annotation.ManagedObject;
+import org.eclipse.jetty.util.annotation.ManagedOperation;
+import org.eclipse.jetty.util.component.ContainerLifeCycle;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.statistic.CounterStatistic;
+import org.eclipse.jetty.util.statistic.SampleStatistic;
+
+/* ------------------------------------------------------------ */
+/**
+ * An Abstract implementation of SessionManager. The partial implementation of
+ * SessionManager interface provides the majority of the handling required to
+ * implement a SessionManager. Concrete implementations of SessionManager based
+ * on AbstractSessionManager need only implement the newSession method to return
+ * a specialised version of the Session inner class that provides an attribute
+ * Map.
+ * <p>
+ */
+@SuppressWarnings("deprecation")
+@ManagedObject("Abstract Session Manager")
+public abstract class AbstractSessionManager extends ContainerLifeCycle implements SessionManager
+{
+    final static Logger __log = SessionHandler.LOG;
+
+    public Set<SessionTrackingMode> __defaultSessionTrackingModes =
+        Collections.unmodifiableSet(
+            new HashSet<SessionTrackingMode>(
+                    Arrays.asList(new SessionTrackingMode[]{SessionTrackingMode.COOKIE,SessionTrackingMode.URL})));
+
+    
+
+    /* ------------------------------------------------------------ */
+    public final static int __distantFuture=60*60*24*7*52*20;
+
+    static final HttpSessionContext __nullSessionContext=new HttpSessionContext()
+    {
+        @Override
+        public HttpSession getSession(String sessionId)
+        {
+            return null;
+        }
+
+        @Override
+        @SuppressWarnings({ "rawtypes", "unchecked" })
+        public Enumeration getIds()
+        {
+            return Collections.enumeration(Collections.EMPTY_LIST);
+        }
+    };
+
+    private boolean _usingCookies=true;
+
+    /* ------------------------------------------------------------ */
+    // Setting of max inactive interval for new sessions
+    // -1 means no timeout
+    protected int _dftMaxIdleSecs=-1;
+    protected SessionHandler _sessionHandler;
+    protected boolean _httpOnly=false;
+    protected SessionIdManager _sessionIdManager;
+    protected boolean _secureCookies=false;
+    protected boolean _secureRequestOnly=true;
+
+    protected final List<HttpSessionAttributeListener> _sessionAttributeListeners = new CopyOnWriteArrayList<HttpSessionAttributeListener>();
+    protected final List<HttpSessionListener> _sessionListeners= new CopyOnWriteArrayList<HttpSessionListener>();
+    protected final List<HttpSessionIdListener> _sessionIdListeners = new CopyOnWriteArrayList<HttpSessionIdListener>();
+
+    protected ClassLoader _loader;
+    protected ContextHandler.Context _context;
+    protected String _sessionCookie=__DefaultSessionCookie;
+    protected String _sessionIdPathParameterName = __DefaultSessionIdPathParameterName;
+    protected String _sessionIdPathParameterNamePrefix =";"+ _sessionIdPathParameterName +"=";
+    protected String _sessionDomain;
+    protected String _sessionPath;
+    protected int _maxCookieAge=-1;
+    protected int _refreshCookieAge;
+    protected boolean _nodeIdInSessionId;
+    protected boolean _checkingRemoteSessionIdEncoding;
+    protected String _sessionComment;
+
+    public Set<SessionTrackingMode> _sessionTrackingModes;
+
+    private boolean _usingURLs;
+
+    protected final CounterStatistic _sessionsStats = new CounterStatistic();
+    protected final SampleStatistic _sessionTimeStats = new SampleStatistic();
+
+
+    /* ------------------------------------------------------------ */
+    public AbstractSessionManager()
+    {
+        setSessionTrackingModes(__defaultSessionTrackingModes);
+    }
+
+    /* ------------------------------------------------------------ */
+    public ContextHandler.Context getContext()
+    {
+        return _context;
+    }
+
+    /* ------------------------------------------------------------ */
+    public ContextHandler getContextHandler()
+    {
+        return _context.getContextHandler();
+    }
+
+    @ManagedAttribute("path of the session cookie, or null for default")
+    public String getSessionPath()
+    {
+        return _sessionPath;
+    }
+
+    @ManagedAttribute("if greater the zero, the time in seconds a session cookie will last for")
+    public int getMaxCookieAge()
+    {
+        return _maxCookieAge;
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public HttpCookie access(HttpSession session,boolean secure)
+    {
+        long now=System.currentTimeMillis();
+
+        AbstractSession s = ((SessionIf)session).getSession();
+
+       if (s.access(now))
+       {
+            // Do we need to refresh the cookie?
+            if (isUsingCookies() &&
+                (s.isIdChanged() ||
+                (getSessionCookieConfig().getMaxAge()>0 && getRefreshCookieAge()>0 && ((now-s.getCookieSetTime())/1000>getRefreshCookieAge()))
+                )
+               )
+            {
+                HttpCookie cookie=getSessionCookie(session,_context==null?"/":(_context.getContextPath()),secure);
+                s.cookieSet();
+                s.setIdChanged(false);
+                return cookie;
+            }
+        }
+        return null;
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public void addEventListener(EventListener listener)
+    {
+        if (listener instanceof HttpSessionAttributeListener)
+            _sessionAttributeListeners.add((HttpSessionAttributeListener)listener);
+        if (listener instanceof HttpSessionListener)
+            _sessionListeners.add((HttpSessionListener)listener);
+        if (listener instanceof HttpSessionIdListener)
+            _sessionIdListeners.add((HttpSessionIdListener)listener);
+        addBean(listener,false);
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public void clearEventListeners()
+    {
+        for (EventListener e :getBeans(EventListener.class))
+            removeBean(e);
+        _sessionAttributeListeners.clear();
+        _sessionListeners.clear();
+        _sessionIdListeners.clear();
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public void complete(HttpSession session)
+    {
+        AbstractSession s = ((SessionIf)session).getSession();
+        s.complete();
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public void doStart() throws Exception
+    {
+        _context=ContextHandler.getCurrentContext();
+        _loader=Thread.currentThread().getContextClassLoader();
+
+        if (_sessionIdManager==null)
+        {
+            final Server server=getSessionHandler().getServer();
+            synchronized (server)
+            {
+                _sessionIdManager=server.getSessionIdManager();
+                if (_sessionIdManager==null)
+                {
+                    //create a default SessionIdManager and set it as the shared
+                    //SessionIdManager for the Server, being careful NOT to use
+                    //the webapp context's classloader, otherwise if the context
+                    //is stopped, the classloader is leaked.
+                    ClassLoader serverLoader = server.getClass().getClassLoader();
+                    try
+                    {
+                        Thread.currentThread().setContextClassLoader(serverLoader);
+                        _sessionIdManager=new HashSessionIdManager();
+                        server.setSessionIdManager(_sessionIdManager);
+                        server.manage(_sessionIdManager);
+                        _sessionIdManager.start();
+                    }
+                    finally
+                    {
+                        Thread.currentThread().setContextClassLoader(_loader);
+                    }
+                }
+
+                // server session id is never managed by this manager
+                addBean(_sessionIdManager,false);
+            }
+        }
+        
+
+        // Look for a session cookie name
+        if (_context!=null)
+        {
+            String tmp=_context.getInitParameter(SessionManager.__SessionCookieProperty);
+            if (tmp!=null)
+                _sessionCookie=tmp;
+
+            tmp=_context.getInitParameter(SessionManager.__SessionIdPathParameterNameProperty);
+            if (tmp!=null)
+                setSessionIdPathParameterName(tmp);
+
+            // set up the max session cookie age if it isn't already
+            if (_maxCookieAge==-1)
+            {
+                tmp=_context.getInitParameter(SessionManager.__MaxAgeProperty);
+                if (tmp!=null)
+                    _maxCookieAge=Integer.parseInt(tmp.trim());
+            }
+
+            // set up the session domain if it isn't already
+            if (_sessionDomain==null)
+                _sessionDomain=_context.getInitParameter(SessionManager.__SessionDomainProperty);
+
+            // set up the sessionPath if it isn't already
+            if (_sessionPath==null)
+                _sessionPath=_context.getInitParameter(SessionManager.__SessionPathProperty);
+
+            tmp=_context.getInitParameter(SessionManager.__CheckRemoteSessionEncoding);
+            if (tmp!=null)
+                _checkingRemoteSessionIdEncoding=Boolean.parseBoolean(tmp);
+        }
+
+        super.doStart();
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public void doStop() throws Exception
+    {
+        super.doStop();
+
+        shutdownSessions();
+
+        _loader=null;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Returns the httpOnly.
+     */
+    @Override
+    @ManagedAttribute("true if cookies use the http only flag")
+    public boolean getHttpOnly()
+    {
+        return _httpOnly;
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public HttpSession getHttpSession(String nodeId)
+    {
+        String cluster_id = getSessionIdManager().getClusterId(nodeId);
+
+        AbstractSession session = getSession(cluster_id);
+        if (session!=null && !session.getNodeId().equals(nodeId))
+            session.setIdChanged(true);
+        return session;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Returns the SessionIdManager used for cross context session management
+     */
+    @Override
+    @ManagedAttribute("Session ID Manager")
+    public SessionIdManager getSessionIdManager()
+    {
+        return _sessionIdManager;
+    }
+
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return seconds
+     */
+    @Override
+    @ManagedAttribute("defailt maximum time a session may be idle for (in s)")
+    public int getMaxInactiveInterval()
+    {
+        return _dftMaxIdleSecs;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return maximum number of sessions
+     */
+    @ManagedAttribute("maximum number of simultaneous sessions")
+    public int getSessionsMax()
+    {
+        return (int)_sessionsStats.getMax();
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return total number of sessions
+     */
+    @ManagedAttribute("total number of sessions")
+    public int getSessionsTotal()
+    {
+        return (int)_sessionsStats.getTotal();
+    }
+
+    /* ------------------------------------------------------------ */
+    @ManagedAttribute("time before a session cookie is re-set (in s)")
+    public int getRefreshCookieAge()
+    {
+        return _refreshCookieAge;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return same as SessionCookieConfig.getSecure(). If true, session
+     * cookies are ALWAYS marked as secure. If false, a session cookie is
+     * ONLY marked as secure if _secureRequestOnly == true and it is a HTTPS request.
+     */
+    @ManagedAttribute("if true, secure cookie flag is set on session cookies")
+    public boolean getSecureCookies()
+    {
+        return _secureCookies;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return true if session cookie is to be marked as secure only on HTTPS requests
+     */
+    public boolean isSecureRequestOnly()
+    {
+        return _secureRequestOnly;
+    }
+
+
+    /* ------------------------------------------------------------ */
+    /**
+     * HTTPS request. Can be overridden by setting SessionCookieConfig.setSecure(true),
+     * in which case the session cookie will be marked as secure on both HTTPS and HTTP.
+     */
+    public void setSecureRequestOnly(boolean secureRequestOnly)
+    {
+        _secureRequestOnly = secureRequestOnly;
+    }
+
+    /* ------------------------------------------------------------ */
+    @ManagedAttribute("the set session cookie")
+    public String getSessionCookie()
+    {
+        return _sessionCookie;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * A sessioncookie is marked as secure IFF any of the following conditions are true:
+     * <ol>
+     * <li>SessionCookieConfig.setSecure == true</li>
+     * <li>SessionCookieConfig.setSecure == false && _secureRequestOnly==true && request is HTTPS</li>
+     * </ol>
+     * According to SessionCookieConfig javadoc, case 1 can be used when:
+     * "... even though the request that initiated the session came over HTTP,
+     * is to support a topology where the web container is front-ended by an
+     * SSL offloading load balancer. In this case, the traffic between the client
+     * and the load balancer will be over HTTPS, whereas the traffic between the
+     * load balancer and the web container will be over HTTP."
+     *
+     * For case 2, you can use _secureRequestOnly to determine if you want the
+     * Servlet Spec 3.0  default behaviour when SessionCookieConfig.setSecure==false,
+     * which is:
+     * "they shall be marked as secure only if the request that initiated the
+     * corresponding session was also secure"
+     *
+     * The default for _secureRequestOnly is true, which gives the above behaviour. If
+     * you set it to false, then a session cookie is NEVER marked as secure, even if
+     * the initiating request was secure.
+     *
+     * @see org.eclipse.jetty.server.SessionManager#getSessionCookie(javax.servlet.http.HttpSession, java.lang.String, boolean)
+     */
+    @Override
+    public HttpCookie getSessionCookie(HttpSession session, String contextPath, boolean requestIsSecure)
+    {
+        if (isUsingCookies())
+        {
+            String sessionPath = (_cookieConfig.getPath()==null) ? contextPath : _cookieConfig.getPath();
+            sessionPath = (sessionPath==null||sessionPath.length()==0) ? "/" : sessionPath;
+            String id = getNodeId(session);
+            HttpCookie cookie = null;
+            if (_sessionComment == null)
+            {
+                cookie = new HttpCookie(
+                                        _cookieConfig.getName(),
+                                        id,
+                                        _cookieConfig.getDomain(),
+                                        sessionPath,
+                                        _cookieConfig.getMaxAge(),
+                                        _cookieConfig.isHttpOnly(),
+                                        _cookieConfig.isSecure() || (isSecureRequestOnly() && requestIsSecure));
+            }
+            else
+            {
+                cookie = new HttpCookie(
+                                        _cookieConfig.getName(),
+                                        id,
+                                        _cookieConfig.getDomain(),
+                                        sessionPath,
+                                        _cookieConfig.getMaxAge(),
+                                        _cookieConfig.isHttpOnly(),
+                                        _cookieConfig.isSecure() || (isSecureRequestOnly() && requestIsSecure),
+                                        _sessionComment,
+                                        1);
+            }
+
+            return cookie;
+        }
+        return null;
+    }
+
+    @ManagedAttribute("domain of the session cookie, or null for the default")
+    public String getSessionDomain()
+    {
+        return _sessionDomain;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Returns the sessionHandler.
+     */
+    public SessionHandler getSessionHandler()
+    {
+        return _sessionHandler;
+    }
+
+    /* ------------------------------------------------------------ */
+    @ManagedAttribute("number of currently active sessions")
+    public int getSessions()
+    {
+        return (int)_sessionsStats.getCurrent();
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    @ManagedAttribute("name of use for URL session tracking")
+    public String getSessionIdPathParameterName()
+    {
+        return _sessionIdPathParameterName;
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public String getSessionIdPathParameterNamePrefix()
+    {
+        return _sessionIdPathParameterNamePrefix;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Returns the usingCookies.
+     */
+    @Override
+    public boolean isUsingCookies()
+    {
+        return _usingCookies;
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public boolean isValid(HttpSession session)
+    {
+        AbstractSession s = ((SessionIf)session).getSession();
+        return s.isValid();
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public String getClusterId(HttpSession session)
+    {
+        AbstractSession s = ((SessionIf)session).getSession();
+        return s.getClusterId();
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public String getNodeId(HttpSession session)
+    {
+        AbstractSession s = ((SessionIf)session).getSession();
+        return s.getNodeId();
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Create a new HttpSession for a request
+     */
+    @Override
+    public HttpSession newHttpSession(HttpServletRequest request)
+    {
+        AbstractSession session=newSession(request);
+        session.setMaxInactiveInterval(_dftMaxIdleSecs);
+        addSession(session,true);
+        return session;
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public void removeEventListener(EventListener listener)
+    {
+        if (listener instanceof HttpSessionAttributeListener)
+            _sessionAttributeListeners.remove(listener);
+        if (listener instanceof HttpSessionListener)
+            _sessionListeners.remove(listener);
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * Reset statistics values
+     */
+    @ManagedOperation(value="reset statistics", impact="ACTION")
+    public void statsReset()
+    {
+        _sessionsStats.reset(getSessions());
+        _sessionTimeStats.reset();
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param httpOnly
+     *            The httpOnly to set.
+     */
+    public void setHttpOnly(boolean httpOnly)
+    {
+        _httpOnly=httpOnly;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param metaManager The metaManager used for cross context session management.
+     */
+    @Override
+    public void setSessionIdManager(SessionIdManager metaManager)
+    {
+        updateBean(_sessionIdManager, metaManager);
+        _sessionIdManager=metaManager;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param seconds
+     */
+    @Override
+    public void setMaxInactiveInterval(int seconds)
+    {
+        _dftMaxIdleSecs=seconds;
+    }
+
+    /* ------------------------------------------------------------ */
+    public void setRefreshCookieAge(int ageInSeconds)
+    {
+        _refreshCookieAge=ageInSeconds;
+    }
+
+    /* ------------------------------------------------------------ */
+    public void setSessionCookie(String cookieName)
+    {
+        _sessionCookie=cookieName;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param sessionHandler
+     *            The sessionHandler to set.
+     */
+    @Override
+    public void setSessionHandler(SessionHandler sessionHandler)
+    {
+        _sessionHandler=sessionHandler;
+    }
+
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public void setSessionIdPathParameterName(String param)
+    {
+        _sessionIdPathParameterName =(param==null||"none".equals(param))?null:param;
+        _sessionIdPathParameterNamePrefix =(param==null||"none".equals(param))?null:(";"+ _sessionIdPathParameterName +"=");
+    }
+    /* ------------------------------------------------------------ */
+    /**
+     * @param usingCookies
+     *            The usingCookies to set.
+     */
+    public void setUsingCookies(boolean usingCookies)
+    {
+        _usingCookies=usingCookies;
+    }
+
+
+    protected abstract void addSession(AbstractSession session);
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Add the session Registers the session with this manager and registers the
+     * session ID with the sessionIDManager;
+     */
+    protected void addSession(AbstractSession session, boolean created)
+    {
+        synchronized (_sessionIdManager)
+        {
+            _sessionIdManager.addSession(session);
+            addSession(session);
+        }
+
+        if (created)
+        {
+            _sessionsStats.increment();
+            if (_sessionListeners!=null)
+            {
+                HttpSessionEvent event=new HttpSessionEvent(session);
+                for (HttpSessionListener listener : _sessionListeners)
+                    listener.sessionCreated(event);
+            }
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Get a known existing session
+     * @param idInCluster The session ID in the cluster, stripped of any worker name.
+     * @return A Session or null if none exists.
+     */
+    public abstract AbstractSession getSession(String idInCluster);
+
+    /**
+     * Prepare sessions for session manager shutdown
+     * 
+     * @throws Exception
+     */
+    protected abstract void shutdownSessions() throws Exception;
+
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Create a new session instance
+     * @param request
+     * @return the new session
+     */
+    protected abstract AbstractSession newSession(HttpServletRequest request);
+
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return true if the cluster node id (worker id) is returned as part of the session id by {@link HttpSession#getId()}. Default is false.
+     */
+    public boolean isNodeIdInSessionId()
+    {
+        return _nodeIdInSessionId;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param nodeIdInSessionId true if the cluster node id (worker id) will be returned as part of the session id by {@link HttpSession#getId()}. Default is false.
+     */
+    public void setNodeIdInSessionId(boolean nodeIdInSessionId)
+    {
+        _nodeIdInSessionId=nodeIdInSessionId;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Remove session from manager
+     * @param session The session to remove
+     * @param invalidate True if {@link HttpSessionListener#sessionDestroyed(HttpSessionEvent)} and
+     * {@link SessionIdManager#invalidateAll(String)} should be called.
+     */
+    public void removeSession(HttpSession session, boolean invalidate)
+    {
+        AbstractSession s = ((SessionIf)session).getSession();
+        removeSession(s,invalidate);
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Remove session from manager
+     * @param session The session to remove
+     * @param invalidate True if {@link HttpSessionListener#sessionDestroyed(HttpSessionEvent)} and
+     * {@link SessionIdManager#invalidateAll(String)} should be called.
+     */
+    public boolean removeSession(AbstractSession session, boolean invalidate)
+    {
+        // Remove session from context and global maps
+        boolean removed = removeSession(session.getClusterId());
+
+        if (removed)
+        {
+            _sessionsStats.decrement();
+            _sessionTimeStats.set(round((System.currentTimeMillis() - session.getCreationTime())/1000.0));
+
+            // Remove session from all context and global id maps
+            _sessionIdManager.removeSession(session);
+            if (invalidate)
+                _sessionIdManager.invalidateAll(session.getClusterId());
+
+            if (invalidate && _sessionListeners!=null)
+            {
+                HttpSessionEvent event=new HttpSessionEvent(session);      
+                for (int i = _sessionListeners.size()-1; i>=0; i--)
+                {
+                    _sessionListeners.get(i).sessionDestroyed(event);
+                }
+            }
+        }
+        
+        return removed;
+    }
+
+    /* ------------------------------------------------------------ */
+    protected abstract boolean removeSession(String idInCluster);
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return maximum amount of time session remained valid
+     */
+    @ManagedAttribute("maximum amount of time sessions have remained active (in s)")
+    public long getSessionTimeMax()
+    {
+        return _sessionTimeStats.getMax();
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public Set<SessionTrackingMode> getDefaultSessionTrackingModes()
+    {
+        return __defaultSessionTrackingModes;
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public Set<SessionTrackingMode> getEffectiveSessionTrackingModes()
+    {
+        return Collections.unmodifiableSet(_sessionTrackingModes);
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public void setSessionTrackingModes(Set<SessionTrackingMode> sessionTrackingModes)
+    {
+        _sessionTrackingModes=new HashSet<SessionTrackingMode>(sessionTrackingModes);
+        _usingCookies=_sessionTrackingModes.contains(SessionTrackingMode.COOKIE);
+        _usingURLs=_sessionTrackingModes.contains(SessionTrackingMode.URL);
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public boolean isUsingURLs()
+    {
+        return _usingURLs;
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public SessionCookieConfig getSessionCookieConfig()
+    {
+        return _cookieConfig;
+    }
+
+    /* ------------------------------------------------------------ */
+    private SessionCookieConfig _cookieConfig =
+        new CookieConfig();
+
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return total amount of time all sessions remained valid
+     */
+    @ManagedAttribute("total time sessions have remained valid")
+    public long getSessionTimeTotal()
+    {
+        return _sessionTimeStats.getTotal();
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return mean amount of time session remained valid
+     */
+    @ManagedAttribute("mean time sessions remain valid (in s)")
+    public double getSessionTimeMean()
+    {
+        return _sessionTimeStats.getMean();
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return standard deviation of amount of time session remained valid
+     */
+    @ManagedAttribute("standard deviation a session remained valid (in s)")
+    public double getSessionTimeStdDev()
+    {
+        return _sessionTimeStats.getStdDev();
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.server.SessionManager#isCheckingRemoteSessionIdEncoding()
+     */
+    @Override
+    @ManagedAttribute("check remote session id encoding")
+    public boolean isCheckingRemoteSessionIdEncoding()
+    {
+        return _checkingRemoteSessionIdEncoding;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.server.SessionManager#setCheckingRemoteSessionIdEncoding(boolean)
+     */
+    @Override
+    public void setCheckingRemoteSessionIdEncoding(boolean remote)
+    {
+        _checkingRemoteSessionIdEncoding=remote;
+    }
+    
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * Tell the HttpSessionIdListeners the id changed.
+     * NOTE: this method must be called LAST in subclass overrides, after the session has been updated
+     * with the new id.
+     * @see org.eclipse.jetty.server.SessionManager#renewSessionId(java.lang.String, java.lang.String, java.lang.String, java.lang.String)
+     */
+    @Override
+    public void renewSessionId(String oldClusterId, String oldNodeId, String newClusterId, String newNodeId)
+    {
+        if (!_sessionIdListeners.isEmpty())
+        {
+            AbstractSession session = getSession(newClusterId);
+            HttpSessionEvent event = new HttpSessionEvent(session);
+            for (HttpSessionIdListener l:_sessionIdListeners)
+            {
+                l.sessionIdChanged(event, oldClusterId);
+            }
+        }
+
+    }
+
+    /**
+     * CookieConfig
+     * 
+     * Implementation of the javax.servlet.SessionCookieConfig.
+     */
+    public final class CookieConfig implements SessionCookieConfig
+    {
+        @Override
+        public String getComment()
+        {
+            return _sessionComment;
+        }
+
+        @Override
+        public String getDomain()
+        {
+            return _sessionDomain;
+        }
+
+        @Override
+        public int getMaxAge()
+        {
+            return _maxCookieAge;
+        }
+
+        @Override
+        public String getName()
+        {
+            return _sessionCookie;
+        }
+
+        @Override
+        public String getPath()
+        {
+            return _sessionPath;
+        }
+
+        @Override
+        public boolean isHttpOnly()
+        {
+            return _httpOnly;
+        }
+
+        @Override
+        public boolean isSecure()
+        {
+            return _secureCookies;
+        }
+
+        @Override
+        public void setComment(String comment)
+        {  
+            if (_context != null && _context.getContextHandler().isAvailable())
+                throw new IllegalStateException("CookieConfig cannot be set after ServletContext is started");
+            _sessionComment = comment;
+        }
+
+        @Override
+        public void setDomain(String domain)
+        {
+            if (_context != null && _context.getContextHandler().isAvailable())
+                throw new IllegalStateException("CookieConfig cannot be set after ServletContext is started");
+            _sessionDomain=domain;
+        }
+
+        @Override
+        public void setHttpOnly(boolean httpOnly)
+        {   
+            if (_context != null && _context.getContextHandler().isAvailable())
+                throw new IllegalStateException("CookieConfig cannot be set after ServletContext is started");
+            _httpOnly=httpOnly;
+        }
+
+        @Override
+        public void setMaxAge(int maxAge)
+        {               
+            if (_context != null && _context.getContextHandler().isAvailable())
+                throw new IllegalStateException("CookieConfig cannot be set after ServletContext is started");
+            _maxCookieAge=maxAge;
+        }
+
+        @Override
+        public void setName(String name)
+        {  
+                if (_context != null && _context.getContextHandler().isAvailable())
+                    throw new IllegalStateException("CookieConfig cannot be set after ServletContext is started");
+            _sessionCookie=name;
+        }
+
+        @Override
+        public void setPath(String path)
+        {
+            if (_context != null && _context.getContextHandler().isAvailable())
+                throw new IllegalStateException("CookieConfig cannot be set after ServletContext is started"); 
+            _sessionPath=path;
+        }
+
+        @Override
+        public void setSecure(boolean secure)
+        {
+            if (_context != null && _context.getContextHandler().isAvailable())
+                throw new IllegalStateException("CookieConfig cannot be set after ServletContext is started");
+            _secureCookies=secure;
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    /**
+     * Interface that any session wrapper should implement so that
+     * SessionManager may access the Jetty session implementation.
+     *
+     */
+    public interface SessionIf extends HttpSession
+    {
+        public AbstractSession getSession();
+    }
+
+    public void doSessionAttributeListeners(AbstractSession session, String name, Object old, Object value)
+    {
+        if (!_sessionAttributeListeners.isEmpty())
+        {
+            HttpSessionBindingEvent event=new HttpSessionBindingEvent(session,name,old==null?value:old);
+
+            for (HttpSessionAttributeListener l : _sessionAttributeListeners)
+            {
+                if (old==null)
+                    l.attributeAdded(event);
+                else if (value==null)
+                    l.attributeRemoved(event);
+                else
+                    l.attributeReplaced(event);
+            }
+        }
+    }
+
+    @Override
+    @Deprecated
+    public SessionIdManager getMetaManager()
+    {
+        throw new UnsupportedOperationException();
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/server/session/HashSessionIdManager.java b/lib/jetty/org/eclipse/jetty/server/session/HashSessionIdManager.java
new file mode 100644 (file)
index 0000000..a17bc06
--- /dev/null
@@ -0,0 +1,231 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server.session;
+
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Random;
+import java.util.Set;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpSession;
+
+import org.eclipse.jetty.server.SessionIdManager;
+
+/* ------------------------------------------------------------ */
+/**
+ * HashSessionIdManager. An in-memory implementation of the session ID manager.
+ */
+public class HashSessionIdManager extends AbstractSessionIdManager
+{
+    private final Map<String, Set<WeakReference<HttpSession>>> _sessions = new HashMap<String, Set<WeakReference<HttpSession>>>();
+
+    /* ------------------------------------------------------------ */
+    public HashSessionIdManager()
+    {
+    }
+
+    /* ------------------------------------------------------------ */
+    public HashSessionIdManager(Random random)
+    {
+        super(random);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Collection of String session IDs
+     */
+    public Collection<String> getSessions()
+    {
+        return Collections.unmodifiableCollection(_sessions.keySet());
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Collection of Sessions for the passed session ID
+     */
+    public Collection<HttpSession> getSession(String id)
+    {
+        ArrayList<HttpSession> sessions = new ArrayList<HttpSession>();
+        Set<WeakReference<HttpSession>> refs =_sessions.get(id);
+        if (refs!=null)
+        {
+            for (WeakReference<HttpSession> ref: refs)
+            {
+                HttpSession session = ref.get();
+                if (session!=null)
+                    sessions.add(session);
+            }
+        }
+        return sessions;
+    }
+    
+    /* ------------------------------------------------------------ */
+    @Override
+    protected void doStart() throws Exception
+    {
+        super.doStart();
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    protected void doStop() throws Exception
+    {
+        _sessions.clear();
+        super.doStop();
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see SessionIdManager#idInUse(String)
+     */
+    @Override
+    public boolean idInUse(String id)
+    {
+        synchronized (this)
+        {
+            return _sessions.containsKey(id);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see SessionIdManager#addSession(HttpSession)
+     */
+    @Override
+    public void addSession(HttpSession session)
+    {
+        String id = getClusterId(session.getId());
+        WeakReference<HttpSession> ref = new WeakReference<HttpSession>(session);
+
+        synchronized (this)
+        {
+            Set<WeakReference<HttpSession>> sessions = _sessions.get(id);
+            if (sessions==null)
+            {
+                sessions=new HashSet<WeakReference<HttpSession>>();
+                _sessions.put(id,sessions);
+            }
+            sessions.add(ref);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see SessionIdManager#removeSession(HttpSession)
+     */
+    @Override
+    public void removeSession(HttpSession session)
+    {
+        String id = getClusterId(session.getId());
+
+        synchronized (this)
+        {
+            Collection<WeakReference<HttpSession>> sessions = _sessions.get(id);
+            if (sessions!=null)
+            {
+                for (Iterator<WeakReference<HttpSession>> iter = sessions.iterator(); iter.hasNext();)
+                {
+                    WeakReference<HttpSession> ref = iter.next();
+                    HttpSession s=ref.get();
+                    if (s==null)
+                    {
+                        iter.remove();
+                        continue;
+                    }
+                    if (s==session)
+                    {
+                        iter.remove();
+                        break;
+                    }
+                }
+                if (sessions.isEmpty())
+                    _sessions.remove(id);
+            }
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see SessionIdManager#invalidateAll(String)
+     */
+    @Override
+    public void invalidateAll(String id)
+    {
+        Collection<WeakReference<HttpSession>> sessions;
+        synchronized (this)
+        {
+            sessions = _sessions.remove(id);
+        }
+
+        if (sessions!=null)
+        {
+            for (WeakReference<HttpSession> ref: sessions)
+            {
+                AbstractSession session=(AbstractSession)ref.get();
+                if (session!=null && session.isValid())
+                    session.invalidate();
+            }
+            sessions.clear();
+        }
+    }
+    
+    
+    /* ------------------------------------------------------------ */
+    @Override
+    public void renewSessionId (String oldClusterId, String oldNodeId, HttpServletRequest request)
+    {
+        //generate a new id
+        String newClusterId = newSessionId(request.hashCode());
+
+
+        synchronized (this)
+        {
+            Set<WeakReference<HttpSession>> sessions = _sessions.remove(oldClusterId); //get the list of sessions with same id from other contexts
+            if (sessions!=null)
+            {
+                for (Iterator<WeakReference<HttpSession>> iter = sessions.iterator(); iter.hasNext();)
+                {
+                    WeakReference<HttpSession> ref = iter.next();
+                    HttpSession s = ref.get();
+                    if (s == null)
+                    {
+                        continue;
+                    }
+                    else
+                    {
+                        if (s instanceof AbstractSession)
+                        {
+                            AbstractSession abstractSession = (AbstractSession)s;
+                            abstractSession.getSessionManager().renewSessionId(oldClusterId, oldNodeId, newClusterId, getNodeId(newClusterId, request));
+                        }
+                    }
+                }
+                _sessions.put(newClusterId, sessions);
+            }
+        }
+    }
+
+}
diff --git a/lib/jetty/org/eclipse/jetty/server/session/HashSessionManager.java b/lib/jetty/org/eclipse/jetty/server/session/HashSessionManager.java
new file mode 100644 (file)
index 0000000..1effd32
--- /dev/null
@@ -0,0 +1,679 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server.session;
+
+import java.io.DataInputStream;
+import java.io.EOFException;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.TimeUnit;
+
+import javax.servlet.ServletContext;
+import javax.servlet.http.HttpServletRequest;
+
+import org.eclipse.jetty.server.SessionIdManager;
+import org.eclipse.jetty.server.handler.ContextHandler;
+import org.eclipse.jetty.util.ClassLoadingObjectInputStream;
+import org.eclipse.jetty.util.IO;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.thread.ScheduledExecutorScheduler;
+import org.eclipse.jetty.util.thread.Scheduler;
+
+
+/* ------------------------------------------------------------ */
+/** 
+ * HashSessionManager
+ * 
+ * An in-memory implementation of SessionManager.
+ * <p>
+ * This manager supports saving sessions to disk, either periodically or at shutdown.
+ * Sessions can also have their content idle saved to disk to reduce the memory overheads of large idle sessions.
+ * <p>
+ * This manager will create it's own Timer instance to scavenge threads, unless it discovers a shared Timer instance
+ * set as the "org.eclipse.jetty.server.session.timer" attribute of the ContextHandler.
+ *
+ */
+public class HashSessionManager extends AbstractSessionManager
+{
+    final static Logger LOG = SessionHandler.LOG;
+
+    protected final ConcurrentMap<String,HashedSession> _sessions=new ConcurrentHashMap<String,HashedSession>();
+    private Scheduler _timer;
+    private Scheduler.Task _task;
+    long _scavengePeriodMs=30000;
+    long _savePeriodMs=0; //don't do period saves by default
+    long _idleSavePeriodMs = 0; // don't idle save sessions by default.
+    private Scheduler.Task _saveTask;
+    File _storeDir;
+    private boolean _lazyLoad=false;
+    private volatile boolean _sessionsLoaded=false;
+    private boolean _deleteUnrestorableSessions=false;
+
+
+    /**
+     * Scavenger
+     *
+     */
+    protected class Scavenger implements Runnable
+    {
+        @Override
+        public void run()
+        {
+            try
+            {
+                scavenge();
+            }
+            finally
+            {
+                if (_timer != null && _timer.isRunning())
+                    _timer.schedule(this, _scavengePeriodMs, TimeUnit.MILLISECONDS);
+            }
+        }
+    }
+
+    /**
+     * Saver
+     *
+     */
+    protected class Saver implements Runnable
+    {
+        @Override
+        public void run()
+        {
+            try
+            {
+                saveSessions(true);
+            }
+            catch (Exception e)
+            {       
+                LOG.warn(e);
+            }
+            finally
+            {
+                if (_timer != null && _timer.isRunning())
+                    _timer.schedule(this, _savePeriodMs, TimeUnit.MILLISECONDS);
+            }
+        }        
+    }
+
+
+    /* ------------------------------------------------------------ */
+    public HashSessionManager()
+    {
+        super();
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see AbstractSessionManager#doStart()
+     */
+    @Override
+    public void doStart() throws Exception
+    {
+        //try shared scheduler from Server first
+        _timer = getSessionHandler().getServer().getBean(Scheduler.class);
+        if (_timer == null)
+        {
+            //try one passed into the context
+            ServletContext context = ContextHandler.getCurrentContext();
+            if (context!=null)
+                _timer = (Scheduler)context.getAttribute("org.eclipse.jetty.server.session.timer");   
+        }         
+      
+        if (_timer == null)
+        {
+            //make a scheduler if none useable
+            _timer=new ScheduledExecutorScheduler(toString()+"Timer",true);
+            addBean(_timer,true);
+        }
+        else
+            addBean(_timer,false);
+            
+        super.doStart();
+
+        setScavengePeriod(getScavengePeriod());
+
+        if (_storeDir!=null)
+        {
+            if (!_storeDir.exists())
+                _storeDir.mkdirs();
+
+            if (!_lazyLoad)
+                restoreSessions();
+        }
+
+        setSavePeriod(getSavePeriod());
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see AbstractSessionManager#doStop()
+     */
+    @Override
+    public void doStop() throws Exception
+    {
+        // stop the scavengers
+        synchronized(this)
+        {
+            if (_saveTask!=null)
+                _saveTask.cancel();
+            _saveTask=null;
+            if (_task!=null)
+                _task.cancel();
+            _task=null;
+            _timer=null;
+        }
+
+        // This will callback invalidate sessions - where we decide if we will save
+        super.doStop();
+
+        _sessions.clear();
+
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return the period in seconds at which a check is made for sessions to be invalidated.
+     */
+    public int getScavengePeriod()
+    {
+        return (int)(_scavengePeriodMs/1000);
+    }
+
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public int getSessions()
+    {
+        int sessions=super.getSessions();
+        if (LOG.isDebugEnabled())
+        {
+            if (_sessions.size()!=sessions)
+                LOG.warn("sessions: "+_sessions.size()+"!="+sessions);
+        }
+        return sessions;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return seconds Idle period after which a session is saved
+     */
+    public int getIdleSavePeriod()
+    {
+      if (_idleSavePeriodMs <= 0)
+        return 0;
+
+      return (int)(_idleSavePeriodMs / 1000);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Configures the period in seconds after which a session is deemed idle and saved
+     * to save on session memory.
+     *
+     * The session is persisted, the values attribute map is cleared and the session set to idled.
+     *
+     * @param seconds Idle period after which a session is saved
+     */
+    public void setIdleSavePeriod(int seconds)
+    {
+      _idleSavePeriodMs = seconds * 1000L;
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public void setMaxInactiveInterval(int seconds)
+    {
+        super.setMaxInactiveInterval(seconds);
+        if (_dftMaxIdleSecs>0&&_scavengePeriodMs>_dftMaxIdleSecs*1000L)
+            setScavengePeriod((_dftMaxIdleSecs+9)/10);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param seconds the period is seconds at which sessions are periodically saved to disk
+     */
+    public void setSavePeriod (int seconds)
+    {
+        long period = (seconds * 1000L);
+        if (period < 0)
+            period=0;
+        _savePeriodMs=period;
+
+        if (_timer!=null)
+        {
+            synchronized (this)
+            {
+                if (_saveTask!=null)
+                    _saveTask.cancel();
+                _saveTask = null;
+                if (_savePeriodMs > 0 && _storeDir!=null) //only save if we have a directory configured
+                {
+                    _saveTask = _timer.schedule(new Saver(),_savePeriodMs,TimeUnit.MILLISECONDS);
+                }
+            }
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return the period in seconds at which sessions are periodically saved to disk
+     */
+    public int getSavePeriod ()
+    {
+        if (_savePeriodMs<=0)
+            return 0;
+
+        return (int)(_savePeriodMs/1000);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param seconds the period in seconds at which a check is made for sessions to be invalidated.
+     */
+    public void setScavengePeriod(int seconds)
+    { 
+        if (seconds==0)
+            seconds=60;
+
+        long old_period=_scavengePeriodMs;
+        long period=seconds*1000L;
+        if (period>60000)
+            period=60000;
+        if (period<1000)
+            period=1000;
+
+        _scavengePeriodMs=period;
+    
+        if (_timer!=null && (period!=old_period || _task==null))
+        {
+            synchronized (this)
+            {
+                if (_task!=null)
+                {
+                    _task.cancel();
+                    _task = null;
+                }
+                _task = _timer.schedule(new Scavenger(),_scavengePeriodMs, TimeUnit.MILLISECONDS);
+            }
+        }
+    }
+
+    /* -------------------------------------------------------------- */
+    /**
+     * Find sessions that have timed out and invalidate them. This runs in the
+     * SessionScavenger thread.
+     */
+    protected void scavenge()
+    {
+        //don't attempt to scavenge if we are shutting down
+        if (isStopping() || isStopped())
+            return;
+
+        Thread thread=Thread.currentThread();
+        ClassLoader old_loader=thread.getContextClassLoader();
+        try
+        {      
+            if (_loader!=null)
+                thread.setContextClassLoader(_loader);
+
+            // For each session
+            long now=System.currentTimeMillis();
+            __log.debug("Scavenging sessions at {}", now); 
+            
+            for (Iterator<HashedSession> i=_sessions.values().iterator(); i.hasNext();)
+            {
+                HashedSession session=i.next();
+                long idleTime=session.getMaxInactiveInterval()*1000L; 
+                if (idleTime>0&&session.getAccessed()+idleTime<now)
+                {
+                    // Found a stale session, add it to the list
+                    try
+                    {
+                        session.timeout();
+                    }
+                    catch (Exception e)
+                    {
+                        __log.warn("Problem scavenging sessions", e);
+                    }
+                }
+                else if (_idleSavePeriodMs > 0 && session.getAccessed()+_idleSavePeriodMs < now)
+                {
+                    try
+                    {
+                        session.idle();
+                    }
+                    catch (Exception e)
+                    {
+                        __log.warn("Problem idling session "+ session.getId(), e);
+                    }
+                }
+            }
+        }       
+        finally
+        {
+            thread.setContextClassLoader(old_loader);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    protected void addSession(AbstractSession session)
+    {
+        if (isRunning())
+            _sessions.put(session.getClusterId(),(HashedSession)session);
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public AbstractSession getSession(String idInCluster)
+    {
+        if ( _lazyLoad && !_sessionsLoaded)
+        {
+            try
+            {
+                restoreSessions();
+            }
+            catch(Exception e)
+            {
+                LOG.warn(e);
+            }
+        }
+
+        Map<String,HashedSession> sessions=_sessions;
+        if (sessions==null)
+            return null;
+
+        HashedSession session = sessions.get(idInCluster);
+
+        if (session == null && _lazyLoad)
+            session=restoreSession(idInCluster);
+        if (session == null)
+            return null;
+
+        if (_idleSavePeriodMs!=0)
+            session.deIdle();
+
+        return session;
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    protected void shutdownSessions() throws Exception
+    {   
+        // Invalidate all sessions to cause unbind events
+        ArrayList<HashedSession> sessions=new ArrayList<HashedSession>(_sessions.values());
+        int loop=100;
+        while (sessions.size()>0 && loop-->0)
+        {
+            // If we are called from doStop
+            if (isStopping() && _storeDir != null && _storeDir.exists() && _storeDir.canWrite())
+            {
+                // Then we only save and remove the session from memory- it is not invalidated.
+                for (HashedSession session : sessions)
+                {
+                    session.save(false);
+                    _sessions.remove(session.getClusterId());
+                }
+            }
+            else
+            {
+                for (HashedSession session : sessions)
+                    session.invalidate();
+            }
+
+            // check that no new sessions were created while we were iterating
+            sessions=new ArrayList<HashedSession>(_sessions.values());
+        }
+    }
+    
+    
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.server.SessionManager#renewSessionId(java.lang.String, java.lang.String, java.lang.String, java.lang.String)
+     */
+    @Override
+    public void renewSessionId(String oldClusterId, String oldNodeId, String newClusterId, String newNodeId)
+    {
+        try
+        {
+            Map<String,HashedSession> sessions=_sessions;
+            if (sessions == null)
+                return;
+
+            HashedSession session = sessions.remove(oldClusterId);
+            if (session == null)
+                return;
+
+            session.remove(); //delete any previously saved session
+            session.setClusterId(newClusterId); //update ids
+            session.setNodeId(newNodeId);
+            session.save(); //save updated session: TODO consider only saving file if idled
+            sessions.put(newClusterId, session);
+            
+            super.renewSessionId(oldClusterId, oldNodeId, newClusterId, newNodeId);
+        }
+        catch (Exception e)
+        {
+            LOG.warn(e);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    protected AbstractSession newSession(HttpServletRequest request)
+    {
+        return new HashedSession(this, request);
+    }
+
+    /* ------------------------------------------------------------ */
+    protected AbstractSession newSession(long created, long accessed, String clusterId)
+    {
+        return new HashedSession(this, created,accessed, clusterId);
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    protected boolean removeSession(String clusterId)
+    {
+        return _sessions.remove(clusterId)!=null;
+    }
+
+    /* ------------------------------------------------------------ */
+    public void setStoreDirectory (File dir) throws IOException
+    { 
+        // CanonicalFile is used to capture the base store directory in a way that will
+        // work on Windows.  Case differences may through off later checks using this directory.
+        _storeDir=dir.getCanonicalFile();
+    }
+
+    /* ------------------------------------------------------------ */
+    public File getStoreDirectory ()
+    {
+        return _storeDir;
+    }
+
+    /* ------------------------------------------------------------ */
+    public void setLazyLoad(boolean lazyLoad)
+    {
+        _lazyLoad = lazyLoad;
+    }
+
+    /* ------------------------------------------------------------ */
+    public boolean isLazyLoad()
+    {
+        return _lazyLoad;
+    }
+
+    /* ------------------------------------------------------------ */
+    public boolean isDeleteUnrestorableSessions()
+    {
+        return _deleteUnrestorableSessions;
+    }
+
+    /* ------------------------------------------------------------ */
+    public void setDeleteUnrestorableSessions(boolean deleteUnrestorableSessions)
+    {
+        _deleteUnrestorableSessions = deleteUnrestorableSessions;
+    }
+
+    /* ------------------------------------------------------------ */
+    public void restoreSessions () throws Exception
+    {
+        _sessionsLoaded = true;
+
+        if (_storeDir==null || !_storeDir.exists())
+        {
+            return;
+        }
+
+        if (!_storeDir.canRead())
+        {
+            LOG.warn ("Unable to restore Sessions: Cannot read from Session storage directory "+_storeDir.getAbsolutePath());
+            return;
+        }
+
+        String[] files = _storeDir.list();
+        for (int i=0;files!=null&&i<files.length;i++)
+        {
+            restoreSession(files[i]);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    protected synchronized HashedSession restoreSession(String idInCuster)
+    {        
+        File file = new File(_storeDir,idInCuster);
+
+        FileInputStream in = null;
+        Exception error = null;
+        try
+        {
+            if (file.exists())
+            {
+                in = new FileInputStream(file);
+                HashedSession session = restoreSession(in, null);
+                addSession(session, false);
+                session.didActivate();
+                return session;
+            }
+        }
+        catch (Exception e)
+        {
+           error = e;
+        }
+        finally
+        {
+            if (in != null) IO.close(in);
+            
+            if (error != null)
+            {
+                if (isDeleteUnrestorableSessions() && file.exists() && file.getParentFile().equals(_storeDir) )
+                {
+                    file.delete();
+                    LOG.warn("Deleting file for unrestorable session "+idInCuster, error);
+                }
+                else
+                {
+                    __log.warn("Problem restoring session "+idInCuster, error);
+                }
+            }
+            else
+               file.delete(); //delete successfully restored file
+        }
+        return null;
+    }
+
+    /* ------------------------------------------------------------ */
+    public void saveSessions(boolean reactivate) throws Exception
+    {
+        if (_storeDir==null || !_storeDir.exists())
+        {
+            return;
+        }
+
+        if (!_storeDir.canWrite())
+        {
+            LOG.warn ("Unable to save Sessions: Session persistence storage directory "+_storeDir.getAbsolutePath()+ " is not writeable");
+            return;
+        }
+
+        for (HashedSession session : _sessions.values())
+            session.save(reactivate);
+    }
+    
+
+    /* ------------------------------------------------------------ */
+    public HashedSession restoreSession (InputStream is, HashedSession session) throws Exception
+    {
+        DataInputStream di = new DataInputStream(is);
+
+        String clusterId = di.readUTF();
+        di.readUTF(); // nodeId
+
+        long created = di.readLong();
+        long accessed = di.readLong();
+        int requests = di.readInt();
+
+        if (session == null)
+            session = (HashedSession)newSession(created, accessed, clusterId);
+        session.setRequests(requests);
+
+        int size = di.readInt();
+
+        restoreSessionAttributes(di, size, session);
+
+        try
+        {
+            int maxIdle = di.readInt();
+            session.setMaxInactiveInterval(maxIdle);
+        }
+        catch (EOFException e)
+        {
+            LOG.debug("No maxInactiveInterval persisted for session "+clusterId);
+            LOG.ignore(e);
+        }
+
+        return session;
+    }
+
+    
+    private void restoreSessionAttributes (InputStream is, int size, HashedSession session)
+    throws Exception
+    {
+        if (size>0)
+        {
+            ClassLoadingObjectInputStream ois =  new ClassLoadingObjectInputStream(is);
+            for (int i=0; i<size;i++)
+            {
+                String key = ois.readUTF();
+                Object value = ois.readObject();
+                session.setAttribute(key,value);
+            }
+        }
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/server/session/HashedSession.java b/lib/jetty/org/eclipse/jetty/server/session/HashedSession.java
new file mode 100644 (file)
index 0000000..d36e427
--- /dev/null
@@ -0,0 +1,286 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server.session;
+
+import java.io.DataOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.ObjectOutputStream;
+import java.io.OutputStream;
+import java.util.Enumeration;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.eclipse.jetty.util.IO;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+public class HashedSession extends MemSession
+{
+    private static final Logger LOG = Log.getLogger(HashedSession.class);
+
+    private final HashSessionManager _hashSessionManager;
+
+    /** Whether the session has been saved because it has been deemed idle;
+     * in which case its attribute map will have been saved and cleared. */
+    private transient boolean _idled = false;
+
+    /** Whether there has already been an attempt to save this session
+     * which has failed.  If there has, there will be no more save attempts
+     * for this session.  This is to stop the logs being flooded with errors
+     * due to serialization failures that are most likely caused by user
+     * data stored in the session that is not serializable. */
+    private transient boolean _saveFailed = false;
+    
+    /**
+     * True if an attempt has been made to de-idle a session and it failed. Once
+     * true, the session will not be attempted to be de-idled again.
+     */
+    private transient boolean _deIdleFailed = false;
+
+    /* ------------------------------------------------------------- */
+    protected HashedSession(HashSessionManager hashSessionManager, HttpServletRequest request)
+    {
+        super(hashSessionManager,request);
+        _hashSessionManager = hashSessionManager;
+    }
+
+    /* ------------------------------------------------------------- */
+    protected HashedSession(HashSessionManager hashSessionManager, long created, long accessed, String clusterId)
+    {
+        super(hashSessionManager,created, accessed, clusterId);
+        _hashSessionManager = hashSessionManager;
+    }
+
+    /* ------------------------------------------------------------- */
+    protected void checkValid()
+    {
+        if (!_deIdleFailed && _hashSessionManager._idleSavePeriodMs!=0)
+            deIdle();
+        super.checkValid();
+    }
+
+    /* ------------------------------------------------------------- */
+    @Override
+    public void setMaxInactiveInterval(int secs)
+    {
+        super.setMaxInactiveInterval(secs);
+        if (getMaxInactiveInterval()>0&&(getMaxInactiveInterval()*1000L/10)<_hashSessionManager._scavengePeriodMs)
+            _hashSessionManager.setScavengePeriod((secs+9)/10);
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    protected void doInvalidate()
+    throws IllegalStateException
+    {
+        super.doInvalidate();
+        remove();
+    }
+    
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * Remove from the disk
+     */
+    synchronized void remove ()
+    {
+        if (_hashSessionManager._storeDir!=null && getId()!=null)
+        {
+            String id=getId();
+            File f = new File(_hashSessionManager._storeDir, id);
+            f.delete();
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    synchronized void save(boolean reactivate)
+    throws Exception
+    {
+        // Only idle the session if not already idled and no previous save/idle has failed
+        if (!isIdled() && !_saveFailed)
+        {
+            if (LOG.isDebugEnabled())
+                LOG.debug("Saving {} {}",super.getId(),reactivate);
+
+            try
+            {
+                willPassivate();
+                save();
+                if (reactivate)
+                    didActivate();
+                else
+                    clearAttributes();
+            }
+            catch (Exception e)
+            {       
+                LOG.warn("Problem saving session " + super.getId(), e);
+                _idled=false; // assume problem was before _values.clear();
+            }
+        }
+    }
+    
+    
+    
+    synchronized void save ()
+    throws Exception
+    {   
+        File file = null;
+        FileOutputStream fos = null;
+        if (!_saveFailed && _hashSessionManager._storeDir != null)
+        {
+            try
+            {
+                file = new File(_hashSessionManager._storeDir, super.getId());
+                if (file.exists())
+                    file.delete();
+                file.createNewFile();
+                fos = new FileOutputStream(file);
+                save(fos);
+                IO.close(fos);
+            }
+            catch (Exception e)
+            {
+                saveFailed(); // We won't try again for this session
+                if (fos != null) IO.close(fos);
+                if (file != null) file.delete(); // No point keeping the file if we didn't save the whole session
+                throw e;             
+            }
+        }
+    }
+    
+    
+    /* ------------------------------------------------------------ */
+    public synchronized void save(OutputStream os)  throws IOException
+    {
+        DataOutputStream out = new DataOutputStream(os);
+        out.writeUTF(getClusterId());
+        out.writeUTF(getNodeId());
+        out.writeLong(getCreationTime());
+        out.writeLong(getAccessed());
+
+        /* Don't write these out, as they don't make sense to store because they
+         * either they cannot be true or their value will be restored in the
+         * Session constructor.
+         */
+        //out.writeBoolean(_invalid);
+        //out.writeBoolean(_doInvalidate);
+        //out.writeBoolean( _newSession);
+        out.writeInt(getRequests());
+        out.writeInt(getAttributes());
+        ObjectOutputStream oos = new ObjectOutputStream(out);
+        Enumeration<String> e=getAttributeNames();
+        while(e.hasMoreElements())
+        {
+            String key=e.nextElement();
+            oos.writeUTF(key);
+            oos.writeObject(doGet(key));
+        }
+        
+        out.writeInt(getMaxInactiveInterval());
+    }
+
+    /* ------------------------------------------------------------ */
+    public synchronized void deIdle()
+    {
+        if (isIdled() && !_deIdleFailed)
+        {
+            // Access now to prevent race with idling period
+            access(System.currentTimeMillis());
+
+            if (LOG.isDebugEnabled())
+                LOG.debug("De-idling " + super.getId());
+
+            FileInputStream fis = null;
+
+            try
+            {
+                File file = new File(_hashSessionManager._storeDir, super.getId());
+                if (!file.exists() || !file.canRead())
+                    throw new FileNotFoundException(file.getName());
+
+                fis = new FileInputStream(file);
+                _idled = false;
+                _hashSessionManager.restoreSession(fis, this);
+                IO.close(fis); 
+                
+                didActivate();
+
+                // If we are doing period saves, then there is no point deleting at this point 
+                if (_hashSessionManager._savePeriodMs == 0)
+                    file.delete();
+            }
+            catch (Exception e)
+            {
+                deIdleFailed();
+                LOG.warn("Problem de-idling session " + super.getId(), e);
+                if (fis != null) IO.close(fis);//Must ensure closed before invalidate
+                invalidate();
+            }
+        }
+    }
+
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Idle the session to reduce session memory footprint.
+     *
+     * The session is idled by persisting it, then clearing the session values attribute map and finally setting
+     * it to an idled state.
+     */
+    public synchronized void idle()
+    throws Exception
+    {
+        save(false);
+        _idled = true;
+    }
+
+    /* ------------------------------------------------------------ */
+    public synchronized boolean isIdled()
+    {
+      return _idled;
+    }
+
+    /* ------------------------------------------------------------ */
+    public synchronized boolean isSaveFailed()
+    {
+        return _saveFailed;
+    }
+
+    /* ------------------------------------------------------------ */
+    public synchronized void saveFailed()
+    {
+        _saveFailed = true;
+    }
+
+    /* ------------------------------------------------------------ */
+    public synchronized void deIdleFailed()
+    {
+        _deIdleFailed = true;
+    }
+    
+    /* ------------------------------------------------------------ */
+    public synchronized boolean isDeIdleFailed()
+    {
+        return _deIdleFailed;
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/server/session/JDBCSessionIdManager.java b/lib/jetty/org/eclipse/jetty/server/session/JDBCSessionIdManager.java
new file mode 100644 (file)
index 0000000..4c7227d
--- /dev/null
@@ -0,0 +1,1496 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server.session;
+
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+import java.sql.Blob;
+import java.sql.Connection;
+import java.sql.DatabaseMetaData;
+import java.sql.Driver;
+import java.sql.DriverManager;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.util.HashSet;
+import java.util.Locale;
+import java.util.Random;
+import java.util.Set;
+import java.util.concurrent.TimeUnit;
+
+import javax.naming.InitialContext;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpSession;
+import javax.sql.DataSource;
+
+import org.eclipse.jetty.server.Handler;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.SessionManager;
+import org.eclipse.jetty.server.handler.ContextHandler;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.thread.ScheduledExecutorScheduler;
+import org.eclipse.jetty.util.thread.Scheduler;
+
+
+
+/**
+ * JDBCSessionIdManager
+ *
+ * SessionIdManager implementation that uses a database to store in-use session ids,
+ * to support distributed sessions.
+ *
+ */
+public class JDBCSessionIdManager extends AbstractSessionIdManager
+{
+    final static Logger LOG = SessionHandler.LOG;
+    public final static int MAX_INTERVAL_NOT_SET = -999;
+
+    protected final HashSet<String> _sessionIds = new HashSet<String>();
+    protected Server _server;
+    protected Driver _driver;
+    protected String _driverClassName;
+    protected String _connectionUrl;
+    protected DataSource _datasource;
+    protected String _jndiName;
+
+    protected int _deleteBlockSize = 10; //number of ids to include in where 'in' clause
+
+    protected Scheduler.Task _task; //scavenge task
+    protected Scheduler _scheduler;
+    protected Scavenger _scavenger;
+    protected boolean _ownScheduler;
+    protected long _lastScavengeTime;
+    protected long _scavengeIntervalMs = 1000L * 60 * 10; //10mins
+
+
+    protected String _createSessionIdTable;
+    protected String _createSessionTable;
+
+    protected String _selectBoundedExpiredSessions;
+    private String _selectExpiredSessions;
+    
+    protected String _insertId;
+    protected String _deleteId;
+    protected String _queryId;
+
+    protected  String _insertSession;
+    protected  String _deleteSession;
+    protected  String _updateSession;
+    protected  String _updateSessionNode;
+    protected  String _updateSessionAccessTime;
+
+    protected DatabaseAdaptor _dbAdaptor = new DatabaseAdaptor();
+    protected SessionIdTableSchema _sessionIdTableSchema = new SessionIdTableSchema();
+    protected SessionTableSchema _sessionTableSchema = new SessionTableSchema();
+    
+  
+
+    /**
+     * SessionTableSchema
+     *
+     */
+    public static class SessionTableSchema
+    {        
+        protected DatabaseAdaptor _dbAdaptor;
+        protected String _tableName = "JettySessions";
+        protected String _rowIdColumn = "rowId";
+        protected String _idColumn = "sessionId";
+        protected String _contextPathColumn = "contextPath";
+        protected String _virtualHostColumn = "virtualHost"; 
+        protected String _lastNodeColumn = "lastNode";
+        protected String _accessTimeColumn = "accessTime"; 
+        protected String _lastAccessTimeColumn = "lastAccessTime";
+        protected String _createTimeColumn = "createTime";
+        protected String _cookieTimeColumn = "cookieTime";
+        protected String _lastSavedTimeColumn = "lastSavedTime";
+        protected String _expiryTimeColumn = "expiryTime";
+        protected String _maxIntervalColumn = "maxInterval";
+        protected String _mapColumn = "map";
+        
+        
+        protected void setDatabaseAdaptor(DatabaseAdaptor dbadaptor)
+        {
+            _dbAdaptor = dbadaptor;
+        }
+        
+        
+        public String getTableName()
+        {
+            return _tableName;
+        }
+        public void setTableName(String tableName)
+        {
+            checkNotNull(tableName);
+            _tableName = tableName;
+        }
+        public String getRowIdColumn()
+        {       
+            if ("rowId".equals(_rowIdColumn) && _dbAdaptor.isRowIdReserved())
+                _rowIdColumn = "srowId";
+            return _rowIdColumn;
+        }
+        public void setRowIdColumn(String rowIdColumn)
+        {
+            checkNotNull(rowIdColumn);
+            if (_dbAdaptor == null)
+                throw new IllegalStateException ("DbAdaptor is null");
+            
+            if (_dbAdaptor.isRowIdReserved() && "rowId".equals(rowIdColumn))
+                throw new IllegalArgumentException("rowId is reserved word for Oracle");
+            
+            _rowIdColumn = rowIdColumn;
+        }
+        public String getIdColumn()
+        {
+            return _idColumn;
+        }
+        public void setIdColumn(String idColumn)
+        {
+            checkNotNull(idColumn);
+            _idColumn = idColumn;
+        }
+        public String getContextPathColumn()
+        {
+            return _contextPathColumn;
+        }
+        public void setContextPathColumn(String contextPathColumn)
+        {
+            checkNotNull(contextPathColumn);
+            _contextPathColumn = contextPathColumn;
+        }
+        public String getVirtualHostColumn()
+        {
+            return _virtualHostColumn;
+        }
+        public void setVirtualHostColumn(String virtualHostColumn)
+        {
+            checkNotNull(virtualHostColumn);
+            _virtualHostColumn = virtualHostColumn;
+        }
+        public String getLastNodeColumn()
+        {
+            return _lastNodeColumn;
+        }
+        public void setLastNodeColumn(String lastNodeColumn)
+        {
+            checkNotNull(lastNodeColumn);
+            _lastNodeColumn = lastNodeColumn;
+        }
+        public String getAccessTimeColumn()
+        {
+            return _accessTimeColumn;
+        }
+        public void setAccessTimeColumn(String accessTimeColumn)
+        {
+            checkNotNull(accessTimeColumn);
+            _accessTimeColumn = accessTimeColumn;
+        }
+        public String getLastAccessTimeColumn()
+        {
+            return _lastAccessTimeColumn;
+        }
+        public void setLastAccessTimeColumn(String lastAccessTimeColumn)
+        {
+            checkNotNull(lastAccessTimeColumn);
+            _lastAccessTimeColumn = lastAccessTimeColumn;
+        }
+        public String getCreateTimeColumn()
+        {
+            return _createTimeColumn;
+        }
+        public void setCreateTimeColumn(String createTimeColumn)
+        {
+            checkNotNull(createTimeColumn);
+            _createTimeColumn = createTimeColumn;
+        }
+        public String getCookieTimeColumn()
+        {
+            return _cookieTimeColumn;
+        }
+        public void setCookieTimeColumn(String cookieTimeColumn)
+        {
+            checkNotNull(cookieTimeColumn);
+            _cookieTimeColumn = cookieTimeColumn;
+        }
+        public String getLastSavedTimeColumn()
+        {
+            return _lastSavedTimeColumn;
+        }
+        public void setLastSavedTimeColumn(String lastSavedTimeColumn)
+        {
+            checkNotNull(lastSavedTimeColumn);
+            _lastSavedTimeColumn = lastSavedTimeColumn;
+        }
+        public String getExpiryTimeColumn()
+        {
+            return _expiryTimeColumn;
+        }
+        public void setExpiryTimeColumn(String expiryTimeColumn)
+        {
+            checkNotNull(expiryTimeColumn);
+            _expiryTimeColumn = expiryTimeColumn;
+        }
+        public String getMaxIntervalColumn()
+        {
+            return _maxIntervalColumn;
+        }
+        public void setMaxIntervalColumn(String maxIntervalColumn)
+        {
+            checkNotNull(maxIntervalColumn);
+            _maxIntervalColumn = maxIntervalColumn;
+        }
+        public String getMapColumn()
+        {
+            return _mapColumn;
+        }
+        public void setMapColumn(String mapColumn)
+        {
+            checkNotNull(mapColumn);
+            _mapColumn = mapColumn;
+        }
+        
+        public String getCreateStatementAsString ()
+        {
+            if (_dbAdaptor == null)
+                throw new IllegalStateException ("No DBAdaptor");
+            
+            String blobType = _dbAdaptor.getBlobType();
+            String longType = _dbAdaptor.getLongType();
+            
+            return "create table "+_tableName+" ("+getRowIdColumn()+" varchar(120), "+_idColumn+" varchar(120), "+
+                    _contextPathColumn+" varchar(60), "+_virtualHostColumn+" varchar(60), "+_lastNodeColumn+" varchar(60), "+_accessTimeColumn+" "+longType+", "+
+                    _lastAccessTimeColumn+" "+longType+", "+_createTimeColumn+" "+longType+", "+_cookieTimeColumn+" "+longType+", "+
+                    _lastSavedTimeColumn+" "+longType+", "+_expiryTimeColumn+" "+longType+", "+_maxIntervalColumn+" "+longType+", "+
+                    _mapColumn+" "+blobType+", primary key("+getRowIdColumn()+"))";
+        }
+        
+        public String getCreateIndexOverExpiryStatementAsString (String indexName)
+        {
+            return "create index "+indexName+" on "+getTableName()+" ("+getExpiryTimeColumn()+")";
+        }
+        
+        public String getCreateIndexOverSessionStatementAsString (String indexName)
+        {
+            return "create index "+indexName+" on "+getTableName()+" ("+getIdColumn()+", "+getContextPathColumn()+")";
+        }
+        
+        public String getAlterTableForMaxIntervalAsString ()
+        {
+            if (_dbAdaptor == null)
+                throw new IllegalStateException ("No DBAdaptor");
+            String longType = _dbAdaptor.getLongType();
+            return "alter table "+getTableName()+" add "+getMaxIntervalColumn()+" "+longType+" not null default "+MAX_INTERVAL_NOT_SET;
+        }
+        
+        private void checkNotNull(String s)
+        {
+            if (s == null)
+                throw new IllegalArgumentException(s);
+        }
+        public String getInsertSessionStatementAsString()
+        {
+           return "insert into "+getTableName()+
+            " ("+getRowIdColumn()+", "+getIdColumn()+", "+getContextPathColumn()+", "+getVirtualHostColumn()+", "+getLastNodeColumn()+
+            ", "+getAccessTimeColumn()+", "+getLastAccessTimeColumn()+", "+getCreateTimeColumn()+", "+getCookieTimeColumn()+
+            ", "+getLastSavedTimeColumn()+", "+getExpiryTimeColumn()+", "+getMaxIntervalColumn()+", "+getMapColumn()+") "+
+            " values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)";
+        }
+        public String getDeleteSessionStatementAsString()
+        {
+            return "delete from "+getTableName()+
+            " where "+getRowIdColumn()+" = ?";
+        }
+        public String getUpdateSessionStatementAsString()
+        {
+            return "update "+getTableName()+
+                    " set "+getIdColumn()+" = ?, "+getLastNodeColumn()+" = ?, "+getAccessTimeColumn()+" = ?, "+
+                    getLastAccessTimeColumn()+" = ?, "+getLastSavedTimeColumn()+" = ?, "+getExpiryTimeColumn()+" = ?, "+
+                    getMaxIntervalColumn()+" = ?, "+getMapColumn()+" = ? where "+getRowIdColumn()+" = ?";
+        }
+        public String getUpdateSessionNodeStatementAsString()
+        {
+            return "update "+getTableName()+
+                    " set "+getLastNodeColumn()+" = ? where "+getRowIdColumn()+" = ?";
+        }
+        public String getUpdateSessionAccessTimeStatementAsString()
+        {
+           return "update "+getTableName()+
+            " set "+getLastNodeColumn()+" = ?, "+getAccessTimeColumn()+" = ?, "+getLastAccessTimeColumn()+" = ?, "+
+                   getLastSavedTimeColumn()+" = ?, "+getExpiryTimeColumn()+" = ?, "+getMaxIntervalColumn()+" = ? where "+getRowIdColumn()+" = ?";
+        }
+        
+        public String getBoundedExpiredSessionsStatementAsString()
+        {
+            return "select * from "+getTableName()+" where "+getLastNodeColumn()+" = ? and "+getExpiryTimeColumn()+" >= ? and "+getExpiryTimeColumn()+" <= ?";
+        }
+        
+        public String getSelectExpiredSessionsStatementAsString()
+        {
+            return "select * from "+getTableName()+" where "+getExpiryTimeColumn()+" >0 and "+getExpiryTimeColumn()+" <= ?";
+        }
+     
+        public PreparedStatement getLoadStatement (Connection connection, String rowId, String contextPath, String virtualHosts)
+        throws SQLException
+        { 
+            if (_dbAdaptor == null)
+                throw new IllegalStateException("No DB adaptor");
+
+
+            if (contextPath == null || "".equals(contextPath))
+            {
+                if (_dbAdaptor.isEmptyStringNull())
+                {
+                    PreparedStatement statement = connection.prepareStatement("select * from "+getTableName()+
+                                                                              " where "+getIdColumn()+" = ? and "+
+                                                                              getContextPathColumn()+" is null and "+
+                                                                              getVirtualHostColumn()+" = ?");
+                    statement.setString(1, rowId);
+                    statement.setString(2, virtualHosts);
+
+                    return statement;
+                }
+            }
+
+            PreparedStatement statement = connection.prepareStatement("select * from "+getTableName()+
+                                                                      " where "+getIdColumn()+" = ? and "+getContextPathColumn()+
+                                                                      " = ? and "+getVirtualHostColumn()+" = ?");
+            statement.setString(1, rowId);
+            statement.setString(2, contextPath);
+            statement.setString(3, virtualHosts);
+
+            return statement;
+        }
+    }
+    
+    
+    
+    /**
+     * SessionIdTableSchema
+     *
+     */
+    public static class SessionIdTableSchema
+    {
+        protected DatabaseAdaptor _dbAdaptor;
+        protected String _tableName = "JettySessionIds";
+        protected String _idColumn = "id";
+
+        public void setDatabaseAdaptor(DatabaseAdaptor dbAdaptor)
+        {
+            _dbAdaptor = dbAdaptor;
+        }
+        public String getIdColumn()
+        {
+            return _idColumn;
+        }
+
+        public void setIdColumn(String idColumn)
+        {
+            checkNotNull(idColumn);
+            _idColumn = idColumn;
+        }
+
+        public String getTableName()
+        {
+            return _tableName;
+        }
+
+        public void setTableName(String tableName)
+        {
+            checkNotNull(tableName);
+            _tableName = tableName;
+        }
+
+        public String getInsertStatementAsString ()
+        {
+            return "insert into "+_tableName+" ("+_idColumn+")  values (?)";
+        }
+
+        public String getDeleteStatementAsString ()
+        {
+            return "delete from "+_tableName+" where "+_idColumn+" = ?";
+        }
+
+        public String getSelectStatementAsString ()
+        {
+            return  "select * from "+_tableName+" where "+_idColumn+" = ?";
+        }
+        
+        public String getCreateStatementAsString ()
+        {
+            return "create table "+_tableName+" ("+_idColumn+" varchar(120), primary key("+_idColumn+"))";
+        }
+        
+        private void checkNotNull(String s)
+        {
+            if (s == null)
+                throw new IllegalArgumentException(s);
+        }
+    }
+
+
+    /**
+     * DatabaseAdaptor
+     *
+     * Handles differences between databases.
+     *
+     * Postgres uses the getBytes and setBinaryStream methods to access
+     * a "bytea" datatype, which can be up to 1Gb of binary data. MySQL
+     * is happy to use the "blob" type and getBlob() methods instead.
+     *
+     * TODO if the differences become more major it would be worthwhile
+     * refactoring this class.
+     */
+    public static class DatabaseAdaptor
+    {
+        String _dbName;
+        boolean _isLower;
+        boolean _isUpper;
+        
+        protected String _blobType; //if not set, is deduced from the type of the database at runtime
+        protected String _longType; //if not set, is deduced from the type of the database at runtime
+
+
+        public DatabaseAdaptor ()
+        {           
+        }
+        
+        
+        public void adaptTo(DatabaseMetaData dbMeta)  
+        throws SQLException
+        {
+            _dbName = dbMeta.getDatabaseProductName().toLowerCase(Locale.ENGLISH);
+            LOG.debug ("Using database {}",_dbName);
+            _isLower = dbMeta.storesLowerCaseIdentifiers();
+            _isUpper = dbMeta.storesUpperCaseIdentifiers(); 
+        }
+        
+       
+        public void setBlobType(String blobType)
+        {
+            _blobType = blobType;
+        }
+        
+        public String getBlobType ()
+        {
+            if (_blobType != null)
+                return _blobType;
+
+            if (_dbName.startsWith("postgres"))
+                return "bytea";
+
+            return "blob";
+        }
+        
+
+        public void setLongType(String longType)
+        {
+            _longType = longType;
+        }
+        
+
+        public String getLongType ()
+        {
+            if (_longType != null)
+                return _longType;
+
+            if (_dbName == null)
+                throw new IllegalStateException ("DbAdaptor missing metadata");
+            
+            if (_dbName.startsWith("oracle"))
+                return "number(20)";
+
+            return "bigint";
+        }
+        
+
+        /**
+         * Convert a camel case identifier into either upper or lower
+         * depending on the way the db stores identifiers.
+         *
+         * @param identifier
+         * @return the converted identifier
+         */
+        public String convertIdentifier (String identifier)
+        {
+            if (_dbName == null)
+                throw new IllegalStateException ("DbAdaptor missing metadata");
+            
+            if (_isLower)
+                return identifier.toLowerCase(Locale.ENGLISH);
+            if (_isUpper)
+                return identifier.toUpperCase(Locale.ENGLISH);
+
+            return identifier;
+        }
+
+        public String getDBName ()
+        {
+            return _dbName;
+        }
+
+
+        public InputStream getBlobInputStream (ResultSet result, String columnName)
+        throws SQLException
+        {
+            if (_dbName == null)
+                throw new IllegalStateException ("DbAdaptor missing metadata");
+            
+            if (_dbName.startsWith("postgres"))
+            {
+                byte[] bytes = result.getBytes(columnName);
+                return new ByteArrayInputStream(bytes);
+            }
+
+            Blob blob = result.getBlob(columnName);
+            return blob.getBinaryStream();
+        }
+
+
+        public boolean isEmptyStringNull ()
+        {
+            if (_dbName == null)
+                throw new IllegalStateException ("DbAdaptor missing metadata");
+            
+            return (_dbName.startsWith("oracle"));
+        }
+        
+        /**
+         * rowId is a reserved word for Oracle, so change the name of this column
+         * @return true if db in use is oracle
+         */
+        public boolean isRowIdReserved ()
+        {
+            if (_dbName == null)
+                throw new IllegalStateException ("DbAdaptor missing metadata");
+            
+            return (_dbName != null && _dbName.startsWith("oracle"));
+        }
+    }
+
+    
+    /**
+     * Scavenger
+     *
+     */
+    protected class Scavenger implements Runnable
+    {
+
+        @Override
+        public void run()
+        {
+           try
+           {
+               scavenge();
+           }
+           finally
+           {
+               if (_scheduler != null && _scheduler.isRunning())
+                   _scheduler.schedule(this, _scavengeIntervalMs, TimeUnit.MILLISECONDS);
+           }
+        }
+    }
+
+
+    public JDBCSessionIdManager(Server server)
+    {
+        super();
+        _server=server;
+    }
+
+    public JDBCSessionIdManager(Server server, Random random)
+    {
+       super(random);
+       _server=server;
+    }
+
+    /**
+     * Configure jdbc connection information via a jdbc Driver
+     *
+     * @param driverClassName
+     * @param connectionUrl
+     */
+    public void setDriverInfo (String driverClassName, String connectionUrl)
+    {
+        _driverClassName=driverClassName;
+        _connectionUrl=connectionUrl;
+    }
+
+    /**
+     * Configure jdbc connection information via a jdbc Driver
+     *
+     * @param driverClass
+     * @param connectionUrl
+     */
+    public void setDriverInfo (Driver driverClass, String connectionUrl)
+    {
+        _driver=driverClass;
+        _connectionUrl=connectionUrl;
+    }
+
+
+    public void setDatasource (DataSource ds)
+    {
+        _datasource = ds;
+    }
+
+    public DataSource getDataSource ()
+    {
+        return _datasource;
+    }
+
+    public String getDriverClassName()
+    {
+        return _driverClassName;
+    }
+
+    public String getConnectionUrl ()
+    {
+        return _connectionUrl;
+    }
+
+    public void setDatasourceName (String jndi)
+    {
+        _jndiName=jndi;
+    }
+
+    public String getDatasourceName ()
+    {
+        return _jndiName;
+    }
+
+    /**
+     * @param name
+     * @deprecated see DbAdaptor.setBlobType
+     */
+    public void setBlobType (String name)
+    {
+        _dbAdaptor.setBlobType(name);
+    }
+
+    public DatabaseAdaptor getDbAdaptor()
+    {
+        return _dbAdaptor;
+    }
+
+    public void setDbAdaptor(DatabaseAdaptor dbAdaptor)
+    {
+        if (dbAdaptor == null)
+            throw new IllegalStateException ("DbAdaptor cannot be null");
+        
+        _dbAdaptor = dbAdaptor;
+    }
+
+    /**
+     * @return
+     * @deprecated see DbAdaptor.getBlobType
+     */
+    public String getBlobType ()
+    {
+        return _dbAdaptor.getBlobType();
+    }
+
+    /**
+     * @return
+     * @deprecated see DbAdaptor.getLogType
+     */
+    public String getLongType()
+    {
+        return _dbAdaptor.getLongType();
+    }
+
+    /**
+     * @param longType
+     * @deprecated see DbAdaptor.setLongType
+     */
+    public void setLongType(String longType)
+    {
+       _dbAdaptor.setLongType(longType);
+    }
+    
+    public SessionIdTableSchema getSessionIdTableSchema()
+    {
+        return _sessionIdTableSchema;
+    }
+
+    public void setSessionIdTableSchema(SessionIdTableSchema sessionIdTableSchema)
+    {
+        if (sessionIdTableSchema == null)
+            throw new IllegalArgumentException("Null SessionIdTableSchema");
+        
+        _sessionIdTableSchema = sessionIdTableSchema;
+    }
+
+    public SessionTableSchema getSessionTableSchema()
+    {
+        return _sessionTableSchema;
+    }
+
+    public void setSessionTableSchema(SessionTableSchema sessionTableSchema)
+    {
+        _sessionTableSchema = sessionTableSchema;
+    }
+
+    public void setDeleteBlockSize (int bsize)
+    {
+        this._deleteBlockSize = bsize;
+    }
+
+    public int getDeleteBlockSize ()
+    {
+        return this._deleteBlockSize;
+    }
+    
+    public void setScavengeInterval (long sec)
+    {
+        if (sec<=0)
+            sec=60;
+
+        long old_period=_scavengeIntervalMs;
+        long period=sec*1000L;
+
+        _scavengeIntervalMs=period;
+
+        //add a bit of variability into the scavenge time so that not all
+        //nodes with the same scavenge time sync up
+        long tenPercent = _scavengeIntervalMs/10;
+        if ((System.currentTimeMillis()%2) == 0)
+            _scavengeIntervalMs += tenPercent;
+
+        if (LOG.isDebugEnabled())
+            LOG.debug("Scavenging every "+_scavengeIntervalMs+" ms");
+        
+        //if (_timer!=null && (period!=old_period || _task==null))
+        if (_scheduler != null && (period!=old_period || _task==null))
+        {
+            synchronized (this)
+            {
+                if (_task!=null)
+                    _task.cancel();
+                if (_scavenger == null)
+                    _scavenger = new Scavenger();
+                _task = _scheduler.schedule(_scavenger,_scavengeIntervalMs,TimeUnit.MILLISECONDS);
+            }
+        }
+    }
+
+    public long getScavengeInterval ()
+    {
+        return _scavengeIntervalMs/1000;
+    }
+
+
+    @Override
+    public void addSession(HttpSession session)
+    {
+        if (session == null)
+            return;
+
+        synchronized (_sessionIds)
+        {
+            String id = ((JDBCSessionManager.Session)session).getClusterId();
+            try
+            {
+                insert(id);
+                _sessionIds.add(id);
+            }
+            catch (Exception e)
+            {
+                LOG.warn("Problem storing session id="+id, e);
+            }
+        }
+    }
+    
+  
+    public void addSession(String id)
+    {
+        if (id == null)
+            return;
+
+        synchronized (_sessionIds)
+        {           
+            try
+            {
+                insert(id);
+                _sessionIds.add(id);
+            }
+            catch (Exception e)
+            {
+                LOG.warn("Problem storing session id="+id, e);
+            }
+        }
+    }
+
+
+
+    @Override
+    public void removeSession(HttpSession session)
+    {
+        if (session == null)
+            return;
+
+        removeSession(((JDBCSessionManager.Session)session).getClusterId());
+    }
+
+
+
+    public void removeSession (String id)
+    {
+
+        if (id == null)
+            return;
+
+        synchronized (_sessionIds)
+        {
+            if (LOG.isDebugEnabled())
+                LOG.debug("Removing sessionid="+id);
+            try
+            {
+                _sessionIds.remove(id);
+                delete(id);
+            }
+            catch (Exception e)
+            {
+                LOG.warn("Problem removing session id="+id, e);
+            }
+        }
+
+    }
+
+
+    @Override
+    public boolean idInUse(String id)
+    {
+        if (id == null)
+            return false;
+
+        String clusterId = getClusterId(id);
+        boolean inUse = false;
+        synchronized (_sessionIds)
+        {
+            inUse = _sessionIds.contains(clusterId);
+        }
+
+        
+        if (inUse)
+            return true; //optimisation - if this session is one we've been managing, we can check locally
+
+        //otherwise, we need to go to the database to check
+        try
+        {
+            return exists(clusterId);
+        }
+        catch (Exception e)
+        {
+            LOG.warn("Problem checking inUse for id="+clusterId, e);
+            return false;
+        }
+    }
+
+    /**
+     * Invalidate the session matching the id on all contexts.
+     *
+     * @see org.eclipse.jetty.server.SessionIdManager#invalidateAll(java.lang.String)
+     */
+    @Override
+    public void invalidateAll(String id)
+    {
+        //take the id out of the list of known sessionids for this node
+        removeSession(id);
+
+        synchronized (_sessionIds)
+        {
+            //tell all contexts that may have a session object with this id to
+            //get rid of them
+            Handler[] contexts = _server.getChildHandlersByClass(ContextHandler.class);
+            for (int i=0; contexts!=null && i<contexts.length; i++)
+            {
+                SessionHandler sessionHandler = ((ContextHandler)contexts[i]).getChildHandlerByClass(SessionHandler.class);
+                if (sessionHandler != null)
+                {
+                    SessionManager manager = sessionHandler.getSessionManager();
+
+                    if (manager != null && manager instanceof JDBCSessionManager)
+                    {
+                        ((JDBCSessionManager)manager).invalidateSession(id);
+                    }
+                }
+            }
+        }
+    }
+
+
+    @Override
+    public void renewSessionId (String oldClusterId, String oldNodeId, HttpServletRequest request)
+    {
+        //generate a new id
+        String newClusterId = newSessionId(request.hashCode());
+
+        synchronized (_sessionIds)
+        {
+            removeSession(oldClusterId);//remove the old one from the list (and database)
+            addSession(newClusterId); //add in the new session id to the list (and database)
+
+            //tell all contexts to update the id 
+            Handler[] contexts = _server.getChildHandlersByClass(ContextHandler.class);
+            for (int i=0; contexts!=null && i<contexts.length; i++)
+            {
+                SessionHandler sessionHandler = ((ContextHandler)contexts[i]).getChildHandlerByClass(SessionHandler.class);
+                if (sessionHandler != null) 
+                {
+                    SessionManager manager = sessionHandler.getSessionManager();
+
+                    if (manager != null && manager instanceof JDBCSessionManager)
+                    {
+                        ((JDBCSessionManager)manager).renewSessionId(oldClusterId, oldNodeId, newClusterId, getNodeId(newClusterId, request));
+                    }
+                }
+            }
+        }
+    }
+
+
+    /**
+     * Start up the id manager.
+     *
+     * Makes necessary database tables and starts a Session
+     * scavenger thread.
+     */
+    @Override
+    public void doStart()
+    throws Exception
+    {           
+        initializeDatabase();
+        prepareTables();   
+        super.doStart();
+        if (LOG.isDebugEnabled()) 
+            LOG.debug("Scavenging interval = "+getScavengeInterval()+" sec");
+        
+         //try and use a common scheduler, fallback to own
+         _scheduler =_server.getBean(Scheduler.class);
+         if (_scheduler == null)
+         {
+             _scheduler = new ScheduledExecutorScheduler();
+             _ownScheduler = true;
+             _scheduler.start();
+         }
+  
+        setScavengeInterval(getScavengeInterval());
+    }
+
+    /**
+     * Stop the scavenger.
+     */
+    @Override
+    public void doStop ()
+    throws Exception
+    {
+        synchronized(this)
+        {
+            if (_task!=null)
+                _task.cancel();
+            _task=null;
+            if (_ownScheduler && _scheduler !=null)
+                _scheduler.stop();
+            _scheduler=null;
+        }
+        _sessionIds.clear();
+        super.doStop();
+    }
+
+    /**
+     * Get a connection from the driver or datasource.
+     *
+     * @return the connection for the datasource
+     * @throws SQLException
+     */
+    protected Connection getConnection ()
+    throws SQLException
+    {
+        if (_datasource != null)
+            return _datasource.getConnection();
+        else
+            return DriverManager.getConnection(_connectionUrl);
+    }
+    
+
+
+
+
+
+    /**
+     * Set up the tables in the database
+     * @throws SQLException
+     */
+    /**
+     * @throws SQLException
+     */
+    private void prepareTables()
+    throws SQLException
+    {
+        if (_sessionIdTableSchema == null)
+            throw new IllegalStateException ("No SessionIdTableSchema");
+        
+        if (_sessionTableSchema == null)
+            throw new IllegalStateException ("No SessionTableSchema");
+        
+        try (Connection connection = getConnection();
+             Statement statement = connection.createStatement())
+        {
+            //make the id table
+            connection.setAutoCommit(true);
+            DatabaseMetaData metaData = connection.getMetaData();
+            _dbAdaptor.adaptTo(metaData);
+            _sessionTableSchema.setDatabaseAdaptor(_dbAdaptor);
+            _sessionIdTableSchema.setDatabaseAdaptor(_dbAdaptor);
+            
+            _createSessionIdTable = _sessionIdTableSchema.getCreateStatementAsString();
+            _insertId = _sessionIdTableSchema.getInsertStatementAsString();
+            _deleteId =  _sessionIdTableSchema.getDeleteStatementAsString();
+            _queryId = _sessionIdTableSchema.getSelectStatementAsString();
+            
+            //checking for table existence is case-sensitive, but table creation is not
+            String tableName = _dbAdaptor.convertIdentifier(_sessionIdTableSchema.getTableName());
+            try (ResultSet result = metaData.getTables(null, null, tableName, null))
+            {
+                if (!result.next())
+                {
+                    //table does not exist, so create it
+                    statement.executeUpdate(_createSessionIdTable);
+                }
+            }         
+            
+            //make the session table if necessary
+            tableName = _dbAdaptor.convertIdentifier(_sessionTableSchema.getTableName());
+            try (ResultSet result = metaData.getTables(null, null, tableName, null))
+            {
+                if (!result.next())
+                {
+                    //table does not exist, so create it
+                    _createSessionTable = _sessionTableSchema.getCreateStatementAsString();
+                    statement.executeUpdate(_createSessionTable);
+                }
+                else
+                {
+                    //session table exists, check it has maxinterval column
+                    ResultSet colResult = null;
+                    try
+                    {
+                        colResult = metaData.getColumns(null, null,
+                                                        _dbAdaptor.convertIdentifier(_sessionTableSchema.getTableName()), 
+                                                        _dbAdaptor.convertIdentifier(_sessionTableSchema.getMaxIntervalColumn()));
+                    }
+                    catch (SQLException s)
+                    {
+                        LOG.warn("Problem checking if "+_sessionTableSchema.getTableName()+
+                                 " table contains "+_sessionTableSchema.getMaxIntervalColumn()+" column. Ensure table contains column definition: \""
+                                +_sessionTableSchema.getMaxIntervalColumn()+" long not null default -999\"");
+                        throw s;
+                    }
+                    try
+                    {
+                        if (!colResult.next())
+                        {
+                            try
+                            {
+                                //add the maxinterval column
+                                statement.executeUpdate(_sessionTableSchema.getAlterTableForMaxIntervalAsString());
+                            }
+                            catch (SQLException s)
+                            {
+                                LOG.warn("Problem adding "+_sessionTableSchema.getMaxIntervalColumn()+
+                                         " column. Ensure table contains column definition: \""+_sessionTableSchema.getMaxIntervalColumn()+
+                                         " long not null default -999\"");
+                                throw s;
+                            }
+                        }
+                    }
+                    finally
+                    {
+                        colResult.close();
+                    }
+                }
+            }
+            //make some indexes on the JettySessions table
+            String index1 = "idx_"+_sessionTableSchema.getTableName()+"_expiry";
+            String index2 = "idx_"+_sessionTableSchema.getTableName()+"_session";
+
+            boolean index1Exists = false;
+            boolean index2Exists = false;
+            try (ResultSet result = metaData.getIndexInfo(null, null, tableName, false, false))
+            {
+                while (result.next())
+                {
+                    String idxName = result.getString("INDEX_NAME");
+                    if (index1.equalsIgnoreCase(idxName))
+                        index1Exists = true;
+                    else if (index2.equalsIgnoreCase(idxName))
+                        index2Exists = true;
+                }
+            }
+            if (!index1Exists)
+                statement.executeUpdate(_sessionTableSchema.getCreateIndexOverExpiryStatementAsString(index1));
+            if (!index2Exists)
+                statement.executeUpdate(_sessionTableSchema.getCreateIndexOverSessionStatementAsString(index2));
+
+            //set up some strings representing the statements for session manipulation
+            _insertSession = _sessionTableSchema.getInsertSessionStatementAsString();
+            _deleteSession = _sessionTableSchema.getDeleteSessionStatementAsString();
+            _updateSession = _sessionTableSchema.getUpdateSessionStatementAsString();
+            _updateSessionNode = _sessionTableSchema.getUpdateSessionNodeStatementAsString();
+            _updateSessionAccessTime = _sessionTableSchema.getUpdateSessionAccessTimeStatementAsString();
+            _selectBoundedExpiredSessions = _sessionTableSchema.getBoundedExpiredSessionsStatementAsString();
+            _selectExpiredSessions = _sessionTableSchema.getSelectExpiredSessionsStatementAsString();
+        }
+    }
+
+    /**
+     * Insert a new used session id into the table.
+     *
+     * @param id
+     * @throws SQLException
+     */
+    private void insert (String id)
+    throws SQLException
+    {
+        try (Connection connection = getConnection();
+                PreparedStatement query = connection.prepareStatement(_queryId))
+        {
+            connection.setAutoCommit(true);
+            query.setString(1, id);
+            try (ResultSet result = query.executeQuery())
+            {
+                //only insert the id if it isn't in the db already
+                if (!result.next())
+                {
+                    try (PreparedStatement statement = connection.prepareStatement(_insertId))
+                    {
+                        statement.setString(1, id);
+                        statement.executeUpdate();
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Remove a session id from the table.
+     *
+     * @param id
+     * @throws SQLException
+     */
+    private void delete (String id)
+    throws SQLException
+    {
+        try (Connection connection = getConnection();
+                PreparedStatement statement = connection.prepareStatement(_deleteId))
+        {
+            connection.setAutoCommit(true);
+            statement.setString(1, id);
+            statement.executeUpdate();
+        }
+    }
+
+
+    /**
+     * Check if a session id exists.
+     *
+     * @param id
+     * @return
+     * @throws SQLException
+     */
+    private boolean exists (String id)
+    throws SQLException
+    {
+        try (Connection connection = getConnection();
+                PreparedStatement statement = connection.prepareStatement(_queryId))
+        {
+            connection.setAutoCommit(true);
+            statement.setString(1, id);
+            try (ResultSet result = statement.executeQuery())
+            {
+                return result.next();
+            }
+        }
+    }
+
+    /**
+     * Look for sessions in the database that have expired.
+     *
+     * We do this in the SessionIdManager and not the SessionManager so
+     * that we only have 1 scavenger, otherwise if there are n SessionManagers
+     * there would be n scavengers, all contending for the database.
+     *
+     * We look first for sessions that expired in the previous interval, then
+     * for sessions that expired previously - these are old sessions that no
+     * node is managing any more and have become stuck in the database.
+     */
+    private void scavenge ()
+    {
+        Connection connection = null;
+        try
+        {
+            if (LOG.isDebugEnabled())
+                LOG.debug(getWorkerName()+"- Scavenge sweep started at "+System.currentTimeMillis());
+            if (_lastScavengeTime > 0)
+            {
+                connection = getConnection();
+                connection.setAutoCommit(true);
+                Set<String> expiredSessionIds = new HashSet<String>();
+                
+                
+                //Pass 1: find sessions for which we were last managing node that have just expired since last pass
+                long lowerBound = (_lastScavengeTime - _scavengeIntervalMs);
+                long upperBound = _lastScavengeTime;
+                if (LOG.isDebugEnabled())
+                    LOG.debug (getWorkerName()+"- Pass 1: Searching for sessions expired between "+lowerBound + " and "+upperBound);
+
+                try (PreparedStatement statement = connection.prepareStatement(_selectBoundedExpiredSessions))
+                {
+                    statement.setString(1, getWorkerName());
+                    statement.setLong(2, lowerBound);
+                    statement.setLong(3, upperBound);
+                    try (ResultSet result = statement.executeQuery())
+                    {
+                        while (result.next())
+                        {
+                            String sessionId = result.getString(_sessionTableSchema.getIdColumn());
+                            expiredSessionIds.add(sessionId);
+                            if (LOG.isDebugEnabled()) LOG.debug ("Found expired sessionId="+sessionId);
+                        }
+                    }
+                }
+                scavengeSessions(expiredSessionIds, false);
+
+
+                //Pass 2: find sessions that have expired a while ago for which this node was their last manager
+                try (PreparedStatement selectExpiredSessions = connection.prepareStatement(_selectExpiredSessions))
+                {
+                    expiredSessionIds.clear();
+                    upperBound = _lastScavengeTime - (2 * _scavengeIntervalMs);
+                    if (upperBound > 0)
+                    {
+                        if (LOG.isDebugEnabled()) LOG.debug(getWorkerName()+"- Pass 2: Searching for sessions expired before "+upperBound);
+                        selectExpiredSessions.setLong(1, upperBound);
+                        try (ResultSet result = selectExpiredSessions.executeQuery())
+                        {
+                            while (result.next())
+                            {
+                                String sessionId = result.getString(_sessionTableSchema.getIdColumn());
+                                String lastNode = result.getString(_sessionTableSchema.getLastNodeColumn());
+                                if ((getWorkerName() == null && lastNode == null) || (getWorkerName() != null && getWorkerName().equals(lastNode)))
+                                    expiredSessionIds.add(sessionId);
+                                if (LOG.isDebugEnabled()) LOG.debug ("Found expired sessionId="+sessionId+" last managed by "+getWorkerName());
+                            }
+                        }
+                        scavengeSessions(expiredSessionIds, false);
+                    }
+
+
+                    //Pass 3:
+                    //find all sessions that have expired at least a couple of scanIntervals ago
+                    //if we did not succeed in loading them (eg their related context no longer exists, can't be loaded etc) then
+                    //they are simply deleted
+                    upperBound = _lastScavengeTime - (3 * _scavengeIntervalMs);
+                    expiredSessionIds.clear();
+                    if (upperBound > 0)
+                    {
+                        if (LOG.isDebugEnabled()) LOG.debug(getWorkerName()+"- Pass 3: searching for sessions expired before "+upperBound);
+                        selectExpiredSessions.setLong(1, upperBound);
+                        try (ResultSet result = selectExpiredSessions.executeQuery())
+                        {
+                            while (result.next())
+                            {
+                                String sessionId = result.getString(_sessionTableSchema.getIdColumn());
+                                expiredSessionIds.add(sessionId);
+                                if (LOG.isDebugEnabled()) LOG.debug ("Found expired sessionId="+sessionId);
+                            }
+                        }
+                        scavengeSessions(expiredSessionIds, true);
+                    }
+                }
+            }
+        }
+        catch (Exception e)
+        {
+            if (isRunning())
+                LOG.warn("Problem selecting expired sessions", e);
+            else
+                LOG.ignore(e);
+        }
+        finally
+        {
+            _lastScavengeTime=System.currentTimeMillis();
+            if (LOG.isDebugEnabled()) LOG.debug(getWorkerName()+"- Scavenge sweep ended at "+_lastScavengeTime);
+            if (connection != null)
+            {
+                try
+                {
+                    connection.close();
+                }
+                catch (SQLException e)
+                {
+                    LOG.warn(e);
+                }
+            }
+        }
+    }
+    
+    
+    /**
+     * @param expiredSessionIds
+     */
+    private void scavengeSessions (Set<String> expiredSessionIds, boolean forceDelete)
+    {       
+        Set<String> remainingIds = new HashSet<String>(expiredSessionIds);
+        Handler[] contexts = _server.getChildHandlersByClass(ContextHandler.class);
+        for (int i=0; contexts!=null && i<contexts.length; i++)
+        {
+            SessionHandler sessionHandler = ((ContextHandler)contexts[i]).getChildHandlerByClass(SessionHandler.class);
+            if (sessionHandler != null)
+            {
+                SessionManager manager = sessionHandler.getSessionManager();
+                if (manager != null && manager instanceof JDBCSessionManager)
+                {
+                    Set<String> successfullyExpiredIds = ((JDBCSessionManager)manager).expire(expiredSessionIds);
+                    if (successfullyExpiredIds != null)
+                        remainingIds.removeAll(successfullyExpiredIds);
+                }
+            }
+        }
+
+        //Any remaining ids are of those sessions that no context removed
+        if (!remainingIds.isEmpty() && forceDelete)
+        {
+            LOG.info("Forcibly deleting unrecoverable expired sessions {}", remainingIds);
+            try
+            {
+                //ensure they aren't in the local list of in-use session ids
+                synchronized (_sessionIds)
+                {
+                    _sessionIds.removeAll(remainingIds);
+                }
+                
+                cleanExpiredSessionIds(remainingIds);
+            }
+            catch (Exception e)
+            {
+                LOG.warn("Error removing expired session ids", e);
+            }
+        }
+    }
+
+
+   
+    
+    private void cleanExpiredSessionIds (Set<String> expiredIds)
+    throws Exception
+    {
+        if (expiredIds == null || expiredIds.isEmpty())
+            return;
+
+        String[] ids = expiredIds.toArray(new String[expiredIds.size()]);
+        try (Connection con = getConnection())
+        {
+            con.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED);
+            con.setAutoCommit(false);
+
+            int start = 0;
+            int end = 0;
+            int blocksize = _deleteBlockSize;
+            int block = 0;
+       
+            try (Statement statement = con.createStatement())
+            {
+                while (end < ids.length)
+                {
+                    start = block*blocksize;
+                    if ((ids.length -  start)  >= blocksize)
+                        end = start + blocksize;
+                    else
+                        end = ids.length;
+
+                    //take them out of the sessionIds table
+                    statement.executeUpdate(fillInClause("delete from "+_sessionIdTableSchema.getTableName()+" where "+_sessionIdTableSchema.getIdColumn()+" in ", ids, start, end));
+                    //take them out of the sessions table
+                    statement.executeUpdate(fillInClause("delete from "+_sessionTableSchema.getTableName()+" where "+_sessionTableSchema.getIdColumn()+" in ", ids, start, end));
+                    block++;
+                }
+            }
+            catch (Exception e)
+            {
+                con.rollback();
+                throw e;
+            }
+            con.commit();
+        }
+    }
+
+    
+    
+    /**
+     * 
+     * @param sql
+     * @param atoms
+     * @throws Exception
+     */
+    private String fillInClause (String sql, String[] literals, int start, int end)
+    throws Exception
+    {
+        StringBuffer buff = new StringBuffer();
+        buff.append(sql);
+        buff.append("(");
+        for (int i=start; i<end; i++)
+        {
+            buff.append("'"+(literals[i])+"'");
+            if (i+1<end)
+                buff.append(",");
+        }
+        buff.append(")");
+        return buff.toString();
+    }
+    
+    
+    
+    private void initializeDatabase ()
+    throws Exception
+    {
+        if (_datasource != null)
+            return; //already set up
+        
+        if (_jndiName!=null)
+        {
+            InitialContext ic = new InitialContext();
+            _datasource = (DataSource)ic.lookup(_jndiName);
+        }
+        else if ( _driver != null && _connectionUrl != null )
+        {
+            DriverManager.registerDriver(_driver);
+        }
+        else if (_driverClassName != null && _connectionUrl != null)
+        {
+            Class.forName(_driverClassName);
+        }
+        else
+            throw new IllegalStateException("No database configured for sessions");
+    }
+    
+   
+}
diff --git a/lib/jetty/org/eclipse/jetty/server/session/JDBCSessionManager.java b/lib/jetty/org/eclipse/jetty/server/session/JDBCSessionManager.java
new file mode 100644 (file)
index 0000000..dbe9533
--- /dev/null
@@ -0,0 +1,1201 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+
+package org.eclipse.jetty.server.session;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.InputStream;
+import java.io.ObjectOutputStream;
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicReference;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpSessionEvent;
+import javax.servlet.http.HttpSessionListener;
+
+import org.eclipse.jetty.server.SessionIdManager;
+import org.eclipse.jetty.server.handler.ContextHandler;
+import org.eclipse.jetty.server.session.JDBCSessionIdManager.SessionTableSchema;
+import org.eclipse.jetty.util.ClassLoadingObjectInputStream;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/**
+ * JDBCSessionManager
+ *
+ * SessionManager that persists sessions to a database to enable clustering.
+ *
+ * Session data is persisted to the JettySessions table:
+ *
+ * rowId (unique in cluster: webapp name/path + virtualhost + sessionId)
+ * contextPath (of the context owning the session)
+ * sessionId (unique in a context)
+ * lastNode (name of node last handled session)
+ * accessTime (time in milliseconds session was accessed)
+ * lastAccessTime (previous time in milliseconds session was accessed)
+ * createTime (time in milliseconds session created)
+ * cookieTime (time in milliseconds session cookie created)
+ * lastSavedTime (last time in milliseconds session access times were saved)
+ * expiryTime (time in milliseconds that the session is due to expire)
+ * map (attribute map)
+ *
+ * As an optimization, to prevent thrashing the database, we do not persist
+ * the accessTime and lastAccessTime every time the session is accessed. Rather,
+ * we write it out every so often. The frequency is controlled by the saveIntervalSec
+ * field.
+ */
+public class JDBCSessionManager extends AbstractSessionManager
+{
+    private static final Logger LOG = Log.getLogger(JDBCSessionManager.class);
+
+    private ConcurrentHashMap<String, Session> _sessions;
+    protected JDBCSessionIdManager _jdbcSessionIdMgr = null;
+    protected long _saveIntervalSec = 60; //only persist changes to session access times every 60 secs
+    protected SessionTableSchema _sessionTableSchema;
+
+   
+
+
+    /**
+     * Session
+     *
+     * Session instance.
+     */
+    public class Session extends MemSession
+    {
+        private static final long serialVersionUID = 5208464051134226143L;
+        
+        /**
+         * If dirty, session needs to be (re)persisted
+         */
+        protected boolean _dirty=false;
+        
+        
+        /**
+         * Time in msec since the epoch that a session cookie was set for this session
+         */
+        protected long _cookieSet;
+        
+        
+        /**
+         * Time in msec since the epoch that the session will expire
+         */
+        protected long _expiryTime;
+        
+        
+        /**
+         * Time in msec since the epoch that the session was last persisted
+         */
+        protected long _lastSaved;
+        
+        
+        /**
+         * Unique identifier of the last node to host the session
+         */
+        protected String _lastNode;
+        
+        
+        /**
+         * Virtual host for context (used to help distinguish 2 sessions with same id on different contexts)
+         */
+        protected String _virtualHost;
+        
+        
+        /**
+         * Unique row in db for session
+         */
+        protected String _rowId;
+        
+        
+        /**
+         * Mangled context name (used to help distinguish 2 sessions with same id on different contexts)
+         */
+        protected String _canonicalContext;
+        
+   
+        /**
+         * Session from a request.
+         *
+         * @param request
+         */
+        protected Session (HttpServletRequest request)
+        {
+            super(JDBCSessionManager.this,request);
+            int maxInterval=getMaxInactiveInterval();
+            _expiryTime = (maxInterval <= 0 ? 0 : (System.currentTimeMillis() + maxInterval*1000L));
+            _virtualHost = JDBCSessionManager.getVirtualHost(_context);
+            _canonicalContext = canonicalize(_context.getContextPath());
+            _lastNode = getSessionIdManager().getWorkerName();
+        }
+        
+        
+        /**
+         * Session restored from database
+         * @param sessionId
+         * @param rowId
+         * @param created
+         * @param accessed
+         */
+        protected Session (String sessionId, String rowId, long created, long accessed, long maxInterval)
+        {
+            super(JDBCSessionManager.this, created, accessed, sessionId);
+            _rowId = rowId;
+            super.setMaxInactiveInterval((int)maxInterval); //restore the session's previous inactivity interval setting
+            _expiryTime = (maxInterval <= 0 ? 0 : (System.currentTimeMillis() + maxInterval*1000L));
+        }
+        
+        
+        protected synchronized String getRowId()
+        {
+            return _rowId;
+        }
+        
+        protected synchronized void setRowId(String rowId)
+        {
+            _rowId = rowId;
+        }
+        
+        public synchronized void setVirtualHost (String vhost)
+        {
+            _virtualHost=vhost;
+        }
+
+        public synchronized String getVirtualHost ()
+        {
+            return _virtualHost;
+        }
+        
+        public synchronized long getLastSaved ()
+        {
+            return _lastSaved;
+        }
+
+        public synchronized void setLastSaved (long time)
+        {
+            _lastSaved=time;
+        }
+
+        public synchronized void setExpiryTime (long time)
+        {
+            _expiryTime=time;
+        }
+
+        public synchronized long getExpiryTime ()
+        {
+            return _expiryTime;
+        }
+        
+
+        public synchronized void setCanonicalContext(String str)
+        {
+            _canonicalContext=str;
+        }
+
+        public synchronized String getCanonicalContext ()
+        {
+            return _canonicalContext;
+        }
+        
+        public void setCookieSet (long ms)
+        {
+            _cookieSet = ms;
+        }
+
+        public synchronized long getCookieSet ()
+        {
+            return _cookieSet;
+        }
+
+        public synchronized void setLastNode (String node)
+        {
+            _lastNode=node;
+        }
+
+        public synchronized String getLastNode ()
+        {
+            return _lastNode;
+        }
+
+        @Override
+        public void setAttribute (String name, Object value)
+        {
+            Object old = changeAttribute(name, value);
+            if (value == null && old == null)
+                return; //if same as remove attribute but attribute was already removed, no change
+            
+            _dirty = true;
+        }
+
+        @Override
+        public void removeAttribute (String name)
+        {
+            Object old = changeAttribute(name, null);
+            if (old != null) //only dirty if there was a previous value
+                _dirty=true;
+        }
+
+        @Override
+        protected void cookieSet()
+        {
+            _cookieSet = getAccessed();
+        }
+
+        /**
+         * Entry to session.
+         * Called by SessionHandler on inbound request and the session already exists in this node's memory.
+         *
+         * @see org.eclipse.jetty.server.session.AbstractSession#access(long)
+         */
+        @Override
+        protected boolean access(long time)
+        {
+            synchronized (this)
+            {
+                if (super.access(time))
+                {
+                    int maxInterval=getMaxInactiveInterval();
+                    _expiryTime = (maxInterval <= 0 ? 0 : (time + maxInterval*1000L));
+                    return true;
+                }
+                return false;
+            }
+        }
+        
+        
+        
+
+
+        /** 
+         * Change the max idle time for this session. This recalculates the expiry time.
+         * @see org.eclipse.jetty.server.session.AbstractSession#setMaxInactiveInterval(int)
+         */
+        @Override
+        public void setMaxInactiveInterval(int secs)
+        {
+            synchronized (this)
+            {
+                super.setMaxInactiveInterval(secs);
+                int maxInterval=getMaxInactiveInterval();
+                _expiryTime = (maxInterval <= 0 ? 0 : (System.currentTimeMillis() + maxInterval*1000L));
+                //force the session to be written out right now
+                try
+                {
+                    updateSessionAccessTime(this);
+                }
+                catch (Exception e)
+                {
+                    LOG.warn("Problem saving changed max idle time for session "+ this, e);
+                }
+            }
+        }
+
+
+        /**
+         * Exit from session
+         * @see org.eclipse.jetty.server.session.AbstractSession#complete()
+         */
+        @Override
+        protected void complete()
+        {
+            synchronized (this)
+            {
+                super.complete();
+                try
+                {
+                    if (isValid())
+                    {
+                        if (_dirty)
+                        {
+                            //The session attributes have changed, write to the db, ensuring
+                            //http passivation/activation listeners called
+                            save(true);
+                        }
+                        else if ((getAccessed() - _lastSaved) >= (getSaveInterval() * 1000L))
+                        {
+                            updateSessionAccessTime(this);
+                        }
+                    }
+                }
+                catch (Exception e)
+                {
+                    LOG.warn("Problem persisting changed session data id="+getId(), e);
+                }
+                finally
+                {
+                    _dirty=false;
+                }
+            }
+        }
+
+        protected void save() throws Exception
+        {
+            synchronized (this)
+            {
+                try
+                {
+                    updateSession(this);
+                }
+                finally
+                {
+                    _dirty = false;
+                }
+            }
+        }
+
+        protected void save (boolean reactivate) throws Exception
+        {
+            synchronized (this)
+            {
+                if (_dirty)
+                {
+                    //The session attributes have changed, write to the db, ensuring
+                    //http passivation/activation listeners called
+                    willPassivate();                      
+                    updateSession(this);
+                    if (reactivate)
+                        didActivate();  
+                }
+            }
+        }
+
+        
+        @Override
+        protected void timeout() throws IllegalStateException
+        {
+            if (LOG.isDebugEnabled())
+                LOG.debug("Timing out session id="+getClusterId());
+            super.timeout();
+        }
+        
+        
+        @Override
+        public String toString ()
+        {
+            return "Session rowId="+_rowId+",id="+getId()+",lastNode="+_lastNode+
+                            ",created="+getCreationTime()+",accessed="+getAccessed()+
+                            ",lastAccessed="+getLastAccessedTime()+",cookieSet="+_cookieSet+
+                            ",maxInterval="+getMaxInactiveInterval()+",lastSaved="+_lastSaved+",expiry="+_expiryTime;
+        }
+    }
+
+
+
+
+    /**
+     * Set the time in seconds which is the interval between
+     * saving the session access time to the database.
+     *
+     * This is an optimization that prevents the database from
+     * being overloaded when a session is accessed very frequently.
+     *
+     * On session exit, if the session attributes have NOT changed,
+     * the time at which we last saved the accessed
+     * time is compared to the current accessed time. If the interval
+     * is at least saveIntervalSecs, then the access time will be
+     * persisted to the database.
+     *
+     * If any session attribute does change, then the attributes and
+     * the accessed time are persisted.
+     *
+     * @param sec
+     */
+    public void setSaveInterval (long sec)
+    {
+        _saveIntervalSec=sec;
+    }
+
+    public long getSaveInterval ()
+    {
+        return _saveIntervalSec;
+    }
+
+
+
+    /**
+     * A method that can be implemented in subclasses to support
+     * distributed caching of sessions. This method will be
+     * called whenever the session is written to the database
+     * because the session data has changed.
+     *
+     * This could be used eg with a JMS backplane to notify nodes
+     * that the session has changed and to delete the session from
+     * the node's cache, and re-read it from the database.
+     * @param session
+     */
+    public void cacheInvalidate (Session session)
+    {
+
+    }
+
+
+    /**
+     * A session has been requested by its id on this node.
+     *
+     * Load the session by id AND context path from the database.
+     * Multiple contexts may share the same session id (due to dispatching)
+     * but they CANNOT share the same contents.
+     *
+     * Check if last node id is my node id, if so, then the session we have
+     * in memory cannot be stale. If another node used the session last, then
+     * we need to refresh from the db.
+     *
+     * NOTE: this method will go to the database, so if you only want to check
+     * for the existence of a Session in memory, use _sessions.get(id) instead.
+     *
+     * @see org.eclipse.jetty.server.session.AbstractSessionManager#getSession(java.lang.String)
+     */
+    @Override
+    public Session getSession(String idInCluster)
+    {
+        Session session = null;
+        
+        synchronized (this)
+        {
+            Session memSession = (Session)_sessions.get(idInCluster);
+            
+            //check if we need to reload the session -
+            //as an optimization, don't reload on every access
+            //to reduce the load on the database. This introduces a window of
+            //possibility that the node may decide that the session is local to it,
+            //when the session has actually been live on another node, and then
+            //re-migrated to this node. This should be an extremely rare occurrence,
+            //as load-balancers are generally well-behaved and consistently send
+            //sessions to the same node, changing only iff that node fails.
+            //Session data = null;
+            long now = System.currentTimeMillis();
+            if (LOG.isDebugEnabled())
+            {
+                if (memSession==null)
+                    LOG.debug("getSession("+idInCluster+"): not in session map,"+
+                            " now="+now+
+                            " lastSaved="+(memSession==null?0:memSession._lastSaved)+
+                            " interval="+(_saveIntervalSec * 1000L));
+                else
+                    LOG.debug("getSession("+idInCluster+"): in session map, "+
+                            " hashcode="+memSession.hashCode()+
+                            " now="+now+
+                            " lastSaved="+(memSession==null?0:memSession._lastSaved)+
+                            " interval="+(_saveIntervalSec * 1000L)+
+                            " lastNode="+memSession._lastNode+
+                            " thisNode="+getSessionIdManager().getWorkerName()+
+                            " difference="+(now - memSession._lastSaved));
+            }
+
+            try
+            {
+                if (memSession==null)
+                {
+                    LOG.debug("getSession("+idInCluster+"): no session in session map. Reloading session data from db.");
+                    session = loadSession(idInCluster, canonicalize(_context.getContextPath()), getVirtualHost(_context));
+                }
+                else if ((now - memSession._lastSaved) >= (_saveIntervalSec * 1000L))
+                {
+                    LOG.debug("getSession("+idInCluster+"): stale session. Reloading session data from db.");
+                    session = loadSession(idInCluster, canonicalize(_context.getContextPath()), getVirtualHost(_context));
+                }
+                else
+                {
+                    LOG.debug("getSession("+idInCluster+"): session in session map");
+                    session = memSession;
+                }
+            }
+            catch (Exception e)
+            {
+                LOG.warn("Unable to load session "+idInCluster, e);
+                return null;
+            }
+
+
+            //If we have a session
+            if (session != null)
+            {
+                //If the session was last used on a different node, or session doesn't exist on this node
+                if (!session.getLastNode().equals(getSessionIdManager().getWorkerName()) || memSession==null)
+                {
+                    //if session doesn't expire, or has not already expired, update it and put it in this nodes' memory
+                    if (session._expiryTime <= 0 || session._expiryTime > now)
+                    {
+                        if (LOG.isDebugEnabled()) 
+                            LOG.debug("getSession("+idInCluster+"): lastNode="+session.getLastNode()+" thisNode="+getSessionIdManager().getWorkerName());
+
+                        session.setLastNode(getSessionIdManager().getWorkerName());                            
+                        _sessions.put(idInCluster, session);
+
+                        //update in db
+                        try
+                        {
+                            updateSessionNode(session);
+                            session.didActivate();
+                        }
+                        catch (Exception e)
+                        {
+                            LOG.warn("Unable to update freshly loaded session "+idInCluster, e);
+                            return null;
+                        }
+                    }
+                    else
+                    {
+                        LOG.debug("getSession ({}): Session has expired", idInCluster);  
+                        //ensure that the session id for the expired session is deleted so that a new session with the 
+                        //same id cannot be created (because the idInUse() test would succeed)
+                        _jdbcSessionIdMgr.removeSession(idInCluster);
+                        session=null;
+                    }
+
+                }
+                else
+                {
+                    //the session loaded from the db and the one in memory are the same, so keep using the one in memory
+                    session = memSession;
+                    LOG.debug("getSession({}): Session not stale {}", idInCluster,session);
+                }
+            }
+            else
+            {
+                //No session in db with matching id and context path.
+                LOG.debug("getSession({}): No session in database matching id={}",idInCluster,idInCluster);
+            }
+
+            return session;
+        }
+    }
+    
+
+    /**
+     * Get the number of sessions.
+     *
+     * @see org.eclipse.jetty.server.session.AbstractSessionManager#getSessions()
+     */
+    @Override
+    public int getSessions()
+    {
+        return _sessions.size();
+    }
+
+
+    /**
+     * Start the session manager.
+     *
+     * @see org.eclipse.jetty.server.session.AbstractSessionManager#doStart()
+     */
+    @Override
+    public void doStart() throws Exception
+    {
+        if (_sessionIdManager==null)
+            throw new IllegalStateException("No session id manager defined");
+
+        _jdbcSessionIdMgr = (JDBCSessionIdManager)_sessionIdManager;
+        _sessionTableSchema = _jdbcSessionIdMgr.getSessionTableSchema();
+
+        _sessions = new ConcurrentHashMap<String, Session>();
+
+        super.doStart();
+    }
+
+
+    /**
+     * Stop the session manager.
+     *
+     * @see org.eclipse.jetty.server.session.AbstractSessionManager#doStop()
+     */
+    @Override
+    public void doStop() throws Exception
+    {
+        super.doStop();
+        _sessions.clear();
+        _sessions = null;
+    }
+
+    @Override
+    protected void shutdownSessions()
+    {
+        //Save the current state of all of our sessions,
+        //do NOT delete them (so other nodes can manage them)
+        long gracefulStopMs = getContextHandler().getServer().getStopTimeout();
+        long stopTime = 0;
+        if (gracefulStopMs > 0)
+            stopTime = System.nanoTime() + (TimeUnit.NANOSECONDS.convert(gracefulStopMs, TimeUnit.MILLISECONDS));        
+
+        ArrayList<Session> sessions = (_sessions == null? new ArrayList<Session>() :new ArrayList<Session>(_sessions.values()) );
+
+        // loop while there are sessions, and while there is stop time remaining, or if no stop time, just 1 loop
+        while (sessions.size() > 0 && ((stopTime > 0 && (System.nanoTime() < stopTime)) || (stopTime == 0)))
+        {
+            for (Session session : sessions)
+            {
+                try
+                {
+                    session.save(false);
+                }
+                catch (Exception e)
+                {
+                    LOG.warn(e);
+                }
+                _sessions.remove(session.getClusterId());
+            }
+
+            //check if we should terminate our loop if we're not using the stop timer
+            if (stopTime == 0)
+                break;
+            
+            // Get any sessions that were added by other requests during processing and go around the loop again
+            sessions=new ArrayList<Session>(_sessions.values());
+        }
+    }
+
+    
+    /**
+     * 
+     * @see org.eclipse.jetty.server.SessionManager#renewSessionId(java.lang.String, java.lang.String, java.lang.String, java.lang.String)
+     */
+    public void renewSessionId (String oldClusterId, String oldNodeId, String newClusterId, String newNodeId)
+    {
+        Session session = null;
+        try
+        {
+            session = (Session)_sessions.remove(oldClusterId);
+            if (session != null)
+            {
+                synchronized (session)
+                {
+                    session.setClusterId(newClusterId); //update ids
+                    session.setNodeId(newNodeId);
+                    _sessions.put(newClusterId, session); //put it into list in memory
+                    updateSession(session); //update database
+                }
+            }
+        }
+        catch (Exception e)
+        {
+            LOG.warn(e);
+        }
+
+        super.renewSessionId(oldClusterId, oldNodeId, newClusterId, newNodeId);
+    }
+
+    
+
+    /**
+     * Invalidate a session.
+     *
+     * @param idInCluster
+     */
+    protected void invalidateSession (String idInCluster)
+    {
+        Session session = (Session)_sessions.get(idInCluster);
+
+        if (session != null)
+        {
+            session.invalidate();
+        }
+    }
+
+    /**
+     * Delete an existing session, both from the in-memory map and
+     * the database.
+     *
+     * @see org.eclipse.jetty.server.session.AbstractSessionManager#removeSession(java.lang.String)
+     */
+    @Override
+    protected boolean removeSession(String idInCluster)
+    {
+        Session session = (Session)_sessions.remove(idInCluster);
+        try
+        {
+            if (session != null)
+                deleteSession(session);
+        }
+        catch (Exception e)
+        {
+            LOG.warn("Problem deleting session id="+idInCluster, e);
+        }
+        return session!=null;
+    }
+
+
+    /**
+     * Add a newly created session to our in-memory list for this node and persist it.
+     *
+     * @see org.eclipse.jetty.server.session.AbstractSessionManager#addSession(org.eclipse.jetty.server.session.AbstractSession)
+     */
+    @Override
+    protected void addSession(AbstractSession session)
+    {
+        if (session==null)
+            return;
+
+        _sessions.put(session.getClusterId(), (Session)session);
+
+        try
+        {
+            synchronized (session)
+            {
+                session.willPassivate();
+                storeSession(((JDBCSessionManager.Session)session));
+                session.didActivate();
+            }
+        }
+        catch (Exception e)
+        {
+            LOG.warn("Unable to store new session id="+session.getId() , e);
+        }
+    }
+
+
+    /**
+     * Make a new Session.
+     *
+     * @see org.eclipse.jetty.server.session.AbstractSessionManager#newSession(javax.servlet.http.HttpServletRequest)
+     */
+    @Override
+    protected AbstractSession newSession(HttpServletRequest request)
+    {
+        return new Session(request);
+    }
+    
+    
+    /**
+     * @param sessionId
+     * @param rowId
+     * @param created
+     * @param accessed
+     * @param maxInterval
+     * @return
+     */
+    protected AbstractSession newSession (String sessionId, String rowId, long created, long accessed, long maxInterval)
+    {
+        return new Session(sessionId, rowId, created, accessed, maxInterval);
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Remove session from manager
+     * @param session The session to remove
+     * @param invalidate True if {@link HttpSessionListener#sessionDestroyed(HttpSessionEvent)} and
+     * {@link SessionIdManager#invalidateAll(String)} should be called.
+     */
+    @Override
+    public boolean removeSession(AbstractSession session, boolean invalidate)
+    {
+        // Remove session from context and global maps
+        boolean removed = super.removeSession(session, invalidate);
+
+        if (removed)
+        {
+            if (!invalidate)
+            {
+                session.willPassivate();
+            }
+        }
+        
+        return removed;
+    }
+
+
+    /**
+     * Expire any Sessions we have in memory matching the list of
+     * expired Session ids.
+     *
+     * @param sessionIds
+     */
+    protected Set<String> expire (Set<String> sessionIds)
+    {
+        //don't attempt to scavenge if we are shutting down
+        if (isStopping() || isStopped())
+            return null;
+
+        
+        Thread thread=Thread.currentThread();
+        ClassLoader old_loader=thread.getContextClassLoader();
+        
+        Set<String> successfullyExpiredIds = new HashSet<String>();
+        try
+        {
+            Iterator<?> itor = sessionIds.iterator();
+            while (itor.hasNext())
+            {
+                String sessionId = (String)itor.next();
+                if (LOG.isDebugEnabled())
+                    LOG.debug("Expiring session id "+sessionId);
+
+                Session session = (Session)_sessions.get(sessionId);
+
+                //if session is not in our memory, then fetch from db so we can call the usual listeners on it
+                if (session == null)
+                {
+                    if (LOG.isDebugEnabled())LOG.debug("Force loading session id "+sessionId);
+                    session = loadSession(sessionId, canonicalize(_context.getContextPath()), getVirtualHost(_context));
+                    if (session != null)
+                    {
+                        //loaded an expired session last managed on this node for this context, add it to the list so we can 
+                        //treat it like a normal expired session
+                        _sessions.put(session.getClusterId(), session);
+                    }
+                    else
+                    {
+                        if (LOG.isDebugEnabled())
+                            LOG.debug("Unrecognized session id="+sessionId);
+                        continue;
+                    }
+                }
+
+                if (session != null)
+                {
+                    session.timeout();
+                    successfullyExpiredIds.add(session.getClusterId());
+                }
+            }
+            return successfullyExpiredIds;
+        }
+        catch (Throwable t)
+        {
+            LOG.warn("Problem expiring sessions", t);
+            return successfullyExpiredIds;
+        }
+        finally
+        {
+            thread.setContextClassLoader(old_loader);
+        }
+    }
+    
+  
+    /**
+     * Load a session from the database
+     * @param id
+     * @return the session data that was loaded
+     * @throws Exception
+     */
+    protected Session loadSession (final String id, final String canonicalContextPath, final String vhost)
+    throws Exception
+    {
+        final AtomicReference<Session> _reference = new AtomicReference<Session>();
+        final AtomicReference<Exception> _exception = new AtomicReference<Exception>();
+        Runnable load = new Runnable()
+        {
+            /** 
+             * @see java.lang.Runnable#run()
+             */
+            @SuppressWarnings("unchecked")
+            public void run()
+            {
+                try (Connection connection = getConnection();
+                        PreparedStatement statement = _sessionTableSchema.getLoadStatement(connection, id, canonicalContextPath, vhost);
+                        ResultSet result = statement.executeQuery())
+                {
+                    Session session = null;
+                    if (result.next())
+                    {                    
+                        long maxInterval = result.getLong(_sessionTableSchema.getMaxIntervalColumn());
+                        if (maxInterval == JDBCSessionIdManager.MAX_INTERVAL_NOT_SET)
+                        {
+                            maxInterval = getMaxInactiveInterval(); //if value not saved for maxInactiveInterval, use current value from sessionmanager
+                        }
+                        session = (Session)newSession(id, result.getString(_sessionTableSchema.getRowIdColumn()), 
+                                                  result.getLong(_sessionTableSchema.getCreateTimeColumn()), 
+                                                  result.getLong(_sessionTableSchema.getAccessTimeColumn()), 
+                                                  maxInterval);
+                        session.setCookieSet(result.getLong(_sessionTableSchema.getCookieTimeColumn()));
+                        session.setLastAccessedTime(result.getLong(_sessionTableSchema.getLastAccessTimeColumn()));
+                        session.setLastNode(result.getString(_sessionTableSchema.getLastNodeColumn()));
+                        session.setLastSaved(result.getLong(_sessionTableSchema.getLastSavedTimeColumn()));
+                        session.setExpiryTime(result.getLong(_sessionTableSchema.getExpiryTimeColumn()));
+                        session.setCanonicalContext(result.getString(_sessionTableSchema.getContextPathColumn()));
+                        session.setVirtualHost(result.getString(_sessionTableSchema.getVirtualHostColumn()));
+                                           
+                        try (InputStream is = ((JDBCSessionIdManager)getSessionIdManager())._dbAdaptor.getBlobInputStream(result, _sessionTableSchema.getMapColumn());
+                                ClassLoadingObjectInputStream ois = new ClassLoadingObjectInputStream(is))
+                        {
+                            Object o = ois.readObject();
+                            session.addAttributes((Map<String,Object>)o);
+                        }
+
+                        if (LOG.isDebugEnabled())
+                            LOG.debug("LOADED session "+session);
+                    }
+                    else
+                        if (LOG.isDebugEnabled())
+                            LOG.debug("Failed to load session "+id);
+                    _reference.set(session);
+                }
+                catch (Exception e)
+                {
+                    _exception.set(e);
+                }
+            }
+        };
+
+        if (_context==null)
+            load.run();
+        else
+            _context.getContextHandler().handle(load);
+
+        if (_exception.get()!=null)
+        {
+            //if the session could not be restored, take its id out of the pool of currently-in-use
+            //session ids
+            _jdbcSessionIdMgr.removeSession(id);
+            throw _exception.get();
+        }
+
+        return _reference.get();
+    }
+
+    /**
+     * Insert a session into the database.
+     *
+     * @param session
+     * @throws Exception
+     */
+    protected void storeSession (Session session)
+    throws Exception
+    {
+        if (session==null)
+            return;
+
+        //put into the database
+        try (Connection connection = getConnection();
+                PreparedStatement statement = connection.prepareStatement(_jdbcSessionIdMgr._insertSession))
+        {
+            String rowId = calculateRowId(session);
+
+            long now = System.currentTimeMillis();
+            connection.setAutoCommit(true);
+            statement.setString(1, rowId); //rowId
+            statement.setString(2, session.getClusterId()); //session id
+            statement.setString(3, session.getCanonicalContext()); //context path
+            statement.setString(4, session.getVirtualHost()); //first vhost
+            statement.setString(5, getSessionIdManager().getWorkerName());//my node id
+            statement.setLong(6, session.getAccessed());//accessTime
+            statement.setLong(7, session.getLastAccessedTime()); //lastAccessTime
+            statement.setLong(8, session.getCreationTime()); //time created
+            statement.setLong(9, session.getCookieSet());//time cookie was set
+            statement.setLong(10, now); //last saved time
+            statement.setLong(11, session.getExpiryTime());
+            statement.setLong(12, session.getMaxInactiveInterval());
+
+            ByteArrayOutputStream baos = new ByteArrayOutputStream();
+            ObjectOutputStream oos = new ObjectOutputStream(baos);
+            oos.writeObject(session.getAttributeMap());
+            oos.flush();
+            byte[] bytes = baos.toByteArray();
+
+            ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
+            statement.setBinaryStream(13, bais, bytes.length);//attribute map as blob
+           
+
+            statement.executeUpdate();
+            session.setRowId(rowId); //set it on the in-memory data as well as in db
+            session.setLastSaved(now);
+        }
+        if (LOG.isDebugEnabled())
+            LOG.debug("Stored session "+session);
+    }
+
+
+    /**
+     * Update data on an existing persisted session.
+     *
+     * @param data the session
+     * @throws Exception
+     */
+    protected void updateSession (Session data)
+    throws Exception
+    {
+        if (data==null)
+            return;
+
+        try (Connection connection = getConnection();
+                PreparedStatement statement = connection.prepareStatement(_jdbcSessionIdMgr._updateSession))
+        {
+            long now = System.currentTimeMillis();
+            connection.setAutoCommit(true);
+            statement.setString(1, data.getClusterId());
+            statement.setString(2, getSessionIdManager().getWorkerName());//my node id
+            statement.setLong(3, data.getAccessed());//accessTime
+            statement.setLong(4, data.getLastAccessedTime()); //lastAccessTime
+            statement.setLong(5, now); //last saved time
+            statement.setLong(6, data.getExpiryTime());
+            statement.setLong(7, data.getMaxInactiveInterval());
+
+            ByteArrayOutputStream baos = new ByteArrayOutputStream();
+            ObjectOutputStream oos = new ObjectOutputStream(baos);
+            oos.writeObject(data.getAttributeMap());
+            oos.flush();
+            byte[] bytes = baos.toByteArray();
+            ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
+
+            statement.setBinaryStream(8, bais, bytes.length);//attribute map as blob
+            statement.setString(9, data.getRowId()); //rowId
+            statement.executeUpdate();
+
+            data.setLastSaved(now);
+        }
+        if (LOG.isDebugEnabled())
+            LOG.debug("Updated session "+data);
+    }
+
+
+    /**
+     * Update the node on which the session was last seen to be my node.
+     *
+     * @param data the session
+     * @throws Exception
+     */
+    protected void updateSessionNode (Session data)
+    throws Exception
+    {
+        String nodeId = getSessionIdManager().getWorkerName();
+        try (Connection connection = getConnection();
+                PreparedStatement statement = connection.prepareStatement(_jdbcSessionIdMgr._updateSessionNode))
+        {
+            connection.setAutoCommit(true);
+            statement.setString(1, nodeId);
+            statement.setString(2, data.getRowId());
+            statement.executeUpdate();
+        }
+        if (LOG.isDebugEnabled())
+            LOG.debug("Updated last node for session id="+data.getId()+", lastNode = "+nodeId);
+    }
+
+    /**
+     * Persist the time the session was last accessed.
+     *
+     * @param data the session
+     * @throws Exception
+     */
+    private void updateSessionAccessTime (Session data)
+    throws Exception
+    {
+        try (Connection connection = getConnection();
+                PreparedStatement statement = connection.prepareStatement(_jdbcSessionIdMgr._updateSessionAccessTime))
+        {
+            long now = System.currentTimeMillis();
+            connection.setAutoCommit(true);
+            statement.setString(1, getSessionIdManager().getWorkerName());
+            statement.setLong(2, data.getAccessed());
+            statement.setLong(3, data.getLastAccessedTime());
+            statement.setLong(4, now);
+            statement.setLong(5, data.getExpiryTime());
+            statement.setLong(6, data.getMaxInactiveInterval());
+            statement.setString(7, data.getRowId());
+          
+            statement.executeUpdate();
+            data.setLastSaved(now);
+        }
+        if (LOG.isDebugEnabled())
+            LOG.debug("Updated access time session id="+data.getId()+" with lastsaved="+data.getLastSaved());
+    }
+
+
+
+
+    /**
+     * Delete a session from the database. Should only be called
+     * when the session has been invalidated.
+     *
+     * @param data
+     * @throws Exception
+     */
+    protected void deleteSession (Session data)
+    throws Exception
+    {
+        try (Connection connection = getConnection();
+                PreparedStatement statement = connection.prepareStatement(_jdbcSessionIdMgr._deleteSession))
+        {
+            connection.setAutoCommit(true);
+            statement.setString(1, data.getRowId());
+            statement.executeUpdate();
+            if (LOG.isDebugEnabled())
+                LOG.debug("Deleted Session "+data);
+        }
+    }
+
+
+
+    /**
+     * Get a connection from the driver.
+     * @return
+     * @throws SQLException
+     */
+    private Connection getConnection ()
+    throws SQLException
+    {
+        return ((JDBCSessionIdManager)getSessionIdManager()).getConnection();
+    }
+
+    /**
+     * Calculate a unique id for this session across the cluster.
+     *
+     * Unique id is composed of: contextpath_virtualhost0_sessionid
+     * @param data
+     * @return
+     */
+    private String calculateRowId (Session data)
+    {
+        String rowId = canonicalize(_context.getContextPath());
+        rowId = rowId + "_" + getVirtualHost(_context);
+        rowId = rowId+"_"+data.getId();
+        return rowId;
+    }
+
+    /**
+     * Get the first virtual host for the context.
+     *
+     * Used to help identify the exact session/contextPath.
+     *
+     * @return 0.0.0.0 if no virtual host is defined
+     */
+    private static String getVirtualHost (ContextHandler.Context context)
+    {
+        String vhost = "0.0.0.0";
+
+        if (context==null)
+            return vhost;
+
+        String [] vhosts = context.getContextHandler().getVirtualHosts();
+        if (vhosts==null || vhosts.length==0 || vhosts[0]==null)
+            return vhost;
+
+        return vhosts[0];
+    }
+
+    /**
+     * Make an acceptable file name from a context path.
+     *
+     * @param path
+     * @return
+     */
+    private static String canonicalize (String path)
+    {
+        if (path==null)
+            return "";
+
+        return path.replace('/', '_').replace('.','_').replace('\\','_');
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/server/session/MemSession.java b/lib/jetty/org/eclipse/jetty/server/session/MemSession.java
new file mode 100644 (file)
index 0000000..72dbea8
--- /dev/null
@@ -0,0 +1,146 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server.session;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import javax.servlet.http.HttpServletRequest;
+
+
+/**
+ * MemSession
+ *
+ * A session whose data is kept in memory
+ */
+public class MemSession extends AbstractSession
+{
+
+    private final Map<String,Object> _attributes=new HashMap<String, Object>();
+
+    protected MemSession(AbstractSessionManager abstractSessionManager, HttpServletRequest request)
+    {
+        super(abstractSessionManager, request);
+    }
+
+    public MemSession(AbstractSessionManager abstractSessionManager, long created, long accessed, String clusterId)
+    {
+        super(abstractSessionManager, created, accessed, clusterId);
+    }
+    
+    
+    /* ------------------------------------------------------------- */
+    @Override
+    public Map<String,Object> getAttributeMap()
+    {
+        return _attributes;
+    }
+   
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public int getAttributes()
+    {
+        synchronized (this)
+        {
+            checkValid();
+            return _attributes.size();
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    @SuppressWarnings({ "unchecked" })
+    @Override
+    public Enumeration<String> doGetAttributeNames()
+    {
+        List<String> names=_attributes==null?Collections.EMPTY_LIST:new ArrayList<String>(_attributes.keySet());
+        return Collections.enumeration(names);
+    }
+
+    
+    /* ------------------------------------------------------------ */
+    @Override
+    public Set<String> getNames()
+    {
+        synchronized (this)
+        {
+            return new HashSet<String>(_attributes.keySet());
+        }
+    }
+   
+    
+    /* ------------------------------------------------------------- */
+    @Override
+    public void clearAttributes()
+    {
+        while (_attributes!=null && _attributes.size()>0)
+        {
+            ArrayList<String> keys;
+            synchronized(this)
+            {
+                keys=new ArrayList<String>(_attributes.keySet());
+            }
+
+            Iterator<String> iter=keys.iterator();
+            while (iter.hasNext())
+            {
+                String key=(String)iter.next();
+
+                Object value;
+                synchronized(this)
+                {
+                    value=doPutOrRemove(key,null);
+                }
+                unbindValue(key,value);
+
+                ((AbstractSessionManager)getSessionManager()).doSessionAttributeListeners(this,key,value,null);
+            }
+        }
+        if (_attributes!=null)
+            _attributes.clear();
+    }
+
+    /* ------------------------------------------------------------ */
+    public void addAttributes(Map<String,Object> map)
+    {
+        _attributes.putAll(map);
+    }
+    
+    /* ------------------------------------------------------------ */
+    @Override
+    public Object doPutOrRemove(String name, Object value)
+    {
+        return value==null?_attributes.remove(name):_attributes.put(name,value);
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public Object doGet(String name)
+    {
+        return _attributes.get(name);
+    }
+    
+}
diff --git a/lib/jetty/org/eclipse/jetty/server/session/SessionHandler.java b/lib/jetty/org/eclipse/jetty/server/session/SessionHandler.java
new file mode 100644 (file)
index 0000000..e6aedc5
--- /dev/null
@@ -0,0 +1,346 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server.session;
+
+import java.io.IOException;
+import java.util.EnumSet;
+import java.util.EventListener;
+
+import javax.servlet.DispatcherType;
+import javax.servlet.ServletException;
+import javax.servlet.SessionTrackingMode;
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpSession;
+import javax.servlet.http.HttpSessionAttributeListener;
+import javax.servlet.http.HttpSessionIdListener;
+import javax.servlet.http.HttpSessionListener;
+
+import org.eclipse.jetty.http.HttpCookie;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.SessionManager;
+import org.eclipse.jetty.server.handler.ScopedHandler;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/* ------------------------------------------------------------ */
+/**
+ * SessionHandler.
+ */
+public class SessionHandler extends ScopedHandler
+{
+    final static Logger LOG = Log.getLogger("org.eclipse.jetty.server.session");
+
+    public final static EnumSet<SessionTrackingMode> DEFAULT_TRACKING = EnumSet.of(SessionTrackingMode.COOKIE,SessionTrackingMode.URL);
+    
+    public static final Class[] SESSION_LISTENER_TYPES = new Class[] {HttpSessionAttributeListener.class,
+                                                                      HttpSessionIdListener.class,
+                                                                      HttpSessionListener.class};
+
+
+
+    /* -------------------------------------------------------------- */
+    private SessionManager _sessionManager;
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Constructor. Construct a SessionHandler witha a HashSessionManager with a standard java.util.Random generator is created.
+     */
+    public SessionHandler()
+    {
+        this(new HashSessionManager());
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param manager
+     *            The session manager
+     */
+    public SessionHandler(SessionManager manager)
+    {
+        setSessionManager(manager);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Returns the sessionManager.
+     */
+    public SessionManager getSessionManager()
+    {
+        return _sessionManager;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param sessionManager
+     *            The sessionManager to set.
+     */
+    public void setSessionManager(SessionManager sessionManager)
+    {
+        if (isStarted())
+            throw new IllegalStateException();
+        if (sessionManager != null)
+            sessionManager.setSessionHandler(this);
+        updateBean(_sessionManager,sessionManager);
+        _sessionManager=sessionManager;
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see org.eclipse.thread.AbstractLifeCycle#doStart()
+     */
+    @Override
+    protected void doStart() throws Exception
+    {
+        if (_sessionManager==null)
+            setSessionManager(new HashSessionManager());
+        super.doStart();
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see org.eclipse.thread.AbstractLifeCycle#doStop()
+     */
+    @Override
+    protected void doStop() throws Exception
+    {
+        // Destroy sessions before destroying servlets/filters see JETTY-1266
+        super.doStop();
+    }
+
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see org.eclipse.jetty.server.Handler#handle(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, int)
+     */
+    @Override
+    public void doScope(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+    {
+        SessionManager old_session_manager = null;
+        HttpSession old_session = null;
+        HttpSession access = null;
+        try
+        {
+            old_session_manager = baseRequest.getSessionManager();
+            old_session = baseRequest.getSession(false);
+
+            if (old_session_manager != _sessionManager)
+            {
+                // new session context
+                baseRequest.setSessionManager(_sessionManager);
+                baseRequest.setSession(null);
+                checkRequestedSessionId(baseRequest,request);
+            }
+
+            // access any existing session
+            HttpSession session = null;
+            if (_sessionManager != null)
+            {
+                session = baseRequest.getSession(false);
+                if (session != null)
+                {
+                    if (session != old_session)
+                    {
+                        access = session;
+                        HttpCookie cookie = _sessionManager.access(session,request.isSecure());
+                        if (cookie != null) // Handle changed ID or max-age refresh
+                            baseRequest.getResponse().addCookie(cookie);
+                    }
+                }
+                else
+                {
+                    session = baseRequest.recoverNewSession(_sessionManager);
+                    if (session != null)
+                        baseRequest.setSession(session);
+                }
+            }
+
+            if (LOG.isDebugEnabled())
+            {
+                LOG.debug("sessionManager=" + _sessionManager);
+                LOG.debug("session=" + session);
+            }
+
+            // start manual inline of nextScope(target,baseRequest,request,response);
+            if (_nextScope != null)
+                _nextScope.doScope(target,baseRequest,request,response);
+            else if (_outerScope != null)
+                _outerScope.doHandle(target,baseRequest,request,response);
+            else
+                doHandle(target,baseRequest,request,response);
+            // end manual inline (pathentic attempt to reduce stack depth)
+
+        }
+        finally
+        {
+            if (access != null)
+                _sessionManager.complete(access);
+
+            HttpSession session = baseRequest.getSession(false);
+            if (session != null && old_session == null && session != access)
+                _sessionManager.complete(session);
+
+            if (old_session_manager != null && old_session_manager != _sessionManager)
+            {
+                baseRequest.setSessionManager(old_session_manager);
+                baseRequest.setSession(old_session);
+            }
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see org.eclipse.jetty.server.Handler#handle(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, int)
+     */
+    @Override
+    public void doHandle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+    {
+        // start manual inline of nextHandle(target,baseRequest,request,response);
+        if (never())
+            nextHandle(target,baseRequest,request,response);
+        else if (_nextScope != null && _nextScope == _handler)
+            _nextScope.doHandle(target,baseRequest,request,response);
+        else if (_handler != null)
+            _handler.handle(target,baseRequest,request,response);
+        // end manual inline
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Look for a requested session ID in cookies and URI parameters
+     *
+     * @param baseRequest
+     * @param request
+     */
+    protected void checkRequestedSessionId(Request baseRequest, HttpServletRequest request)
+    {
+        String requested_session_id = request.getRequestedSessionId();
+
+        SessionManager sessionManager = getSessionManager();
+
+        if (requested_session_id != null && sessionManager != null)
+        {
+            HttpSession session = sessionManager.getHttpSession(requested_session_id);
+            if (session != null && sessionManager.isValid(session))
+                baseRequest.setSession(session);
+            return;
+        }
+        else if (!DispatcherType.REQUEST.equals(baseRequest.getDispatcherType()))
+            return;
+
+        boolean requested_session_id_from_cookie = false;
+        HttpSession session = null;
+
+        // Look for session id cookie
+        if (_sessionManager.isUsingCookies())
+        {
+            Cookie[] cookies = request.getCookies();
+            if (cookies != null && cookies.length > 0)
+            {
+                final String sessionCookie=sessionManager.getSessionCookieConfig().getName();
+                for (int i = 0; i < cookies.length; i++)
+                {
+                    if (sessionCookie.equalsIgnoreCase(cookies[i].getName()))
+                    {
+                        requested_session_id = cookies[i].getValue();
+                        requested_session_id_from_cookie = true;
+
+                        LOG.debug("Got Session ID {} from cookie",requested_session_id);
+
+                        if (requested_session_id != null)
+                        {
+                            session = sessionManager.getHttpSession(requested_session_id);
+
+                            if (session != null && sessionManager.isValid(session))
+                            {
+                                break;
+                            }
+                        }
+                        else
+                        {
+                            LOG.warn("null session id from cookie");
+                        }
+                    }
+                }
+            }
+        }
+
+        if (requested_session_id == null || session == null)
+        {
+            String uri = request.getRequestURI();
+
+            String prefix = sessionManager.getSessionIdPathParameterNamePrefix();
+            if (prefix != null)
+            {
+                int s = uri.indexOf(prefix);
+                if (s >= 0)
+                {
+                    s += prefix.length();
+                    int i = s;
+                    while (i < uri.length())
+                    {
+                        char c = uri.charAt(i);
+                        if (c == ';' || c == '#' || c == '?' || c == '/')
+                            break;
+                        i++;
+                    }
+
+                    requested_session_id = uri.substring(s,i);
+                    requested_session_id_from_cookie = false;
+                    session = sessionManager.getHttpSession(requested_session_id);
+                    if (LOG.isDebugEnabled())
+                        LOG.debug("Got Session ID {} from URL",requested_session_id);
+                }
+            }
+        }
+
+        baseRequest.setRequestedSessionId(requested_session_id);
+        baseRequest.setRequestedSessionIdFromCookie(requested_session_id!=null && requested_session_id_from_cookie);
+        if (session != null && sessionManager.isValid(session))
+            baseRequest.setSession(session);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param listener
+     */
+    public void addEventListener(EventListener listener)
+    {
+        if (_sessionManager != null)
+            _sessionManager.addEventListener(listener);
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * @param listener
+     */
+    public void removeEventListener(EventListener listener)
+    {
+        if (_sessionManager != null)
+            _sessionManager.removeEventListener(listener);
+    }
+
+    /* ------------------------------------------------------------ */
+    public void clearEventListeners()
+    {
+        if (_sessionManager != null)
+            _sessionManager.clearEventListeners();
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/server/session/package-info.java b/lib/jetty/org/eclipse/jetty/server/session/package-info.java
new file mode 100644 (file)
index 0000000..ed180a1
--- /dev/null
@@ -0,0 +1,23 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+/**
+ * Jetty Server : Session Management Implementations
+ */
+package org.eclipse.jetty.server.session;
+
diff --git a/lib/jetty/org/eclipse/jetty/servlet/BaseHolder.java b/lib/jetty/org/eclipse/jetty/servlet/BaseHolder.java
new file mode 100644 (file)
index 0000000..a32e1a6
--- /dev/null
@@ -0,0 +1,209 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.servlet;
+
+import java.io.IOException;
+
+import javax.servlet.ServletContext;
+import javax.servlet.UnavailableException;
+
+import org.eclipse.jetty.server.handler.ContextHandler;
+import org.eclipse.jetty.util.Loader;
+import org.eclipse.jetty.util.annotation.ManagedAttribute;
+import org.eclipse.jetty.util.component.AbstractLifeCycle;
+import org.eclipse.jetty.util.component.ContainerLifeCycle;
+import org.eclipse.jetty.util.component.Dumpable;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/**
+ * AbstractHolder
+ * 
+ * Base class for all servlet-related classes that may be lazily instantiated  (eg servlet, filter, 
+ * listener), and/or require metadata to be held regarding their origin 
+ * (web.xml, annotation, programmatic api etc).
+ * 
+ */
+public abstract class BaseHolder<T> extends AbstractLifeCycle implements Dumpable
+{
+    private static final Logger LOG = Log.getLogger(BaseHolder.class);
+    
+    
+    public enum Source { EMBEDDED, JAVAX_API, DESCRIPTOR, ANNOTATION };
+    
+    final protected Source _source;
+    protected transient Class<? extends T> _class;
+    protected String _className;
+    protected boolean _extInstance;
+    protected ServletHandler _servletHandler;
+    
+    /* ---------------------------------------------------------------- */
+    protected BaseHolder(Source source)
+    {
+        _source=source;
+    }
+
+    /* ------------------------------------------------------------ */
+    public Source getSource()
+    {
+        return _source;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * Do any setup necessary after starting
+     * @throws Exception
+     */
+    public void initialize()
+    throws Exception
+    {
+        if (!isStarted())
+            throw new IllegalStateException("Not started: "+this);
+    }
+
+    /* ------------------------------------------------------------ */
+    @SuppressWarnings("unchecked")
+    @Override
+    public void doStart()
+        throws Exception
+    {
+        //if no class already loaded and no classname, make permanently unavailable
+        if (_class==null && (_className==null || _className.equals("")))
+            throw new UnavailableException("No class in holder");
+        
+        //try to load class
+        if (_class==null)
+        {
+            try
+            {
+                _class=Loader.loadClass(Holder.class, _className);
+                if(LOG.isDebugEnabled())
+                    LOG.debug("Holding {} from {}",_class,_class.getClassLoader());
+            }
+            catch (Exception e)
+            {
+                LOG.warn(e);
+                throw new UnavailableException(e.getMessage());
+            }
+        }
+    }
+    
+    
+    /* ------------------------------------------------------------ */
+    @Override
+    public void doStop()
+        throws Exception
+    {
+        if (!_extInstance)
+            _class=null;
+    }
+
+
+    /* ------------------------------------------------------------ */
+    @ManagedAttribute(value="Class Name", readonly=true)
+    public String getClassName()
+    {
+        return _className;
+    }
+
+    /* ------------------------------------------------------------ */
+    public Class<? extends T> getHeldClass()
+    {
+        return _class;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Returns the servletHandler.
+     */
+    public ServletHandler getServletHandler()
+    {
+        return _servletHandler;
+    }
+    
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param servletHandler The {@link ServletHandler} that will handle requests dispatched to this servlet.
+     */
+    public void setServletHandler(ServletHandler servletHandler)
+    {
+        _servletHandler = servletHandler;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * @param className The className to set.
+     */
+    public void setClassName(String className)
+    {
+        _className = className;
+        _class=null;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param held The class to hold
+     */
+    public void setHeldClass(Class<? extends T> held)
+    {
+        _class=held;
+        if (held!=null)
+        {
+            _className=held.getName();
+        }
+    }
+    
+
+    /* ------------------------------------------------------------ */
+    protected void illegalStateIfContextStarted()
+    {
+        if (_servletHandler!=null)
+        {
+            ServletContext context=_servletHandler.getServletContext();
+            if ((context instanceof ContextHandler.Context) && ((ContextHandler.Context)context).getContextHandler().isStarted())
+                throw new IllegalStateException("Started");
+        }
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * @return True if this holder was created for a specific instance.
+     */
+    public boolean isInstance()
+    {
+        return _extInstance;
+    }
+    
+    
+    /* ------------------------------------------------------------ */
+    @Override
+    public void dump(Appendable out, String indent) throws IOException
+    {
+        out.append(toString())
+        .append(" - ").append(AbstractLifeCycle.getState(this)).append("\n");
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public String dump()
+    {
+        return ContainerLifeCycle.dump(this);
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/servlet/DefaultServlet.java b/lib/jetty/org/eclipse/jetty/servlet/DefaultServlet.java
new file mode 100644 (file)
index 0000000..c378739
--- /dev/null
@@ -0,0 +1,1100 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.servlet;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.nio.ByteBuffer;
+import java.util.Enumeration;
+import java.util.List;
+
+import javax.servlet.AsyncContext;
+import javax.servlet.RequestDispatcher;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+import javax.servlet.UnavailableException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.http.HttpContent;
+import org.eclipse.jetty.http.HttpField;
+import org.eclipse.jetty.http.HttpFields;
+import org.eclipse.jetty.http.HttpGenerator.CachedHttpField;
+import org.eclipse.jetty.http.HttpHeader;
+import org.eclipse.jetty.http.HttpMethod;
+import org.eclipse.jetty.http.MimeTypes;
+import org.eclipse.jetty.http.PathMap.MappedEntry;
+import org.eclipse.jetty.io.WriterOutputStream;
+import org.eclipse.jetty.server.HttpOutput;
+import org.eclipse.jetty.server.InclusiveByteRange;
+import org.eclipse.jetty.server.ResourceCache;
+import org.eclipse.jetty.server.Response;
+import org.eclipse.jetty.server.handler.ContextHandler;
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.Callback;
+import org.eclipse.jetty.util.IO;
+import org.eclipse.jetty.util.MultiPartOutputStream;
+import org.eclipse.jetty.util.QuotedStringTokenizer;
+import org.eclipse.jetty.util.URIUtil;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.resource.Resource;
+import org.eclipse.jetty.util.resource.ResourceCollection;
+import org.eclipse.jetty.util.resource.ResourceFactory;
+
+
+
+/* ------------------------------------------------------------ */
+/** The default servlet.
+ * This servlet, normally mapped to /, provides the handling for static
+ * content, OPTION and TRACE methods for the context.
+ * The following initParameters are supported, these can be set either
+ * on the servlet itself or as ServletContext initParameters with a prefix
+ * of org.eclipse.jetty.servlet.Default. :
+ * <PRE>
+ *  acceptRanges      If true, range requests and responses are
+ *                    supported
+ *
+ *  dirAllowed        If true, directory listings are returned if no
+ *                    welcome file is found. Else 403 Forbidden.
+ *
+ *  welcomeServlets   If true, attempt to dispatch to welcome files
+ *                    that are servlets, but only after no matching static
+ *                    resources could be found. If false, then a welcome
+ *                    file must exist on disk. If "exact", then exact
+ *                    servlet matches are supported without an existing file.
+ *                    Default is true.
+ *
+ *                    This must be false if you want directory listings,
+ *                    but have index.jsp in your welcome file list.
+ *
+ *  redirectWelcome   If true, welcome files are redirected rather than
+ *                    forwarded to.
+ *
+ *  gzip              If set to true, then static content will be served as
+ *                    gzip content encoded if a matching resource is
+ *                    found ending with ".gz"
+ *
+ *  resourceBase      Set to replace the context resource base
+ *
+ *  resourceCache     If set, this is a context attribute name, which the servlet
+ *                    will use to look for a shared ResourceCache instance.
+ *
+ *  relativeResourceBase
+ *                    Set with a pathname relative to the base of the
+ *                    servlet context root. Useful for only serving static content out
+ *                    of only specific subdirectories.
+ *
+ *  pathInfoOnly      If true, only the path info will be applied to the resourceBase
+ *
+ *  stylesheet       Set with the location of an optional stylesheet that will be used
+ *                    to decorate the directory listing html.
+ *
+ *  etags             If True, weak etags will be generated and handled.
+ *
+ *  maxCacheSize      The maximum total size of the cache or 0 for no cache.
+ *  maxCachedFileSize The maximum size of a file to cache
+ *  maxCachedFiles    The maximum number of files to cache
+ *
+ *  useFileMappedBuffer
+ *                    If set to true, it will use mapped file buffer to serve static content
+ *                    when using NIO connector. Setting this value to false means that
+ *                    a direct buffer will be used instead of a mapped file buffer.
+ *                    By default, this is set to true.
+ *
+ *  cacheControl      If set, all static content will have this value set as the cache-control
+ *                    header.
+ *
+ *
+ * </PRE>
+ *
+ *
+ *
+ *
+ */
+public class DefaultServlet extends HttpServlet implements ResourceFactory
+{
+    private static final Logger LOG = Log.getLogger(DefaultServlet.class);
+
+    private static final long serialVersionUID = 4930458713846881193L;
+    
+    private static final CachedHttpField ACCEPT_RANGES = new CachedHttpField(HttpHeader.ACCEPT_RANGES, "bytes");
+    
+    private ServletContext _servletContext;
+    private ContextHandler _contextHandler;
+
+    private boolean _acceptRanges=true;
+    private boolean _dirAllowed=true;
+    private boolean _welcomeServlets=false;
+    private boolean _welcomeExactServlets=false;
+    private boolean _redirectWelcome=false;
+    private boolean _gzip=false;
+    private boolean _pathInfoOnly=false;
+    private boolean _etags=false;
+
+    private Resource _resourceBase;
+    private ResourceCache _cache;
+
+    private MimeTypes _mimeTypes;
+    private String[] _welcomes;
+    private Resource _stylesheet;
+    private boolean _useFileMappedBuffer=false;
+    private HttpField _cacheControl;
+    private String _relativeResourceBase;
+    private ServletHandler _servletHandler;
+    private ServletHolder _defaultHolder;
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public void init()
+    throws UnavailableException
+    {
+        _servletContext=getServletContext();
+        _contextHandler = initContextHandler(_servletContext);
+
+        _mimeTypes = _contextHandler.getMimeTypes();
+
+        _welcomes = _contextHandler.getWelcomeFiles();
+        if (_welcomes==null)
+            _welcomes=new String[] {"index.html","index.jsp"};
+
+        _acceptRanges=getInitBoolean("acceptRanges",_acceptRanges);
+        _dirAllowed=getInitBoolean("dirAllowed",_dirAllowed);
+        _redirectWelcome=getInitBoolean("redirectWelcome",_redirectWelcome);
+        _gzip=getInitBoolean("gzip",_gzip);
+        _pathInfoOnly=getInitBoolean("pathInfoOnly",_pathInfoOnly);
+
+        if ("exact".equals(getInitParameter("welcomeServlets")))
+        {
+            _welcomeExactServlets=true;
+            _welcomeServlets=false;
+        }
+        else
+            _welcomeServlets=getInitBoolean("welcomeServlets", _welcomeServlets);
+
+        _useFileMappedBuffer=getInitBoolean("useFileMappedBuffer",_useFileMappedBuffer);
+
+        _relativeResourceBase = getInitParameter("relativeResourceBase");
+
+        String rb=getInitParameter("resourceBase");
+        if (rb!=null)
+        {
+            if (_relativeResourceBase!=null)
+                throw new  UnavailableException("resourceBase & relativeResourceBase");
+            try{_resourceBase=_contextHandler.newResource(rb);}
+            catch (Exception e)
+            {
+                LOG.warn(Log.EXCEPTION,e);
+                throw new UnavailableException(e.toString());
+            }
+        }
+
+        String css=getInitParameter("stylesheet");
+        try
+        {
+            if(css!=null)
+            {
+                _stylesheet = Resource.newResource(css);
+                if(!_stylesheet.exists())
+                {
+                    LOG.warn("!" + css);
+                    _stylesheet = null;
+                }
+            }
+            if(_stylesheet == null)
+            {
+                _stylesheet = Resource.newResource(this.getClass().getResource("/jetty-dir.css"));
+            }
+        }
+        catch(Exception e)
+        {
+            LOG.warn(e.toString());
+            LOG.debug(e);
+        }
+
+        String cc=getInitParameter("cacheControl");
+        if (cc!=null)
+            _cacheControl=new CachedHttpField(HttpHeader.CACHE_CONTROL, cc);
+        
+        String resourceCache = getInitParameter("resourceCache");
+        int max_cache_size=getInitInt("maxCacheSize", -2);
+        int max_cached_file_size=getInitInt("maxCachedFileSize", -2);
+        int max_cached_files=getInitInt("maxCachedFiles", -2);
+        if (resourceCache!=null)
+        {
+            if (max_cache_size!=-1 || max_cached_file_size!= -2 || max_cached_files!=-2)
+                LOG.debug("ignoring resource cache configuration, using resourceCache attribute");
+            if (_relativeResourceBase!=null || _resourceBase!=null)
+                throw new UnavailableException("resourceCache specified with resource bases");
+            _cache=(ResourceCache)_servletContext.getAttribute(resourceCache);
+
+            LOG.debug("Cache {}={}",resourceCache,_cache);
+        }
+
+        _etags = getInitBoolean("etags",_etags);
+        
+        try
+        {
+            if (_cache==null && max_cached_files>0)
+            {
+                _cache= new ResourceCache(null,this,_mimeTypes,_useFileMappedBuffer,_etags);
+
+                if (max_cache_size>0)
+                    _cache.setMaxCacheSize(max_cache_size);
+                if (max_cached_file_size>=-1)
+                    _cache.setMaxCachedFileSize(max_cached_file_size);
+                if (max_cached_files>=-1)
+                    _cache.setMaxCachedFiles(max_cached_files);
+            }
+        }
+        catch (Exception e)
+        {
+            LOG.warn(Log.EXCEPTION,e);
+            throw new UnavailableException(e.toString());
+        }
+
+        _servletHandler= _contextHandler.getChildHandlerByClass(ServletHandler.class);
+        for (ServletHolder h :_servletHandler.getServlets())
+            if (h.getServletInstance()==this)
+                _defaultHolder=h;
+
+        
+        if (LOG.isDebugEnabled())
+            LOG.debug("resource base = "+_resourceBase);
+    }
+
+    /**
+     * Compute the field _contextHandler.<br/>
+     * In the case where the DefaultServlet is deployed on the HttpService it is likely that
+     * this method needs to be overwritten to unwrap the ServletContext facade until we reach
+     * the original jetty's ContextHandler.
+     * @param servletContext The servletContext of this servlet.
+     * @return the jetty's ContextHandler for this servletContext.
+     */
+    protected ContextHandler initContextHandler(ServletContext servletContext)
+    {
+        ContextHandler.Context scontext=ContextHandler.getCurrentContext();
+        if (scontext==null)
+        {
+            if (servletContext instanceof ContextHandler.Context)
+                return ((ContextHandler.Context)servletContext).getContextHandler();
+            else
+                throw new IllegalArgumentException("The servletContext " + servletContext + " " +
+                    servletContext.getClass().getName() + " is not " + ContextHandler.Context.class.getName());
+        }
+        else
+            return ContextHandler.getCurrentContext().getContextHandler();
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public String getInitParameter(String name)
+    {
+        String value=getServletContext().getInitParameter("org.eclipse.jetty.servlet.Default."+name);
+        if (value==null)
+            value=super.getInitParameter(name);
+        return value;
+    }
+
+    /* ------------------------------------------------------------ */
+    private boolean getInitBoolean(String name, boolean dft)
+    {
+        String value=getInitParameter(name);
+        if (value==null || value.length()==0)
+            return dft;
+        return (value.startsWith("t")||
+                value.startsWith("T")||
+                value.startsWith("y")||
+                value.startsWith("Y")||
+                value.startsWith("1"));
+    }
+
+    /* ------------------------------------------------------------ */
+    private int getInitInt(String name, int dft)
+    {
+        String value=getInitParameter(name);
+        if (value==null)
+            value=getInitParameter(name);
+        if (value!=null && value.length()>0)
+            return Integer.parseInt(value);
+        return dft;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** get Resource to serve.
+     * Map a path to a resource. The default implementation calls
+     * HttpContext.getResource but derived servlets may provide
+     * their own mapping.
+     * @param pathInContext The path to find a resource for.
+     * @return The resource to serve.
+     */
+    @Override
+    public Resource getResource(String pathInContext)
+    {
+        Resource r=null;
+        if (_relativeResourceBase!=null)
+            pathInContext=URIUtil.addPaths(_relativeResourceBase,pathInContext);
+
+        try
+        {
+            if (_resourceBase!=null)
+            {
+                r = _resourceBase.addPath(pathInContext);
+                if (!_contextHandler.checkAlias(pathInContext,r))
+                    r=null;
+            }
+            else if (_servletContext instanceof ContextHandler.Context)
+            {
+                r = _contextHandler.getResource(pathInContext);
+            }
+            else
+            {
+                URL u = _servletContext.getResource(pathInContext);
+                r = _contextHandler.newResource(u);
+            }
+
+            if (LOG.isDebugEnabled())
+                LOG.debug("Resource "+pathInContext+"="+r);
+        }
+        catch (IOException e)
+        {
+            LOG.ignore(e);
+        }
+
+        if((r==null || !r.exists()) && pathInContext.endsWith("/jetty-dir.css"))
+            r=_stylesheet;
+
+        return r;
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    protected void doGet(HttpServletRequest request, HttpServletResponse response)
+    throws ServletException, IOException
+    {
+        String servletPath=null;
+        String pathInfo=null;
+        Enumeration<String> reqRanges = null;
+        Boolean included =request.getAttribute(RequestDispatcher.INCLUDE_REQUEST_URI)!=null;
+        if (included!=null && included.booleanValue())
+        {
+            servletPath=(String)request.getAttribute(RequestDispatcher.INCLUDE_SERVLET_PATH);
+            pathInfo=(String)request.getAttribute(RequestDispatcher.INCLUDE_PATH_INFO);
+            if (servletPath==null)
+            {
+                servletPath=request.getServletPath();
+                pathInfo=request.getPathInfo();
+            }
+        }
+        else
+        {
+            included = Boolean.FALSE;
+            servletPath = _pathInfoOnly?"/":request.getServletPath();
+            pathInfo = request.getPathInfo();
+
+            // Is this a Range request?
+            reqRanges = request.getHeaders(HttpHeader.RANGE.asString());
+            if (!hasDefinedRange(reqRanges))
+                reqRanges = null;
+        }
+
+        String pathInContext=URIUtil.addPaths(servletPath,pathInfo);
+        boolean endsWithSlash=(pathInfo==null?request.getServletPath():pathInfo).endsWith(URIUtil.SLASH);
+
+
+        // Find the resource and content
+        Resource resource=null;
+        HttpContent content=null;
+        try
+        {
+            // is gzip enabled?
+            String pathInContextGz=null;
+            boolean gzip=false;
+            if (!included.booleanValue() && _gzip && reqRanges==null && !endsWithSlash )
+            {
+                // Look for a gzip resource
+                pathInContextGz=pathInContext+".gz";
+                if (_cache==null)
+                    resource=getResource(pathInContextGz);
+                else
+                {
+                    content=_cache.lookup(pathInContextGz);
+                    resource=(content==null)?null:content.getResource();
+                }
+
+                // Does a gzip resource exist?
+                if (resource!=null && resource.exists() && !resource.isDirectory())
+                {
+                    // Tell caches that response may vary by accept-encoding
+                    response.addHeader(HttpHeader.VARY.asString(),HttpHeader.ACCEPT_ENCODING.asString());
+                    
+                    // Does the client accept gzip?
+                    String accept=request.getHeader(HttpHeader.ACCEPT_ENCODING.asString());
+                    if (accept!=null && accept.indexOf("gzip")>=0)
+                        gzip=true;
+                }
+            }
+
+            // find resource
+            if (!gzip)
+            {
+                if (_cache==null)
+                    resource=getResource(pathInContext);
+                else
+                {
+                    content=_cache.lookup(pathInContext);
+                    resource=content==null?null:content.getResource();
+                }
+            }
+
+            if (LOG.isDebugEnabled())
+                LOG.debug("uri="+request.getRequestURI()+" resource="+resource+(content!=null?" content":""));
+
+            // Handle resource
+            if (resource==null || !resource.exists())
+            {
+                if (included)
+                    throw new FileNotFoundException("!" + pathInContext);
+                response.sendError(HttpServletResponse.SC_NOT_FOUND);
+            }
+            else if (!resource.isDirectory())
+            {
+                if (endsWithSlash && pathInContext.length()>1)
+                {
+                    String q=request.getQueryString();
+                    pathInContext=pathInContext.substring(0,pathInContext.length()-1);
+                    if (q!=null&&q.length()!=0)
+                        pathInContext+="?"+q;
+                    response.sendRedirect(response.encodeRedirectURL(URIUtil.addPaths(_servletContext.getContextPath(),pathInContext)));
+                }
+                else
+                {
+                    // ensure we have content
+                    if (content==null)
+                        content=new HttpContent.ResourceAsHttpContent(resource,_mimeTypes.getMimeByExtension(resource.toString()),response.getBufferSize(),_etags);
+
+                    if (included.booleanValue() || passConditionalHeaders(request,response, resource,content))
+                    {
+                        if (gzip)
+                        {
+                            response.setHeader(HttpHeader.CONTENT_ENCODING.asString(),"gzip");
+                            String mt=_servletContext.getMimeType(pathInContext);
+                            if (mt!=null)
+                                response.setContentType(mt);
+                        }
+                        sendData(request,response,included.booleanValue(),resource,content,reqRanges);
+                    }
+                }
+            }
+            else
+            {
+                String welcome=null;
+
+                if (!endsWithSlash || (pathInContext.length()==1 && request.getAttribute("org.eclipse.jetty.server.nullPathInfo")!=null))
+                {
+                    StringBuffer buf=request.getRequestURL();
+                    synchronized(buf)
+                    {
+                        int param=buf.lastIndexOf(";");
+                        if (param<0)
+                            buf.append('/');
+                        else
+                            buf.insert(param,'/');
+                        String q=request.getQueryString();
+                        if (q!=null&&q.length()!=0)
+                        {
+                            buf.append('?');
+                            buf.append(q);
+                        }
+                        response.setContentLength(0);
+                        response.sendRedirect(response.encodeRedirectURL(buf.toString()));
+                    }
+                }
+                // else look for a welcome file
+                else if (null!=(welcome=getWelcomeFile(pathInContext)))
+                {
+                    LOG.debug("welcome={}",welcome);
+                    if (_redirectWelcome)
+                    {
+                        // Redirect to the index
+                        response.setContentLength(0);
+                        String q=request.getQueryString();
+                        if (q!=null&&q.length()!=0)
+                            response.sendRedirect(response.encodeRedirectURL(URIUtil.addPaths( _servletContext.getContextPath(),welcome)+"?"+q));
+                        else
+                            response.sendRedirect(response.encodeRedirectURL(URIUtil.addPaths( _servletContext.getContextPath(),welcome)));
+                    }
+                    else
+                    {
+                        // Forward to the index
+                        RequestDispatcher dispatcher=request.getRequestDispatcher(welcome);
+                        if (dispatcher!=null)
+                        {
+                            if (included.booleanValue())
+                                dispatcher.include(request,response);
+                            else
+                            {
+                                request.setAttribute("org.eclipse.jetty.server.welcome",welcome);
+                                dispatcher.forward(request,response);
+                            }
+                        }
+                    }
+                }
+                else
+                {
+                    content=new HttpContent.ResourceAsHttpContent(resource,_mimeTypes.getMimeByExtension(resource.toString()),_etags);
+                    if (included.booleanValue() || passConditionalHeaders(request,response, resource,content))
+                        sendDirectory(request,response,resource,pathInContext);
+                }
+            }
+        }
+        catch(IllegalArgumentException e)
+        {
+            LOG.warn(Log.EXCEPTION,e);
+            if(!response.isCommitted())
+                response.sendError(500, e.getMessage());
+        }
+        finally
+        {
+            if (content!=null)
+                content.release();
+            else if (resource!=null)
+                resource.close();
+        }
+
+    }
+
+    /* ------------------------------------------------------------ */
+    private boolean hasDefinedRange(Enumeration<String> reqRanges)
+    {
+        return (reqRanges!=null && reqRanges.hasMoreElements());
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    protected void doPost(HttpServletRequest request, HttpServletResponse response)
+    throws ServletException, IOException
+    {
+        doGet(request,response);
+    }
+
+    /* ------------------------------------------------------------ */
+    /* (non-Javadoc)
+     * @see javax.servlet.http.HttpServlet#doTrace(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
+     */
+    @Override
+    protected void doTrace(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
+    {
+        resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    protected void doOptions(HttpServletRequest req, HttpServletResponse resp)
+    throws ServletException, IOException
+    {
+        resp.setHeader("Allow", "GET,HEAD,POST,OPTIONS");
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Finds a matching welcome file for the supplied {@link Resource}. This will be the first entry in the list of
+     * configured {@link #_welcomes welcome files} that existing within the directory referenced by the <code>Resource</code>.
+     * If the resource is not a directory, or no matching file is found, then it may look for a valid servlet mapping.
+     * If there is none, then <code>null</code> is returned.
+     * The list of welcome files is read from the {@link ContextHandler} for this servlet, or
+     * <code>"index.jsp" , "index.html"</code> if that is <code>null</code>.
+     * @param resource
+     * @return The path of the matching welcome file in context or null.
+     * @throws IOException
+     * @throws MalformedURLException
+     */
+    private String getWelcomeFile(String pathInContext) throws MalformedURLException, IOException
+    {
+        if (_welcomes==null)
+            return null;
+
+        String welcome_servlet=null;
+        for (int i=0;i<_welcomes.length;i++)
+        {
+            String welcome_in_context=URIUtil.addPaths(pathInContext,_welcomes[i]);
+            Resource welcome=getResource(welcome_in_context);
+            if (welcome!=null && welcome.exists())
+                return _welcomes[i];
+
+            if ((_welcomeServlets || _welcomeExactServlets) && welcome_servlet==null)
+            {
+                MappedEntry<?> entry=_servletHandler.getHolderEntry(welcome_in_context);
+                if (entry!=null && entry.getValue()!=_defaultHolder &&
+                        (_welcomeServlets || (_welcomeExactServlets && entry.getKey().equals(welcome_in_context))))
+                    welcome_servlet=welcome_in_context;
+
+            }
+        }
+        return welcome_servlet;
+    }
+
+    /* ------------------------------------------------------------ */
+    /* Check modification date headers.
+     */
+    protected boolean passConditionalHeaders(HttpServletRequest request,HttpServletResponse response, Resource resource, HttpContent content)
+    throws IOException
+    {
+        try
+        {
+            if (!HttpMethod.HEAD.is(request.getMethod()))
+            {
+                if (_etags)
+                {
+                    String ifm=request.getHeader(HttpHeader.IF_MATCH.asString());
+                    if (ifm!=null)
+                    {
+                        boolean match=false;
+                        if (content.getETag()!=null)
+                        {
+                            QuotedStringTokenizer quoted = new QuotedStringTokenizer(ifm,", ",false,true);
+                            while (!match && quoted.hasMoreTokens())
+                            {
+                                String tag = quoted.nextToken();
+                                if (content.getETag().equals(tag))
+                                    match=true;
+                            }
+                        }
+
+                        if (!match)
+                        {
+                            response.setStatus(HttpServletResponse.SC_PRECONDITION_FAILED);
+                            return false;
+                        }
+                    }
+                    
+                    String if_non_match_etag=request.getHeader(HttpHeader.IF_NONE_MATCH.asString());
+                    if (if_non_match_etag!=null && content.getETag()!=null)
+                    {
+                        // Look for GzipFiltered version of etag
+                        if (content.getETag().equals(request.getAttribute("o.e.j.s.GzipFilter.ETag")))
+                        {
+                            response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
+                            response.setHeader(HttpHeader.ETAG.asString(),if_non_match_etag);
+                            return false;
+                        }
+                        
+                        // Handle special case of exact match.
+                        if (content.getETag().equals(if_non_match_etag))
+                        {
+                            response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
+                            response.setHeader(HttpHeader.ETAG.asString(),content.getETag());
+                            return false;
+                        }
+
+                        // Handle list of tags
+                        QuotedStringTokenizer quoted = new QuotedStringTokenizer(if_non_match_etag,", ",false,true);
+                        while (quoted.hasMoreTokens())
+                        {
+                            String tag = quoted.nextToken();
+                            if (content.getETag().equals(tag))
+                            {
+                                response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
+                                response.setHeader(HttpHeader.ETAG.asString(),content.getETag());
+                                return false;
+                            }
+                        }
+                        
+                        // If etag requires content to be served, then do not check if-modified-since
+                        return true;
+                    }
+                }
+                
+                // Handle if modified since
+                String ifms=request.getHeader(HttpHeader.IF_MODIFIED_SINCE.asString());
+                if (ifms!=null)
+                {
+                    //Get jetty's Response impl
+                    String mdlm=content.getLastModified();
+                    if (mdlm!=null && ifms.equals(mdlm))
+                    {
+                        response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
+                        if (_etags)
+                            response.setHeader(HttpHeader.ETAG.asString(),content.getETag());
+                        response.flushBuffer();
+                        return false;
+                    }
+
+                    long ifmsl=request.getDateHeader(HttpHeader.IF_MODIFIED_SINCE.asString());
+                    if (ifmsl!=-1 && resource.lastModified()/1000 <= ifmsl/1000)
+                    { 
+                        response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
+                        if (_etags)
+                            response.setHeader(HttpHeader.ETAG.asString(),content.getETag());
+                        response.flushBuffer();
+                        return false;
+                    }
+                }
+
+                // Parse the if[un]modified dates and compare to resource
+                long date=request.getDateHeader(HttpHeader.IF_UNMODIFIED_SINCE.asString());
+                if (date!=-1 && resource.lastModified()/1000 > date/1000)
+                {
+                    response.sendError(HttpServletResponse.SC_PRECONDITION_FAILED);
+                    return false;
+                }
+
+            }
+        }
+        catch(IllegalArgumentException iae)
+        {
+            if(!response.isCommitted())
+                response.sendError(400, iae.getMessage());
+            throw iae;
+        }
+        return true;
+    }
+
+
+    /* ------------------------------------------------------------------- */
+    protected void sendDirectory(HttpServletRequest request,
+            HttpServletResponse response,
+            Resource resource,
+            String pathInContext)
+    throws IOException
+    {
+        if (!_dirAllowed)
+        {
+            response.sendError(HttpServletResponse.SC_FORBIDDEN);
+            return;
+        }
+
+        byte[] data=null;
+        String base = URIUtil.addPaths(request.getRequestURI(),URIUtil.SLASH);
+
+        //If the DefaultServlet has a resource base set, use it
+        if (_resourceBase != null)
+        {
+            // handle ResourceCollection
+            if (_resourceBase instanceof ResourceCollection)
+                resource=_resourceBase.addPath(pathInContext);
+        }
+        //Otherwise, try using the resource base of its enclosing context handler
+        else if (_contextHandler.getBaseResource() instanceof ResourceCollection)
+            resource=_contextHandler.getBaseResource().addPath(pathInContext);
+
+        String dir = resource.getListHTML(base,pathInContext.length()>1);
+        if (dir==null)
+        {
+            response.sendError(HttpServletResponse.SC_FORBIDDEN,
+            "No directory");
+            return;
+        }
+
+        data=dir.getBytes("UTF-8");
+        response.setContentType("text/html; charset=UTF-8");
+        response.setContentLength(data.length);
+        response.getOutputStream().write(data);
+    }
+
+    /* ------------------------------------------------------------ */
+    protected void sendData(HttpServletRequest request,
+            HttpServletResponse response,
+            boolean include,
+            Resource resource,
+            HttpContent content,
+            Enumeration<String> reqRanges)
+    throws IOException
+    {
+        final long content_length = (content==null)?resource.length():content.getContentLength();
+
+        // Get the output stream (or writer)
+        OutputStream out =null;
+        boolean written;
+        try
+        {
+            out = response.getOutputStream();
+
+            // has a filter already written to the response?
+            written = out instanceof HttpOutput
+                ? ((HttpOutput)out).isWritten()
+                : true;
+        }
+        catch(IllegalStateException e)
+        {
+            out = new WriterOutputStream(response.getWriter());
+            written=true; // there may be data in writer buffer, so assume written
+        }
+
+        if ( reqRanges == null || !reqRanges.hasMoreElements() || content_length<0)
+        {
+            //  if there were no ranges, send entire entity
+            if (include)
+            {
+                resource.writeTo(out,0,content_length);
+            }
+            // else if we can't do a bypass write because of wrapping
+            else if (content==null || written || !(out instanceof HttpOutput))
+            {
+                // write normally
+                writeHeaders(response,content,written?-1:content_length);
+                ByteBuffer buffer = (content==null)?null:content.getIndirectBuffer();
+                if (buffer!=null)
+                    BufferUtil.writeTo(buffer,out);
+                else
+                    resource.writeTo(out,0,content_length);
+            }
+            // else do a bypass write
+            else
+            {
+                // write the headers
+                if (response instanceof Response)
+                {
+                    Response r = (Response)response;
+                    writeOptionHeaders(r.getHttpFields());
+                    r.setHeaders(content);
+                }
+                else
+                    writeHeaders(response,content,content_length);
+
+                // write the content asynchronously if supported
+                if (request.isAsyncSupported())
+                {
+                    final AsyncContext context = request.startAsync();
+
+                    ((HttpOutput)out).sendContent(content,new Callback()
+                    {
+                        @Override
+                        public void succeeded()
+                        {   
+                            context.complete();
+                        }
+
+                        @Override
+                        public void failed(Throwable x)
+                        {
+                            if (x instanceof IOException)
+                                LOG.debug(x);
+                            else
+                                LOG.warn(x);
+                            context.complete();
+                        }
+                    });
+                }
+                // otherwise write content blocking
+                else
+                {
+                    ((HttpOutput)out).sendContent(content);
+                }
+            }
+        }
+        else
+        {
+            // Parse the satisfiable ranges
+            List<InclusiveByteRange> ranges =InclusiveByteRange.satisfiableRanges(reqRanges,content_length);
+
+            //  if there are no satisfiable ranges, send 416 response
+            if (ranges==null || ranges.size()==0)
+            {
+                writeHeaders(response, content, content_length);
+                response.setStatus(HttpServletResponse.SC_REQUESTED_RANGE_NOT_SATISFIABLE);
+                response.setHeader(HttpHeader.CONTENT_RANGE.asString(),
+                        InclusiveByteRange.to416HeaderRangeString(content_length));
+                resource.writeTo(out,0,content_length);
+                return;
+            }
+
+            //  if there is only a single valid range (must be satisfiable
+            //  since were here now), send that range with a 216 response
+            if ( ranges.size()== 1)
+            {
+                InclusiveByteRange singleSatisfiableRange = ranges.get(0);
+                long singleLength = singleSatisfiableRange.getSize(content_length);
+                writeHeaders(response,content,singleLength                     );
+                response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);
+                if (!response.containsHeader(HttpHeader.DATE.asString()))
+                    response.addDateHeader(HttpHeader.DATE.asString(),System.currentTimeMillis());
+                response.setHeader(HttpHeader.CONTENT_RANGE.asString(),
+                        singleSatisfiableRange.toHeaderRangeString(content_length));
+                resource.writeTo(out,singleSatisfiableRange.getFirst(content_length),singleLength);
+                return;
+            }
+
+            //  multiple non-overlapping valid ranges cause a multipart
+            //  216 response which does not require an overall
+            //  content-length header
+            //
+            writeHeaders(response,content,-1);
+            String mimetype=(content==null?null:content.getContentType());
+            if (mimetype==null)
+                LOG.warn("Unknown mimetype for "+request.getRequestURI());
+            MultiPartOutputStream multi = new MultiPartOutputStream(out);
+            response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);
+            if (!response.containsHeader(HttpHeader.DATE.asString()))
+                response.addDateHeader(HttpHeader.DATE.asString(),System.currentTimeMillis());
+
+            // If the request has a "Request-Range" header then we need to
+            // send an old style multipart/x-byteranges Content-Type. This
+            // keeps Netscape and acrobat happy. This is what Apache does.
+            String ctp;
+            if (request.getHeader(HttpHeader.REQUEST_RANGE.asString())!=null)
+                ctp = "multipart/x-byteranges; boundary=";
+            else
+                ctp = "multipart/byteranges; boundary=";
+            response.setContentType(ctp+multi.getBoundary());
+
+            InputStream in=resource.getInputStream();
+            long pos=0;
+
+            // calculate the content-length
+            int length=0;
+            String[] header = new String[ranges.size()];
+            for (int i=0;i<ranges.size();i++)
+            {
+                InclusiveByteRange ibr = ranges.get(i);
+                header[i]=ibr.toHeaderRangeString(content_length);
+                length+=
+                    ((i>0)?2:0)+
+                    2+multi.getBoundary().length()+2+
+                    (mimetype==null?0:HttpHeader.CONTENT_TYPE.asString().length()+2+mimetype.length())+2+
+                    HttpHeader.CONTENT_RANGE.asString().length()+2+header[i].length()+2+
+                    2+
+                    (ibr.getLast(content_length)-ibr.getFirst(content_length))+1;
+            }
+            length+=2+2+multi.getBoundary().length()+2+2;
+            response.setContentLength(length);
+
+            for (int i=0;i<ranges.size();i++)
+            {
+                InclusiveByteRange ibr =  ranges.get(i);
+                multi.startPart(mimetype,new String[]{HttpHeader.CONTENT_RANGE+": "+header[i]});
+
+                long start=ibr.getFirst(content_length);
+                long size=ibr.getSize(content_length);
+                if (in!=null)
+                {
+                    // Handle non cached resource
+                    if (start<pos)
+                    {
+                        in.close();
+                        in=resource.getInputStream();
+                        pos=0;
+                    }
+                    if (pos<start)
+                    {
+                        in.skip(start-pos);
+                        pos=start;
+                    }
+                    
+                    IO.copy(in,multi,size);
+                    pos+=size;
+                }
+                else
+                    // Handle cached resource
+                    (resource).writeTo(multi,start,size);
+            }
+            if (in!=null)
+                in.close();
+            multi.close();
+        }
+        return;
+    }
+
+    /* ------------------------------------------------------------ */
+    protected void writeHeaders(HttpServletResponse response,HttpContent content,long count)
+    {        
+        if (content.getContentType()!=null && response.getContentType()==null)
+            response.setContentType(content.getContentType().toString());
+
+        if (response instanceof Response)
+        {
+            Response r=(Response)response;
+            HttpFields fields = r.getHttpFields();
+
+            if (content.getLastModified()!=null)
+                fields.put(HttpHeader.LAST_MODIFIED,content.getLastModified());
+            else if (content.getResource()!=null)
+            {
+                long lml=content.getResource().lastModified();
+                if (lml!=-1)
+                    fields.putDateField(HttpHeader.LAST_MODIFIED,lml);
+            }
+
+            if (count != -1)
+                r.setLongContentLength(count);
+
+            writeOptionHeaders(fields);
+            
+            if (_etags)
+                fields.put(HttpHeader.ETAG,content.getETag());
+        }
+        else
+        {
+            long lml=content.getResource().lastModified();
+            if (lml>=0)
+                response.setDateHeader(HttpHeader.LAST_MODIFIED.asString(),lml);
+
+            if (count != -1)
+            {
+                if (count<Integer.MAX_VALUE)
+                    response.setContentLength((int)count);
+                else
+                    response.setHeader(HttpHeader.CONTENT_LENGTH.asString(),Long.toString(count));
+            }
+
+            writeOptionHeaders(response);
+
+            if (_etags)
+                response.setHeader(HttpHeader.ETAG.asString(),content.getETag());
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    protected void writeOptionHeaders(HttpFields fields)
+    {
+        if (_acceptRanges)
+            fields.put(ACCEPT_RANGES);
+
+        if (_cacheControl!=null)
+            fields.put(_cacheControl);
+    }
+
+    /* ------------------------------------------------------------ */
+    protected void writeOptionHeaders(HttpServletResponse response)
+    {
+        if (_acceptRanges)
+            response.setHeader(HttpHeader.ACCEPT_RANGES.asString(),"bytes");
+
+        if (_cacheControl!=null)
+            response.setHeader(HttpHeader.CACHE_CONTROL.asString(),_cacheControl.getValue());
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.Servlet#destroy()
+     */
+    @Override
+    public void destroy()
+    {
+        if (_cache!=null)
+            _cache.flushCache();
+        super.destroy();
+    }
+
+}
diff --git a/lib/jetty/org/eclipse/jetty/servlet/ErrorPageErrorHandler.java b/lib/jetty/org/eclipse/jetty/servlet/ErrorPageErrorHandler.java
new file mode 100644 (file)
index 0000000..b8efeb1
--- /dev/null
@@ -0,0 +1,229 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.servlet;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+
+import org.eclipse.jetty.server.Dispatcher;
+import org.eclipse.jetty.server.handler.ContextHandler;
+import org.eclipse.jetty.server.handler.ErrorHandler;
+
+/* ------------------------------------------------------------ */
+/** Error Page Error Handler
+ *
+ * An ErrorHandler that maps exceptions and status codes to URIs for dispatch using
+ * the internal ERROR style of dispatch.
+ *
+ */
+public class ErrorPageErrorHandler extends ErrorHandler implements ErrorHandler.ErrorPageMapper
+{
+    public final static String GLOBAL_ERROR_PAGE = "org.eclipse.jetty.server.error_page.global";
+
+    protected ServletContext _servletContext;
+    private final Map<String,String> _errorPages= new HashMap<String,String>(); // code or exception to URL
+    private final List<ErrorCodeRange> _errorPageList=new ArrayList<ErrorCodeRange>(); // list of ErrorCode by range
+
+    /* ------------------------------------------------------------ */
+    public ErrorPageErrorHandler()
+    {}
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public String getErrorPage(HttpServletRequest request)
+    {
+        String error_page= null;
+
+        Throwable th= (Throwable)request.getAttribute(Dispatcher.ERROR_EXCEPTION);
+
+        // Walk the cause hierarchy
+        while (error_page == null && th != null )
+        {
+            Class<?> exClass=th.getClass();
+            error_page= (String)_errorPages.get(exClass.getName());
+
+            // walk the inheritance hierarchy
+            while (error_page == null)
+            {
+                exClass= exClass.getSuperclass();
+                if (exClass==null)
+                    break;
+                error_page= (String)_errorPages.get(exClass.getName());
+            }
+
+            th=(th instanceof ServletException)?((ServletException)th).getRootCause():null;
+        }
+
+        if (error_page == null)
+        {
+            // look for an exact code match
+            Integer code=(Integer)request.getAttribute(Dispatcher.ERROR_STATUS_CODE);
+            if (code!=null)
+            {
+                error_page= (String)_errorPages.get(Integer.toString(code));
+
+                // if still not found
+                if ((error_page == null) && (_errorPageList != null))
+                {
+                    // look for an error code range match.
+                    for (int i = 0; i < _errorPageList.size(); i++)
+                    {
+                        ErrorCodeRange errCode = (ErrorCodeRange) _errorPageList.get(i);
+                        if (errCode.isInRange(code))
+                        {
+                            error_page = errCode.getUri();
+                            break;
+                        }
+                    }
+                }
+            }
+        }
+
+        //try servlet 3.x global error page
+        if (error_page == null)
+            error_page = _errorPages.get(GLOBAL_ERROR_PAGE);
+        
+        return error_page;
+    }
+
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Returns the errorPages.
+     */
+    public Map<String,String> getErrorPages()
+    {
+        return _errorPages;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param errorPages The errorPages to set. A map of Exception class name  or error code as a string to URI string
+     */
+    public void setErrorPages(Map<String,String> errorPages)
+    {
+        _errorPages.clear();
+        if (errorPages!=null)
+            _errorPages.putAll(errorPages);
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Add Error Page mapping for an exception class
+     * This method is called as a result of an exception-type element in a web.xml file
+     * or may be called directly
+     * @param exception The exception
+     * @param uri The URI of the error page.
+     */
+    public void addErrorPage(Class<? extends Throwable> exception,String uri)
+    {
+        _errorPages.put(exception.getName(),uri);
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Add Error Page mapping for an exception class
+     * This method is called as a result of an exception-type element in a web.xml file
+     * or may be called directly
+     * @param exceptionClassName The exception
+     * @param uri The URI of the error page.
+     */
+    public void addErrorPage(String exceptionClassName,String uri)
+    {
+        _errorPages.put(exceptionClassName,uri);
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Add Error Page mapping for a status code.
+     * This method is called as a result of an error-code element in a web.xml file
+     * or may be called directly
+     * @param code The HTTP status code to match
+     * @param uri The URI of the error page.
+     */
+    public void addErrorPage(int code,String uri)
+    {
+        _errorPages.put(Integer.toString(code),uri);
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Add Error Page mapping for a status code range.
+     * This method is not available from web.xml and must be called
+     * directly.
+     * @param from The lowest matching status code
+     * @param to The highest matching status code
+     * @param uri The URI of the error page.
+     */
+    public void addErrorPage(int from, int to, String uri)
+    {
+        _errorPageList.add(new ErrorCodeRange(from, to, uri));
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    protected void doStart() throws Exception
+    {
+        super.doStart();
+        _servletContext=ContextHandler.getCurrentContext();
+    }
+
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    private class ErrorCodeRange
+    {
+        private int _from;
+        private int _to;
+        private String _uri;
+
+        ErrorCodeRange(int from, int to, String uri)
+            throws IllegalArgumentException
+        {
+            if (from > to)
+                throw new IllegalArgumentException("from>to");
+
+            _from = from;
+            _to = to;
+            _uri = uri;
+        }
+
+        boolean isInRange(int value)
+        {
+            if ((value >= _from) && (value <= _to))
+            {
+                return true;
+            }
+
+            return false;
+        }
+
+        String getUri()
+        {
+            return _uri;
+        }
+
+        @Override
+        public String toString()
+        {
+            return "from: " + _from + ",to: " + _to + ",uri: " + _uri;
+        }
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/servlet/FilterHolder.java b/lib/jetty/org/eclipse/jetty/servlet/FilterHolder.java
new file mode 100644 (file)
index 0000000..d9635fa
--- /dev/null
@@ -0,0 +1,289 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.servlet;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.EnumSet;
+import java.util.List;
+
+import javax.servlet.DispatcherType;
+import javax.servlet.Filter;
+import javax.servlet.FilterConfig;
+import javax.servlet.FilterRegistration;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+
+import org.eclipse.jetty.util.TypeUtil;
+import org.eclipse.jetty.util.component.Dumpable;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/* --------------------------------------------------------------------- */
+/**
+ *
+ */
+public class FilterHolder extends Holder<Filter>
+{
+    private static final Logger LOG = Log.getLogger(FilterHolder.class);
+
+    /* ------------------------------------------------------------ */
+    private transient Filter _filter;
+    private transient Config _config;
+    private transient FilterRegistration.Dynamic _registration;
+
+    /* ---------------------------------------------------------------- */
+    /** Constructor
+     */
+    public FilterHolder()
+    {
+        this(Source.EMBEDDED);
+    }
+
+
+    /* ---------------------------------------------------------------- */
+    /** Constructor
+     */
+    public FilterHolder(Holder.Source source)
+    {
+        super(source);
+    }
+
+    /* ---------------------------------------------------------------- */
+    /** Constructor
+     */
+    public FilterHolder(Class<? extends Filter> filter)
+    {
+        this(Source.EMBEDDED);
+        setHeldClass(filter);
+    }
+
+    /* ---------------------------------------------------------------- */
+    /** Constructor for existing filter.
+     */
+    public FilterHolder(Filter filter)
+    {
+        this(Source.EMBEDDED);
+        setFilter(filter);
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public void doStart()
+        throws Exception
+    {
+        super.doStart();
+
+        if (!javax.servlet.Filter.class
+            .isAssignableFrom(_class))
+        {
+            String msg = _class+" is not a javax.servlet.Filter";
+            super.stop();
+            throw new IllegalStateException(msg);
+        }
+    }
+    
+    
+    
+
+
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public void initialize() throws Exception
+    {
+        super.initialize();
+        
+        if (_filter==null)
+        {
+            try
+            {
+                ServletContext context=_servletHandler.getServletContext();
+                _filter=(context instanceof ServletContextHandler.Context)
+                    ?((ServletContextHandler.Context)context).createFilter(getHeldClass())
+                    :getHeldClass().newInstance();
+            }
+            catch (ServletException se)
+            {
+                Throwable cause = se.getRootCause();
+                if (cause instanceof InstantiationException)
+                    throw (InstantiationException)cause;
+                if (cause instanceof IllegalAccessException)
+                    throw (IllegalAccessException)cause;
+                throw se;
+            }
+        }
+
+        _config=new Config();
+        LOG.debug("Filter.init {}",_filter);
+        _filter.init(_config);
+    }
+
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public void doStop()
+        throws Exception
+    {
+        if (_filter!=null)
+        {
+            try
+            {
+                destroyInstance(_filter);
+            }
+            catch (Exception e)
+            {
+                LOG.warn(e);
+            }
+        }
+        if (!_extInstance)
+            _filter=null;
+
+        _config=null;
+        super.doStop();
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public void destroyInstance (Object o)
+        throws Exception
+    {
+        if (o==null)
+            return;
+        Filter f = (Filter)o;
+        f.destroy();
+        getServletHandler().destroyFilter(f);
+    }
+
+    /* ------------------------------------------------------------ */
+    public synchronized void setFilter(Filter filter)
+    {
+        _filter=filter;
+        _extInstance=true;
+        setHeldClass(filter.getClass());
+        if (getName()==null)
+            setName(filter.getClass().getName());
+    }
+
+    /* ------------------------------------------------------------ */
+    public Filter getFilter()
+    {
+        return _filter;
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public String toString()
+    {
+        return getName();
+    }
+    
+    /* ------------------------------------------------------------ */
+    @Override
+    public void dump(Appendable out, String indent) throws IOException
+    {
+        super.dump(out, indent);
+        if(_filter instanceof Dumpable) {
+            ((Dumpable) _filter).dump(out, indent);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    public FilterRegistration.Dynamic getRegistration()
+    {
+        if (_registration == null)
+            _registration = new Registration();
+        return _registration;
+    }
+
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    protected class Registration extends HolderRegistration implements FilterRegistration.Dynamic
+    {
+        public void addMappingForServletNames(EnumSet<DispatcherType> dispatcherTypes, boolean isMatchAfter, String... servletNames)
+        {
+            illegalStateIfContextStarted();
+            FilterMapping mapping = new FilterMapping();
+            mapping.setFilterHolder(FilterHolder.this);
+            mapping.setServletNames(servletNames);
+            mapping.setDispatcherTypes(dispatcherTypes);
+            if (isMatchAfter)
+                _servletHandler.addFilterMapping(mapping);
+            else
+                _servletHandler.prependFilterMapping(mapping);
+        }
+
+        public void addMappingForUrlPatterns(EnumSet<DispatcherType> dispatcherTypes, boolean isMatchAfter, String... urlPatterns)
+        {
+            illegalStateIfContextStarted();
+            FilterMapping mapping = new FilterMapping();
+            mapping.setFilterHolder(FilterHolder.this);
+            mapping.setPathSpecs(urlPatterns);
+            mapping.setDispatcherTypes(dispatcherTypes);
+            if (isMatchAfter)
+                _servletHandler.addFilterMapping(mapping);
+            else
+                _servletHandler.prependFilterMapping(mapping);
+        }
+
+        public Collection<String> getServletNameMappings()
+        {
+            FilterMapping[] mappings =_servletHandler.getFilterMappings();
+            List<String> names=new ArrayList<String>();
+            for (FilterMapping mapping : mappings)
+            {
+                if (mapping.getFilterHolder()!=FilterHolder.this)
+                    continue;
+                String[] servlets=mapping.getServletNames();
+                if (servlets!=null && servlets.length>0)
+                    names.addAll(Arrays.asList(servlets));
+            }
+            return names;
+        }
+
+        public Collection<String> getUrlPatternMappings()
+        {
+            FilterMapping[] mappings =_servletHandler.getFilterMappings();
+            List<String> patterns=new ArrayList<String>();
+            for (FilterMapping mapping : mappings)
+            {
+                if (mapping.getFilterHolder()!=FilterHolder.this)
+                    continue;
+                String[] specs=mapping.getPathSpecs();
+                patterns.addAll(TypeUtil.asList(specs));
+            }
+            return patterns;
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    class Config extends HolderConfig implements FilterConfig
+    {
+        /* ------------------------------------------------------------ */
+        public String getFilterName()
+        {
+            return _name;
+        }
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/servlet/FilterMapping.java b/lib/jetty/org/eclipse/jetty/servlet/FilterMapping.java
new file mode 100644 (file)
index 0000000..1d5a9a4
--- /dev/null
@@ -0,0 +1,294 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.servlet;
+
+import java.io.IOException;
+import java.util.EnumSet;
+
+import javax.servlet.DispatcherType;
+
+import org.eclipse.jetty.http.PathMap;
+import org.eclipse.jetty.server.Handler;
+import org.eclipse.jetty.util.TypeUtil;
+import org.eclipse.jetty.util.annotation.ManagedAttribute;
+import org.eclipse.jetty.util.annotation.ManagedObject;
+import org.eclipse.jetty.util.component.ContainerLifeCycle;
+import org.eclipse.jetty.util.component.Dumpable;
+
+@ManagedObject("Filter Mappings")
+public class FilterMapping implements Dumpable
+{
+    /** Dispatch types */
+    public static final int DEFAULT=0;
+    public static final int REQUEST=1;
+    public static final int FORWARD=2;
+    public static final int INCLUDE=4;
+    public static final int ERROR=8;
+    public static final int ASYNC=16;
+    public static final int ALL=31;
+
+
+    /* ------------------------------------------------------------ */
+    /** Dispatch type from name
+     */
+    public static DispatcherType dispatch(String type)
+    {
+        if ("request".equalsIgnoreCase(type))
+            return DispatcherType.REQUEST;
+        if ("forward".equalsIgnoreCase(type))
+            return DispatcherType.FORWARD;
+        if ("include".equalsIgnoreCase(type))
+            return DispatcherType.INCLUDE;
+        if ("error".equalsIgnoreCase(type))
+            return DispatcherType.ERROR;
+        if ("async".equalsIgnoreCase(type))
+            return DispatcherType.ASYNC;
+        throw new IllegalArgumentException(type);
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Dispatch type from name
+     */
+    public static int dispatch(DispatcherType type)
+    {
+       switch(type)
+       {
+         case REQUEST:
+                 return REQUEST;
+         case ASYNC:
+                 return ASYNC;
+         case FORWARD:
+                 return FORWARD;
+         case INCLUDE:
+                 return INCLUDE;
+         case ERROR:
+                 return ERROR;
+       }
+        throw new IllegalArgumentException(type.toString());
+    }
+
+
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+
+
+    private int _dispatches=DEFAULT;
+    private String _filterName;
+    private transient FilterHolder _holder;
+    private String[] _pathSpecs;
+    private String[] _servletNames;
+
+    /* ------------------------------------------------------------ */
+    public FilterMapping()
+    {}
+
+    /* ------------------------------------------------------------ */
+    /** Check if this filter applies to a path.
+     * @param path The path to check or null to just check type
+     * @param type The type of request: __REQUEST,__FORWARD,__INCLUDE, __ASYNC or __ERROR.
+     * @return True if this filter applies
+     */
+    boolean appliesTo(String path, int type)
+    {
+        if (appliesTo(type))
+        {
+            for (int i=0;i<_pathSpecs.length;i++)
+                if (_pathSpecs[i]!=null &&  PathMap.match(_pathSpecs[i], path,true))
+                    return true;
+        }
+
+        return false;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Check if this filter applies to a particular dispatch type.
+     * @param type The type of request:
+     *      {@link Handler#REQUEST}, {@link Handler#FORWARD}, {@link Handler#INCLUDE} or {@link Handler#ERROR}.
+     * @return <code>true</code> if this filter applies
+     */
+    boolean appliesTo(int type)
+    {
+       if (_dispatches==0)
+               return type==REQUEST || type==ASYNC && _holder.isAsyncSupported();
+        return (_dispatches&type)!=0;
+    }
+
+    /* ------------------------------------------------------------ */
+    public boolean appliesTo(DispatcherType t)
+    {
+        return appliesTo(dispatch(t));
+    }
+    
+    /* ------------------------------------------------------------ */
+    public boolean isDefaultDispatches()
+    {
+        return _dispatches==0;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Returns the filterName.
+     */
+    @ManagedAttribute(value="filter name", readonly=true)
+    public String getFilterName()
+    {
+        return _filterName;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Returns the holder.
+     */
+    FilterHolder getFilterHolder()
+    {
+        return _holder;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Returns the pathSpec.
+     */
+    @ManagedAttribute(value="url patterns", readonly=true)
+    public String[] getPathSpecs()
+    {
+        return _pathSpecs;
+    }
+
+    /* ------------------------------------------------------------ */
+    public void setDispatcherTypes(EnumSet<DispatcherType> dispatcherTypes)
+    {
+        _dispatches=DEFAULT;
+        if (dispatcherTypes!=null)
+        {
+            if (dispatcherTypes.contains(DispatcherType.ERROR))
+                _dispatches|=ERROR;
+            if (dispatcherTypes.contains(DispatcherType.FORWARD))
+                _dispatches|=FORWARD;
+            if (dispatcherTypes.contains(DispatcherType.INCLUDE))
+                _dispatches|=INCLUDE;
+            if (dispatcherTypes.contains(DispatcherType.REQUEST))
+                _dispatches|=REQUEST;
+            if (dispatcherTypes.contains(DispatcherType.ASYNC))
+                _dispatches|=ASYNC;
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param dispatches The dispatches to set.
+     * @see #DEFAULT
+     * @see #REQUEST
+     * @see #ERROR
+     * @see #FORWARD
+     * @see #INCLUDE
+     */
+    public void setDispatches(int dispatches)
+    {
+        _dispatches = dispatches;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param filterName The filterName to set.
+     */
+    public void setFilterName(String filterName)
+    {
+        _filterName = filterName;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param holder The holder to set.
+     */
+    void setFilterHolder(FilterHolder holder)
+    {
+        _holder = holder;
+        setFilterName(holder.getName());
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param pathSpecs The Path specifications to which this filter should be mapped.
+     */
+    public void setPathSpecs(String[] pathSpecs)
+    {
+        _pathSpecs = pathSpecs;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param pathSpec The pathSpec to set.
+     */
+    public void setPathSpec(String pathSpec)
+    {
+        _pathSpecs = new String[]{pathSpec};
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Returns the servletName.
+     */
+    @ManagedAttribute(value="servlet names", readonly=true)
+    public String[] getServletNames()
+    {
+        return _servletNames;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param servletNames Maps the {@link #setFilterName(String) named filter} to multiple servlets
+     * @see #setServletName
+     */
+    public void setServletNames(String[] servletNames)
+    {
+        _servletNames = servletNames;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param servletName Maps the {@link #setFilterName(String) named filter} to a single servlet
+     * @see #setServletNames
+     */
+    public void setServletName(String servletName)
+    {
+        _servletNames = new String[]{servletName};
+    }
+
+    /* ------------------------------------------------------------ */
+    public String toString()
+    {
+        return
+        TypeUtil.asList(_pathSpecs)+"/"+
+        TypeUtil.asList(_servletNames)+"=="+
+        _dispatches+"=>"+
+        _filterName;
+    }
+
+    /* ------------------------------------------------------------ */
+    public void dump(Appendable out, String indent) throws IOException
+    {
+        out.append(String.valueOf(this)).append("\n");
+    }
+
+    /* ------------------------------------------------------------ */
+    public String dump()
+    {
+        return ContainerLifeCycle.dump(this);
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/servlet/Holder.java b/lib/jetty/org/eclipse/jetty/servlet/Holder.java
new file mode 100644 (file)
index 0000000..2f690b1
--- /dev/null
@@ -0,0 +1,318 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.servlet;
+
+import java.io.IOException;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import javax.servlet.Registration;
+import javax.servlet.ServletContext;
+
+import org.eclipse.jetty.util.annotation.ManagedAttribute;
+import org.eclipse.jetty.util.annotation.ManagedObject;
+import org.eclipse.jetty.util.component.ContainerLifeCycle;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+
+/* --------------------------------------------------------------------- */
+/**
+ * Holder
+ * 
+ * Specialization of AbstractHolder for servlet-related classes that 
+ * have init-params etc
+ * 
+ */
+@ManagedObject("Holder - a container for servlets and the like")
+public class Holder<T> extends BaseHolder<T>
+{
+    private static final Logger LOG = Log.getLogger(Holder.class);
+
+    protected final Map<String,String> _initParams=new HashMap<String,String>(3);
+    protected String _displayName;
+    protected boolean _asyncSupported;
+    protected String _name;
+
+
+    /* ---------------------------------------------------------------- */
+    protected Holder(Source source)
+    {
+        super(source);
+        switch(_source)
+        {
+            case JAVAX_API:
+            case DESCRIPTOR:
+            case ANNOTATION:
+                _asyncSupported=false;
+                break;
+            default:
+                _asyncSupported=true;
+        }
+    }
+
+  
+
+    /* ------------------------------------------------------------ */
+    @ManagedAttribute(value="Display Name", readonly=true)
+    public String getDisplayName()
+    {
+        return _displayName;
+    }
+
+    /* ---------------------------------------------------------------- */
+    public String getInitParameter(String param)
+    {
+        if (_initParams==null)
+            return null;
+        return (String)_initParams.get(param);
+    }
+
+    /* ------------------------------------------------------------ */
+    public Enumeration getInitParameterNames()
+    {
+        if (_initParams==null)
+            return Collections.enumeration(Collections.EMPTY_LIST);
+        return Collections.enumeration(_initParams.keySet());
+    }
+
+    /* ---------------------------------------------------------------- */
+    @ManagedAttribute(value="Initial Parameters", readonly=true)
+    public Map<String,String> getInitParameters()
+    {
+        return _initParams;
+    }
+
+    /* ------------------------------------------------------------ */
+    @ManagedAttribute(value="Name", readonly=true)
+    public String getName()
+    {
+        return _name;
+    }
+
+  
+    /* ------------------------------------------------------------ */
+    public void destroyInstance(Object instance)
+    throws Exception
+    {
+    }
+    /* ------------------------------------------------------------ */
+    /**
+     * @param className The className to set.
+     */
+    public void setClassName(String className)
+    {
+        super.setClassName(className);
+        if (_name==null)
+            _name=className+"-"+Integer.toHexString(this.hashCode());
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param held The class to hold
+     */
+    public void setHeldClass(Class<? extends T> held)
+    {
+        super.setHeldClass(held);
+        if (held!=null)
+        {
+            if (_name==null)
+                _name=held.getName()+"-"+Integer.toHexString(this.hashCode());
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    public void setDisplayName(String name)
+    {
+        _displayName=name;
+    }
+
+    /* ------------------------------------------------------------ */
+    public void setInitParameter(String param,String value)
+    {
+        _initParams.put(param,value);
+    }
+
+    /* ---------------------------------------------------------------- */
+    public void setInitParameters(Map<String,String> map)
+    {
+        _initParams.clear();
+        _initParams.putAll(map);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * The name is a primary key for the held object.
+     * Ensure that the name is set BEFORE adding a Holder
+     * (eg ServletHolder or FilterHolder) to a ServletHandler.
+     * @param name The name to set.
+     */
+    public void setName(String name)
+    {
+        _name = name;
+    }
+
+
+    /* ------------------------------------------------------------ */
+    public void setAsyncSupported(boolean suspendable)
+    {
+        _asyncSupported=suspendable;
+    }
+
+    /* ------------------------------------------------------------ */
+    public boolean isAsyncSupported()
+    {
+        return _asyncSupported;
+    }
+
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public void dump(Appendable out, String indent) throws IOException
+    {
+        super.dump(out,indent);
+        ContainerLifeCycle.dump(out,indent,_initParams.entrySet());
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public String dump()
+    {
+        return super.dump();
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public String toString()
+    {
+        return String.format("%s@%x==%s",_name,hashCode(),_className);
+    }
+    
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    protected class HolderConfig
+    {
+
+        /* -------------------------------------------------------- */
+        public ServletContext getServletContext()
+        {
+            return _servletHandler.getServletContext();
+        }
+
+        /* -------------------------------------------------------- */
+        public String getInitParameter(String param)
+        {
+            return Holder.this.getInitParameter(param);
+        }
+
+        /* -------------------------------------------------------- */
+        public Enumeration getInitParameterNames()
+        {
+            return Holder.this.getInitParameterNames();
+        }
+    }
+
+    /* -------------------------------------------------------- */
+    /* -------------------------------------------------------- */
+    /* -------------------------------------------------------- */
+    protected class HolderRegistration implements Registration.Dynamic
+    {
+        public void setAsyncSupported(boolean isAsyncSupported)
+        {
+            illegalStateIfContextStarted();
+            Holder.this.setAsyncSupported(isAsyncSupported);
+        }
+
+        public void setDescription(String description)
+        {
+            if (LOG.isDebugEnabled())
+                LOG.debug(this+" is "+description);
+        }
+
+        public String getClassName()
+        {
+            return Holder.this.getClassName();
+        }
+
+        public String getInitParameter(String name)
+        {
+            return Holder.this.getInitParameter(name);
+        }
+
+        public Map<String, String> getInitParameters()
+        {
+            return Holder.this.getInitParameters();
+        }
+
+        public String getName()
+        {
+            return Holder.this.getName();
+        }
+
+        public boolean setInitParameter(String name, String value)
+        {
+            illegalStateIfContextStarted();
+            if (name == null) {
+                throw new IllegalArgumentException("init parameter name required");
+            }
+            if (value == null) {
+                throw new IllegalArgumentException("non-null value required for init parameter " + name);
+            }
+            if (Holder.this.getInitParameter(name)!=null)
+                return false;
+            Holder.this.setInitParameter(name,value);
+            return true;
+        }
+
+        public Set<String> setInitParameters(Map<String, String> initParameters)
+        {
+            illegalStateIfContextStarted();
+            Set<String> clash=null;
+            for (Map.Entry<String, String> entry : initParameters.entrySet())
+            {
+                if (entry.getKey() == null) {
+                    throw new IllegalArgumentException("init parameter name required");
+                }
+                if (entry.getValue() == null) {
+                    throw new IllegalArgumentException("non-null value required for init parameter " + entry.getKey());
+                }
+                if (Holder.this.getInitParameter(entry.getKey())!=null)
+                {
+                    if (clash==null)
+                        clash=new HashSet<String>();
+                    clash.add(entry.getKey());
+                }
+            }
+            if (clash!=null)
+                return clash;
+            Holder.this.getInitParameters().putAll(initParameters);
+            return Collections.emptySet();
+        }
+    }
+}
+
+
+
+
+
diff --git a/lib/jetty/org/eclipse/jetty/servlet/Invoker.java b/lib/jetty/org/eclipse/jetty/servlet/Invoker.java
new file mode 100644 (file)
index 0000000..4613144
--- /dev/null
@@ -0,0 +1,314 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.servlet;
+
+import java.io.IOException;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Locale;
+import java.util.Map;
+
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+import javax.servlet.UnavailableException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletRequestWrapper;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.server.Dispatcher;
+import org.eclipse.jetty.server.Handler;
+import org.eclipse.jetty.server.HttpChannel;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.handler.ContextHandler;
+import org.eclipse.jetty.server.handler.HandlerWrapper;
+import org.eclipse.jetty.util.ArrayUtil;
+import org.eclipse.jetty.util.URIUtil;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/* ------------------------------------------------------------ */
+/**  Dynamic Servlet Invoker.
+ * This servlet invokes anonymous servlets that have not been defined
+ * in the web.xml or by other means. The first element of the pathInfo
+ * of a request passed to the envoker is treated as a servlet name for
+ * an existing servlet, or as a class name of a new servlet.
+ * This servlet is normally mapped to /servlet/*
+ * This servlet support the following initParams:
+ * <PRE>
+ *  nonContextServlets       If false, the invoker can only load
+ *                           servlets from the contexts classloader.
+ *                           This is false by default and setting this
+ *                           to true may have security implications.
+ *
+ *  verbose                  If true, log dynamic loads
+ *
+ *  *                        All other parameters are copied to the
+ *                           each dynamic servlet as init parameters
+ * </PRE>
+ * @version $Id: Invoker.java 4780 2009-03-17 15:36:08Z jesse $
+ *
+ */
+public class Invoker extends HttpServlet
+{
+    private static final Logger LOG = Log.getLogger(Invoker.class);
+
+
+    private ContextHandler _contextHandler;
+    private ServletHandler _servletHandler;
+    private Map.Entry _invokerEntry;
+    private Map _parameters;
+    private boolean _nonContextServlets;
+    private boolean _verbose;
+
+    /* ------------------------------------------------------------ */
+    public void init()
+    {
+        ServletContext config=getServletContext();
+        _contextHandler=((ContextHandler.Context)config).getContextHandler();
+
+        Handler handler=_contextHandler.getHandler();
+        while (handler!=null && !(handler instanceof ServletHandler) && (handler instanceof HandlerWrapper))
+            handler=((HandlerWrapper)handler).getHandler();
+        _servletHandler = (ServletHandler)handler;
+        Enumeration e = getInitParameterNames();
+        while(e.hasMoreElements())
+        {
+            String param=(String)e.nextElement();
+            String value=getInitParameter(param);
+            String lvalue=value.toLowerCase(Locale.ENGLISH);
+            if ("nonContextServlets".equals(param))
+            {
+                _nonContextServlets=value.length()>0 && lvalue.startsWith("t");
+            }
+            if ("verbose".equals(param))
+            {
+                _verbose=value.length()>0 && lvalue.startsWith("t");
+            }
+            else
+            {
+                if (_parameters==null)
+                    _parameters=new HashMap();
+                _parameters.put(param,value);
+            }
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    protected void service(HttpServletRequest request, HttpServletResponse response)
+       throws ServletException, IOException
+    {
+        // Get the requested path and info
+        boolean included=false;
+        String servlet_path=(String)request.getAttribute(Dispatcher.INCLUDE_SERVLET_PATH);
+        if (servlet_path==null)
+            servlet_path=request.getServletPath();
+        else
+            included=true;
+        String path_info = (String)request.getAttribute(Dispatcher.INCLUDE_PATH_INFO);
+        if (path_info==null)
+            path_info=request.getPathInfo();
+
+        // Get the servlet class
+        String servlet = path_info;
+        if (servlet==null || servlet.length()<=1 )
+        {
+            response.sendError(404);
+            return;
+        }
+
+
+        int i0=servlet.charAt(0)=='/'?1:0;
+        int i1=servlet.indexOf('/',i0);
+        servlet=i1<0?servlet.substring(i0):servlet.substring(i0,i1);
+
+        // look for a named holder
+        ServletHolder[] holders = _servletHandler.getServlets();
+        ServletHolder holder = getHolder (holders, servlet);
+
+        if (holder!=null)
+        {
+            // Found a named servlet (from a user's web.xml file) so
+            // now we add a mapping for it
+            if (LOG.isDebugEnabled())
+                LOG.debug("Adding servlet mapping for named servlet:"+servlet+":"+URIUtil.addPaths(servlet_path,servlet)+"/*");
+            ServletMapping mapping = new ServletMapping();
+            mapping.setServletName(servlet);
+            mapping.setPathSpec(URIUtil.addPaths(servlet_path,servlet)+"/*");
+            _servletHandler.setServletMappings((ServletMapping[])ArrayUtil.addToArray(_servletHandler.getServletMappings(), mapping, ServletMapping.class));
+        }
+        else
+        {
+            // look for a class mapping
+            if (servlet.endsWith(".class"))
+                servlet=servlet.substring(0,servlet.length()-6);
+            if (servlet==null || servlet.length()==0)
+            {
+                response.sendError(404);
+                return;
+            }
+
+            synchronized(_servletHandler)
+            {
+                // find the entry for the invoker (me)
+                 _invokerEntry=_servletHandler.getHolderEntry(servlet_path);
+
+                // Check for existing mapping (avoid threaded race).
+                String path=URIUtil.addPaths(servlet_path,servlet);
+                Map.Entry entry = _servletHandler.getHolderEntry(path);
+
+                if (entry!=null && !entry.equals(_invokerEntry))
+                {
+                    // Use the holder
+                    holder=(ServletHolder)entry.getValue();
+                }
+                else
+                {
+                    // Make a holder
+                    if (LOG.isDebugEnabled())
+                        LOG.debug("Making new servlet="+servlet+" with path="+path+"/*");
+                    holder=_servletHandler.addServletWithMapping(servlet, path+"/*");
+
+                    if (_parameters!=null)
+                        holder.setInitParameters(_parameters);
+
+                    try {holder.start();}
+                    catch (Exception e)
+                    {
+                        LOG.debug(e);
+                        throw new UnavailableException(e.toString());
+                    }
+
+                    // Check it is from an allowable classloader
+                    if (!_nonContextServlets)
+                    {
+                        Object s=holder.getServlet();
+
+                        if (_contextHandler.getClassLoader()!=
+                            s.getClass().getClassLoader())
+                        {
+                            try
+                            {
+                                holder.stop();
+                            }
+                            catch (Exception e)
+                            {
+                                LOG.ignore(e);
+                            }
+
+                            LOG.warn("Dynamic servlet "+s+
+                                         " not loaded from context "+
+                                         request.getContextPath());
+                            throw new UnavailableException("Not in context");
+                        }
+                    }
+
+                    if (_verbose && LOG.isDebugEnabled())
+                        LOG.debug("Dynamic load '"+servlet+"' at "+path);
+                }
+            }
+        }
+
+        if (holder!=null)
+        {
+            final Request baseRequest=(request instanceof Request)?((Request)request):HttpChannel.getCurrentHttpChannel().getRequest();
+            holder.handle(baseRequest,
+                    new InvokedRequest(request,included,servlet,servlet_path,path_info),
+                          response);
+        }
+        else
+        {
+            LOG.info("Can't find holder for servlet: "+servlet);
+            response.sendError(404);
+        }
+
+
+    }
+
+    /* ------------------------------------------------------------ */
+    class InvokedRequest extends HttpServletRequestWrapper
+    {
+        String _servletPath;
+        String _pathInfo;
+        boolean _included;
+
+        /* ------------------------------------------------------------ */
+        InvokedRequest(HttpServletRequest request,
+                boolean included,
+                String name,
+                String servletPath,
+                String pathInfo)
+        {
+            super(request);
+            _included=included;
+            _servletPath=URIUtil.addPaths(servletPath,name);
+            _pathInfo=pathInfo.substring(name.length()+1);
+            if (_pathInfo.length()==0)
+                _pathInfo=null;
+        }
+
+        /* ------------------------------------------------------------ */
+        public String getServletPath()
+        {
+            if (_included)
+                return super.getServletPath();
+            return _servletPath;
+        }
+
+        /* ------------------------------------------------------------ */
+        public String getPathInfo()
+        {
+            if (_included)
+                return super.getPathInfo();
+            return _pathInfo;
+        }
+
+        /* ------------------------------------------------------------ */
+        public Object getAttribute(String name)
+        {
+            if (_included)
+            {
+                if (name.equals(Dispatcher.INCLUDE_REQUEST_URI))
+                    return URIUtil.addPaths(URIUtil.addPaths(getContextPath(),_servletPath),_pathInfo);
+                if (name.equals(Dispatcher.INCLUDE_PATH_INFO))
+                    return _pathInfo;
+                if (name.equals(Dispatcher.INCLUDE_SERVLET_PATH))
+                    return _servletPath;
+            }
+            return super.getAttribute(name);
+        }
+    }
+
+
+    private ServletHolder getHolder(ServletHolder[] holders, String servlet)
+    {
+        if (holders == null)
+            return null;
+
+        ServletHolder holder = null;
+        for (int i=0; holder==null && i<holders.length; i++)
+        {
+            if (holders[i].getName().equals(servlet))
+            {
+                holder = holders[i];
+            }
+        }
+        return holder;
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/servlet/JspPropertyGroupServlet.java b/lib/jetty/org/eclipse/jetty/servlet/JspPropertyGroupServlet.java
new file mode 100644 (file)
index 0000000..d0fcbfe
--- /dev/null
@@ -0,0 +1,144 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.servlet;
+
+import java.io.IOException;
+
+import javax.servlet.GenericServlet;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+
+import org.eclipse.jetty.server.Dispatcher;
+import org.eclipse.jetty.server.handler.ContextHandler;
+import org.eclipse.jetty.util.URIUtil;
+import org.eclipse.jetty.util.resource.Resource;
+
+
+/* ------------------------------------------------------------ */
+/** Servlet handling JSP Property Group mappings
+ * <p>
+ * This servlet is mapped to by any URL pattern for a JSP property group. 
+ * Resources handled by this servlet that are not directories will be passed
+ * directly to the JSP servlet.    Resources that are directories will be 
+ * passed directly to the default servlet.
+ */
+public class JspPropertyGroupServlet extends GenericServlet
+{
+    private static final long serialVersionUID = 3681783214726776945L;
+    
+    public final static String NAME = "__org.eclipse.jetty.servlet.JspPropertyGroupServlet__";
+    private final ServletHandler _servletHandler;
+    private final ContextHandler _contextHandler;
+    private ServletHolder _dftServlet;
+    private ServletHolder _jspServlet;
+    private boolean _starJspMapped;
+    
+    public JspPropertyGroupServlet(ContextHandler context, ServletHandler servletHandler)
+    {
+        _contextHandler=context;
+        _servletHandler=servletHandler;        
+    }
+    
+    @Override
+    public void init() throws ServletException
+    {            
+        String jsp_name = "jsp";
+        ServletMapping servlet_mapping =_servletHandler.getServletMapping("*.jsp");
+        if (servlet_mapping!=null)
+        {
+            _starJspMapped=true;
+           
+            //now find the jsp servlet, ignoring the mapping that is for ourself
+            ServletMapping[] mappings = _servletHandler.getServletMappings();
+            for (ServletMapping m:mappings)
+            {
+                String[] paths = m.getPathSpecs();
+                if (paths!=null)
+                {
+                    for (String path:paths)
+                    {
+                        if ("*.jsp".equals(path) && !NAME.equals(m.getServletName()))
+                            servlet_mapping = m;
+                    }
+                }
+            }
+            
+            jsp_name=servlet_mapping.getServletName();
+        }
+        _jspServlet=_servletHandler.getServlet(jsp_name);
+        
+        String dft_name="default";
+        ServletMapping default_mapping=_servletHandler.getServletMapping("/");
+        if (default_mapping!=null)
+            dft_name=default_mapping.getServletName();
+        _dftServlet=_servletHandler.getServlet(dft_name);
+    }
+
+    @Override
+    public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException
+    {           
+        HttpServletRequest request = null;
+        if (req instanceof HttpServletRequest)
+            request = (HttpServletRequest)req;
+        else
+            throw new ServletException("Request not HttpServletRequest");
+
+        String servletPath=null;
+        String pathInfo=null;
+        if (request.getAttribute(Dispatcher.INCLUDE_REQUEST_URI)!=null)
+        {
+            servletPath=(String)request.getAttribute(Dispatcher.INCLUDE_SERVLET_PATH);
+            pathInfo=(String)request.getAttribute(Dispatcher.INCLUDE_PATH_INFO);
+            if (servletPath==null)
+            {
+                servletPath=request.getServletPath();
+                pathInfo=request.getPathInfo();
+            }
+        }
+        else
+        {
+            servletPath = request.getServletPath();
+            pathInfo = request.getPathInfo();
+        }
+        
+        String pathInContext=URIUtil.addPaths(servletPath,pathInfo);
+        
+        if (pathInContext.endsWith("/"))
+        {
+            _dftServlet.getServlet().service(req,res);
+        }
+        else if (_starJspMapped && pathInContext.toLowerCase().endsWith(".jsp"))
+        {
+            _jspServlet.getServlet().service(req,res);
+        }
+        else
+        {
+         
+            Resource resource = _contextHandler.getResource(pathInContext);
+            if (resource!=null && resource.isDirectory())
+                _dftServlet.getServlet().service(req,res);
+            else
+                _jspServlet.getServlet().service(req,res);
+        }
+        
+    }
+
+}
diff --git a/lib/jetty/org/eclipse/jetty/servlet/ListenerHolder.java b/lib/jetty/org/eclipse/jetty/servlet/ListenerHolder.java
new file mode 100644 (file)
index 0000000..346d478
--- /dev/null
@@ -0,0 +1,65 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+
+package org.eclipse.jetty.servlet;
+
+import java.util.EventListener;
+
+/**
+ * ListenerHolder
+ *
+ * Specialization of AbstractHolder for servlet listeners. This
+ * allows us to record where the listener originated - web.xml,
+ * annotation, api etc.
+ */
+public class ListenerHolder extends BaseHolder<EventListener>
+{
+    private EventListener _listener;
+    
+
+    public ListenerHolder(Source source)
+    {
+        super(source);
+    }
+   
+    
+    public void setListener(EventListener listener)
+    {
+        _listener = listener;
+        setClassName(listener.getClass().getName());
+        setHeldClass(listener.getClass());
+        _extInstance=true;
+    }
+
+    public EventListener getListener()
+    {
+        return _listener;
+    }
+
+
+    @Override
+    public void doStart() throws Exception
+    {
+        //Listeners always have an instance eagerly created, it cannot be deferred to the doStart method
+        if (_listener == null)
+            throw new IllegalStateException("No listener instance");
+        
+        super.doStart();
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/servlet/NoJspServlet.java b/lib/jetty/org/eclipse/jetty/servlet/NoJspServlet.java
new file mode 100644 (file)
index 0000000..afd066b
--- /dev/null
@@ -0,0 +1,45 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.servlet;
+
+import java.io.IOException;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+public class NoJspServlet extends HttpServlet
+{
+    private boolean _warned;
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.http.HttpServlet#doGet(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
+     */
+    protected void doGet(HttpServletRequest req, HttpServletResponse response) throws ServletException, IOException
+    {
+        if (!_warned)
+            getServletContext().log("No JSP support.  Check that JSP jars are in lib/jsp and that the JSP option has been specified to start.jar");
+        _warned=true;
+
+        response.sendError(500,"JSP support not configured");
+    }
+
+}
diff --git a/lib/jetty/org/eclipse/jetty/servlet/ServletContextHandler.java b/lib/jetty/org/eclipse/jetty/servlet/ServletContextHandler.java
new file mode 100644 (file)
index 0000000..d01ddd2
--- /dev/null
@@ -0,0 +1,1367 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.servlet;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.EventListener;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import javax.servlet.DispatcherType;
+import javax.servlet.Filter;
+import javax.servlet.FilterRegistration;
+import javax.servlet.RequestDispatcher;
+import javax.servlet.Servlet;
+import javax.servlet.ServletContainerInitializer;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletContextEvent;
+import javax.servlet.ServletContextListener;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRegistration;
+import javax.servlet.ServletSecurityElement;
+import javax.servlet.SessionCookieConfig;
+import javax.servlet.SessionTrackingMode;
+import javax.servlet.descriptor.JspConfigDescriptor;
+import javax.servlet.descriptor.JspPropertyGroupDescriptor;
+import javax.servlet.descriptor.TaglibDescriptor;
+
+import org.eclipse.jetty.security.ConstraintAware;
+import org.eclipse.jetty.security.ConstraintMapping;
+import org.eclipse.jetty.security.ConstraintSecurityHandler;
+import org.eclipse.jetty.security.SecurityHandler;
+import org.eclipse.jetty.server.Dispatcher;
+import org.eclipse.jetty.server.Handler;
+import org.eclipse.jetty.server.HandlerContainer;
+import org.eclipse.jetty.server.handler.ContextHandler;
+import org.eclipse.jetty.server.handler.ErrorHandler;
+import org.eclipse.jetty.server.handler.HandlerCollection;
+import org.eclipse.jetty.server.handler.HandlerWrapper;
+import org.eclipse.jetty.server.session.SessionHandler;
+import org.eclipse.jetty.servlet.BaseHolder.Source;
+import org.eclipse.jetty.util.annotation.ManagedAttribute;
+import org.eclipse.jetty.util.annotation.ManagedObject;
+import org.eclipse.jetty.util.component.LifeCycle;
+
+
+/* ------------------------------------------------------------ */
+/** Servlet Context.
+ * This extension to the ContextHandler allows for
+ * simple construction of a context with ServletHandler and optionally
+ * session and security handlers, et.<pre>
+ *   new ServletContext("/context",Context.SESSIONS|Context.NO_SECURITY);
+ * </pre>
+ * <p/>
+ * This class should have been called ServletContext, but this would have
+ * cause confusion with {@link ServletContext}.
+ */
+@ManagedObject("Servlet Context Handler")
+public class ServletContextHandler extends ContextHandler
+{
+    public final static int SESSIONS=1;
+    public final static int SECURITY=2;
+    public final static int NO_SESSIONS=0;
+    public final static int NO_SECURITY=0;
+    
+    public interface ServletContainerInitializerCaller extends LifeCycle {};
+
+    protected final List<Decorator> _decorators= new ArrayList<>();
+    protected Class<? extends SecurityHandler> _defaultSecurityHandlerClass=org.eclipse.jetty.security.ConstraintSecurityHandler.class;
+    protected SessionHandler _sessionHandler;
+    protected SecurityHandler _securityHandler;
+    protected ServletHandler _servletHandler;
+    protected int _options;
+    protected JspConfigDescriptor _jspConfig;
+
+    /* ------------------------------------------------------------ */
+    public ServletContextHandler()
+    {
+        this(null,null,null,null,null);
+    }
+
+    /* ------------------------------------------------------------ */
+    public ServletContextHandler(int options)
+    {
+        this(null,null,options);
+    }
+
+    /* ------------------------------------------------------------ */
+    public ServletContextHandler(HandlerContainer parent, String contextPath)
+    {
+        this(parent,contextPath,null,null,null,null);
+    }
+
+    /* ------------------------------------------------------------ */
+    public ServletContextHandler(HandlerContainer parent, String contextPath, int options)
+    {
+        this(parent,contextPath,null,null,null,null,options);
+    }
+
+    /* ------------------------------------------------------------ */
+    public ServletContextHandler(HandlerContainer parent, String contextPath, boolean sessions, boolean security)
+    {
+        this(parent,contextPath,(sessions?SESSIONS:0)|(security?SECURITY:0));
+    }
+
+    /* ------------------------------------------------------------ */
+    public ServletContextHandler(HandlerContainer parent, SessionHandler sessionHandler, SecurityHandler securityHandler, ServletHandler servletHandler, ErrorHandler errorHandler)
+    {
+        this(parent,null,sessionHandler,securityHandler,servletHandler,errorHandler);
+    }
+
+    /* ------------------------------------------------------------ */
+    public ServletContextHandler(HandlerContainer parent, String contextPath, SessionHandler sessionHandler, SecurityHandler securityHandler, ServletHandler servletHandler, ErrorHandler errorHandler)
+    {
+        this(parent,contextPath,sessionHandler,securityHandler,servletHandler,errorHandler,0);
+    }
+    
+    public ServletContextHandler(HandlerContainer parent, String contextPath, SessionHandler sessionHandler, SecurityHandler securityHandler, ServletHandler servletHandler, ErrorHandler errorHandler,int options)
+    {
+        super((ContextHandler.Context)null);
+        _options=options;
+        _scontext = new Context();
+        _sessionHandler = sessionHandler;
+        _securityHandler = securityHandler;
+        _servletHandler = servletHandler;
+
+        if (contextPath!=null)
+            setContextPath(contextPath);
+        
+        if (parent instanceof HandlerWrapper)
+            ((HandlerWrapper)parent).setHandler(this);
+        else if (parent instanceof HandlerCollection)
+            ((HandlerCollection)parent).addHandler(this);
+        
+        
+        // Link the handlers
+        relinkHandlers();
+        
+        if (errorHandler!=null)
+            setErrorHandler(errorHandler);
+
+    }
+    
+    /* ------------------------------------------------------------ */
+    private void relinkHandlers()
+    {
+        HandlerWrapper handler=this;
+        
+        // Skip any injected handlers
+        while (handler.getHandler() instanceof HandlerWrapper)
+        {
+            HandlerWrapper wrapper = (HandlerWrapper)handler.getHandler();
+            if (wrapper instanceof SessionHandler ||
+                wrapper instanceof SecurityHandler ||
+                wrapper instanceof ServletHandler)
+                break;
+            handler=wrapper;
+        }
+        
+        if (getSessionHandler()!=null)
+        {
+            if (handler==this)
+                super.setHandler(_sessionHandler);
+            else
+                handler.setHandler(_sessionHandler);
+            handler=_sessionHandler;
+        }
+
+        if (getSecurityHandler()!=null)
+        {
+            if (handler==this)
+                super.setHandler(_securityHandler);
+            else
+                handler.setHandler(_securityHandler);
+            handler=_securityHandler;
+        }
+
+        if (getServletHandler()!=null)
+        {
+            if (handler==this)
+                super.setHandler(_servletHandler);
+            else
+                handler.setHandler(_servletHandler);
+            handler=_servletHandler;
+        } 
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.server.handler.ContextHandler#doStop()
+     */
+    @Override
+    protected void doStop() throws Exception
+    {
+        super.doStop();
+        if (_decorators != null)
+            _decorators.clear();
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Get the defaultSecurityHandlerClass.
+     * @return the defaultSecurityHandlerClass
+     */
+    public Class<? extends SecurityHandler> getDefaultSecurityHandlerClass()
+    {
+        return _defaultSecurityHandlerClass;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Set the defaultSecurityHandlerClass.
+     * @param defaultSecurityHandlerClass the defaultSecurityHandlerClass to set
+     */
+    public void setDefaultSecurityHandlerClass(Class<? extends SecurityHandler> defaultSecurityHandlerClass)
+    {
+        _defaultSecurityHandlerClass = defaultSecurityHandlerClass;
+    }
+
+    /* ------------------------------------------------------------ */
+    protected SessionHandler newSessionHandler()
+    {
+        return new SessionHandler();
+    }
+
+    /* ------------------------------------------------------------ */
+    protected SecurityHandler newSecurityHandler()
+    {
+        try
+        {
+            return (SecurityHandler)_defaultSecurityHandlerClass.newInstance();
+        }
+        catch(Exception e)
+        {
+            throw new IllegalStateException(e);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    protected ServletHandler newServletHandler()
+    {
+        return new ServletHandler();
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Finish constructing handlers and link them together.
+     *
+     * @see org.eclipse.jetty.server.handler.ContextHandler#startContext()
+     */
+    @Override
+    protected void startContext() throws Exception
+    {
+        ServletContainerInitializerCaller sciBean = getBean(ServletContainerInitializerCaller.class);
+        if (sciBean!=null)
+            sciBean.start();
+
+        if (_servletHandler != null)
+        {
+            //Call decorators on all holders, and also on any EventListeners before
+            //decorators are called on any other classes (like servlets and filters)
+            for (int i=_decorators.size()-1;i>=0; i--)
+            {
+                Decorator decorator = _decorators.get(i);
+                //Do any decorations on the ListenerHolders AND the listener instances first up
+                if (_servletHandler.getListeners()!=null)
+                {
+                    for (ListenerHolder holder:_servletHandler.getListeners())
+                    {             
+                        decorator.decorate(holder.getListener());
+                    }
+                }
+           }
+       }
+       
+        super.startContext();
+
+        // OK to Initialize servlet handler now that all relevant object trees have been started
+        if (_servletHandler != null)
+            _servletHandler.initialize();
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Returns the securityHandler.
+     */
+    @ManagedAttribute(value="context security handler", readonly=true)
+    public SecurityHandler getSecurityHandler()
+    {
+        if (_securityHandler==null && (_options&SECURITY)!=0 && !isStarted())
+            _securityHandler=newSecurityHandler();
+
+        return _securityHandler;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Returns the servletHandler.
+     */
+    @ManagedAttribute(value="context servlet handler", readonly=true)
+    public ServletHandler getServletHandler()
+    {
+        if (_servletHandler==null && !isStarted())
+            _servletHandler=newServletHandler();
+        return _servletHandler;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Returns the sessionHandler.
+     */
+    @ManagedAttribute(value="context session handler", readonly=true)
+    public SessionHandler getSessionHandler()
+    {
+        if (_sessionHandler==null && (_options&SESSIONS)!=0 && !isStarted())
+            _sessionHandler=newSessionHandler();
+        return _sessionHandler;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** conveniance method to add a servlet.
+     */
+    public ServletHolder addServlet(String className,String pathSpec)
+    {
+        return getServletHandler().addServletWithMapping(className, pathSpec);
+    }
+
+    /* ------------------------------------------------------------ */
+    /** conveniance method to add a servlet.
+     */
+    public ServletHolder addServlet(Class<? extends Servlet> servlet,String pathSpec)
+    {
+        return getServletHandler().addServletWithMapping(servlet.getName(), pathSpec);
+    }
+
+    /* ------------------------------------------------------------ */
+    /** conveniance method to add a servlet.
+     */
+    public void addServlet(ServletHolder servlet,String pathSpec)
+    {
+        getServletHandler().addServletWithMapping(servlet, pathSpec);
+    }
+
+    /* ------------------------------------------------------------ */
+    /** conveniance method to add a filter
+     */
+    public void addFilter(FilterHolder holder,String pathSpec,EnumSet<DispatcherType> dispatches)
+    {
+        getServletHandler().addFilterWithMapping(holder,pathSpec,dispatches);
+    }
+
+    /* ------------------------------------------------------------ */
+    /** convenience method to add a filter
+     */
+    public FilterHolder addFilter(Class<? extends Filter> filterClass,String pathSpec,EnumSet<DispatcherType> dispatches)
+    {
+        return getServletHandler().addFilterWithMapping(filterClass,pathSpec,dispatches);
+    }
+
+    /* ------------------------------------------------------------ */
+    /** convenience method to add a filter
+     */
+    public FilterHolder addFilter(String filterClass,String pathSpec,EnumSet<DispatcherType> dispatches)
+    {
+        return getServletHandler().addFilterWithMapping(filterClass,pathSpec,dispatches);
+    }
+
+    /**
+     * notification that a ServletRegistration has been created so we can track the annotations
+     * @param holder new holder created through the api.
+     * @return the ServletRegistration.Dynamic
+     */
+    protected ServletRegistration.Dynamic dynamicHolderAdded(ServletHolder holder) {
+        return holder.getRegistration();
+    }
+
+    /**
+     * delegate for ServletContext.declareRole method
+     * @param roleNames role names to add
+     */
+    protected void addRoles(String... roleNames) {
+        //Get a reference to the SecurityHandler, which must be ConstraintAware
+        if (_securityHandler != null && _securityHandler instanceof ConstraintAware)
+        {
+            HashSet<String> union = new HashSet<String>();
+            Set<String> existing = ((ConstraintAware)_securityHandler).getRoles();
+            if (existing != null)
+                union.addAll(existing);
+            union.addAll(Arrays.asList(roleNames));
+            ((ConstraintSecurityHandler)_securityHandler).setRoles(union);
+        }
+    }
+
+    /**
+     * Delegate for ServletRegistration.Dynamic.setServletSecurity method
+     * @param registration ServletRegistration.Dynamic instance that setServletSecurity was called on
+     * @param servletSecurityElement new security info
+     * @return the set of exact URL mappings currently associated with the registration that are also present in the web.xml
+     * security constraints and thus will be unaffected by this call.
+     */
+    public Set<String> setServletSecurity(ServletRegistration.Dynamic registration, ServletSecurityElement servletSecurityElement)
+    {
+        //Default implementation is to just accept them all. If using a webapp, then this behaviour is overridden in WebAppContext.setServletSecurity       
+        Collection<String> pathSpecs = registration.getMappings();
+        if (pathSpecs != null)
+        {
+            for (String pathSpec:pathSpecs)
+            {
+                List<ConstraintMapping> mappings = ConstraintSecurityHandler.createConstraintsWithMappingsForPath(registration.getName(), pathSpec, servletSecurityElement);
+                for (ConstraintMapping m:mappings)
+                    ((ConstraintAware)getSecurityHandler()).addConstraintMapping(m);
+            }
+        }
+        return Collections.emptySet();
+    }
+
+    @Override
+    public void callContextInitialized(ServletContextListener l, ServletContextEvent e)
+    {
+        try
+        {
+            //toggle state of the dynamic API so that the listener cannot use it
+            if(isProgrammaticListener(l))
+                this.getServletContext().setEnabled(false);
+
+            super.callContextInitialized(l, e);
+        }
+        finally
+        {
+            //untoggle the state of the dynamic API
+            this.getServletContext().setEnabled(true);
+        }
+    }
+
+
+    @Override
+    public void callContextDestroyed(ServletContextListener l, ServletContextEvent e)
+    {
+        super.callContextDestroyed(l, e);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param sessionHandler The sessionHandler to set.
+     */
+    public void setSessionHandler(SessionHandler sessionHandler)
+    {
+        if (isStarted())
+            throw new IllegalStateException("STARTED");
+
+        if (_sessionHandler!=null)
+            _sessionHandler.setHandler(null);
+
+        _sessionHandler = sessionHandler;
+        relinkHandlers();
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param securityHandler The {@link SecurityHandler} to set on this context.
+     */
+    public void setSecurityHandler(SecurityHandler securityHandler)
+    {
+        if (isStarted())
+            throw new IllegalStateException("STARTED");
+
+        if (_securityHandler!=null)
+            _securityHandler.setHandler(null);
+        _securityHandler = securityHandler;
+        relinkHandlers();
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param servletHandler The servletHandler to set.
+     */
+    public void setServletHandler(ServletHandler servletHandler)
+    {
+        if (isStarted())
+            throw new IllegalStateException("STARTED");
+
+        Handler next=null;
+        if (_servletHandler!=null)
+        {
+            next=_servletHandler.getHandler();
+            _servletHandler.setHandler(null);
+        }
+        _servletHandler = servletHandler;
+        relinkHandlers();
+        _servletHandler.setHandler(next);
+    }
+    
+    /* ------------------------------------------------------------ */
+    @Override
+    public void setHandler(Handler handler)
+    {
+        if (handler instanceof ServletHandler)
+            setServletHandler((ServletHandler) handler);
+        else if (handler instanceof SessionHandler)
+            setSessionHandler((SessionHandler) handler);
+        else if (handler instanceof SecurityHandler)
+            setSecurityHandler((SecurityHandler)handler);
+        else if (handler == null || handler instanceof HandlerWrapper)
+        {
+            super.setHandler(handler);
+            relinkHandlers();
+        }
+        else
+            throw new IllegalArgumentException();
+    }
+    
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * Insert a HandlerWrapper before the first Session,Security or ServletHandler
+     * but after any other HandlerWrappers.
+     */
+    public void insertHandler(HandlerWrapper handler)
+    {
+        HandlerWrapper h=this;
+        
+        // Skip any injected handlers
+        while (h.getHandler() instanceof HandlerWrapper)
+        {
+            HandlerWrapper wrapper = (HandlerWrapper)h.getHandler();
+            if (wrapper instanceof SessionHandler ||
+                wrapper instanceof SecurityHandler ||
+                wrapper instanceof ServletHandler)
+                break;
+            h=wrapper;
+        }
+        
+        h.setHandler(handler);
+        relinkHandlers();
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return The decorator list used to resource inject new Filters, Servlets and EventListeners
+     */
+    public List<Decorator> getDecorators()
+    {
+        return Collections.unmodifiableList(_decorators);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param decorators The lis of {@link Decorator}s
+     */
+    public void setDecorators(List<Decorator> decorators)
+    {
+        _decorators.clear();
+        _decorators.addAll(decorators);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param decorator The decorator to add
+     */
+    public void addDecorator(Decorator decorator)
+    {
+        _decorators.add(decorator);
+    }
+
+    /* ------------------------------------------------------------ */
+    void destroyServlet(Servlet servlet)
+    {
+        for (Decorator decorator : _decorators)
+            decorator.destroy(servlet);
+    }
+
+    /* ------------------------------------------------------------ */
+    void destroyFilter(Filter filter)
+    {
+        for (Decorator decorator : _decorators)
+            decorator.destroy(filter);
+    }
+
+    /* ------------------------------------------------------------ */
+    public static class JspPropertyGroup implements JspPropertyGroupDescriptor
+    {
+        private List<String> _urlPatterns = new ArrayList<String>();
+        private String _elIgnored;
+        private String _pageEncoding;
+        private String _scriptingInvalid;
+        private String _isXml;
+        private List<String> _includePreludes = new ArrayList<String>();
+        private List<String> _includeCodas = new ArrayList<String>();
+        private String _deferredSyntaxAllowedAsLiteral;
+        private String _trimDirectiveWhitespaces;
+        private String _defaultContentType;
+        private String _buffer;
+        private String _errorOnUndeclaredNamespace;
+
+
+
+        /**
+         * @see javax.servlet.descriptor.JspPropertyGroupDescriptor#getUrlPatterns()
+         */
+        public Collection<String> getUrlPatterns()
+        {
+            return new ArrayList<String>(_urlPatterns); // spec says must be a copy
+        }
+
+        public void addUrlPattern (String s)
+        {
+            if (!_urlPatterns.contains(s))
+                _urlPatterns.add(s);
+        }
+
+        /**
+         * @see javax.servlet.descriptor.JspPropertyGroupDescriptor#getElIgnored()
+         */
+        public String getElIgnored()
+        {
+            return _elIgnored;
+        }
+
+        public void setElIgnored (String s)
+        {
+            _elIgnored = s;
+        }
+
+        /**
+         * @see javax.servlet.descriptor.JspPropertyGroupDescriptor#getPageEncoding()
+         */
+        public String getPageEncoding()
+        {
+            return _pageEncoding;
+        }
+
+        public void setPageEncoding(String pageEncoding)
+        {
+            _pageEncoding = pageEncoding;
+        }
+
+        public void setScriptingInvalid(String scriptingInvalid)
+        {
+            _scriptingInvalid = scriptingInvalid;
+        }
+
+        public void setIsXml(String isXml)
+        {
+            _isXml = isXml;
+        }
+
+        public void setDeferredSyntaxAllowedAsLiteral(String deferredSyntaxAllowedAsLiteral)
+        {
+            _deferredSyntaxAllowedAsLiteral = deferredSyntaxAllowedAsLiteral;
+        }
+
+        public void setTrimDirectiveWhitespaces(String trimDirectiveWhitespaces)
+        {
+            _trimDirectiveWhitespaces = trimDirectiveWhitespaces;
+        }
+
+        public void setDefaultContentType(String defaultContentType)
+        {
+            _defaultContentType = defaultContentType;
+        }
+
+        public void setBuffer(String buffer)
+        {
+            _buffer = buffer;
+        }
+
+        public void setErrorOnUndeclaredNamespace(String errorOnUndeclaredNamespace)
+        {
+            _errorOnUndeclaredNamespace = errorOnUndeclaredNamespace;
+        }
+
+        /**
+         * @see javax.servlet.descriptor.JspPropertyGroupDescriptor#getScriptingInvalid()
+         */
+        public String getScriptingInvalid()
+        {
+            return _scriptingInvalid;
+        }
+
+        /**
+         * @see javax.servlet.descriptor.JspPropertyGroupDescriptor#getIsXml()
+         */
+        public String getIsXml()
+        {
+            return _isXml;
+        }
+
+        /**
+         * @see javax.servlet.descriptor.JspPropertyGroupDescriptor#getIncludePreludes()
+         */
+        public Collection<String> getIncludePreludes()
+        {
+            return new ArrayList<String>(_includePreludes); //must be a copy
+        }
+
+        public void addIncludePrelude(String prelude)
+        {
+            if (!_includePreludes.contains(prelude))
+                _includePreludes.add(prelude);
+        }
+
+        /**
+         * @see javax.servlet.descriptor.JspPropertyGroupDescriptor#getIncludeCodas()
+         */
+        public Collection<String> getIncludeCodas()
+        {
+            return new ArrayList<String>(_includeCodas); //must be a copy
+        }
+
+        public void addIncludeCoda (String coda)
+        {
+            if (!_includeCodas.contains(coda))
+                _includeCodas.add(coda);
+        }
+
+        /**
+         * @see javax.servlet.descriptor.JspPropertyGroupDescriptor#getDeferredSyntaxAllowedAsLiteral()
+         */
+        public String getDeferredSyntaxAllowedAsLiteral()
+        {
+            return _deferredSyntaxAllowedAsLiteral;
+        }
+
+        /**
+         * @see javax.servlet.descriptor.JspPropertyGroupDescriptor#getTrimDirectiveWhitespaces()
+         */
+        public String getTrimDirectiveWhitespaces()
+        {
+            return _trimDirectiveWhitespaces;
+        }
+
+        /**
+         * @see javax.servlet.descriptor.JspPropertyGroupDescriptor#getDefaultContentType()
+         */
+        public String getDefaultContentType()
+        {
+            return _defaultContentType;
+        }
+
+        /**
+         * @see javax.servlet.descriptor.JspPropertyGroupDescriptor#getBuffer()
+         */
+        public String getBuffer()
+        {
+            return _buffer;
+        }
+
+        /**
+         * @see javax.servlet.descriptor.JspPropertyGroupDescriptor#getErrorOnUndeclaredNamespace()
+         */
+        public String getErrorOnUndeclaredNamespace()
+        {
+            return _errorOnUndeclaredNamespace;
+        }
+
+        public String toString ()
+        {
+            StringBuffer sb = new StringBuffer();
+            sb.append("JspPropertyGroupDescriptor:");
+            sb.append(" el-ignored="+_elIgnored);
+            sb.append(" is-xml="+_isXml);
+            sb.append(" page-encoding="+_pageEncoding);
+            sb.append(" scripting-invalid="+_scriptingInvalid);
+            sb.append(" deferred-syntax-allowed-as-literal="+_deferredSyntaxAllowedAsLiteral);
+            sb.append(" trim-directive-whitespaces"+_trimDirectiveWhitespaces);
+            sb.append(" default-content-type="+_defaultContentType);
+            sb.append(" buffer="+_buffer);
+            sb.append(" error-on-undeclared-namespace="+_errorOnUndeclaredNamespace);
+            for (String prelude:_includePreludes)
+                sb.append(" include-prelude="+prelude);
+            for (String coda:_includeCodas)
+                sb.append(" include-coda="+coda);
+            return sb.toString();
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    public static class TagLib implements TaglibDescriptor
+    {
+        private String _uri;
+        private String _location;
+
+        /**
+         * @see javax.servlet.descriptor.TaglibDescriptor#getTaglibURI()
+         */
+        public String getTaglibURI()
+        {
+           return _uri;
+        }
+
+        public void setTaglibURI(String uri)
+        {
+            _uri = uri;
+        }
+
+        /**
+         * @see javax.servlet.descriptor.TaglibDescriptor#getTaglibLocation()
+         */
+        public String getTaglibLocation()
+        {
+            return _location;
+        }
+
+        public void setTaglibLocation(String location)
+        {
+            _location = location;
+        }
+
+        public String toString()
+        {
+            return ("TagLibDescriptor: taglib-uri="+_uri+" location="+_location);
+        }
+    }
+
+
+    /* ------------------------------------------------------------ */
+    public static class JspConfig implements JspConfigDescriptor
+    {
+        private List<TaglibDescriptor> _taglibs = new ArrayList<TaglibDescriptor>();
+        private List<JspPropertyGroupDescriptor> _jspPropertyGroups = new ArrayList<JspPropertyGroupDescriptor>();
+
+        public JspConfig() {}
+
+        /**
+         * @see javax.servlet.descriptor.JspConfigDescriptor#getTaglibs()
+         */
+        public Collection<TaglibDescriptor> getTaglibs()
+        {
+            return new ArrayList<TaglibDescriptor>(_taglibs);
+        }
+
+        public void addTaglibDescriptor (TaglibDescriptor d)
+        {
+            _taglibs.add(d);
+        }
+
+        /**
+         * @see javax.servlet.descriptor.JspConfigDescriptor#getJspPropertyGroups()
+         */
+        public Collection<JspPropertyGroupDescriptor> getJspPropertyGroups()
+        {
+           return new ArrayList<JspPropertyGroupDescriptor>(_jspPropertyGroups);
+        }
+
+        public void addJspPropertyGroup(JspPropertyGroupDescriptor g)
+        {
+            _jspPropertyGroups.add(g);
+        }
+
+        public String toString()
+        {
+            StringBuffer sb = new StringBuffer();
+            sb.append("JspConfigDescriptor: \n");
+            for (TaglibDescriptor taglib:_taglibs)
+                sb.append(taglib+"\n");
+            for (JspPropertyGroupDescriptor jpg:_jspPropertyGroups)
+                sb.append(jpg+"\n");
+            return sb.toString();
+        }
+    }
+
+
+    /* ------------------------------------------------------------ */
+    public class Context extends ContextHandler.Context
+    {
+        /* ------------------------------------------------------------ */
+        /*
+         * @see javax.servlet.ServletContext#getNamedDispatcher(java.lang.String)
+         */
+        @Override
+        public RequestDispatcher getNamedDispatcher(String name)
+        {
+            ContextHandler context=org.eclipse.jetty.servlet.ServletContextHandler.this;
+            if (_servletHandler==null)
+                return null;
+            ServletHolder holder = _servletHandler.getServlet(name);
+            if (holder==null || !holder.isEnabled())
+                return null;
+            return new Dispatcher(context, name);
+        }
+
+        /* ------------------------------------------------------------ */
+        /**
+         * @since servlet-api-3.0
+         */
+        @Override
+        public FilterRegistration.Dynamic addFilter(String filterName, Class<? extends Filter> filterClass)
+        {
+            if (isStarted())
+                throw new IllegalStateException();
+            
+            if (filterName == null || "".equals(filterName.trim()))
+                throw new IllegalStateException("Missing filter name");
+
+            if (!_enabled)
+                throw new UnsupportedOperationException();
+
+            final ServletHandler handler = ServletContextHandler.this.getServletHandler();
+            FilterHolder holder = handler.getFilter(filterName);
+            if (holder == null)
+            {
+                //new filter
+                holder = handler.newFilterHolder(Source.JAVAX_API);
+                holder.setName(filterName);
+                holder.setHeldClass(filterClass);
+                handler.addFilter(holder);
+                return holder.getRegistration();
+            }
+            if (holder.getClassName()==null && holder.getHeldClass()==null)
+            {
+                //preliminary filter registration completion
+                holder.setHeldClass(filterClass);
+                return holder.getRegistration();
+            }
+            else
+                return null; //existing filter
+        }
+
+        /* ------------------------------------------------------------ */
+        /**
+         * @since servlet-api-3.0
+         */
+        @Override
+        public FilterRegistration.Dynamic addFilter(String filterName, String className)
+        {
+            if (isStarted())
+                throw new IllegalStateException();
+            
+            if (filterName == null || "".equals(filterName.trim()))
+                throw new IllegalStateException("Missing filter name");
+
+            if (!_enabled)
+                throw new UnsupportedOperationException();
+
+            final ServletHandler handler = ServletContextHandler.this.getServletHandler();
+            FilterHolder holder = handler.getFilter(filterName);
+            if (holder == null)
+            {
+                //new filter
+                holder = handler.newFilterHolder(Source.JAVAX_API);
+                holder.setName(filterName);
+                holder.setClassName(className);
+                handler.addFilter(holder);
+                return holder.getRegistration();
+            }
+            if (holder.getClassName()==null && holder.getHeldClass()==null)
+            {
+                //preliminary filter registration completion
+                holder.setClassName(className);
+                return holder.getRegistration();
+            }
+            else
+                return null; //existing filter
+        }
+
+
+        /* ------------------------------------------------------------ */
+        /**
+         * @since servlet-api-3.0
+         */
+        @Override
+        public FilterRegistration.Dynamic addFilter(String filterName, Filter filter)
+        {
+            if (isStarted())
+                throw new IllegalStateException();
+
+            if (filterName == null || "".equals(filterName.trim()))
+                throw new IllegalStateException("Missing filter name");
+            
+            if (!_enabled)
+                throw new UnsupportedOperationException();
+
+            final ServletHandler handler = ServletContextHandler.this.getServletHandler();
+            FilterHolder holder = handler.getFilter(filterName);
+            if (holder == null)
+            {
+                //new filter
+                holder = handler.newFilterHolder(Source.JAVAX_API);
+                holder.setName(filterName);
+                holder.setFilter(filter);
+                handler.addFilter(holder);
+                return holder.getRegistration();
+            }
+
+            if (holder.getClassName()==null && holder.getHeldClass()==null)
+            {
+                //preliminary filter registration completion
+                holder.setFilter(filter);
+                return holder.getRegistration();
+            }
+            else
+                return null; //existing filter
+        }
+
+        /* ------------------------------------------------------------ */
+        /**
+         * @since servlet-api-3.0
+         */
+        @Override
+        public ServletRegistration.Dynamic addServlet(String servletName, Class<? extends Servlet> servletClass)
+        {
+            if (!isStarting())
+                throw new IllegalStateException();
+
+            if (servletName == null || "".equals(servletName.trim()))
+                throw new IllegalStateException("Missing servlet name");
+            
+            if (!_enabled)
+                throw new UnsupportedOperationException();
+
+            final ServletHandler handler = ServletContextHandler.this.getServletHandler();
+            ServletHolder holder = handler.getServlet(servletName);
+            if (holder == null)
+            {
+                //new servlet
+                holder = handler.newServletHolder(Source.JAVAX_API);
+                holder.setName(servletName);
+                holder.setHeldClass(servletClass);
+                handler.addServlet(holder);
+                return dynamicHolderAdded(holder);
+            }
+
+            //complete a partial registration
+            if (holder.getClassName()==null && holder.getHeldClass()==null)
+            {
+                holder.setHeldClass(servletClass);
+                return holder.getRegistration();
+            }
+            else
+                return null; //existing completed registration for servlet name
+        }
+
+        /* ------------------------------------------------------------ */
+        /**
+         * @since servlet-api-3.0
+         */
+        @Override
+        public ServletRegistration.Dynamic addServlet(String servletName, String className)
+        {
+            if (!isStarting())
+                throw new IllegalStateException();
+
+            if (servletName == null || "".equals(servletName.trim()))
+                throw new IllegalStateException("Missing servlet name");
+            
+            if (!_enabled)
+                throw new UnsupportedOperationException();
+
+
+            final ServletHandler handler = ServletContextHandler.this.getServletHandler();
+            ServletHolder holder = handler.getServlet(servletName);
+            if (holder == null)
+            {
+                //new servlet
+                holder = handler.newServletHolder(Source.JAVAX_API);
+                holder.setName(servletName);
+                holder.setClassName(className);
+                handler.addServlet(holder);
+                return dynamicHolderAdded(holder);
+            }
+
+            //complete a partial registration
+            if (holder.getClassName()==null && holder.getHeldClass()==null)
+            {
+                holder.setClassName(className);
+                return holder.getRegistration();
+            }
+            else
+                return null; //existing completed registration for servlet name
+        }
+
+        /* ------------------------------------------------------------ */
+        /**
+         * @since servlet-api-3.0
+         */
+        @Override
+        public ServletRegistration.Dynamic addServlet(String servletName, Servlet servlet)
+        {
+            if (!isStarting())
+                throw new IllegalStateException();
+            
+            if (servletName == null || "".equals(servletName.trim()))
+                throw new IllegalStateException("Missing servlet name");
+            
+            if (!_enabled)
+                throw new UnsupportedOperationException();
+
+            final ServletHandler handler = ServletContextHandler.this.getServletHandler();
+            ServletHolder holder = handler.getServlet(servletName);
+            if (holder == null)
+            {
+                holder = handler.newServletHolder(Source.JAVAX_API);
+                holder.setName(servletName);
+                holder.setServlet(servlet);
+                handler.addServlet(holder);
+                return dynamicHolderAdded(holder);
+            }
+
+            //complete a partial registration
+            if (holder.getClassName()==null && holder.getHeldClass()==null)
+            {
+                holder.setServlet(servlet);
+                return holder.getRegistration();
+            }
+            else
+                return null; //existing completed registration for servlet name
+        }
+
+        /* ------------------------------------------------------------ */
+        @Override
+        public boolean setInitParameter(String name, String value)
+        {
+            if (!isStarting())
+                throw new IllegalStateException();
+
+            if (!_enabled)
+                throw new UnsupportedOperationException();
+
+            return super.setInitParameter(name,value);
+        }
+
+        /* ------------------------------------------------------------ */
+        @Override
+        public <T extends Filter> T createFilter(Class<T> c) throws ServletException
+        {
+            try
+            {
+                T f = createInstance(c);
+                for (int i=_decorators.size()-1; i>=0; i--)
+                {
+                    Decorator decorator = _decorators.get(i);
+                    f=decorator.decorate(f);
+                }
+                return f;
+            }
+            catch (Exception e)
+            {
+                throw new ServletException(e);
+            }
+        }
+
+        /* ------------------------------------------------------------ */
+        @Override
+        public <T extends Servlet> T createServlet(Class<T> c) throws ServletException
+        {
+            try
+            {
+                T s = createInstance(c);
+                for (int i=_decorators.size()-1; i>=0; i--)
+                {
+                    Decorator decorator = _decorators.get(i);
+                    s=decorator.decorate(s);
+                }
+                return s;
+            }
+            catch (Exception e)
+            {
+                throw new ServletException(e);
+            }
+        }
+        
+
+        @Override
+        public Set<SessionTrackingMode> getDefaultSessionTrackingModes()
+        {
+            if (_sessionHandler!=null)
+                return _sessionHandler.getSessionManager().getDefaultSessionTrackingModes();
+            return null;
+        }
+
+        @Override
+        public Set<SessionTrackingMode> getEffectiveSessionTrackingModes()
+        {
+            if (_sessionHandler!=null)
+                return _sessionHandler.getSessionManager().getEffectiveSessionTrackingModes();
+            return null;
+        }
+
+        @Override
+        public FilterRegistration getFilterRegistration(String filterName)
+        {
+            if (!_enabled)
+                throw new UnsupportedOperationException();
+
+            final FilterHolder holder=ServletContextHandler.this.getServletHandler().getFilter(filterName);
+            return (holder==null)?null:holder.getRegistration();
+        }
+
+        @Override
+        public Map<String, ? extends FilterRegistration> getFilterRegistrations()
+        {
+            if (!_enabled)
+                throw new UnsupportedOperationException();
+
+            HashMap<String, FilterRegistration> registrations = new HashMap<String, FilterRegistration>();
+            ServletHandler handler=ServletContextHandler.this.getServletHandler();
+            FilterHolder[] holders=handler.getFilters();
+            if (holders!=null)
+            {
+                for (FilterHolder holder : holders)
+                    registrations.put(holder.getName(),holder.getRegistration());
+            }
+            return registrations;
+        }
+
+        @Override
+        public ServletRegistration getServletRegistration(String servletName)
+        {
+            if (!_enabled)
+                throw new UnsupportedOperationException();
+
+            final ServletHolder holder=ServletContextHandler.this.getServletHandler().getServlet(servletName);
+            return (holder==null)?null:holder.getRegistration();
+        }
+
+        @Override
+        public Map<String, ? extends ServletRegistration> getServletRegistrations()
+        {
+            if (!_enabled)
+                throw new UnsupportedOperationException();
+
+            HashMap<String, ServletRegistration> registrations = new HashMap<String, ServletRegistration>();
+            ServletHandler handler=ServletContextHandler.this.getServletHandler();
+            ServletHolder[] holders=handler.getServlets();
+            if (holders!=null)
+            {
+                for (ServletHolder holder : holders)
+                    registrations.put(holder.getName(),holder.getRegistration());
+            }
+            return registrations;
+        }
+
+        @Override
+        public SessionCookieConfig getSessionCookieConfig()
+        {
+            if (!_enabled)
+                throw new UnsupportedOperationException();
+
+            if (_sessionHandler!=null)
+                return _sessionHandler.getSessionManager().getSessionCookieConfig();
+            return null;
+        }
+
+        @Override
+        public void setSessionTrackingModes(Set<SessionTrackingMode> sessionTrackingModes)
+        {
+            if (!isStarting())
+                throw new IllegalStateException();
+            if (!_enabled)
+                throw new UnsupportedOperationException();
+
+
+            if (_sessionHandler!=null)
+                _sessionHandler.getSessionManager().setSessionTrackingModes(sessionTrackingModes);
+        }
+
+        @Override
+        public void addListener(String className)
+        {
+            if (!isStarting())
+                throw new IllegalStateException();
+            if (!_enabled)
+                throw new UnsupportedOperationException();
+            super.addListener(className);
+        }
+
+        @Override
+        public <T extends EventListener> void addListener(T t)
+        {
+            if (!isStarting())
+                throw new IllegalStateException();
+            if (!_enabled)
+                throw new UnsupportedOperationException();
+            super.addListener(t);
+            ListenerHolder holder = getServletHandler().newListenerHolder(Source.JAVAX_API);
+            holder.setListener(t);
+            getServletHandler().addListener(holder);
+        }
+
+        @Override
+        public void addListener(Class<? extends EventListener> listenerClass)
+        {
+            if (!isStarting())
+                throw new IllegalStateException();
+            if (!_enabled)
+                throw new UnsupportedOperationException();
+            super.addListener(listenerClass);
+        }
+
+        @Override
+        public <T extends EventListener> T createListener(Class<T> clazz) throws ServletException
+        {
+            try
+            {
+                T l = createInstance(clazz);
+                for (int i=_decorators.size()-1; i>=0; i--)
+                {
+                    Decorator decorator = _decorators.get(i);
+                    l=decorator.decorate(l);
+                }
+                return l;
+            }            
+            catch (Exception e)
+            {
+                throw new ServletException(e);
+            }
+        }
+
+
+        @Override
+        public JspConfigDescriptor getJspConfigDescriptor()
+        {
+            return _jspConfig;
+        }
+
+        @Override
+        public void setJspConfigDescriptor(JspConfigDescriptor d)
+        {
+            _jspConfig = d;
+        }
+
+
+        @Override
+        public void declareRoles(String... roleNames)
+        {
+            if (!isStarting())
+                throw new IllegalStateException();
+            if (!_enabled)
+                throw new UnsupportedOperationException();
+            addRoles(roleNames);
+
+
+        }
+
+    }
+
+
+
+    /* ------------------------------------------------------------ */
+    /** Interface to decorate loaded classes.
+     */
+    public interface Decorator
+    {
+        <T> T decorate (T o);
+        void destroy (Object o);
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/servlet/ServletHandler.java b/lib/jetty/org/eclipse/jetty/servlet/ServletHandler.java
new file mode 100644 (file)
index 0000000..81243e0
--- /dev/null
@@ -0,0 +1,1807 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.servlet;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.ListIterator;
+import java.util.Map;
+import java.util.Queue;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.ConcurrentMap;
+
+import javax.servlet.DispatcherType;
+import javax.servlet.Filter;
+import javax.servlet.FilterChain;
+import javax.servlet.RequestDispatcher;
+import javax.servlet.Servlet;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRegistration;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.ServletSecurityElement;
+import javax.servlet.UnavailableException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.http.HttpHeader;
+import org.eclipse.jetty.http.HttpHeaderValue;
+import org.eclipse.jetty.http.PathMap;
+import org.eclipse.jetty.io.EofException;
+import org.eclipse.jetty.io.RuntimeIOException;
+import org.eclipse.jetty.security.IdentityService;
+import org.eclipse.jetty.security.SecurityHandler;
+import org.eclipse.jetty.server.HttpChannel;
+import org.eclipse.jetty.server.QuietServletException;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.ServletRequestHttpWrapper;
+import org.eclipse.jetty.server.ServletResponseHttpWrapper;
+import org.eclipse.jetty.server.UserIdentity;
+import org.eclipse.jetty.server.handler.ContextHandler;
+import org.eclipse.jetty.server.handler.ScopedHandler;
+import org.eclipse.jetty.servlet.BaseHolder.Source;
+import org.eclipse.jetty.util.ArrayUtil;
+import org.eclipse.jetty.util.LazyList;
+import org.eclipse.jetty.util.MultiException;
+import org.eclipse.jetty.util.MultiMap;
+import org.eclipse.jetty.util.URIUtil;
+import org.eclipse.jetty.util.annotation.ManagedAttribute;
+import org.eclipse.jetty.util.annotation.ManagedObject;
+import org.eclipse.jetty.util.component.LifeCycle;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/* --------------------------------------------------------------------- */
+/** Servlet HttpHandler.
+ * This handler maps requests to servlets that implement the
+ * javax.servlet.http.HttpServlet API.
+ * <P>
+ * This handler does not implement the full J2EE features and is intended to
+ * be used directly when a full web application is not required.  If a Web application is required,
+ * then this handler should be used as part of a <code>org.eclipse.jetty.webapp.WebAppContext</code>.
+ * <p>
+ * Unless run as part of a {@link ServletContextHandler} or derivative, the {@link #initialize()}
+ * method must be called manually after start().
+ */
+
+/* ------------------------------------------------------------ */
+/**
+ */
+@ManagedObject("Servlet Handler")
+public class ServletHandler extends ScopedHandler
+{
+    private static final Logger LOG = Log.getLogger(ServletHandler.class);
+
+    /* ------------------------------------------------------------ */
+    public static final String __DEFAULT_SERVLET="default";
+
+    /* ------------------------------------------------------------ */
+    private ServletContextHandler _contextHandler;
+    private ServletContext _servletContext;
+    private FilterHolder[] _filters=new FilterHolder[0];
+    private FilterMapping[] _filterMappings;
+    private int _matchBeforeIndex = -1; //index of last programmatic FilterMapping with isMatchAfter=false
+    private int _matchAfterIndex = -1;  //index of 1st programmatic FilterMapping with isMatchAfter=true
+    private boolean _filterChainsCached=true;
+    private int _maxFilterChainsCacheSize=512;
+    private boolean _startWithUnavailable=false;
+    private boolean _ensureDefaultServlet=true;
+    private IdentityService _identityService;
+
+    private ServletHolder[] _servlets=new ServletHolder[0];
+    private ServletMapping[] _servletMappings;
+    private Map<String,ServletMapping> _servletPathMappings = new HashMap<String,ServletMapping>();
+
+    private final Map<String,FilterHolder> _filterNameMap= new HashMap<>();
+    private List<FilterMapping> _filterPathMappings;
+    private MultiMap<FilterMapping> _filterNameMappings;
+
+    private final Map<String,ServletHolder> _servletNameMap=new HashMap<>();
+    private PathMap<ServletHolder> _servletPathMap;
+    
+    private ListenerHolder[] _listeners=new ListenerHolder[0];
+
+    protected final ConcurrentMap<?, ?> _chainCache[] = new ConcurrentMap[FilterMapping.ALL];
+    protected final Queue<?>[] _chainLRU = new Queue[FilterMapping.ALL];
+    
+
+
+    /* ------------------------------------------------------------ */
+    /** Constructor.
+     */
+    public ServletHandler()
+    {
+    }
+
+    /* ----------------------------------------------------------------- */
+    @Override
+    protected synchronized void doStart()
+        throws Exception
+    {
+        ContextHandler.Context context=ContextHandler.getCurrentContext();
+        _servletContext=context==null?new ContextHandler.NoContext():context;
+        _contextHandler=(ServletContextHandler)(context==null?null:context.getContextHandler());
+
+        if (_contextHandler!=null)
+        {
+            SecurityHandler security_handler = _contextHandler.getChildHandlerByClass(SecurityHandler.class);
+            if (security_handler!=null)
+                _identityService=security_handler.getIdentityService();
+        }
+
+        updateNameMappings();
+        updateMappings();        
+        
+        if (getServletMapping("/")==null && _ensureDefaultServlet)
+        {
+            LOG.debug("Adding Default404Servlet to {}",this);
+            addServletWithMapping(Default404Servlet.class,"/");
+            updateMappings();  
+            getServletMapping("/").setDefault(true);
+        }
+
+        if(_filterChainsCached)
+        {
+            _chainCache[FilterMapping.REQUEST]=new ConcurrentHashMap<String,FilterChain>();
+            _chainCache[FilterMapping.FORWARD]=new ConcurrentHashMap<String,FilterChain>();
+            _chainCache[FilterMapping.INCLUDE]=new ConcurrentHashMap<String,FilterChain>();
+            _chainCache[FilterMapping.ERROR]=new ConcurrentHashMap<String,FilterChain>();
+            _chainCache[FilterMapping.ASYNC]=new ConcurrentHashMap<String,FilterChain>();
+
+            _chainLRU[FilterMapping.REQUEST]=new ConcurrentLinkedQueue<String>();
+            _chainLRU[FilterMapping.FORWARD]=new ConcurrentLinkedQueue<String>();
+            _chainLRU[FilterMapping.INCLUDE]=new ConcurrentLinkedQueue<String>();
+            _chainLRU[FilterMapping.ERROR]=new ConcurrentLinkedQueue<String>();
+            _chainLRU[FilterMapping.ASYNC]=new ConcurrentLinkedQueue<String>();
+        }
+
+        if (_contextHandler==null)
+            initialize();
+        
+        super.doStart();
+    }
+    
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * @return true if ServletHandler always has a default servlet, using {@link Default404Servlet} if no other
+     * default servlet is configured.
+     */
+    public boolean isEnsureDefaultServlet()
+    {
+        return _ensureDefaultServlet;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param ensureDefaultServlet true if ServletHandler always has a default servlet, using {@link Default404Servlet} if no other
+     * default servlet is configured.
+     */
+    public void setEnsureDefaultServlet(boolean ensureDefaultServlet)
+    {
+        _ensureDefaultServlet=ensureDefaultServlet;
+    }
+
+    /* ----------------------------------------------------------------- */
+    @Override
+    protected void start(LifeCycle l) throws Exception
+    {
+        //Don't start the whole object tree (ie all the servlet and filter Holders) when
+        //this handler starts. They have a slightly special lifecycle, and should only be
+        //started AFTER the handlers have all started (and the ContextHandler has called
+        //the context listeners).
+        if (!(l instanceof Holder))
+            super.start(l);
+    }
+
+    /* ----------------------------------------------------------------- */
+    @Override
+    protected synchronized void doStop()
+        throws Exception
+    {
+        super.doStop();
+
+        // Stop filters
+        List<FilterHolder> filterHolders = new ArrayList<FilterHolder>();
+        List<FilterMapping> filterMappings = ArrayUtil.asMutableList(_filterMappings);      
+        if (_filters!=null)
+        {
+            for (int i=_filters.length; i-->0;)
+            {
+                try 
+                {
+                    _filters[i].stop(); 
+                }
+                catch(Exception e)
+                {
+                    LOG.warn(Log.EXCEPTION,e);
+                }
+                if (_filters[i].getSource() != Source.EMBEDDED)
+                {
+                    //remove all of the mappings that were for non-embedded filters
+                    _filterNameMap.remove(_filters[i].getName());
+                    //remove any mappings associated with this filter
+                    ListIterator<FilterMapping> fmitor = filterMappings.listIterator();
+                    while (fmitor.hasNext())
+                    {
+                        FilterMapping fm = fmitor.next();
+                        if (fm.getFilterName().equals(_filters[i].getName()))
+                            fmitor.remove();
+                    }
+                }
+                else
+                    filterHolders.add(_filters[i]); //only retain embedded
+            }
+        }
+        
+        //Retain only filters and mappings that were added using jetty api (ie Source.EMBEDDED)
+        FilterHolder[] fhs = (FilterHolder[]) LazyList.toArray(filterHolders, FilterHolder.class);
+        updateBeans(_filters, fhs);
+        _filters = fhs;
+        FilterMapping[] fms = (FilterMapping[]) LazyList.toArray(filterMappings, FilterMapping.class);
+        updateBeans(_filterMappings, fms);
+        _filterMappings = fms;
+        
+        _matchAfterIndex = (_filterMappings == null || _filterMappings.length == 0 ? -1 : _filterMappings.length-1);
+        _matchBeforeIndex = -1;
+
+        // Stop servlets
+        List<ServletHolder> servletHolders = new ArrayList<ServletHolder>();  //will be remaining servlets
+        List<ServletMapping> servletMappings = ArrayUtil.asMutableList(_servletMappings); //will be remaining mappings
+        if (_servlets!=null)
+        {
+            for (int i=_servlets.length; i-->0;)
+            {
+                try 
+                { 
+                    _servlets[i].stop(); 
+                }
+                catch(Exception e)
+                {
+                    LOG.warn(Log.EXCEPTION,e);
+                }
+                
+                if (_servlets[i].getSource() != Source.EMBEDDED)
+                {
+                    //remove from servlet name map
+                    _servletNameMap.remove(_servlets[i].getName());
+                    //remove any mappings associated with this servlet
+                    ListIterator<ServletMapping> smitor = servletMappings.listIterator();
+                    while (smitor.hasNext())
+                    {
+                        ServletMapping sm = smitor.next();
+                        if (sm.getServletName().equals(_servlets[i].getName()))
+                            smitor.remove();
+                    }
+                }
+                else
+                    servletHolders.add(_servlets[i]); //only retain embedded 
+            }
+        }
+
+        //Retain only Servlets and mappings added via jetty apis (ie Source.EMBEDDED)
+        ServletHolder[] shs = (ServletHolder[]) LazyList.toArray(servletHolders, ServletHolder.class);
+        updateBeans(_servlets, shs);
+        _servlets = shs;
+        ServletMapping[] sms = (ServletMapping[])LazyList.toArray(servletMappings, ServletMapping.class); 
+        updateBeans(_servletMappings, sms);
+        _servletMappings = sms;
+
+        //Retain only Listeners added via jetty apis (is Source.EMBEDDED)
+        List<ListenerHolder> listenerHolders = new ArrayList<ListenerHolder>();
+        if (_listeners != null)
+        { 
+            for (int i=_listeners.length; i-->0;)
+            {
+                try
+                {
+                    _listeners[i].stop();
+                } 
+                catch(Exception e)
+                {
+                    LOG.warn(Log.EXCEPTION,e);
+                }
+                if (_listeners[i].getSource() == Source.EMBEDDED)
+                    listenerHolders.add(_listeners[i]);
+            }
+        }
+        ListenerHolder[] listeners = (ListenerHolder[])LazyList.toArray(listenerHolders, ListenerHolder.class);
+        updateBeans(_listeners, listeners);
+        _listeners = listeners;
+
+        //will be regenerated on next start
+        _filterPathMappings=null;
+        _filterNameMappings=null;
+        _servletPathMap=null;
+        _servletPathMappings=null;
+    }
+
+    /* ------------------------------------------------------------ */
+    protected IdentityService getIdentityService()
+    {
+        return _identityService;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Returns the contextLog.
+     */
+    public Object getContextLog()
+    {
+        return null;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Returns the filterMappings.
+     */
+    @ManagedAttribute(value="filters", readonly=true)
+    public FilterMapping[] getFilterMappings()
+    {
+        return _filterMappings;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Get Filters.
+     * @return Array of defined servlets
+     */
+    @ManagedAttribute(value="filters", readonly=true)
+    public FilterHolder[] getFilters()
+    {
+        return _filters;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** ServletHolder matching path.
+     * @param pathInContext Path within _context.
+     * @return PathMap Entries pathspec to ServletHolder
+     */
+    public PathMap.MappedEntry<ServletHolder> getHolderEntry(String pathInContext)
+    {
+        if (_servletPathMap==null)
+            return null;
+        return _servletPathMap.getMatch(pathInContext);
+    }
+
+    /* ------------------------------------------------------------ */
+    public ServletContext getServletContext()
+    {
+        return _servletContext;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Returns the servletMappings.
+     */
+    @ManagedAttribute(value="mappings of servlets", readonly=true)
+    public ServletMapping[] getServletMappings()
+    {
+        return _servletMappings;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * Get the ServletMapping matching the path
+     * 
+     * @param pathSpec
+     * @return
+     */
+    public ServletMapping getServletMapping(String pathSpec)
+    {
+        if (pathSpec == null || _servletPathMappings == null)
+            return null;
+        
+        return _servletPathMappings.get(pathSpec);
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** Get Servlets.
+     * @return Array of defined servlets
+     */
+    @ManagedAttribute(value="servlets", readonly=true)
+    public ServletHolder[] getServlets()
+    {
+        return _servlets;
+    }
+
+    /* ------------------------------------------------------------ */
+    public ServletHolder getServlet(String name)
+    {
+        return _servletNameMap.get(name);
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public void doScope(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+    {
+        // Get the base requests
+        final String old_servlet_path=baseRequest.getServletPath();
+        final String old_path_info=baseRequest.getPathInfo();
+
+        DispatcherType type = baseRequest.getDispatcherType();
+
+        ServletHolder servlet_holder=null;
+        UserIdentity.Scope old_scope=null;
+
+        // find the servlet
+        if (target.startsWith("/"))
+        {
+            // Look for the servlet by path
+            PathMap.MappedEntry<ServletHolder> entry=getHolderEntry(target);
+            if (entry!=null)
+            {
+                servlet_holder=entry.getValue();
+
+                String servlet_path_spec= entry.getKey();
+                String servlet_path=entry.getMapped()!=null?entry.getMapped():PathMap.pathMatch(servlet_path_spec,target);
+                String path_info=PathMap.pathInfo(servlet_path_spec,target);
+
+                if (DispatcherType.INCLUDE.equals(type))
+                {
+                    baseRequest.setAttribute(RequestDispatcher.INCLUDE_SERVLET_PATH,servlet_path);
+                    baseRequest.setAttribute(RequestDispatcher.INCLUDE_PATH_INFO, path_info);
+                }
+                else
+                {
+                    baseRequest.setServletPath(servlet_path);
+                    baseRequest.setPathInfo(path_info);
+                }
+            }
+        }
+        else
+        {
+            // look for a servlet by name!
+            servlet_holder= _servletNameMap.get(target);
+        }
+
+        if (LOG.isDebugEnabled())
+            LOG.debug("servlet {}|{}|{} -> {}",baseRequest.getContextPath(),baseRequest.getServletPath(),baseRequest.getPathInfo(),servlet_holder);
+
+        try
+        {
+            // Do the filter/handling thang
+            old_scope=baseRequest.getUserIdentityScope();
+            baseRequest.setUserIdentityScope(servlet_holder);
+
+            // start manual inline of nextScope(target,baseRequest,request,response);
+            if (never())
+                nextScope(target,baseRequest,request,response);
+            else if (_nextScope!=null)
+                _nextScope.doScope(target,baseRequest,request, response);
+            else if (_outerScope!=null)
+                _outerScope.doHandle(target,baseRequest,request, response);
+            else
+                doHandle(target,baseRequest,request, response);
+            // end manual inline (pathentic attempt to reduce stack depth)
+        }
+        finally
+        {
+            if (old_scope!=null)
+                baseRequest.setUserIdentityScope(old_scope);
+
+            if (!(DispatcherType.INCLUDE.equals(type)))
+            {
+                baseRequest.setServletPath(old_servlet_path);
+                baseRequest.setPathInfo(old_path_info);
+            }
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see org.eclipse.jetty.server.Handler#handle(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, int)
+     */
+    @Override
+    public void doHandle(String target, Request baseRequest,HttpServletRequest request, HttpServletResponse response)
+        throws IOException, ServletException
+    {
+        DispatcherType type = baseRequest.getDispatcherType();
+
+        ServletHolder servlet_holder=(ServletHolder) baseRequest.getUserIdentityScope();
+        FilterChain chain=null;
+
+        // find the servlet
+        if (target.startsWith("/"))
+        {
+            if (servlet_holder!=null && _filterMappings!=null && _filterMappings.length>0)
+                chain=getFilterChain(baseRequest, target, servlet_holder);
+        }
+        else
+        {
+            if (servlet_holder!=null)
+            {
+                if (_filterMappings!=null && _filterMappings.length>0)
+                {
+                    chain=getFilterChain(baseRequest, null,servlet_holder);
+                }
+            }
+        }
+
+        LOG.debug("chain={}",chain);
+        Throwable th=null;
+        try
+        {
+            if (servlet_holder==null)
+                notFound(baseRequest,request, response);
+            else
+            {
+                // unwrap any tunnelling of base Servlet request/responses
+                ServletRequest req = request;
+                if (req instanceof ServletRequestHttpWrapper)
+                    req = ((ServletRequestHttpWrapper)req).getRequest();
+                ServletResponse res = response;
+                if (res instanceof ServletResponseHttpWrapper)
+                    res = ((ServletResponseHttpWrapper)res).getResponse();
+
+                // Do the filter/handling thang
+                if (chain!=null)
+                    chain.doFilter(req, res);
+                else
+                    servlet_holder.handle(baseRequest,req,res);
+            }
+        }
+        catch(EofException e)
+        {
+            throw e;
+        }
+        catch(RuntimeIOException e)
+        {
+            throw e;
+        }
+        catch(Exception e)
+        {
+            if (!(DispatcherType.REQUEST.equals(type) || DispatcherType.ASYNC.equals(type)))
+            {
+                if (e instanceof IOException)
+                    throw (IOException)e;
+                if (e instanceof RuntimeException)
+                    throw (RuntimeException)e;
+                if (e instanceof ServletException)
+                    throw (ServletException)e;
+            }
+
+            // unwrap cause
+            th=e;
+            if (th instanceof ServletException)
+            {
+                if (th instanceof QuietServletException)
+                { 
+                    LOG.warn(th.toString());
+                    LOG.debug(th);
+                }
+                else
+                    LOG.warn(th);
+            }
+            else if (th instanceof EofException)
+            {
+                throw (EofException)th;
+            }
+            else
+            {
+                LOG.warn(request.getRequestURI(),th);
+                if (LOG.isDebugEnabled())
+                    LOG.debug(request.toString());
+            }
+
+            request.setAttribute(RequestDispatcher.ERROR_EXCEPTION_TYPE,th.getClass());
+            request.setAttribute(RequestDispatcher.ERROR_EXCEPTION,th);
+            if (!response.isCommitted())
+            {
+                baseRequest.getResponse().getHttpFields().put(HttpHeader.CONNECTION,HttpHeaderValue.CLOSE);
+                if (th instanceof UnavailableException)
+                {
+                    UnavailableException ue = (UnavailableException)th;
+                    if (ue.isPermanent())
+                        response.sendError(HttpServletResponse.SC_NOT_FOUND);
+                    else
+                        response.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE);
+                }
+                else
+                    response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
+            }
+            else
+            {
+                if (th instanceof IOException)
+                    throw (IOException)th;
+                if (th instanceof RuntimeException)
+                    throw (RuntimeException)th;
+                if (th instanceof ServletException)
+                    throw (ServletException)th;
+                throw new IllegalStateException("response already committed",th);
+            }
+        }
+        catch(Error e)
+        {
+            if ("ContinuationThrowable".equals(e.getClass().getSimpleName()))
+                throw e;
+            th=e;
+            if (!(DispatcherType.REQUEST.equals(type) || DispatcherType.ASYNC.equals(type)))
+                throw e;
+            LOG.warn("Error for "+request.getRequestURI(),e);
+            if(LOG.isDebugEnabled())
+                LOG.debug(request.toString());
+
+            request.setAttribute(RequestDispatcher.ERROR_EXCEPTION_TYPE,e.getClass());
+            request.setAttribute(RequestDispatcher.ERROR_EXCEPTION,e);
+            if (!response.isCommitted())
+            {
+                baseRequest.getResponse().getHttpFields().put(HttpHeader.CONNECTION,HttpHeaderValue.CLOSE);
+                response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
+            }
+            else
+                LOG.debug("Response already committed for handling ",e);
+        }
+        finally
+        {
+            // Complete async errored requests 
+            if (th!=null && request.isAsyncStarted())
+                baseRequest.getHttpChannelState().errorComplete();
+            
+            if (servlet_holder!=null)
+                baseRequest.setHandled(true);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    protected FilterChain getFilterChain(Request baseRequest, String pathInContext, ServletHolder servletHolder)
+    {
+        String key=pathInContext==null?servletHolder.getName():pathInContext;
+        int dispatch = FilterMapping.dispatch(baseRequest.getDispatcherType());
+
+        if (_filterChainsCached && _chainCache!=null)
+        {
+            FilterChain chain = (FilterChain)_chainCache[dispatch].get(key);
+            if (chain!=null)
+                return chain;
+        }
+
+        // Build list of filters (list of FilterHolder objects)
+        List<FilterHolder> filters = new ArrayList<>();
+
+        // Path filters
+        if (pathInContext!=null && _filterPathMappings!=null)
+        {
+            for (FilterMapping filterPathMapping : _filterPathMappings)
+            {
+                if (filterPathMapping.appliesTo(pathInContext, dispatch))
+                    filters.add(filterPathMapping.getFilterHolder());
+            }
+        }
+
+        // Servlet name filters
+        if (servletHolder != null && _filterNameMappings!=null && _filterNameMappings.size() > 0)
+        {
+            // Servlet name filters
+            if (_filterNameMappings.size() > 0)
+            {
+                Object o= _filterNameMappings.get(servletHolder.getName());
+
+                for (int i=0; i<LazyList.size(o);i++)
+                {
+                    FilterMapping mapping = (FilterMapping)LazyList.get(o,i);
+                    if (mapping.appliesTo(dispatch))
+                        filters.add(mapping.getFilterHolder());
+                }
+
+                o= _filterNameMappings.get("*");
+                for (int i=0; i<LazyList.size(o);i++)
+                {
+                    FilterMapping mapping = (FilterMapping)LazyList.get(o,i);
+                    if (mapping.appliesTo(dispatch))
+                        filters.add(mapping.getFilterHolder());
+                }
+            }
+        }
+
+        if (filters.isEmpty())
+            return null;
+
+
+        FilterChain chain = null;
+        if (_filterChainsCached)
+        {
+            if (filters.size() > 0)
+                chain= new CachedChain(filters, servletHolder);
+
+            final Map<String,FilterChain> cache=(Map<String, FilterChain>)_chainCache[dispatch];
+            final Queue<String> lru=(Queue<String>)_chainLRU[dispatch];
+
+               // Do we have too many cached chains?
+               while (_maxFilterChainsCacheSize>0 && cache.size()>=_maxFilterChainsCacheSize)
+               {
+                   // The LRU list is not atomic with the cache map, so be prepared to invalidate if
+                   // a key is not found to delete.
+                   // Delete by LRU (where U==created)
+                   String k=lru.poll();
+                   if (k==null)
+                   {
+                       cache.clear();
+                       break;
+                   }
+                   cache.remove(k);
+               }
+
+               cache.put(key,chain);
+               lru.add(key);
+        }
+        else if (filters.size() > 0)
+            chain = new Chain(baseRequest,filters, servletHolder);
+
+        return chain;
+    }
+
+    /* ------------------------------------------------------------ */
+    protected void invalidateChainsCache()
+    {
+        if (_chainLRU[FilterMapping.REQUEST]!=null)
+        {
+            _chainLRU[FilterMapping.REQUEST].clear();
+            _chainLRU[FilterMapping.FORWARD].clear();
+            _chainLRU[FilterMapping.INCLUDE].clear();
+            _chainLRU[FilterMapping.ERROR].clear();
+            _chainLRU[FilterMapping.ASYNC].clear();
+
+            _chainCache[FilterMapping.REQUEST].clear();
+            _chainCache[FilterMapping.FORWARD].clear();
+            _chainCache[FilterMapping.INCLUDE].clear();
+            _chainCache[FilterMapping.ERROR].clear();
+            _chainCache[FilterMapping.ASYNC].clear();
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return true if the handler is started and there are no unavailable servlets
+     */
+    public boolean isAvailable()
+    {
+        if (!isStarted())
+            return false;
+        ServletHolder[] holders = getServlets();
+        for (ServletHolder holder : holders)
+        {
+            if (holder != null && !holder.isAvailable())
+                return false;
+        }
+        return true;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param start True if this handler will start with unavailable servlets
+     */
+    public void setStartWithUnavailable(boolean start)
+    {
+        _startWithUnavailable=start;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return True if this handler will start with unavailable servlets
+     */
+    public boolean isStartWithUnavailable()
+    {
+        return _startWithUnavailable;
+    }
+
+
+
+    /* ------------------------------------------------------------ */
+    /** Initialize filters and load-on-startup servlets.
+     */
+    public void initialize()
+        throws Exception
+    {
+        MultiException mx = new MultiException();
+
+        //start filter holders now
+        if (_filters != null)
+        {
+            for (FilterHolder f: _filters)
+            {
+                try
+                {
+                    f.start();
+                    f.initialize();
+                }
+                catch (Exception e)
+                {
+                    mx.add(e);
+                }
+            }
+        }
+        
+        // Sort and Initialize servlets
+        if (_servlets!=null)
+        {
+            ServletHolder[] servlets = _servlets.clone();
+            Arrays.sort(servlets);
+            for (ServletHolder servlet : servlets)
+            {
+                try
+                {
+                   /* if (servlet.getClassName() == null && servlet.getForcedPath() != null)
+                    {
+                        ServletHolder forced_holder = _servletPathMap.match(servlet.getForcedPath());
+                        if (forced_holder == null || forced_holder.getClassName() == null)
+                        {
+                            mx.add(new IllegalStateException("No forced path servlet for " + servlet.getForcedPath()));
+                            continue;
+                        }
+                        System.err.println("ServletHandler setting forced path classname to "+forced_holder.getClassName()+ " for "+servlet.getForcedPath());
+                        servlet.setClassName(forced_holder.getClassName());
+                    }*/
+                    
+                    servlet.start();
+                    servlet.initialize();
+                }
+                catch (Throwable e)
+                {
+                    LOG.debug(Log.EXCEPTION, e);
+                    mx.add(e);
+                }
+            }
+        }
+
+        //any other beans
+        for (Holder<?> h: getBeans(Holder.class))
+        {
+            try
+            {
+                if (!h.isStarted())
+                {
+                    h.start();
+                    h.initialize();
+                }
+            }
+            catch (Exception e)
+            {
+                mx.add(e);
+            }
+        }
+        
+        mx.ifExceptionThrow();
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Returns the filterChainsCached.
+     */
+    public boolean isFilterChainsCached()
+    {
+        return _filterChainsCached;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** Add a holder for a listener
+     * @param filter
+     */
+    public void addListener (ListenerHolder listener)
+    {
+        if (listener != null)
+            setListeners(ArrayUtil.addToArray(getListeners(), listener, ListenerHolder.class));
+    }
+    
+    
+    /* ------------------------------------------------------------ */
+    public ListenerHolder[] getListeners()
+    {
+        return _listeners;
+    }
+    
+    /* ------------------------------------------------------------ */
+    public void setListeners(ListenerHolder[] listeners)
+    {
+        if (listeners!=null)
+            for (ListenerHolder holder:listeners)
+                holder.setServletHandler(this);
+
+        updateBeans(_listeners,listeners);
+        _listeners = listeners;
+    }
+    
+    /* ------------------------------------------------------------ */
+    public ListenerHolder newListenerHolder(Holder.Source source)
+    {
+        return new ListenerHolder(source);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * see also newServletHolder(Class)
+     */
+    public ServletHolder newServletHolder(Holder.Source source)
+    {
+        return new ServletHolder(source);
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Convenience method to add a servlet.
+     * @return The servlet holder.
+     */
+    public ServletHolder addServletWithMapping (String className,String pathSpec)
+    {
+        ServletHolder holder = newServletHolder(Source.EMBEDDED);
+        holder.setClassName(className);
+        addServletWithMapping(holder,pathSpec);
+        return holder;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** conveniance method to add a servlet.
+     * @return The servlet holder.
+     */
+    public ServletHolder addServletWithMapping (Class<? extends Servlet> servlet,String pathSpec)
+    {
+        ServletHolder holder = newServletHolder(Source.EMBEDDED);
+        holder.setHeldClass(servlet);
+        addServletWithMapping(holder,pathSpec);
+
+        return holder;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** conveniance method to add a servlet.
+     * @param servlet servlet holder to add
+     * @param pathSpec servlet mappings for the servletHolder
+     */
+    public void addServletWithMapping (ServletHolder servlet,String pathSpec)
+    {
+        ServletHolder[] holders=getServlets();
+        if (holders!=null)
+            holders = holders.clone();
+
+        try
+        {
+            setServlets(ArrayUtil.addToArray(holders, servlet, ServletHolder.class));
+
+            ServletMapping mapping = new ServletMapping();
+            mapping.setServletName(servlet.getName());
+            mapping.setPathSpec(pathSpec);
+            setServletMappings(ArrayUtil.addToArray(getServletMappings(), mapping, ServletMapping.class));
+        }
+        catch (Exception e)
+        {
+            setServlets(holders);
+            if (e instanceof RuntimeException)
+                throw (RuntimeException)e;
+            throw new RuntimeException(e);
+        }
+    }
+
+
+    /* ------------------------------------------------------------ */
+    /**Convenience method to add a pre-constructed ServletHolder.
+     * @param holder
+     */
+    public void addServlet(ServletHolder holder)
+    {
+        setServlets(ArrayUtil.addToArray(getServlets(), holder, ServletHolder.class));
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Convenience method to add a pre-constructed ServletMapping.
+     * @param mapping
+     */
+    public void addServletMapping (ServletMapping mapping)
+    {
+        setServletMappings(ArrayUtil.addToArray(getServletMappings(), mapping, ServletMapping.class));
+    }
+    
+    /* ------------------------------------------------------------ */
+    public Set<String>  setServletSecurity(ServletRegistration.Dynamic registration, ServletSecurityElement servletSecurityElement) 
+    {
+        if (_contextHandler != null) 
+        {
+            return _contextHandler.setServletSecurity(registration, servletSecurityElement);
+        }
+        return Collections.emptySet();
+    }
+
+    /* ------------------------------------------------------------ */
+    public FilterHolder newFilterHolder(Holder.Source source)
+    {
+        return new FilterHolder(source);
+    }
+
+    /* ------------------------------------------------------------ */
+    public FilterHolder getFilter(String name)
+    {
+        return _filterNameMap.get(name);
+    }
+
+
+    /* ------------------------------------------------------------ */
+    /** Convenience method to add a filter.
+     * @param filter  class of filter to create
+     * @param pathSpec filter mappings for filter
+     * @param dispatches see {@link FilterMapping#setDispatches(int)}
+     * @return The filter holder.
+     */
+    public FilterHolder addFilterWithMapping (Class<? extends Filter> filter,String pathSpec,EnumSet<DispatcherType> dispatches)
+    {
+        FilterHolder holder = newFilterHolder(Source.EMBEDDED);
+        holder.setHeldClass(filter);
+        addFilterWithMapping(holder,pathSpec,dispatches);
+
+        return holder;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Convenience method to add a filter.
+     * @param className of filter
+     * @param pathSpec filter mappings for filter
+     * @param dispatches see {@link FilterMapping#setDispatches(int)}
+     * @return The filter holder.
+     */
+    public FilterHolder addFilterWithMapping (String className,String pathSpec,EnumSet<DispatcherType> dispatches)
+    {
+        FilterHolder holder = newFilterHolder(Source.EMBEDDED);
+        holder.setClassName(className);
+
+        addFilterWithMapping(holder,pathSpec,dispatches);
+        return holder;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Convenience method to add a filter.
+     * @param holder filter holder to add
+     * @param pathSpec filter mappings for filter
+     * @param dispatches see {@link FilterMapping#setDispatches(int)}
+     */
+    public void addFilterWithMapping (FilterHolder holder,String pathSpec,EnumSet<DispatcherType> dispatches)
+    {
+        FilterHolder[] holders = getFilters();
+        if (holders!=null)
+            holders = holders.clone();
+
+        try
+        {
+            setFilters(ArrayUtil.addToArray(holders, holder, FilterHolder.class));
+
+            FilterMapping mapping = new FilterMapping();
+            mapping.setFilterName(holder.getName());
+            mapping.setPathSpec(pathSpec);
+            mapping.setDispatcherTypes(dispatches);
+            addFilterMapping(mapping);
+            
+        }
+        catch (RuntimeException e)
+        {
+            setFilters(holders);
+            throw e;
+        }
+        catch (Error e)
+        {
+            setFilters(holders);
+            throw e;
+        }
+
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Convenience method to add a filter.
+     * @param filter  class of filter to create
+     * @param pathSpec filter mappings for filter
+     * @param dispatches see {@link FilterMapping#setDispatches(int)}
+     * @return The filter holder.
+     */
+    public FilterHolder addFilterWithMapping (Class<? extends Filter> filter,String pathSpec,int dispatches)
+    {
+        FilterHolder holder = newFilterHolder(Source.EMBEDDED);
+        holder.setHeldClass(filter);
+        addFilterWithMapping(holder,pathSpec,dispatches);
+
+        return holder;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Convenience method to add a filter.
+     * @param className of filter
+     * @param pathSpec filter mappings for filter
+     * @param dispatches see {@link FilterMapping#setDispatches(int)}
+     * @return The filter holder.
+     */
+    public FilterHolder addFilterWithMapping (String className,String pathSpec,int dispatches)
+    {
+        FilterHolder holder = newFilterHolder(Source.EMBEDDED);
+        holder.setClassName(className);
+
+        addFilterWithMapping(holder,pathSpec,dispatches);
+        return holder;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Convenience method to add a filter.
+     * @param holder filter holder to add
+     * @param pathSpec filter mappings for filter
+     * @param dispatches see {@link FilterMapping#setDispatches(int)}
+     */
+    public void addFilterWithMapping (FilterHolder holder,String pathSpec,int dispatches)
+    {
+        FilterHolder[] holders = getFilters();
+        if (holders!=null)
+            holders = holders.clone();
+
+        try
+        {
+            setFilters(ArrayUtil.addToArray(holders, holder, FilterHolder.class));
+
+            FilterMapping mapping = new FilterMapping();
+            mapping.setFilterName(holder.getName());
+            mapping.setPathSpec(pathSpec);
+            mapping.setDispatches(dispatches);
+            addFilterMapping(mapping);
+        }
+        catch (RuntimeException e)
+        {
+            setFilters(holders);
+            throw e;
+        }
+        catch (Error e)
+        {
+            setFilters(holders);
+            throw e;
+        }
+
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Convenience method to add a filter with a mapping
+     * @param className
+     * @param pathSpec
+     * @param dispatches
+     * @return the filter holder created
+     * @deprecated use {@link #addFilterWithMapping(Class, String, EnumSet)} instead
+     */
+    public FilterHolder addFilter (String className,String pathSpec,EnumSet<DispatcherType> dispatches)
+    {
+        return addFilterWithMapping(className, pathSpec, dispatches);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * convenience method to add a filter and mapping
+     * @param filter
+     * @param filterMapping
+     */
+    public void addFilter (FilterHolder filter, FilterMapping filterMapping)
+    {
+        if (filter != null)
+            setFilters(ArrayUtil.addToArray(getFilters(), filter, FilterHolder.class));
+        if (filterMapping != null)
+            addFilterMapping(filterMapping);
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Convenience method to add a preconstructed FilterHolder
+     * @param filter
+     */
+    public void addFilter (FilterHolder filter)
+    {
+        if (filter != null)
+            setFilters(ArrayUtil.addToArray(getFilters(), filter, FilterHolder.class));
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Convenience method to add a preconstructed FilterMapping
+     * @param mapping
+     */
+    public void addFilterMapping (FilterMapping mapping)
+    {
+        if (mapping != null)
+        {
+            Source source = (mapping.getFilterHolder()==null?null:mapping.getFilterHolder().getSource());
+            FilterMapping[] mappings =getFilterMappings();
+            if (mappings==null || mappings.length==0)
+            {
+                setFilterMappings(insertFilterMapping(mapping,0,false));
+                if (source != null && source == Source.JAVAX_API)
+                    _matchAfterIndex = 0;
+            }
+            else
+            {
+                //there are existing entries. If this is a programmatic filtermapping, it is added at the end of the list.
+                //If this is a normal filtermapping, it is inserted after all the other filtermappings (matchBefores and normals), 
+                //but before the first matchAfter filtermapping.
+                if (source != null && Source.JAVAX_API == source)
+                {
+                    setFilterMappings(insertFilterMapping(mapping,mappings.length-1, false));
+                    if (_matchAfterIndex < 0)
+                        _matchAfterIndex = getFilterMappings().length-1;
+                }
+                else
+                {
+                    //insert non-programmatic filter mappings before any matchAfters, if any
+                    if (_matchAfterIndex < 0)
+                        setFilterMappings(insertFilterMapping(mapping,mappings.length-1, false));
+                    else
+                    {
+                        FilterMapping[] new_mappings = insertFilterMapping(mapping, _matchAfterIndex, true);
+                        ++_matchAfterIndex;
+                        setFilterMappings(new_mappings);
+                    }
+                }
+            }
+        }
+    }
+    
+
+    /* ------------------------------------------------------------ */
+    /** Convenience method to add a preconstructed FilterMapping
+     * @param mapping
+     */
+    public void prependFilterMapping (FilterMapping mapping)
+    {
+        if (mapping != null)
+        {
+            Source source = mapping.getFilterHolder().getSource();
+            
+            FilterMapping[] mappings = getFilterMappings();
+            if (mappings==null || mappings.length==0)
+            {
+                setFilterMappings(insertFilterMapping(mapping, 0, false));
+                if (source != null && Source.JAVAX_API == source)
+                    _matchBeforeIndex = 0;
+            }
+            else
+            {
+                if (source != null && Source.JAVAX_API == source)
+                {
+                    //programmatically defined filter mappings are prepended to mapping list in the order
+                    //in which they were defined. In other words, insert this mapping at the tail of the 
+                    //programmatically prepended filter mappings, BEFORE the first web.xml defined filter mapping.
+
+                    if (_matchBeforeIndex < 0)
+                    { 
+                        //no programmatically defined prepended filter mappings yet, prepend this one
+                        _matchBeforeIndex = 0;
+                        FilterMapping[] new_mappings = insertFilterMapping(mapping, 0, true);
+                        setFilterMappings(new_mappings);
+                    }
+                    else
+                    {
+                        FilterMapping[] new_mappings = insertFilterMapping(mapping,_matchBeforeIndex, false);
+                        ++_matchBeforeIndex;
+                        setFilterMappings(new_mappings);
+                    }
+                }
+                else
+                {
+                    //non programmatically defined, just prepend to list
+                    FilterMapping[] new_mappings = insertFilterMapping(mapping, 0, true);
+                    setFilterMappings(new_mappings);
+                }
+                
+                //adjust matchAfterIndex ptr to take account of the mapping we just prepended
+                if (_matchAfterIndex >= 0)
+                    ++_matchAfterIndex;
+            }
+        }
+    }
+    
+    
+    
+    /**
+     * Insert a filtermapping in the list
+     * @param mapping the FilterMapping to add
+     * @param pos the position in the existing arry at which to add it
+     * @param before if true, insert before  pos, if false insert after it
+     * @return
+     */
+    protected FilterMapping[] insertFilterMapping (FilterMapping mapping, int pos, boolean before)
+    {
+        if (pos < 0)
+            throw new IllegalArgumentException("FilterMapping insertion pos < 0");
+        FilterMapping[] mappings = getFilterMappings();
+        
+        if (mappings==null || mappings.length==0)
+        {
+            return new FilterMapping[] {mapping};
+        }
+        FilterMapping[] new_mappings = new FilterMapping[mappings.length+1];
+
+    
+        if (before)
+        {
+            //copy existing filter mappings up to but not including the pos
+            System.arraycopy(mappings,0,new_mappings,0,pos);
+
+            //add in the new mapping
+            new_mappings[pos] = mapping; 
+
+            //copy the old pos mapping and any remaining existing mappings
+            System.arraycopy(mappings,pos,new_mappings,pos+1, mappings.length-pos);
+
+        }
+        else
+        {
+            //copy existing filter mappings up to and including the pos
+            System.arraycopy(mappings,0,new_mappings,0,pos+1);
+            //add in the new mapping after the pos
+            new_mappings[pos+1] = mapping;   
+
+            //copy the remaining existing mappings
+            if (mappings.length > pos+1)
+                System.arraycopy(mappings,pos+1,new_mappings,pos+2, mappings.length-(pos+1));
+        }
+        return new_mappings;
+    }
+    
+    
+    /* ------------------------------------------------------------ */
+    protected synchronized void updateNameMappings()
+    {
+        // update filter name map
+        _filterNameMap.clear();
+        if (_filters!=null)
+        {
+            for (FilterHolder filter : _filters)
+            {
+                _filterNameMap.put(filter.getName(), filter);
+                filter.setServletHandler(this);
+            }
+        }
+
+        // Map servlet names to holders
+        _servletNameMap.clear();
+        if (_servlets!=null)
+        {
+            // update the maps
+            for (ServletHolder servlet : _servlets)
+            {
+                _servletNameMap.put(servlet.getName(), servlet);
+                servlet.setServletHandler(this);
+            }
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    protected synchronized void updateMappings()
+    {
+        // update filter mappings
+        if (_filterMappings==null)
+        {
+            _filterPathMappings=null;
+            _filterNameMappings=null;
+        }
+        else
+        {
+            _filterPathMappings=new ArrayList<>();
+            _filterNameMappings=new MultiMap<FilterMapping>();
+            for (FilterMapping filtermapping : _filterMappings)
+            {
+                FilterHolder filter_holder = _filterNameMap.get(filtermapping.getFilterName());
+                if (filter_holder == null)
+                    throw new IllegalStateException("No filter named " + filtermapping.getFilterName());
+                filtermapping.setFilterHolder(filter_holder);
+                if (filtermapping.getPathSpecs() != null)
+                    _filterPathMappings.add(filtermapping);
+
+                if (filtermapping.getServletNames() != null)
+                {
+                    String[] names = filtermapping.getServletNames();
+                    for (String name : names)
+                    {
+                        if (name != null)
+                            _filterNameMappings.add(name, filtermapping);
+                    }
+                }
+            }
+        }
+
+        // Map servlet paths to holders
+        if (_servletMappings==null || _servletNameMap==null)
+        {
+            _servletPathMap=null;
+        }
+        else
+        {
+            PathMap<ServletHolder> pm = new PathMap<>();
+            Map<String,ServletMapping> servletPathMappings = new HashMap<String,ServletMapping>();
+            
+            //create a map of paths to set of ServletMappings that define that mapping
+            HashMap<String, Set<ServletMapping>> sms = new HashMap<String, Set<ServletMapping>>();
+            for (ServletMapping servletMapping : _servletMappings)
+            {
+                String[] pathSpecs = servletMapping.getPathSpecs();
+                if (pathSpecs != null)
+                {
+                    for (String pathSpec : pathSpecs)
+                    {
+                        Set<ServletMapping> mappings = sms.get(pathSpec);
+                        if (mappings == null)
+                        {
+                            mappings = new HashSet<ServletMapping>();
+                            sms.put(pathSpec, mappings);
+                        }
+                        mappings.add(servletMapping);
+                    }
+                }
+            }
+         
+            //evaluate path to servlet map based on servlet mappings
+            for (String pathSpec : sms.keySet())
+            {
+                //for each path, look at the mappings where it is referenced
+                //if a mapping is for a servlet that is not enabled, skip it
+                Set<ServletMapping> mappings = sms.get(pathSpec);
+                
+                
+           
+                ServletMapping finalMapping = null;
+                for (ServletMapping mapping : mappings)
+                {
+                    //Get servlet associated with the mapping and check it is enabled
+                    ServletHolder servlet_holder = _servletNameMap.get(mapping.getServletName());
+                    if (servlet_holder == null)
+                        throw new IllegalStateException("No such servlet: " + mapping.getServletName());
+                    //if the servlet related to the mapping is not enabled, skip it from consideration
+                    if (!servlet_holder.isEnabled())
+                        continue;
+
+                    //only accept a default mapping if we don't have any other 
+                    if (finalMapping == null)
+                        finalMapping = mapping;
+                    else
+                    {
+                        //already have a candidate - only accept another one if the candidate is a default
+                        if (finalMapping.isDefault())
+                            finalMapping = mapping;
+                        else
+                        {
+                            //existing candidate isn't a default, if the one we're looking at isn't a default either, then its an error
+                            if (!mapping.isDefault())
+                                throw new IllegalStateException("Multiple servlets map to path: "+pathSpec+": "+finalMapping.getServletName()+","+mapping.getServletName());
+                        }
+                    }
+                }
+                if (finalMapping == null)
+                    throw new IllegalStateException ("No acceptable servlet mappings for "+pathSpec);
+           
+                if (LOG.isDebugEnabled()) LOG.debug("Chose path={} mapped to servlet={} from default={}", pathSpec, finalMapping.getServletName(), finalMapping.isDefault());
+               
+                servletPathMappings.put(pathSpec, finalMapping);
+                pm.put(pathSpec,_servletNameMap.get(finalMapping.getServletName()));
+            }
+     
+            _servletPathMap=pm;
+            _servletPathMappings=servletPathMappings;
+        }
+
+        // flush filter chain cache
+        if (_chainCache!=null)
+        {
+            for (int i=_chainCache.length;i-->0;)
+            {
+                if (_chainCache[i]!=null)
+                    _chainCache[i].clear();
+            }
+        }
+
+        if (LOG.isDebugEnabled())
+        {
+            LOG.debug("filterNameMap="+_filterNameMap);
+            LOG.debug("pathFilters="+_filterPathMappings);
+            LOG.debug("servletFilterMap="+_filterNameMappings);
+            LOG.debug("servletPathMap="+_servletPathMap);
+            LOG.debug("servletNameMap="+_servletNameMap);
+        }
+
+        try
+        {
+            if (_contextHandler!=null && _contextHandler.isStarted() || _contextHandler==null && isStarted())
+                initialize();
+        }
+        catch (Exception e)
+        {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    protected void notFound(Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+    {
+        LOG.debug("Not Found {}",request.getRequestURI());
+        if (getHandler()!=null)
+            nextHandle(URIUtil.addPaths(request.getServletPath(),request.getPathInfo()),baseRequest,request,response);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param filterChainsCached The filterChainsCached to set.
+     */
+    public void setFilterChainsCached(boolean filterChainsCached)
+    {
+        _filterChainsCached = filterChainsCached;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param filterMappings The filterMappings to set.
+     */
+    public void setFilterMappings(FilterMapping[] filterMappings)
+    {
+        updateBeans(_filterMappings,filterMappings);
+        _filterMappings = filterMappings;
+        updateMappings();
+        invalidateChainsCache();
+    }
+
+    /* ------------------------------------------------------------ */
+    public synchronized void setFilters(FilterHolder[] holders)
+    {
+        if (holders!=null)
+            for (FilterHolder holder:holders)
+                holder.setServletHandler(this);
+        
+        updateBeans(_filters,holders);
+        _filters=holders;
+        updateNameMappings();
+        invalidateChainsCache();
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param servletMappings The servletMappings to set.
+     */
+    public void setServletMappings(ServletMapping[] servletMappings)
+    {
+        updateBeans(_servletMappings,servletMappings);
+        _servletMappings = servletMappings;
+        updateMappings();
+        invalidateChainsCache();
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Set Servlets.
+     * @param holders Array of servlets to define
+     */
+    public synchronized void setServlets(ServletHolder[] holders)
+    {
+        if (holders!=null)
+            for (ServletHolder holder:holders)
+                holder.setServletHandler(this);
+        
+        updateBeans(_servlets,holders);
+        _servlets=holders;
+        updateNameMappings();
+        invalidateChainsCache();
+    }
+
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    private class CachedChain implements FilterChain
+    {
+        FilterHolder _filterHolder;
+        CachedChain _next;
+        ServletHolder _servletHolder;
+
+        /* ------------------------------------------------------------ */
+        /**
+         * @param filters list of {@link FilterHolder} objects
+         * @param servletHolder
+         */
+        CachedChain(List<FilterHolder> filters, ServletHolder servletHolder)
+        {
+            if (filters.size()>0)
+            {
+                _filterHolder=filters.get(0);
+                filters.remove(0);
+                _next=new CachedChain(filters,servletHolder);
+            }
+            else
+                _servletHolder=servletHolder;
+        }
+
+        /* ------------------------------------------------------------ */
+        @Override
+        public void doFilter(ServletRequest request, ServletResponse response)
+            throws IOException, ServletException
+        {
+            final Request baseRequest=(request instanceof Request)?((Request)request):HttpChannel.getCurrentHttpChannel().getRequest();
+
+            // pass to next filter
+            if (_filterHolder!=null)
+            {
+                LOG.debug("call filter {}", _filterHolder);
+                Filter filter= _filterHolder.getFilter();
+                if (_filterHolder.isAsyncSupported())
+                    filter.doFilter(request, response, _next);
+                else
+                {
+                    final boolean suspendable=baseRequest.isAsyncSupported();
+                    if (suspendable)
+                    {
+                        try
+                        {
+                            baseRequest.setAsyncSupported(false);
+                            filter.doFilter(request, response, _next);
+                        }
+                        finally
+                        {
+                            baseRequest.setAsyncSupported(true);
+                        }
+                    }
+                    else
+                        filter.doFilter(request, response, _next);
+                }
+                return;
+            }
+
+            // Call servlet
+            HttpServletRequest srequest = (HttpServletRequest)request;
+            if (_servletHolder == null)
+                notFound(baseRequest, srequest, (HttpServletResponse)response);
+            else
+            {
+                if (LOG.isDebugEnabled())
+                    LOG.debug("call servlet " + _servletHolder);
+                _servletHolder.handle(baseRequest,request, response);
+            }
+        }
+
+        @Override
+        public String toString()
+        {
+            if (_filterHolder!=null)
+                return _filterHolder+"->"+_next.toString();
+            if (_servletHolder!=null)
+                return _servletHolder.toString();
+            return "null";
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    private class Chain implements FilterChain
+    {
+        final Request _baseRequest;
+        final List<FilterHolder> _chain;
+        final ServletHolder _servletHolder;
+        int _filter= 0;
+
+        /* ------------------------------------------------------------ */
+        Chain(Request baseRequest, List<FilterHolder> filters, ServletHolder servletHolder)
+        {
+            _baseRequest=baseRequest;
+            _chain= filters;
+            _servletHolder= servletHolder;
+        }
+
+        /* ------------------------------------------------------------ */
+        @Override
+        public void doFilter(ServletRequest request, ServletResponse response)
+            throws IOException, ServletException
+        {
+            if (LOG.isDebugEnabled())
+                LOG.debug("doFilter " + _filter);
+
+            // pass to next filter
+            if (_filter < _chain.size())
+            {
+                FilterHolder holder= _chain.get(_filter++);
+                if (LOG.isDebugEnabled())
+                    LOG.debug("call filter " + holder);
+                Filter filter= holder.getFilter();
+
+                if (holder.isAsyncSupported() || !_baseRequest.isAsyncSupported())
+                {
+                    filter.doFilter(request, response, this);
+                }
+                else
+                {
+                    try
+                    {
+                        _baseRequest.setAsyncSupported(false);
+                        filter.doFilter(request, response, this);
+                    }
+                    finally
+                    {
+                        _baseRequest.setAsyncSupported(true);
+                    }
+                }
+
+                return;
+            }
+
+            // Call servlet
+            HttpServletRequest srequest = (HttpServletRequest)request;
+            if (_servletHolder == null)
+                notFound((request instanceof Request)?((Request)request):HttpChannel.getCurrentHttpChannel().getRequest(), srequest, (HttpServletResponse)response);
+            else
+            {
+                LOG.debug("call servlet {}", _servletHolder);
+                _servletHolder.handle(_baseRequest,request, response);
+            }    
+        }
+
+        /* ------------------------------------------------------------ */
+        @Override
+        public String toString()
+        {
+            StringBuilder b = new StringBuilder();
+            for(FilterHolder f: _chain)
+            {
+                b.append(f.toString());
+                b.append("->");
+            }
+            b.append(_servletHolder);
+            return b.toString();
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return The maximum entries in a filter chain cache.
+     */
+    public int getMaxFilterChainsCacheSize()
+    {
+        return _maxFilterChainsCacheSize;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Set the maximum filter chain cache size.
+     * Filter chains are cached if {@link #isFilterChainsCached()} is true. If the max cache size
+     * is greater than zero, then the cache is flushed whenever it grows to be this size.
+     *
+     * @param maxFilterChainsCacheSize  the maximum number of entries in a filter chain cache.
+     */
+    public void setMaxFilterChainsCacheSize(int maxFilterChainsCacheSize)
+    {
+        _maxFilterChainsCacheSize = maxFilterChainsCacheSize;
+    }
+
+    /* ------------------------------------------------------------ */
+    void destroyServlet(Servlet servlet)
+    {
+        if (_contextHandler!=null)
+            _contextHandler.destroyServlet(servlet);
+    }
+
+    /* ------------------------------------------------------------ */
+    void destroyFilter(Filter filter)
+    {
+        if (_contextHandler!=null)
+            _contextHandler.destroyFilter(filter);
+    }
+
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    public static class Default404Servlet extends HttpServlet
+    {
+        @Override
+        protected void doGet(HttpServletRequest req, HttpServletResponse resp)
+                throws ServletException, IOException
+        {
+            resp.sendError(HttpServletResponse.SC_NOT_FOUND);
+        }
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/servlet/ServletHolder.java b/lib/jetty/org/eclipse/jetty/servlet/ServletHolder.java
new file mode 100644 (file)
index 0000000..1df08b3
--- /dev/null
@@ -0,0 +1,1124 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.servlet;
+
+import java.io.IOException;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.Stack;
+
+import javax.servlet.MultipartConfigElement;
+import javax.servlet.Servlet;
+import javax.servlet.ServletConfig;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRegistration;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.ServletSecurityElement;
+import javax.servlet.SingleThreadModel;
+import javax.servlet.UnavailableException;
+
+import org.eclipse.jetty.security.IdentityService;
+import org.eclipse.jetty.security.RunAsToken;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.UserIdentity;
+import org.eclipse.jetty.server.handler.ContextHandler;
+import org.eclipse.jetty.util.Loader;
+import org.eclipse.jetty.util.annotation.ManagedAttribute;
+import org.eclipse.jetty.util.annotation.ManagedObject;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+
+
+
+/* --------------------------------------------------------------------- */
+/** Servlet Instance and Context Holder.
+ * Holds the name, params and some state of a javax.servlet.Servlet
+ * instance. It implements the ServletConfig interface.
+ * This class will organise the loading of the servlet when needed or
+ * requested.
+ *
+ *
+ */
+@ManagedObject("Servlet Holder")
+public class ServletHolder extends Holder<Servlet> implements UserIdentity.Scope, Comparable<ServletHolder>
+{
+    private static final Logger LOG = Log.getLogger(ServletHolder.class);
+
+    /* ---------------------------------------------------------------- */
+    private int _initOrder = -1;
+    private boolean _initOnStartup=false;
+    private Map<String, String> _roleMap;
+    private String _forcedPath;
+    private String _runAsRole;
+    private RunAsToken _runAsToken;
+    private IdentityService _identityService;
+    private ServletRegistration.Dynamic _registration;
+
+
+    private transient Servlet _servlet;
+    private transient Config _config;
+    private transient long _unavailable;
+    private transient boolean _enabled = true;
+    private transient UnavailableException _unavailableEx;
+
+    public static final  String JSP_GENERATED_PACKAGE_NAME = "org.eclipse.jetty.servlet.jspPackagePrefix";
+    public static final Map<String,String> NO_MAPPED_ROLES = Collections.emptyMap();
+
+    /* ---------------------------------------------------------------- */
+    /** Constructor .
+     */
+    public ServletHolder()
+    {
+        this(Source.EMBEDDED);
+    }
+
+    /* ---------------------------------------------------------------- */
+    /** Constructor .
+     */
+    public ServletHolder(Holder.Source creator)
+    {
+        super(creator);
+    }
+
+    /* ---------------------------------------------------------------- */
+    /** Constructor for existing servlet.
+     */
+    public ServletHolder(Servlet servlet)
+    {
+        this(Source.EMBEDDED);
+        setServlet(servlet);
+    }
+
+    /* ---------------------------------------------------------------- */
+    /** Constructor for servlet class.
+     */
+    public ServletHolder(String name, Class<? extends Servlet> servlet)
+    {
+        this(Source.EMBEDDED);
+        setName(name);
+        setHeldClass(servlet);
+    }
+
+    /* ---------------------------------------------------------------- */
+    /** Constructor for servlet class.
+     */
+    public ServletHolder(String name, Servlet servlet)
+    {
+        this(Source.EMBEDDED);
+        setName(name);
+        setServlet(servlet);
+    }
+
+    /* ---------------------------------------------------------------- */
+    /** Constructor for servlet class.
+     */
+    public ServletHolder(Class<? extends Servlet> servlet)
+    {
+        this(Source.EMBEDDED);
+        setHeldClass(servlet);
+    }
+
+    /* ---------------------------------------------------------------- */
+    /**
+     * @return The unavailable exception or null if not unavailable
+     */
+    public UnavailableException getUnavailableException()
+    {
+        return _unavailableEx;
+    }
+
+    /* ------------------------------------------------------------ */
+    public synchronized void setServlet(Servlet servlet)
+    {
+        if (servlet==null || servlet instanceof SingleThreadModel)
+            throw new IllegalArgumentException();
+
+        _extInstance=true;
+        _servlet=servlet;
+        setHeldClass(servlet.getClass());
+        if (getName()==null)
+            setName(servlet.getClass().getName()+"-"+super.hashCode());
+    }
+
+    /* ------------------------------------------------------------ */
+    @ManagedAttribute(value="initialization order", readonly=true)
+    public int getInitOrder()
+    {
+        return _initOrder;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Set the initialize order.
+     * Holders with order<0, are initialized on use. Those with
+     * order>=0 are initialized in increasing order when the handler
+     * is started.
+     */
+    public void setInitOrder(int order)
+    {
+        _initOnStartup=order>=0;
+        _initOrder = order;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Comparitor by init order.
+     */
+    @Override
+    public int compareTo(ServletHolder sh)
+    {
+        if (sh==this)
+            return 0;
+        if (sh._initOrder<_initOrder)
+            return 1;
+        if (sh._initOrder>_initOrder)
+            return -1;
+
+        int c=(_className!=null && sh._className!=null)?_className.compareTo(sh._className):0;
+        if (c==0)
+            c=_name.compareTo(sh._name);
+            return c;
+    }
+
+    /* ------------------------------------------------------------ */
+    public boolean equals(Object o)
+    {
+        return o instanceof ServletHolder && compareTo((ServletHolder)o)==0;
+    }
+
+    /* ------------------------------------------------------------ */
+    public int hashCode()
+    {
+        return _name==null?System.identityHashCode(this):_name.hashCode();
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Link a user role.
+     * Translate the role name used by a servlet, to the link name
+     * used by the container.
+     * @param name The role name as used by the servlet
+     * @param link The role name as used by the container.
+     */
+    public synchronized void setUserRoleLink(String name,String link)
+    {
+        if (_roleMap==null)
+            _roleMap=new HashMap<String, String>();
+        _roleMap.put(name,link);
+    }
+
+    /* ------------------------------------------------------------ */
+    /** get a user role link.
+     * @param name The name of the role
+     * @return The name as translated by the link. If no link exists,
+     * the name is returned.
+     */
+    public String getUserRoleLink(String name)
+    {
+        if (_roleMap==null)
+            return name;
+        String link= _roleMap.get(name);
+        return (link==null)?name:link;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Returns the forcedPath.
+     */
+    @ManagedAttribute(value="forced servlet path", readonly=true)
+    public String getForcedPath()
+    {
+        return _forcedPath;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param forcedPath The forcedPath to set.
+     */
+    public void setForcedPath(String forcedPath)
+    {
+        _forcedPath = forcedPath;
+    }
+
+    public boolean isEnabled()
+    {
+        return _enabled;
+    }
+
+
+    public void setEnabled(boolean enabled)
+    {
+        _enabled = enabled;
+    }
+
+    
+    /* ------------------------------------------------------------ */
+    public void doStart()
+        throws Exception
+    {
+        _unavailable=0;
+        if (!_enabled)
+            return;
+        
+        // Handle JSP file forced paths
+        if (_forcedPath != null)
+        {
+            // Look for a precompiled JSP Servlet
+            String precompiled=getClassNameForJsp(_forcedPath);
+            LOG.debug("Checking for precompiled servlet {} for jsp {}", precompiled, _forcedPath);
+            ServletHolder jsp=getServletHandler().getServlet(precompiled);
+            if (jsp!=null)
+            {
+                LOG.debug("JSP file {} for {} mapped to Servlet {}",_forcedPath, getName(),jsp.getClassName());
+                // set the className for this servlet to the precompiled one
+                setClassName(jsp.getClassName());
+            }
+            else
+            { 
+                if (getClassName() == null)
+                {
+                    // Look for normal JSP servlet
+                    jsp=getServletHandler().getServlet("jsp");
+                    if (jsp!=null)
+                    {
+                        LOG.debug("JSP file {} for {} mapped to Servlet {}",_forcedPath, getName(),jsp.getClassName());
+                        setClassName(jsp.getClassName());
+                        //copy jsp init params that don't exist for this servlet
+                        for (Map.Entry<String, String> entry:jsp.getInitParameters().entrySet())
+                        {
+                            if (!_initParams.containsKey(entry.getKey()))
+                                setInitParameter(entry.getKey(), entry.getValue());
+                        }
+                        //jsp specific: set up the jsp-file on the JspServlet so it can precompile iff load-on-startup is >=0
+                        if (_initOnStartup)
+                            setInitParameter("jspFile", _forcedPath);
+                    }                       
+                }
+            }
+        }
+        
+        
+        //check servlet has a class (ie is not a preliminary registration). If preliminary, fail startup.
+        try
+        {
+            super.doStart();
+        }
+        catch (UnavailableException ue)
+        {
+            makeUnavailable(ue);
+            if (_servletHandler.isStartWithUnavailable())
+            {
+                LOG.ignore(ue);
+                return;
+            }
+            else
+                throw ue;
+        }
+
+
+        //servlet is not an instance of javax.servlet.Servlet
+        try
+        {
+            checkServletType();
+        }
+        catch (UnavailableException ue)
+        {
+            makeUnavailable(ue);
+            if (_servletHandler.isStartWithUnavailable())
+            {
+                LOG.ignore(ue);
+                return;
+            }
+            else
+                throw ue;
+        }
+
+        //check if we need to forcibly set load-on-startup
+        checkInitOnStartup();
+
+        _identityService = _servletHandler.getIdentityService();
+        if (_identityService!=null && _runAsRole!=null)
+            _runAsToken=_identityService.newRunAsToken(_runAsRole);
+
+        _config=new Config();
+
+        if (_class!=null && javax.servlet.SingleThreadModel.class.isAssignableFrom(_class))
+            _servlet = new SingleThreadedWrapper();
+     
+    }
+    
+    
+    /* ------------------------------------------------------------ */
+    @Override
+    public void initialize ()
+    throws Exception
+    {
+        super.initialize();
+        if (_extInstance || _initOnStartup)
+        {
+            try
+            {
+                initServlet();
+            }
+            catch(Exception e)
+            {
+                if (_servletHandler.isStartWithUnavailable())
+                    LOG.ignore(e);
+                else
+                    throw e;
+            }
+        }
+    }
+    
+    
+    /* ------------------------------------------------------------ */
+    public void doStop()
+        throws Exception
+    {
+        Object old_run_as = null;
+        if (_servlet!=null)
+        {
+            try
+            {
+                if (_identityService!=null)
+                    old_run_as=_identityService.setRunAs(_identityService.getSystemUserIdentity(),_runAsToken);
+
+                destroyInstance(_servlet);
+            }
+            catch (Exception e)
+            {
+                LOG.warn(e);
+            }
+            finally
+            {
+                if (_identityService!=null)
+                    _identityService.unsetRunAs(old_run_as);
+            }
+        }
+
+        if (!_extInstance)
+            _servlet=null;
+
+        _config=null;
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public void destroyInstance (Object o)
+    throws Exception
+    {
+        if (o==null)
+            return;
+        Servlet servlet =  ((Servlet)o);
+        getServletHandler().destroyServlet(servlet);
+        servlet.destroy();
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Get the servlet.
+     * @return The servlet
+     */
+    public synchronized Servlet getServlet()
+        throws ServletException
+    {
+        // Handle previous unavailability
+        if (_unavailable!=0)
+        {
+            if (_unavailable<0 || _unavailable>0 && System.currentTimeMillis()<_unavailable)
+                throw _unavailableEx;
+            _unavailable=0;
+            _unavailableEx=null;
+        }
+
+        if (_servlet==null)
+            initServlet();
+        return _servlet;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Get the servlet instance (no initialization done).
+     * @return The servlet or null
+     */
+    public Servlet getServletInstance()
+    {
+        return _servlet;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Check to ensure class of servlet is acceptable.
+     * @throws UnavailableException
+     */
+    public void checkServletType ()
+        throws UnavailableException
+    {
+        if (_class==null || !javax.servlet.Servlet.class.isAssignableFrom(_class))
+        {
+            throw new UnavailableException("Servlet "+_class+" is not a javax.servlet.Servlet");
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return true if the holder is started and is not unavailable
+     */
+    public boolean isAvailable()
+    {
+        if (isStarted()&& _unavailable==0)
+            return true;
+        try
+        {
+            getServlet();
+        }
+        catch(Exception e)
+        {
+            LOG.ignore(e);
+        }
+
+        return isStarted()&& _unavailable==0;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * Check if there is a javax.servlet.annotation.ServletSecurity
+     * annotation on the servlet class. If there is, then we force
+     * it to be loaded on startup, because all of the security 
+     * constraints must be calculated as the container starts.
+     * 
+     */
+    private void checkInitOnStartup()
+    {
+        if (_class==null)
+            return;
+        
+        if ((_class.getAnnotation(javax.servlet.annotation.ServletSecurity.class) != null) && !_initOnStartup)
+            setInitOrder(Integer.MAX_VALUE);    
+    }
+
+    /* ------------------------------------------------------------ */
+    private void makeUnavailable(UnavailableException e)
+    {
+        if (_unavailableEx==e && _unavailable!=0)
+            return;
+
+        _servletHandler.getServletContext().log("unavailable",e);
+
+        _unavailableEx=e;
+        _unavailable=-1;
+        if (e.isPermanent())
+            _unavailable=-1;
+        else
+        {
+            if (_unavailableEx.getUnavailableSeconds()>0)
+                _unavailable=System.currentTimeMillis()+1000*_unavailableEx.getUnavailableSeconds();
+            else
+                _unavailable=System.currentTimeMillis()+5000; // TODO configure
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+
+    private void makeUnavailable(final Throwable e)
+    {
+        if (e instanceof UnavailableException)
+            makeUnavailable((UnavailableException)e);
+        else
+        {
+            ServletContext ctx = _servletHandler.getServletContext();
+            if (ctx==null)
+                LOG.info("unavailable",e);
+            else
+                ctx.log("unavailable",e);
+            _unavailableEx=new UnavailableException(String.valueOf(e),-1)
+            {
+                {
+                    initCause(e);
+                }
+            };
+            _unavailable=-1;
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    private void initServlet()
+       throws ServletException
+    {
+        Object old_run_as = null;
+        try
+        {
+            if (_servlet==null)
+                _servlet=newInstance();
+            if (_config==null)
+                _config=new Config();
+            
+          
+
+            // Handle run as
+            if (_identityService!=null)
+            {
+                old_run_as=_identityService.setRunAs(_identityService.getSystemUserIdentity(),_runAsToken);
+            }
+
+            // Handle configuring servlets that implement org.apache.jasper.servlet.JspServlet
+            if (isJspServlet())
+            {
+                initJspServlet();
+            }
+
+            initMultiPart();
+
+            LOG.debug("Filter.init {}",_servlet);
+            _servlet.init(_config);
+        }
+        catch (UnavailableException e)
+        {
+            makeUnavailable(e);
+            _servlet=null;
+            _config=null;
+            throw e;
+        }
+        catch (ServletException e)
+        {
+            makeUnavailable(e.getCause()==null?e:e.getCause());
+            _servlet=null;
+            _config=null;
+            throw e;
+        }
+        catch (Exception e)
+        {
+            makeUnavailable(e);
+            _servlet=null;
+            _config=null;
+            throw new ServletException(this.toString(),e);
+        }
+        finally
+        {
+            // pop run-as role
+            if (_identityService!=null)
+                _identityService.unsetRunAs(old_run_as);
+        }
+    }
+
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @throws Exception
+     */
+    protected void initJspServlet () throws Exception
+    {
+        ContextHandler ch = ContextHandler.getContextHandler(getServletHandler().getServletContext());
+            
+        /* Set the webapp's classpath for Jasper */
+        ch.setAttribute("org.apache.catalina.jsp_classpath", ch.getClassPath());
+
+        /* Set the system classpath for Jasper */
+        setInitParameter("com.sun.appserv.jsp.classpath", Loader.getClassPath(ch.getClassLoader().getParent()));
+
+        /* Set up other classpath attribute */
+        if ("?".equals(getInitParameter("classpath")))
+        {
+            String classpath = ch.getClassPath();
+            LOG.debug("classpath=" + classpath);
+            if (classpath != null)
+                setInitParameter("classpath", classpath);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Register a ServletRequestListener that will ensure tmp multipart
+     * files are deleted when the request goes out of scope.
+     * 
+     * @throws Exception
+     */
+    protected void initMultiPart () throws Exception
+    {
+        //if this servlet can handle multipart requests, ensure tmp files will be
+        //cleaned up correctly
+        if (((Registration)getRegistration()).getMultipartConfig() != null)
+        {
+            //Register a listener to delete tmp files that are created as a result of this
+            //servlet calling Request.getPart() or Request.getParts()
+
+            ContextHandler ch = ContextHandler.getContextHandler(getServletHandler().getServletContext());
+            ch.addEventListener(new Request.MultiPartCleanerListener());
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.server.UserIdentity.Scope#getContextPath()
+     */
+    @Override
+    public String getContextPath()
+    {
+        return _config.getServletContext().getContextPath();
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.server.UserIdentity.Scope#getRoleRefMap()
+     */
+    @Override
+    public Map<String, String> getRoleRefMap()
+    {
+        return _roleMap;
+    }
+
+    /* ------------------------------------------------------------ */
+    @ManagedAttribute(value="role to run servlet as", readonly=true)
+    public String getRunAsRole()
+    {
+        return _runAsRole;
+    }
+
+    /* ------------------------------------------------------------ */
+    public void setRunAsRole(String role)
+    {
+        _runAsRole = role;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Service a request with this servlet.
+     */
+    public void handle(Request baseRequest,
+                       ServletRequest request,
+                       ServletResponse response)
+        throws ServletException,
+               UnavailableException,
+               IOException
+    {
+        if (_class==null)
+            throw new UnavailableException("Servlet Not Initialized");
+
+        Servlet servlet=_servlet;
+        synchronized(this)
+        {
+            if (!isStarted())
+                throw new UnavailableException("Servlet not initialized", -1);
+            if (_unavailable!=0 || (!_initOnStartup && servlet==null))
+                servlet=getServlet();
+            if (servlet==null)
+                throw new UnavailableException("Could not instantiate "+_class);
+        }
+
+        // Service the request
+        boolean servlet_error=true;
+        Object old_run_as = null;
+        boolean suspendable = baseRequest.isAsyncSupported();
+        try
+        {
+            // Handle aliased path
+            if (_forcedPath!=null)
+                // TODO complain about poor naming to the Jasper folks
+                request.setAttribute("org.apache.catalina.jsp_file",_forcedPath);
+
+            // Handle run as
+            if (_identityService!=null)
+                old_run_as=_identityService.setRunAs(baseRequest.getResolvedUserIdentity(),_runAsToken);
+
+            if (!isAsyncSupported())
+                baseRequest.setAsyncSupported(false);
+
+            MultipartConfigElement mpce = ((Registration)getRegistration()).getMultipartConfig();
+            if (mpce != null)
+                request.setAttribute(Request.__MULTIPART_CONFIG_ELEMENT, mpce);
+
+            servlet.service(request,response);
+            servlet_error=false;
+        }
+        catch(UnavailableException e)
+        {
+            makeUnavailable(e);
+            throw _unavailableEx;
+        }
+        finally
+        {
+            baseRequest.setAsyncSupported(suspendable);
+
+            // pop run-as role
+            if (_identityService!=null)
+                _identityService.unsetRunAs(old_run_as);
+
+            // Handle error params.
+            if (servlet_error)
+                request.setAttribute("javax.servlet.error.servlet_name",getName());
+        }
+    }
+
+
+    /* ------------------------------------------------------------ */
+    private boolean isJspServlet ()
+    {
+        if (_servlet == null)
+            return false;
+
+        Class c = _servlet.getClass();
+
+        boolean result = false;
+        while (c != null && !result)
+        {
+            result = isJspServlet(c.getName());
+            c = c.getSuperclass();
+        }
+
+        return result;
+    }
+
+
+    /* ------------------------------------------------------------ */
+    private boolean isJspServlet (String classname)
+    {
+        if (classname == null)
+            return false;
+        return ("org.apache.jasper.servlet.JspServlet".equals(classname));
+    }
+    
+    
+    /* ------------------------------------------------------------ */
+    private String getNameOfJspClass (String jsp)
+    {
+        if (jsp == null)
+            return "";
+        
+        int i = jsp.lastIndexOf('/') + 1;
+        jsp = jsp.substring(i);
+        try
+        {
+            Class jspUtil = Loader.loadClass(Holder.class, "org.apache.jasper.compiler.JspUtil");
+            Method makeJavaIdentifier = jspUtil.getMethod("makeJavaIdentifier", String.class);
+            return (String)makeJavaIdentifier.invoke(null, jsp);
+        }
+        catch (Exception e)
+        {
+            String tmp = jsp.replace('.','_');
+            LOG.warn("Unable to make identifier for jsp "+jsp +" trying "+tmp+" instead");
+            if (LOG.isDebugEnabled())
+                LOG.warn(e);
+            return tmp;
+        }
+    }
+    
+    
+    /* ------------------------------------------------------------ */
+    private String getPackageOfJspClass (String jsp)
+    {
+        if (jsp == null)
+            return "";
+        
+        int i = jsp.lastIndexOf('/');
+        if (i <= 0)
+            return "";
+        try
+        {
+            Class jspUtil = Loader.loadClass(Holder.class, "org.apache.jasper.compiler.JspUtil");
+            Method makeJavaPackage = jspUtil.getMethod("makeJavaPackage", String.class);
+            return (String)makeJavaPackage.invoke(null, jsp.substring(0,i));
+        } 
+        catch (Exception e)
+        {
+            String tmp = jsp.substring(1).replace('/','.');
+            LOG.warn("Unable to make package for jsp "+jsp +" trying "+tmp+" instead");
+            if (LOG.isDebugEnabled())
+                LOG.warn(e);
+            return tmp;
+        }
+    }
+    
+    
+    /* ------------------------------------------------------------ */
+    private String getJspPackagePrefix ()
+    {
+        String jspPackageName = (String)getServletHandler().getServletContext().getInitParameter(JSP_GENERATED_PACKAGE_NAME );
+        if (jspPackageName == null)
+            jspPackageName = "org.apache.jsp";
+        
+        return jspPackageName;
+    }
+    
+    
+    /* ------------------------------------------------------------ */
+    private String getClassNameForJsp (String jsp)
+    {
+        if (jsp == null)
+            return null; 
+        
+        return getJspPackagePrefix() + "." +getPackageOfJspClass(jsp) + "." + getNameOfJspClass(jsp);
+    }
+
+
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    protected class Config extends HolderConfig implements ServletConfig
+    {
+        /* -------------------------------------------------------- */
+        @Override
+        public String getServletName()
+        {
+            return getName();
+        }
+
+    }
+
+    /* -------------------------------------------------------- */
+    /* -------------------------------------------------------- */
+    /* -------------------------------------------------------- */
+    public class Registration extends HolderRegistration implements ServletRegistration.Dynamic
+    {
+        protected MultipartConfigElement _multipartConfig;
+
+        @Override
+        public Set<String> addMapping(String... urlPatterns)
+        {
+            illegalStateIfContextStarted();
+            Set<String> clash=null;
+            for (String pattern : urlPatterns)
+            {
+                ServletMapping mapping = _servletHandler.getServletMapping(pattern);
+                if (mapping!=null)
+                {
+                    //if the servlet mapping was from a default descriptor, then allow it to be overridden
+                    if (!mapping.isDefault())
+                    {
+                        if (clash==null)
+                            clash=new HashSet<String>();
+                        clash.add(pattern);
+                    }
+                }
+            }
+
+            //if there were any clashes amongst the urls, return them
+            if (clash!=null)
+                return clash;
+
+            //otherwise apply all of them
+            ServletMapping mapping = new ServletMapping();
+            mapping.setServletName(ServletHolder.this.getName());
+            mapping.setPathSpecs(urlPatterns);
+            _servletHandler.addServletMapping(mapping);
+
+            return Collections.emptySet();
+        }
+
+        @Override
+        public Collection<String> getMappings()
+        {
+            ServletMapping[] mappings =_servletHandler.getServletMappings();
+            List<String> patterns=new ArrayList<String>();
+            if (mappings!=null)
+            {
+                for (ServletMapping mapping : mappings)
+                {
+                    if (!mapping.getServletName().equals(getName()))
+                        continue;
+                    String[] specs=mapping.getPathSpecs();
+                    if (specs!=null && specs.length>0)
+                        patterns.addAll(Arrays.asList(specs));
+                }
+            }
+            return patterns;
+        }
+
+        @Override
+        public String getRunAsRole()
+        {
+            return _runAsRole;
+        }
+
+        @Override
+        public void setLoadOnStartup(int loadOnStartup)
+        {
+            illegalStateIfContextStarted();
+            ServletHolder.this.setInitOrder(loadOnStartup);
+        }
+
+        public int getInitOrder()
+        {
+            return ServletHolder.this.getInitOrder();
+        }
+
+        @Override
+        public void setMultipartConfig(MultipartConfigElement element)
+        {
+            _multipartConfig = element;
+        }
+
+        public MultipartConfigElement getMultipartConfig()
+        {
+            return _multipartConfig;
+        }
+
+        @Override
+        public void setRunAsRole(String role)
+        {
+            _runAsRole = role;
+        }
+
+        @Override
+        public Set<String> setServletSecurity(ServletSecurityElement securityElement)
+        {
+            return _servletHandler.setServletSecurity(this, securityElement);
+        }
+    }
+
+    public ServletRegistration.Dynamic getRegistration()
+    {
+        if (_registration == null)
+            _registration = new Registration();
+        return _registration;
+    }
+
+    /* -------------------------------------------------------- */
+    /* -------------------------------------------------------- */
+    /* -------------------------------------------------------- */
+    private class SingleThreadedWrapper implements Servlet
+    {
+        Stack<Servlet> _stack=new Stack<Servlet>();
+
+        @Override
+        public void destroy()
+        {
+            synchronized(this)
+            {
+                while(_stack.size()>0)
+                    try { (_stack.pop()).destroy(); } catch (Exception e) { LOG.warn(e); }
+            }
+        }
+
+        @Override
+        public ServletConfig getServletConfig()
+        {
+            return _config;
+        }
+
+        @Override
+        public String getServletInfo()
+        {
+            return null;
+        }
+
+        @Override
+        public void init(ServletConfig config) throws ServletException
+        {
+            synchronized(this)
+            {
+                if(_stack.size()==0)
+                {
+                    try
+                    {
+                        Servlet s = newInstance();
+                        s.init(config);
+                        _stack.push(s);
+                    }
+                    catch (ServletException e)
+                    {
+                        throw e;
+                    }
+                    catch (Exception e)
+                    {
+                        throw new ServletException(e);
+                    }
+                }
+            }
+        }
+
+        @Override
+        public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException
+        {
+            Servlet s;
+            synchronized(this)
+            {
+                if(_stack.size()>0)
+                    s=(Servlet)_stack.pop();
+                else
+                {
+                    try
+                    {
+                        s = newInstance();
+                        s.init(_config);
+                    }
+                    catch (ServletException e)
+                    {
+                        throw e;
+                    }
+                    catch (Exception e)
+                    {
+                        throw new ServletException(e);
+                    }
+                }
+            }
+
+            try
+            {
+                s.service(req,res);
+            }
+            finally
+            {
+                synchronized(this)
+                {
+                    _stack.push(s);
+                }
+            }
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return the newly created Servlet instance
+     * @throws ServletException
+     * @throws IllegalAccessException
+     * @throws InstantiationException
+     */
+    protected Servlet newInstance() throws ServletException, IllegalAccessException, InstantiationException
+    {
+        try
+        {
+            ServletContext ctx = getServletHandler().getServletContext();
+            if (ctx instanceof ServletContextHandler.Context)
+                return ((ServletContextHandler.Context)ctx).createServlet(getHeldClass());
+            return getHeldClass().newInstance();
+        }
+        catch (ServletException se)
+        {
+            Throwable cause = se.getRootCause();
+            if (cause instanceof InstantiationException)
+                throw (InstantiationException)cause;
+            if (cause instanceof IllegalAccessException)
+                throw (IllegalAccessException)cause;
+            throw se;
+        }
+    }
+    
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public String toString()
+    {
+        return String.format("%s@%x==%s,%d,%b",_name,hashCode(),_className,_initOrder,_servlet!=null);
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/servlet/ServletMapping.java b/lib/jetty/org/eclipse/jetty/servlet/ServletMapping.java
new file mode 100644 (file)
index 0000000..df026df
--- /dev/null
@@ -0,0 +1,119 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.servlet;
+
+import java.io.IOException;
+import java.util.Arrays;
+
+import org.eclipse.jetty.util.annotation.ManagedAttribute;
+import org.eclipse.jetty.util.annotation.ManagedObject;
+
+@ManagedObject("Servlet Mapping")
+public class ServletMapping
+{
+    private String[] _pathSpecs;
+    private String _servletName;
+    private boolean _default;
+    
+
+    /* ------------------------------------------------------------ */
+    public ServletMapping()
+    {
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Returns the pathSpecs.
+     */
+    @ManagedAttribute(value="url patterns", readonly=true)
+    public String[] getPathSpecs()
+    {
+        return _pathSpecs;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Returns the servletName.
+     */
+    @ManagedAttribute(value="servlet name", readonly=true)
+    public String getServletName()
+    {
+        return _servletName;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * @param pathSpecs The pathSpecs to set.
+     */
+    public void setPathSpecs(String[] pathSpecs)
+    {
+        _pathSpecs = pathSpecs;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param pathSpec The pathSpec to set.
+     */
+    public void setPathSpec(String pathSpec)
+    {
+        _pathSpecs = new String[]{pathSpec};
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * @param servletName The servletName to set.
+     */
+    public void setServletName(String servletName)
+    {
+        _servletName = servletName;
+    }
+    
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * @return
+     */
+    @ManagedAttribute(value="default", readonly=true)
+    public boolean isDefault()
+    {
+        return _default;
+    }
+    
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * @param fromDefault
+     */
+    public void setDefault(boolean fromDefault)
+    {
+        _default = fromDefault;
+    }
+
+    /* ------------------------------------------------------------ */
+    public String toString()
+    {
+        return (_pathSpecs==null?"[]":Arrays.asList(_pathSpecs).toString())+"=>"+_servletName; 
+    }
+
+    /* ------------------------------------------------------------ */
+    public void dump(Appendable out, String indent) throws IOException
+    {
+        out.append(String.valueOf(this)).append("\n");
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/servlet/ServletTester.java b/lib/jetty/org/eclipse/jetty/servlet/ServletTester.java
new file mode 100644 (file)
index 0000000..2317195
--- /dev/null
@@ -0,0 +1,231 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.servlet;
+
+import java.net.InetAddress;
+import java.nio.ByteBuffer;
+import java.util.EnumSet;
+import java.util.Enumeration;
+import java.util.Map;
+
+import javax.servlet.DispatcherType;
+import javax.servlet.Filter;
+import javax.servlet.Servlet;
+
+import org.eclipse.jetty.server.Connector;
+import org.eclipse.jetty.server.LocalConnector;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.ServerConnector;
+import org.eclipse.jetty.util.Attributes;
+import org.eclipse.jetty.util.component.ContainerLifeCycle;
+import org.eclipse.jetty.util.resource.Resource;
+
+public class ServletTester extends ContainerLifeCycle
+{
+    private final Server _server=new Server();
+    private final LocalConnector _connector=new LocalConnector(_server);
+    private final ServletContextHandler _context;
+    
+    public Server getServer()
+    {
+        return _server;
+    }
+    
+    public LocalConnector getConnector()
+    {
+        return _connector;
+    }
+    
+    public void setVirtualHosts(String[] vhosts)
+    {
+        _context.setVirtualHosts(vhosts);
+    }
+
+    public void addVirtualHosts(String[] virtualHosts)
+    {
+        _context.addVirtualHosts(virtualHosts);
+    }
+
+    public ServletHolder addServlet(String className, String pathSpec)
+    {
+        return _context.addServlet(className,pathSpec);
+    }
+
+    public ServletHolder addServlet(Class<? extends Servlet> servlet, String pathSpec)
+    {
+        return _context.addServlet(servlet,pathSpec);
+    }
+
+    public void addServlet(ServletHolder servlet, String pathSpec)
+    {
+        _context.addServlet(servlet,pathSpec);
+    }
+
+    public void addFilter(FilterHolder holder, String pathSpec, EnumSet<DispatcherType> dispatches)
+    {
+        _context.addFilter(holder,pathSpec,dispatches);
+    }
+
+    public FilterHolder addFilter(Class<? extends Filter> filterClass, String pathSpec, EnumSet<DispatcherType> dispatches)
+    {
+        return _context.addFilter(filterClass,pathSpec,dispatches);
+    }
+
+    public FilterHolder addFilter(String filterClass, String pathSpec, EnumSet<DispatcherType> dispatches)
+    {
+        return _context.addFilter(filterClass,pathSpec,dispatches);
+    }
+
+    public Object getAttribute(String name)
+    {
+        return _context.getAttribute(name);
+    }
+
+    public Enumeration getAttributeNames()
+    {
+        return _context.getAttributeNames();
+    }
+
+    public Attributes getAttributes()
+    {
+        return _context.getAttributes();
+    }
+
+    public String getContextPath()
+    {
+        return _context.getContextPath();
+    }
+
+    public String getInitParameter(String name)
+    {
+        return _context.getInitParameter(name);
+    }
+
+    public String setInitParameter(String name, String value)
+    {
+        return _context.setInitParameter(name,value);
+    }
+
+    public Enumeration getInitParameterNames()
+    {
+        return _context.getInitParameterNames();
+    }
+
+    public Map<String, String> getInitParams()
+    {
+        return _context.getInitParams();
+    }
+
+    public void removeAttribute(String name)
+    {
+        _context.removeAttribute(name);
+    }
+
+    public void setAttribute(String name, Object value)
+    {
+        _context.setAttribute(name,value);
+    }
+
+    public void setContextPath(String contextPath)
+    {
+        _context.setContextPath(contextPath);
+    }
+
+    public Resource getBaseResource()
+    {
+        return _context.getBaseResource();
+    }
+
+    public String getResourceBase()
+    {
+        return _context.getResourceBase();
+    }
+
+    public void setResourceBase(String resourceBase)
+    {
+        _context.setResourceBase(resourceBase);
+    }
+
+    private final ServletHandler _handler;
+
+    public ServletTester()
+    {
+        this("/",ServletContextHandler.SECURITY|ServletContextHandler.SESSIONS);
+    }
+
+    public ServletTester(String ctxPath)
+    {
+        this(ctxPath,ServletContextHandler.SECURITY|ServletContextHandler.SESSIONS);
+    }
+
+    public ServletTester(String contextPath,int options)
+    {
+        _context=new ServletContextHandler(_server,contextPath,options);
+        _handler=_context.getServletHandler();
+        _server.setConnectors(new Connector[]{_connector});
+        addBean(_server);
+    }
+
+    public ServletContextHandler getContext()
+    {
+        return _context;
+    }
+
+    public String getResponses(String request) throws Exception
+    {
+        return _connector.getResponses(request);
+    }
+
+    public ByteBuffer getResponses(ByteBuffer request) throws Exception
+    {
+        return _connector.getResponses(request);
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Create a port based connector.
+     * This methods adds a port connector to the server
+     * @return A URL to access the server via the connector.
+     * @throws Exception
+     */
+    public String createConnector(boolean localhost) throws Exception
+    {
+        ServerConnector connector = new ServerConnector(_server);
+        if (localhost)
+            connector.setHost("127.0.0.1");
+        _server.addConnector(connector);
+        if (_server.isStarted())
+            connector.start();
+        else
+            connector.open();
+
+        return "http://"+(localhost?"127.0.0.1":
+            InetAddress.getLocalHost().getHostAddress()
+        )+":"+connector.getLocalPort();
+    }
+
+    public LocalConnector createLocalConnector()
+    {
+        LocalConnector connector = new LocalConnector(_server);
+        _server.addConnector(connector);
+        return connector;
+    }
+
+
+
+}
diff --git a/lib/jetty/org/eclipse/jetty/servlet/StatisticsServlet.java b/lib/jetty/org/eclipse/jetty/servlet/StatisticsServlet.java
new file mode 100644 (file)
index 0000000..6d1bf7d
--- /dev/null
@@ -0,0 +1,290 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.servlet;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.lang.management.ManagementFactory;
+import java.lang.management.MemoryMXBean;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.server.AbstractConnector;
+import org.eclipse.jetty.server.Connector;
+import org.eclipse.jetty.server.ConnectorStatistics;
+import org.eclipse.jetty.server.Handler;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.handler.ContextHandler;
+import org.eclipse.jetty.server.handler.StatisticsHandler;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/**
+ * StatisticsServlet
+ *
+ *
+ */
+public class StatisticsServlet extends HttpServlet
+{
+    private static final Logger LOG = Log.getLogger(StatisticsServlet.class);
+
+    boolean _restrictToLocalhost = true; // defaults to true
+    private StatisticsHandler _statsHandler;
+    private MemoryMXBean _memoryBean;
+    private Connector[] _connectors;
+
+    
+    
+    /** 
+     * @see javax.servlet.GenericServlet#init()
+     */
+    public void init() throws ServletException
+    {
+        ServletContext context = getServletContext();
+        ContextHandler.Context scontext = (ContextHandler.Context) context;
+        Server _server = scontext.getContextHandler().getServer();
+
+        Handler handler = _server.getChildHandlerByClass(StatisticsHandler.class);
+
+        if (handler != null)
+        {
+            _statsHandler = (StatisticsHandler) handler;
+        }
+        else
+        {
+            LOG.warn("Statistics Handler not installed!");
+            return;
+        }
+        
+        _memoryBean = ManagementFactory.getMemoryMXBean();
+        _connectors = _server.getConnectors();
+
+        if (getInitParameter("restrictToLocalhost") != null)
+        {
+            _restrictToLocalhost = "true".equals(getInitParameter("restrictToLocalhost"));
+        }
+    }
+
+    
+    
+    /** 
+     * @see javax.servlet.http.HttpServlet#doPost(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
+     */
+    public void doPost(HttpServletRequest sreq, HttpServletResponse sres) throws ServletException, IOException
+    {
+        doGet(sreq, sres);
+    }
+
+    
+    
+    /** 
+     * @see javax.servlet.http.HttpServlet#doGet(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
+     */
+    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
+    {
+        if (_statsHandler == null)
+        {
+            LOG.warn("Statistics Handler not installed!");
+            resp.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE);
+            return;
+        }
+        if (_restrictToLocalhost)
+        {
+            if (!isLoopbackAddress(req.getRemoteAddr()))
+            {
+                resp.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE);
+                return;
+            }
+        }
+
+        String wantXml = req.getParameter("xml");
+        if (wantXml == null)
+          wantXml = req.getParameter("XML");
+
+        if (wantXml != null && "true".equalsIgnoreCase(wantXml))
+        {
+            sendXmlResponse(resp);
+        }
+        else
+        {
+            sendTextResponse(resp);
+        }
+
+    }
+
+    private boolean isLoopbackAddress(String address)
+    {
+        try
+        {
+            InetAddress addr = InetAddress.getByName(address); 
+            return addr.isLoopbackAddress();
+        }
+        catch (UnknownHostException e )
+        {
+            LOG.warn("Warning: attempt to access statistics servlet from " + address, e);
+            return false;
+        }
+    }
+
+    private void sendXmlResponse(HttpServletResponse response) throws IOException
+    {
+        StringBuilder sb = new StringBuilder();
+
+        sb.append("<statistics>\n");
+
+        sb.append("  <requests>\n");
+        sb.append("    <statsOnMs>").append(_statsHandler.getStatsOnMs()).append("</statsOnMs>\n");
+        
+        sb.append("    <requests>").append(_statsHandler.getRequests()).append("</requests>\n");
+        sb.append("    <requestsActive>").append(_statsHandler.getRequestsActive()).append("</requestsActive>\n");
+        sb.append("    <requestsActiveMax>").append(_statsHandler.getRequestsActiveMax()).append("</requestsActiveMax>\n");
+        sb.append("    <requestsTimeTotal>").append(_statsHandler.getRequestTimeTotal()).append("</requestsTimeTotal>\n");
+        sb.append("    <requestsTimeMean>").append(_statsHandler.getRequestTimeMean()).append("</requestsTimeMean>\n");
+        sb.append("    <requestsTimeMax>").append(_statsHandler.getRequestTimeMax()).append("</requestsTimeMax>\n");
+        sb.append("    <requestsTimeStdDev>").append(_statsHandler.getRequestTimeStdDev()).append("</requestsTimeStdDev>\n");
+
+        sb.append("    <dispatched>").append(_statsHandler.getDispatched()).append("</dispatched>\n");
+        sb.append("    <dispatchedActive>").append(_statsHandler.getDispatchedActive()).append("</dispatchedActive>\n");
+        sb.append("    <dispatchedActiveMax>").append(_statsHandler.getDispatchedActiveMax()).append("</dispatchedActiveMax>\n");
+        sb.append("    <dispatchedTimeTotalMs>").append(_statsHandler.getDispatchedTimeTotal()).append("</dispatchedTimeTotalMs>\n");
+        sb.append("    <dispatchedTimeMeanMs>").append(_statsHandler.getDispatchedTimeMean()).append("</dispatchedTimeMeanMs>\n");
+        sb.append("    <dispatchedTimeMaxMs>").append(_statsHandler.getDispatchedTimeMax()).append("</dispatchedTimeMaxMs>\n");
+        sb.append("    <dispatchedTimeStdDevMs>").append(_statsHandler.getDispatchedTimeStdDev()).append("</dispatchedTimeStdDevMs>\n");
+        sb.append("    <asyncRequests>").append(_statsHandler.getAsyncRequests()).append("</asyncRequests>\n");
+        sb.append("    <requestsSuspended>").append(_statsHandler.getAsyncRequestsWaiting()).append("</requestsSuspended>\n");
+        sb.append("    <requestsSuspendedMax>").append(_statsHandler.getAsyncRequestsWaitingMax()).append("</requestsSuspendedMax>\n");
+        sb.append("    <requestsResumed>").append(_statsHandler.getAsyncDispatches()).append("</requestsResumed>\n");
+        sb.append("    <requestsExpired>").append(_statsHandler.getExpires()).append("</requestsExpired>\n");
+        sb.append("  </requests>\n");
+
+        sb.append("  <responses>\n");
+        sb.append("    <responses1xx>").append(_statsHandler.getResponses1xx()).append("</responses1xx>\n");
+        sb.append("    <responses2xx>").append(_statsHandler.getResponses2xx()).append("</responses2xx>\n");
+        sb.append("    <responses3xx>").append(_statsHandler.getResponses3xx()).append("</responses3xx>\n");
+        sb.append("    <responses4xx>").append(_statsHandler.getResponses4xx()).append("</responses4xx>\n");
+        sb.append("    <responses5xx>").append(_statsHandler.getResponses5xx()).append("</responses5xx>\n");
+        sb.append("    <responsesBytesTotal>").append(_statsHandler.getResponsesBytesTotal()).append("</responsesBytesTotal>\n");
+        sb.append("  </responses>\n");
+
+        sb.append("  <connections>\n");
+        for (Connector connector : _connectors)
+        {
+            sb.append("    <connector>\n");
+            sb.append("      <name>").append(connector.getClass().getName()).append("@").append(connector.hashCode()).append("</name>\n");
+            sb.append("      <protocols>\n");
+            for (String protocol:connector.getProtocols())
+                sb.append("      <protocol>").append(protocol).append("</protocol>\n");
+            sb.append("      </protocols>\n");
+
+            ConnectorStatistics connectorStats = null;
+
+            if (connector instanceof AbstractConnector)
+                connectorStats = ((AbstractConnector)connector).getBean(ConnectorStatistics.class);
+            if (connectorStats == null)
+                sb.append("      <statsOn>false</statsOn>\n");
+            else
+            {
+                sb.append("      <statsOn>true</statsOn>\n");
+                sb.append("      <connections>").append(connectorStats.getConnections()).append("</connections>\n");
+                sb.append("      <connectionsOpen>").append(connectorStats.getConnectionsOpen()).append("</connectionsOpen>\n");
+                sb.append("      <connectionsOpenMax>").append(connectorStats.getConnectionsOpenMax()).append("</connectionsOpenMax>\n");
+                sb.append("      <connectionsDurationMean>").append(connectorStats.getConnectionDurationMean()).append("</connectionsDurationMean>\n");
+                sb.append("      <connectionsDurationMax>").append(connectorStats.getConnectionDurationMax()).append("</connectionsDurationMax>\n");
+                sb.append("      <connectionsDurationStdDev>").append(connectorStats.getConnectionDurationStdDev()).append("</connectionsDurationStdDev>\n");
+                sb.append("      <messagesIn>").append(connectorStats.getMessagesIn()).append("</messagesIn>\n");
+                sb.append("      <messagesOut>").append(connectorStats.getMessagesIn()).append("</messagesOut>\n");
+                sb.append("      <elapsedMs>").append(connectorStats.getStartedMillis()).append("</elapsedMs>\n");
+            }
+            sb.append("    </connector>\n");
+        }
+        sb.append("  </connections>\n");
+
+        sb.append("  <memory>\n");
+        sb.append("    <heapMemoryUsage>").append(_memoryBean.getHeapMemoryUsage().getUsed()).append("</heapMemoryUsage>\n");
+        sb.append("    <nonHeapMemoryUsage>").append(_memoryBean.getNonHeapMemoryUsage().getUsed()).append("</nonHeapMemoryUsage>\n");
+        sb.append("  </memory>\n");
+        
+        sb.append("</statistics>\n");
+
+        response.setContentType("text/xml");
+        PrintWriter pout = response.getWriter();
+        pout.write(sb.toString());
+    }
+
+    
+    
+    /**
+     * @param response
+     * @throws IOException
+     */
+    private void sendTextResponse(HttpServletResponse response) throws IOException
+    {
+        StringBuilder sb = new StringBuilder();
+        sb.append(_statsHandler.toStatsHTML());
+
+        sb.append("<h2>Connections:</h2>\n");
+        for (Connector connector : _connectors)
+        {
+            sb.append("<h3>").append(connector.getClass().getName()).append("@").append(connector.hashCode()).append("</h3>");
+            sb.append("Protocols:");
+            for (String protocol:connector.getProtocols())
+                sb.append(protocol).append("&nbsp;");
+            sb.append("    <br />\n");
+
+            ConnectorStatistics connectorStats = null;
+
+            if (connector instanceof AbstractConnector)
+                connectorStats = ((AbstractConnector)connector).getBean(ConnectorStatistics.class);
+
+            if (connectorStats != null)
+            {
+                sb.append("Statistics gathering started ").append(connectorStats.getStartedMillis()).append("ms ago").append("<br />\n");
+                sb.append("Total connections: ").append(connectorStats.getConnections()).append("<br />\n");
+                sb.append("Current connections open: ").append(connectorStats.getConnectionsOpen()).append("<br />\n");;
+                sb.append("Max concurrent connections open: ").append(connectorStats.getConnectionsOpenMax()).append("<br />\n");
+                sb.append("Mean connection duration: ").append(connectorStats.getConnectionDurationMean()).append("<br />\n");
+                sb.append("Max connection duration: ").append(connectorStats.getConnectionDurationMax()).append("<br />\n");
+                sb.append("Connection duration standard deviation: ").append(connectorStats.getConnectionDurationStdDev()).append("<br />\n");
+                sb.append("Total messages in: ").append(connectorStats.getMessagesIn()).append("<br />\n");                
+                sb.append("Total messages out: ").append(connectorStats.getMessagesOut()).append("<br />\n");
+            }
+            else
+            {
+                sb.append("Statistics gathering off.\n");
+            }
+
+        }
+
+        sb.append("<h2>Memory:</h2>\n");
+        sb.append("Heap memory usage: ").append(_memoryBean.getHeapMemoryUsage().getUsed()).append(" bytes").append("<br />\n");
+        sb.append("Non-heap memory usage: ").append(_memoryBean.getNonHeapMemoryUsage().getUsed()).append(" bytes").append("<br />\n");
+
+        response.setContentType("text/html");
+        PrintWriter pout = response.getWriter();
+        pout.write(sb.toString());
+
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/servlet/listener/ELContextCleaner.java b/lib/jetty/org/eclipse/jetty/servlet/listener/ELContextCleaner.java
new file mode 100644 (file)
index 0000000..81b7528
--- /dev/null
@@ -0,0 +1,126 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.servlet.listener;
+
+import java.lang.reflect.Field;
+import java.util.Iterator;
+import java.util.concurrent.ConcurrentHashMap;
+
+import javax.servlet.ServletContextEvent;
+import javax.servlet.ServletContextListener;
+
+import org.eclipse.jetty.util.Loader;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/**
+ * ELContextCleaner
+ *
+ * Clean up BeanELResolver when the context is going out
+ * of service:
+ *
+ * See http://java.net/jira/browse/GLASSFISH-1649
+ * See https://bugs.eclipse.org/bugs/show_bug.cgi?id=353095
+ */
+public class ELContextCleaner implements ServletContextListener
+{
+    private static final Logger LOG = Log.getLogger(ELContextCleaner.class);
+
+
+    public void contextInitialized(ServletContextEvent sce)
+    {
+    }
+
+    public void contextDestroyed(ServletContextEvent sce)
+    {
+        try
+        {
+            //Check that the BeanELResolver class is on the classpath
+            Class beanELResolver = Loader.loadClass(this.getClass(), "javax.el.BeanELResolver");
+
+            //Get a reference via reflection to the properties field which is holding class references
+            Field field = getField(beanELResolver);
+
+            //Get rid of references
+            purgeEntries(field);
+
+            LOG.debug("javax.el.BeanELResolver purged");
+        }
+
+        catch (ClassNotFoundException e)
+        {
+            //BeanELResolver not on classpath, ignore
+        }
+        catch (SecurityException e)
+        {
+            LOG.warn("Cannot purge classes from javax.el.BeanELResolver", e);
+        }
+        catch (IllegalArgumentException e)
+        {
+            LOG.warn("Cannot purge classes from javax.el.BeanELResolver", e);
+        }
+        catch (IllegalAccessException e)
+        {
+            LOG.warn("Cannot purge classes from javax.el.BeanELResolver", e);
+        }
+        catch (NoSuchFieldException e)
+        {
+            LOG.debug("Not cleaning cached beans: no such field javax.el.BeanELResolver.properties");
+        }
+
+    }
+
+
+    protected Field getField (Class beanELResolver)
+    throws SecurityException, NoSuchFieldException
+    {
+        if (beanELResolver == null)
+            return  null;
+
+        return beanELResolver.getDeclaredField("properties");
+    }
+
+    protected void purgeEntries (Field properties)
+    throws IllegalArgumentException, IllegalAccessException
+    {
+        if (properties == null)
+            return;
+
+        if (!properties.isAccessible())
+            properties.setAccessible(true);
+
+        ConcurrentHashMap map = (ConcurrentHashMap) properties.get(null);
+        if (map == null)
+            return;
+
+        Iterator<Class> itor = map.keySet().iterator();
+        while (itor.hasNext())
+        {
+            Class clazz = itor.next();
+            LOG.debug("Clazz: "+clazz+" loaded by "+clazz.getClassLoader());
+            if (Thread.currentThread().getContextClassLoader().equals(clazz.getClassLoader()))
+            {
+                itor.remove();
+                LOG.debug("removed");
+            }
+            else
+                LOG.debug("not removed: "+"contextclassloader="+Thread.currentThread().getContextClassLoader()+"clazz's classloader="+clazz.getClassLoader());
+        }
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/servlet/listener/IntrospectorCleaner.java b/lib/jetty/org/eclipse/jetty/servlet/listener/IntrospectorCleaner.java
new file mode 100644 (file)
index 0000000..72a6f0d
--- /dev/null
@@ -0,0 +1,45 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.servlet.listener;
+
+import javax.servlet.ServletContextEvent;
+import javax.servlet.ServletContextListener;
+
+/**
+ * IntrospectorCleaner
+ *
+ * Cleans a static cache of Methods held by java.beans.Introspector
+ * class when a context is undeployed.
+ * 
+ * @see java.beans.Introspector
+ */
+public class IntrospectorCleaner implements ServletContextListener
+{
+
+    public void contextInitialized(ServletContextEvent sce)
+    {
+        
+    }
+
+    public void contextDestroyed(ServletContextEvent sce)
+    {
+        java.beans.Introspector.flushCaches();
+    }
+
+}
diff --git a/lib/jetty/org/eclipse/jetty/servlet/listener/package-info.java b/lib/jetty/org/eclipse/jetty/servlet/listener/package-info.java
new file mode 100644 (file)
index 0000000..2cb69e3
--- /dev/null
@@ -0,0 +1,23 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+/**
+ * Jetty Servlet : Useful Servlet Listeners
+ */
+package org.eclipse.jetty.servlet.listener;
+
diff --git a/lib/jetty/org/eclipse/jetty/servlet/package-info.java b/lib/jetty/org/eclipse/jetty/servlet/package-info.java
new file mode 100644 (file)
index 0000000..f7a9cd5
--- /dev/null
@@ -0,0 +1,23 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+/**
+ * Jetty Server : Modular Servlet Integration
+ */
+package org.eclipse.jetty.servlet;
+
diff --git a/lib/jetty/org/eclipse/jetty/util/AbstractTrie.java b/lib/jetty/org/eclipse/jetty/util/AbstractTrie.java
new file mode 100644 (file)
index 0000000..b49bec1
--- /dev/null
@@ -0,0 +1,85 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util;
+
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+
+
+/* ------------------------------------------------------------ */
+/** Abstract Trie implementation.
+ * <p>Provides some common implementations, which may not be the most
+ * efficient. For byte operations, the assumption is made that the charset
+ * is ISO-8859-1</p>
+ * @param <V>
+ */
+public abstract class AbstractTrie<V> implements Trie<V>
+{
+    final boolean _caseInsensitive;
+    
+    protected AbstractTrie(boolean insensitive)
+    {
+        _caseInsensitive=insensitive;
+    }
+
+    @Override
+    public boolean put(V v)
+    {
+        return put(v.toString(),v);
+    }
+
+    @Override
+    public V remove(String s)
+    {
+        V o=get(s);
+        put(s,null);
+        return o;
+    }
+
+    @Override
+    public V get(String s)
+    {
+        return get(s,0,s.length());
+    }
+
+    @Override
+    public V get(ByteBuffer b)
+    {
+        return get(b,0,b.remaining());
+    }
+
+    @Override
+    public V getBest(String s)
+    {
+        return getBest(s,0,s.length());
+    }
+    
+    @Override
+    public V getBest(byte[] b, int offset, int len)
+    {
+        return getBest(new String(b,offset,len,StandardCharsets.ISO_8859_1));
+    }
+
+    @Override
+    public boolean isCaseInsensitive()
+    {
+        return _caseInsensitive;
+    }
+
+}
diff --git a/lib/jetty/org/eclipse/jetty/util/ArrayQueue.java b/lib/jetty/org/eclipse/jetty/util/ArrayQueue.java
new file mode 100644 (file)
index 0000000..671439e
--- /dev/null
@@ -0,0 +1,408 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util;
+
+import java.util.AbstractList;
+import java.util.NoSuchElementException;
+import java.util.Queue;
+
+/* ------------------------------------------------------------ */
+/**
+ * Queue backed by circular array.
+ * <p/>
+ * This partial Queue implementation (also with {@link #remove()} for stack operation)
+ * is backed by a growable circular array.
+ *
+ * @param <E>
+ */
+public class ArrayQueue<E> extends AbstractList<E> implements Queue<E>
+{
+    public static final int DEFAULT_CAPACITY = 64;
+    public static final int DEFAULT_GROWTH = 32;
+
+    protected final Object _lock;
+    protected final int _growCapacity;
+    protected Object[] _elements;
+    protected int _nextE;
+    protected int _nextSlot;
+    protected int _size;
+
+    /* ------------------------------------------------------------ */
+    public ArrayQueue()
+    {
+        this(DEFAULT_CAPACITY, -1);
+    }
+    
+    /* ------------------------------------------------------------ */
+    public ArrayQueue(Object lock)
+    {
+        this(DEFAULT_CAPACITY, -1,lock);
+    }
+
+    /* ------------------------------------------------------------ */
+    public ArrayQueue(int capacity)
+    {
+        this(capacity, -1);
+    }
+
+    /* ------------------------------------------------------------ */
+    public ArrayQueue(int initCapacity, int growBy)
+    {
+        this(initCapacity, growBy, null);
+    }
+
+    /* ------------------------------------------------------------ */
+    public ArrayQueue(int initCapacity, int growBy, Object lock)
+    {
+        _lock = lock == null ? this : lock;
+        _growCapacity = growBy;
+        _elements = new Object[initCapacity];
+    }
+    
+    /* ------------------------------------------------------------ */
+    public Object lock()
+    {
+        return _lock;
+    }
+    
+    /* ------------------------------------------------------------ */
+    public int getCapacity()
+    {
+        synchronized (_lock)
+        {
+            return _elements.length;
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public boolean add(E e)
+    {
+        if (!offer(e))
+            throw new IllegalStateException("Full");
+        return true;
+    }
+
+    /* ------------------------------------------------------------ */
+    public boolean offer(E e)
+    {
+        synchronized (_lock)
+        {
+            return enqueue(e);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    private boolean enqueue(E e)
+    {
+        if (_size == _elements.length && !grow())
+            return false;
+
+        _size++;
+        _elements[_nextSlot++] = e;
+        if (_nextSlot == _elements.length)
+            _nextSlot = 0;
+
+        return true;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Add without synchronization or bounds checking
+     *
+     * @param e the element to add
+     * @see #add(Object)
+     */
+    public void addUnsafe(E e)
+    {
+        if (!enqueue(e))
+            throw new IllegalStateException("Full");
+    }
+
+    /* ------------------------------------------------------------ */
+    public E element()
+    {
+        synchronized (_lock)
+        {
+            if (isEmpty())
+                throw new NoSuchElementException();
+            return at(_nextE);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    @SuppressWarnings("unchecked")
+    private E at(int index)
+    {
+        return (E)_elements[index];
+    }
+
+    /* ------------------------------------------------------------ */
+    public E peek()
+    {
+        synchronized (_lock)
+        {
+            if (_size == 0)
+                return null;
+            return at(_nextE);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    public E peekUnsafe()
+    {
+        if (_size == 0)
+            return null;
+        return at(_nextE);
+    }
+
+    /* ------------------------------------------------------------ */
+    public E poll()
+    {
+        synchronized (_lock)
+        {
+            if (_size == 0)
+                return null;
+            return dequeue();
+        }
+    }
+    
+    /* ------------------------------------------------------------ */
+    public E pollUnsafe()
+    {
+        if (_size == 0)
+            return null;
+        return dequeue();
+    }
+
+    /* ------------------------------------------------------------ */
+    private E dequeue()
+    {
+        E e = at(_nextE);
+        _elements[_nextE] = null;
+        _size--;
+        if (++_nextE == _elements.length)
+            _nextE = 0;
+        return e;
+    }
+
+    /* ------------------------------------------------------------ */
+    public E remove()
+    {
+        synchronized (_lock)
+        {
+            if (_size == 0)
+                throw new NoSuchElementException();
+            return dequeue();
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public void clear()
+    {
+        synchronized (_lock)
+        {
+            _size = 0;
+            _nextE = 0;
+            _nextSlot = 0;
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public boolean isEmpty()
+    {
+        synchronized (_lock)
+        {
+            return _size == 0;
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public int size()
+    {
+        synchronized (_lock)
+        {
+            return _size;
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public E get(int index)
+    {
+        synchronized (_lock)
+        {
+            if (index < 0 || index >= _size)
+                throw new IndexOutOfBoundsException("!(" + 0 + "<" + index + "<=" + _size + ")");
+            return getUnsafe(index);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Get without synchronization or bounds checking.
+     *
+     * @param  index index of the element to return
+     * @return the element at the specified index
+     * @see #get(int)
+     */
+    public E getUnsafe(int index)
+    {
+        int i = (_nextE + index) % _elements.length;
+        return at(i);
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public E remove(int index)
+    {
+        synchronized (_lock)
+        {
+            if (index < 0 || index >= _size)
+                throw new IndexOutOfBoundsException("!(" + 0 + "<" + index + "<=" + _size + ")");
+
+            int i = (_nextE + index) % _elements.length;
+            E old = at(i);
+
+            if (i < _nextSlot)
+            {
+                // 0                         _elements.length
+                //       _nextE........._nextSlot
+                System.arraycopy(_elements, i + 1, _elements, i, _nextSlot - i);
+                _nextSlot--;
+                _size--;
+            }
+            else
+            {
+                // 0                         _elements.length
+                // ......_nextSlot   _nextE..........
+                System.arraycopy(_elements, i + 1, _elements, i, _elements.length - i - 1);
+                if (_nextSlot > 0)
+                {
+                    _elements[_elements.length - 1] = _elements[0];
+                    System.arraycopy(_elements, 1, _elements, 0, _nextSlot - 1);
+                    _nextSlot--;
+                }
+                else
+                    _nextSlot = _elements.length - 1;
+
+                _size--;
+            }
+
+            return old;
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public E set(int index, E element)
+    {
+        synchronized (_lock)
+        {
+            if (index < 0 || index >= _size)
+                throw new IndexOutOfBoundsException("!(" + 0 + "<" + index + "<=" + _size + ")");
+
+            int i = _nextE + index;
+            if (i >= _elements.length)
+                i -= _elements.length;
+            E old = at(i);
+            _elements[i] = element;
+            return old;
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public void add(int index, E element)
+    {
+        synchronized (_lock)
+        {
+            if (index < 0 || index > _size)
+                throw new IndexOutOfBoundsException("!(" + 0 + "<" + index + "<=" + _size + ")");
+
+            if (_size == _elements.length && !grow())
+                throw new IllegalStateException("Full");
+
+            if (index == _size)
+            {
+                add(element);
+            }
+            else
+            {
+                int i = _nextE + index;
+                if (i >= _elements.length)
+                    i -= _elements.length;
+
+                _size++;
+                _nextSlot++;
+                if (_nextSlot == _elements.length)
+                    _nextSlot = 0;
+
+                if (i < _nextSlot)
+                {
+                    // 0                         _elements.length
+                    //       _nextE.....i..._nextSlot
+                    // 0                         _elements.length
+                    // ..i..._nextSlot   _nextE..........
+                    System.arraycopy(_elements, i, _elements, i + 1, _nextSlot - i);
+                    _elements[i] = element;
+                }
+                else
+                {
+                    // 0                         _elements.length
+                    // ......_nextSlot   _nextE.....i....
+                    if (_nextSlot > 0)
+                    {
+                        System.arraycopy(_elements, 0, _elements, 1, _nextSlot);
+                        _elements[0] = _elements[_elements.length - 1];
+                    }
+
+                    System.arraycopy(_elements, i, _elements, i + 1, _elements.length - i - 1);
+                    _elements[i] = element;
+                }
+            }
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    protected boolean grow()
+    {
+        synchronized (_lock)
+        {
+            if (_growCapacity <= 0)
+                return false;
+
+            Object[] elements = new Object[_elements.length + _growCapacity];
+
+            int split = _elements.length - _nextE;
+            if (split > 0)
+                System.arraycopy(_elements, _nextE, elements, 0, split);
+            if (_nextE != 0)
+                System.arraycopy(_elements, 0, elements, split, _nextSlot);
+
+            _elements = elements;
+            _nextE = 0;
+            _nextSlot = _size;
+            return true;
+        }
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/util/ArrayTernaryTrie.java b/lib/jetty/org/eclipse/jetty/util/ArrayTernaryTrie.java
new file mode 100644 (file)
index 0000000..fdca4ce
--- /dev/null
@@ -0,0 +1,473 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util;
+
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+
+/* ------------------------------------------------------------ */
+/** A Ternary Trie String lookup data structure.
+ * This Trie is of a fixed size and cannot grow (which can be a good thing with regards to DOS when used as a cache).
+ * <p>
+ * The Trie is stored in 3 arrays:<dl>
+ * <dt>char[] _tree</dt><dd>This is semantically 2 dimensional array flattened into a 1 dimensional char array. The second dimension
+ * is that every 4 sequential elements represents a row of: character; hi index; eq index; low index, used to build a
+ * ternary trie of key strings.</dd>
+ * <dt>String[] _key<dt><dd>An array of key values where each element matches a row in the _tree array. A non zero key element 
+ * indicates that the _tree row is a complete key rather than an intermediate character of a longer key.</dd>
+ * <dt>V[] _value</dt><dd>An array of values corresponding to the _key array</dd>
+ * </dl>
+ * <p>The lookup of a value will iterate through the _tree array matching characters. If the equal tree branch is followed,
+ * then the _key array is looked up to see if this is a complete match.  If a match is found then the _value array is looked up
+ * to return the matching value.
+ * </p>
+ * @param <V>
+ */
+public class ArrayTernaryTrie<V> extends AbstractTrie<V>
+{
+    private static int LO=1;
+    private static int EQ=2;
+    private static int HI=3;
+    
+    /**
+     * The Size of a Trie row is the char, and the low, equal and high
+     * child pointers
+     */
+    private static final int ROW_SIZE = 4;
+    
+    /**
+     * The Trie rows in a single array which allows a lookup of row,character
+     * to the next row in the Trie.  This is actually a 2 dimensional
+     * array that has been flattened to achieve locality of reference.
+     */
+    private final char[] _tree;
+    
+    /**
+     * The key (if any) for a Trie row. 
+     * A row may be a leaf, a node or both in the Trie tree.
+     */
+    private final String[] _key;
+    
+    /**
+     * The value (if any) for a Trie row. 
+     * A row may be a leaf, a node or both in the Trie tree.
+     */
+    private final Object[] _value;
+    
+    /**
+     * The number of rows allocated
+     */
+    private char _rows;
+
+    public ArrayTernaryTrie()
+    {
+        this(128);
+    }
+    
+    public ArrayTernaryTrie(boolean insensitive)
+    {
+        this(insensitive,128);
+    }
+
+    public ArrayTernaryTrie(int capacityInNodes)
+    {
+        this(true,capacityInNodes);
+    }
+    
+    public ArrayTernaryTrie(boolean insensitive, int capacityInNodes)
+    {
+        super(insensitive);
+        _value=new Object[capacityInNodes];
+        _tree=new char[capacityInNodes*ROW_SIZE];
+        _key=new String[capacityInNodes];
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Copy Trie and change capacity by a factor
+     * @param trie
+     * @param factor
+     */
+    public ArrayTernaryTrie(ArrayTernaryTrie<V> trie, double factor)
+    {
+        super(trie.isCaseInsensitive());
+        int capacity=(int)(trie._value.length*factor);
+        _rows=trie._rows;
+        _value=Arrays.copyOf(trie._value, capacity);
+        _tree=Arrays.copyOf(trie._tree, capacity*ROW_SIZE);
+        _key=Arrays.copyOf(trie._key, capacity);
+    }
+    
+    /* ------------------------------------------------------------ */
+    @Override
+    public boolean put(String s, V v)
+    {
+        int t=0;
+        int limit = s.length();
+        int last=0;
+        for(int k=0; k < limit; k++)
+        {
+            char c=s.charAt(k);
+            if(isCaseInsensitive() && c<128)
+                c=StringUtil.lowercases[c];
+            
+            while (true)
+            {
+                int row=ROW_SIZE*t;
+                
+                // Do we need to create the new row?
+                if (t==_rows)
+                {
+                    _rows++;
+                    if (_rows>=_key.length)
+                    {
+                        _rows--;
+                        return false;
+                    }
+                    _tree[row]=c;
+                }
+
+                char n=_tree[row];
+                int diff=n-c;
+                if (diff==0)
+                    t=_tree[last=(row+EQ)];
+                else if (diff<0)
+                    t=_tree[last=(row+LO)];
+                else
+                    t=_tree[last=(row+HI)];
+                
+                // do we need a new row?
+                if (t==0)
+                {
+                    t=_rows;
+                    _tree[last]=(char)t;
+                }
+                
+                if (diff==0)
+                    break;
+            }
+        }
+
+        // Do we need to create the new row?
+        if (t==_rows)
+        {
+            _rows++;
+            if (_rows>=_key.length)
+            {
+                _rows--;
+                return false;
+            }
+        }
+
+        // Put the key and value
+        _key[t]=v==null?null:s;
+        _value[t] = v;
+                
+        return true;
+    }
+    
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public V get(String s,int offset, int len)
+    {
+        int t = 0;
+        for(int i=0; i < len;)
+        {
+            char c=s.charAt(offset+i++);
+            if(isCaseInsensitive() && c<128)
+                c=StringUtil.lowercases[c];
+            
+            while (true)
+            {
+                int row = ROW_SIZE*t;
+                char n=_tree[row];
+                int diff=n-c;
+                
+                if (diff==0)
+                {
+                    t=_tree[row+EQ];
+                    if (t==0)
+                        return null;
+                    break;
+                }
+
+                t=_tree[row+hilo(diff)];
+                if (t==0)
+                    return null;
+            }
+        }
+        
+        return (V)_value[t];
+    }
+
+    
+    @Override
+    public V get(ByteBuffer b, int offset, int len)
+    {
+        int t = 0;
+        offset+=b.position();
+        
+        for(int i=0; i < len;)
+        {
+            byte c=(byte)(b.get(offset+i++)&0x7f);
+            if(isCaseInsensitive())
+                c=(byte)StringUtil.lowercases[c];
+            
+            while (true)
+            {
+                int row = ROW_SIZE*t;
+                char n=_tree[row];
+                int diff=n-c;
+                
+                if (diff==0)
+                {
+                    t=_tree[row+EQ];
+                    if (t==0)
+                        return null;
+                    break;
+                }
+
+                t=_tree[row+hilo(diff)];
+                if (t==0)
+                    return null;
+            }
+        }
+
+        return (V)_value[t];
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public V getBest(String s)
+    {
+        return getBest(0,s,0,s.length());
+    }
+    
+    /* ------------------------------------------------------------ */
+    @Override
+    public V getBest(String s, int offset, int length)
+    {
+        return getBest(0,s,offset,length);
+    }
+
+    /* ------------------------------------------------------------ */
+    private V getBest(int t,String s,int offset,int len)
+    {
+        int node=t;
+        loop: for(int i=0; i<len; i++)
+        {
+            char c=s.charAt(offset+i);
+            if(isCaseInsensitive() && c<128)
+                c=StringUtil.lowercases[c];
+
+            while (true)
+            {
+                int row = ROW_SIZE*t;
+                char n=_tree[row];
+                int diff=n-c;
+                
+                if (diff==0)
+                {
+                    t=_tree[row+EQ];
+                    if (t==0)
+                        break loop;
+                    
+                    // if this node is a match, recurse to remember 
+                    if (_key[t]!=null)
+                    {
+                        node=t;
+                        V best=getBest(t,s,offset+i+1,len-i-1);
+                        if (best!=null)
+                            return best;
+                    }
+                    break;
+                }
+
+                t=_tree[row+hilo(diff)];
+                if (t==0)
+                    break loop;
+            }
+        }
+        return (V)_value[node];
+    }
+
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public V getBest(ByteBuffer b, int offset, int len)
+    {
+        if (b.hasArray())
+            return getBest(0,b.array(),b.arrayOffset()+b.position()+offset,len);
+        return getBest(0,b,offset,len);
+    }
+
+    /* ------------------------------------------------------------ */
+    private V getBest(int t,byte[] b, int offset, int len)
+    {
+        int node=t;
+        loop: for(int i=0; i<len; i++)
+        {
+            byte c=(byte)(b[offset+i]&0x7f);
+            if(isCaseInsensitive())
+                c=(byte)StringUtil.lowercases[c];
+
+            while (true)
+            {
+                int row = ROW_SIZE*t;
+                char n=_tree[row];
+                int diff=n-c;
+                
+                if (diff==0)
+                {
+                    t=_tree[row+EQ];
+                    if (t==0)
+                        break loop;
+                    
+                    // if this node is a match, recurse to remember 
+                    if (_key[t]!=null)
+                    {
+                        node=t;
+                        V best=getBest(t,b,offset+i+1,len-i-1);
+                        if (best!=null)
+                            return best;
+                    }
+                    break;
+                }
+
+                t=_tree[row+hilo(diff)];
+                if (t==0)
+                    break loop;
+            }
+        }
+        return (V)_value[node];
+    }
+
+    /* ------------------------------------------------------------ */
+    private V getBest(int t,ByteBuffer b, int offset, int len)
+    {
+        int node=t;
+        int o= offset+b.position();
+        
+        loop: for(int i=0; i<len; i++)
+        {
+            byte c=(byte)(b.get(o+i)&0x7f);
+            if(isCaseInsensitive())
+                c=(byte)StringUtil.lowercases[c];
+
+            while (true)
+            {
+                int row = ROW_SIZE*t;
+                char n=_tree[row];
+                int diff=n-c;
+                
+                if (diff==0)
+                {
+                    t=_tree[row+EQ];
+                    if (t==0)
+                        break loop;
+                    
+                    // if this node is a match, recurse to remember 
+                    if (_key[t]!=null)
+                    {
+                        node=t;
+                        V best=getBest(t,b,offset+i+1,len-i-1);
+                        if (best!=null)
+                            return best;
+                    }
+                    break;
+                }
+
+                t=_tree[row+hilo(diff)];
+                if (t==0)
+                    break loop;
+            }
+        }
+        return (V)_value[node];
+    }
+
+    @Override
+    public String toString()
+    {
+        StringBuilder buf = new StringBuilder();
+        for (int r=0;r<=_rows;r++)
+        {
+            if (_key[r]!=null && _value[r]!=null)
+            {
+                buf.append(',');
+                buf.append(_key[r]);
+                buf.append('=');
+                buf.append(_value[r].toString());
+            }
+        }
+        if (buf.length()==0)
+            return "{}";
+        
+        buf.setCharAt(0,'{');
+        buf.append('}');
+        return buf.toString();
+    }
+
+
+
+    @Override
+    public Set<String> keySet()
+    {
+        Set<String> keys = new HashSet<>();
+
+        for (int r=0;r<=_rows;r++)
+        {
+            if (_key[r]!=null && _value[r]!=null)
+                keys.add(_key[r]);
+        }
+        return keys;
+    }
+
+    @Override
+    public boolean isFull()
+    {
+        return _rows+1==_key.length;
+    }
+    
+    public static int hilo(int diff)
+    {
+        // branchless equivalent to return ((diff<0)?LO:HI);
+        // return 3+2*((diff&Integer.MIN_VALUE)>>Integer.SIZE-1);
+        return 1+(diff|Integer.MAX_VALUE)/(Integer.MAX_VALUE/2);
+    }
+    
+    public void dump()
+    {
+        for (int r=0;r<_rows;r++)
+        {
+            char c=_tree[r*ROW_SIZE+0];
+            System.err.printf("%4d [%s,%d,%d,%d] '%s':%s%n",
+                r,
+                (c<' '||c>127)?(""+(int)c):"'"+c+"'",
+                (int)_tree[r*ROW_SIZE+LO],
+                (int)_tree[r*ROW_SIZE+EQ],
+                (int)_tree[r*ROW_SIZE+HI],
+                _key[r],
+                _value[r]);
+        }
+        
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/util/ArrayTrie.java b/lib/jetty/org/eclipse/jetty/util/ArrayTrie.java
new file mode 100644 (file)
index 0000000..73ccc42
--- /dev/null
@@ -0,0 +1,445 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.HashSet;
+import java.util.Set;
+
+/* ------------------------------------------------------------ */
+/** A Trie String lookup data structure using a fixed size array.
+ * <p>This implementation is always case insensitive and is optimal for
+ * a small number of fixed strings with few special characters.
+ * </p>
+ * @param <V>
+ */
+public class ArrayTrie<V> extends AbstractTrie<V>
+{
+    /**
+     * The Size of a Trie row is how many characters can be looked
+     * up directly without going to a big index.  This is set at 
+     * 32 to cover case insensitive alphabet and a few other common
+     * characters. 
+     */
+    private static final int ROW_SIZE = 32;
+    
+    /**
+     * The index lookup table, this maps a character as a byte 
+     * (ISO-8859-1 or UTF8) to an index within a Trie row
+     */
+    private static final int[] __lookup = 
+    { // 0   1   2   3   4   5   6   7   8   9   A   B   C   D   E   F
+   /*0*/-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 
+   /*1*/-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 30, 
+   /*2*/31, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 26, -1, 27, -1, -1,
+   /*3*/-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 28, 29, -1, -1, -1, -1,
+   /*4*/-1,  0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14,
+   /*5*/15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1,
+   /*6*/-1,  0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14,
+   /*7*/15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1,
+    };
+    
+    /**
+     * The Trie rows in a single array which allows a lookup of row,character
+     * to the next row in the Trie.  This is actually a 2 dimensional
+     * array that has been flattened to achieve locality of reference.
+     * The first ROW_SIZE entries are for row 0, then next ROW_SIZE 
+     * entries are for row 1 etc.   So in general instead of using
+     * _rows[row][index], we use _rows[row*ROW_SIZE+index] to look up
+     * the next row for a given character.
+     * 
+     * The array is of characters rather than integers to save space. 
+     */
+    private final char[] _rowIndex;
+    
+    /**
+     * The key (if any) for a Trie row. 
+     * A row may be a leaf, a node or both in the Trie tree.
+     */
+    private final String[] _key;
+    
+    /**
+     * The value (if any) for a Trie row. 
+     * A row may be a leaf, a node or both in the Trie tree.
+     */
+    private final Object[] _value;
+    
+    /**
+     * A big index for each row.
+     * If a character outside of the lookup map is needed,
+     * then a big index will be created for the row, with
+     * 256 entries, one for each possible byte.
+     */
+    private char[][] _bigIndex;
+    
+    /**
+     * The number of rows allocated
+     */
+    private char _rows;
+
+    public ArrayTrie()
+    {
+        this(128);
+    }
+    
+    public ArrayTrie(int capacityInNodes)
+    {
+        super(true);
+        _value=new Object[capacityInNodes];
+        _rowIndex=new char[capacityInNodes*32];
+        _key=new String[capacityInNodes];
+    }
+    
+    
+    /* ------------------------------------------------------------ */
+    @Override
+    public boolean put(String s, V v)
+    {
+        int t=0;
+        int k;
+        int limit = s.length();
+        for(k=0; k < limit; k++)
+        {
+            char c=s.charAt(k);
+            
+            int index=__lookup[c&0x7f];
+            if (index>=0)
+            {
+                int idx=t*ROW_SIZE+index;
+                t=_rowIndex[idx];
+                if (t==0)
+                {
+                    if (++_rows>=_value.length)
+                        return false;
+                    t=_rowIndex[idx]=_rows;
+                }
+            }
+            else if (c>127)
+                throw new IllegalArgumentException("non ascii character");
+            else
+            {
+                if (_bigIndex==null)
+                    _bigIndex=new char[_value.length][];
+                if (t>=_bigIndex.length)
+                    return false;
+                char[] big=_bigIndex[t];
+                if (big==null)
+                    big=_bigIndex[t]=new char[128];
+                t=big[c];
+                if (t==0)
+                {
+                    if (_rows==_value.length)
+                        return false;
+                    t=big[c]=++_rows;
+                }
+            }
+        }
+        _key[t]=v==null?null:s;
+        V old=(V)_value[t];
+        _value[t] = v;
+        return true;
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public V get(String s, int offset, int len)
+    {
+        int t = 0;
+        for(int i=0; i < len; i++)
+        {
+            char c=s.charAt(offset+i);
+            int index=__lookup[c&0x7f];
+            if (index>=0)
+            {
+                int idx=t*ROW_SIZE+index;
+                t=_rowIndex[idx];
+                if (t==0)
+                    return null;
+            }
+            else
+            {
+                char[] big = _bigIndex==null?null:_bigIndex[t];
+                if (big==null)
+                    return null;
+                t=big[c];
+                if (t==0)
+                    return null;
+            }
+        }
+        return (V)_value[t];
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public V get(ByteBuffer b,int offset,int len)
+    {
+        int t = 0;
+        for(int i=0; i < len; i++)
+        {
+            byte c=b.get(offset+i);
+            int index=__lookup[c&0x7f];
+            if (index>=0)
+            {
+                int idx=t*ROW_SIZE+index;
+                t=_rowIndex[idx];
+                if (t==0)
+                    return null;
+            }
+            else
+            {
+                char[] big = _bigIndex==null?null:_bigIndex[t];
+                if (big==null)
+                    return null;
+                t=big[c];
+                if (t==0)
+                    return null;
+            }
+        }
+        return (V)_value[t];
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public V getBest(byte[] b,int offset,int len)
+    {
+        return getBest(0,b,offset,len);
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public V getBest(ByteBuffer b,int offset,int len)
+    {
+        if (b.hasArray())
+            return getBest(0,b.array(),b.arrayOffset()+b.position()+offset,len);
+        return getBest(0,b,offset,len);
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public V getBest(String s, int offset, int len)
+    {
+        return getBest(0,s,offset,len);
+    }
+    
+    /* ------------------------------------------------------------ */
+    private V getBest(int t, String s, int offset, int len)
+    {
+        int pos=offset;
+        for(int i=0; i < len; i++)
+        {
+            char c=s.charAt(pos++);
+            int index=__lookup[c&0x7f];
+            if (index>=0)
+            {
+                int idx=t*ROW_SIZE+index;
+                int nt=_rowIndex[idx];
+                if (nt==0)
+                    break;
+                t=nt;
+            }
+            else
+            {
+                char[] big = _bigIndex==null?null:_bigIndex[t];
+                if (big==null)
+                    return null;
+                int nt=big[c];
+                if (nt==0)
+                    break;
+                t=nt;
+            }
+            
+            // Is the next Trie is a match
+            if (_key[t]!=null)
+            {
+                // Recurse so we can remember this possibility
+                V best=getBest(t,s,offset+i+1,len-i-1);
+                if (best!=null)
+                    return best;
+                return (V)_value[t];
+            }
+        }
+        return (V)_value[t];
+    }
+
+    /* ------------------------------------------------------------ */
+    private V getBest(int t,byte[] b,int offset,int len)
+    {
+        for(int i=0; i < len; i++)
+        {
+            byte c=b[offset+i];
+            int index=__lookup[c&0x7f];
+            if (index>=0)
+            {
+                int idx=t*ROW_SIZE+index;
+                int nt=_rowIndex[idx];
+                if (nt==0)
+                    break;
+                t=nt;
+            }
+            else
+            {
+                char[] big = _bigIndex==null?null:_bigIndex[t];
+                if (big==null)
+                    return null;
+                int nt=big[c];
+                if (nt==0)
+                    break;
+                t=nt;
+            }
+            
+            // Is the next Trie is a match
+            if (_key[t]!=null)
+            {
+                // Recurse so we can remember this possibility
+                V best=getBest(t,b,offset+i+1,len-i-1);
+                if (best!=null)
+                    return best;
+                break;
+            }
+        }
+        return (V)_value[t];
+    }
+    
+    private V getBest(int t,ByteBuffer b,int offset,int len)
+    {
+        int pos=b.position()+offset;
+        for(int i=0; i < len; i++)
+        {
+            byte c=b.get(pos++);
+            int index=__lookup[c&0x7f];
+            if (index>=0)
+            {
+                int idx=t*ROW_SIZE+index;
+                int nt=_rowIndex[idx];
+                if (nt==0)
+                    break;
+                t=nt;
+            }
+            else
+            {
+                char[] big = _bigIndex==null?null:_bigIndex[t];
+                if (big==null)
+                    return null;
+                int nt=big[c];
+                if (nt==0)
+                    break;
+                t=nt;
+            }
+            
+            // Is the next Trie is a match
+            if (_key[t]!=null)
+            {
+                // Recurse so we can remember this possibility
+                V best=getBest(t,b,offset+i+1,len-i-1);
+                if (best!=null)
+                    return best;
+                break;
+            }
+        }
+        return (V)_value[t];
+    }
+    
+    
+    
+
+    @Override
+    public String toString()
+    {
+        StringBuilder buf = new StringBuilder();
+        toString(buf,0);
+        
+        if (buf.length()==0)
+            return "{}";
+        
+        buf.setCharAt(0,'{');
+        buf.append('}');
+        return buf.toString();
+    }
+
+
+    private <V> void toString(Appendable out, int t)
+    {
+        if (_value[t]!=null)
+        {
+            try
+            {
+                out.append(',');
+                out.append(_key[t]);
+                out.append('=');
+                out.append(_value[t].toString());
+            }
+            catch (IOException e)
+            {
+                throw new RuntimeException(e);
+            }
+        }
+
+        for(int i=0; i < ROW_SIZE; i++)
+        {
+            int idx=t*ROW_SIZE+i;
+            if (_rowIndex[idx] != 0)
+                toString(out,_rowIndex[idx]);
+        }
+
+        char[] big = _bigIndex==null?null:_bigIndex[t];
+        if (big!=null)
+        {
+            for (int i:big)
+                if (i!=0)
+                    toString(out,i);
+        }
+
+    }
+
+    @Override
+    public Set<String> keySet()
+    {
+        Set<String> keys = new HashSet<>();
+        keySet(keys,0);
+        return keys;
+    }
+    
+    private void keySet(Set<String> set, int t)
+    {
+        if (t<_value.length&&_value[t]!=null)
+            set.add(_key[t]);
+
+        for(int i=0; i < ROW_SIZE; i++)
+        {
+            int idx=t*ROW_SIZE+i;
+            if (idx<_rowIndex.length && _rowIndex[idx] != 0)
+                keySet(set,_rowIndex[idx]);
+        }
+        
+        char[] big = _bigIndex==null||t>=_bigIndex.length?null:_bigIndex[t];
+        if (big!=null)
+        {
+            for (int i:big)
+                if (i!=0)
+                    keySet(set,i);
+        }
+    }
+    
+    @Override
+    public boolean isFull()
+    {
+        return _rows+1==_key.length;
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/util/ArrayUtil.java b/lib/jetty/org/eclipse/jetty/util/ArrayUtil.java
new file mode 100644 (file)
index 0000000..76b195d
--- /dev/null
@@ -0,0 +1,142 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util;
+
+import java.io.Serializable;
+import java.lang.reflect.Array;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+
+/* ------------------------------------------------------------ */
+/**
+ */
+public class ArrayUtil
+    implements Cloneable, Serializable
+{
+
+    /* ------------------------------------------------------------ */
+    public static<T> T[] removeFromArray(T[] array, Object item)
+    {
+        if (item==null || array==null)
+            return array;
+        for (int i=array.length;i-->0;)
+        {
+            if (item.equals(array[i]))
+            {
+                Class<?> c = array==null?item.getClass():array.getClass().getComponentType();
+                @SuppressWarnings("unchecked")
+                T[] na = (T[])Array.newInstance(c, Array.getLength(array)-1);
+                if (i>0)
+                    System.arraycopy(array, 0, na, 0, i);
+                if (i+1<array.length)
+                    System.arraycopy(array, i+1, na, i, array.length-(i+1));
+                return na;
+            }
+        }
+        return array;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Add element to an array
+     * @param array The array to add to (or null)
+     * @param item The item to add
+     * @param type The type of the array (in case of null array)
+     * @return new array with contents of array plus item
+     */
+    public static<T> T[] addToArray(T[] array, T item, Class<?> type)
+    {
+        if (array==null)
+        {
+            if (type==null && item!=null)
+                type= item.getClass();
+            @SuppressWarnings("unchecked")
+            T[] na = (T[])Array.newInstance(type, 1);
+            na[0]=item;
+            return na;
+        }
+        else
+        {
+            T[] na = Arrays.copyOf(array,array.length+1);
+            na[array.length]=item;
+            return na;
+        }
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** Add element to the start of an array
+     * @param array The array to add to (or null)
+     * @param item The item to add
+     * @param type The type of the array (in case of null array)
+     * @return new array with contents of array plus item
+     */
+    public static<T> T[] prependToArray(T item, T[] array, Class<?> type)
+    {
+        if (array==null)
+        {
+            if (type==null && item!=null)
+                type= item.getClass();
+            @SuppressWarnings("unchecked")
+            T[] na = (T[])Array.newInstance(type, 1);
+            na[0]=item;
+            return na;
+        }
+        else
+        {
+            Class<?> c = array.getClass().getComponentType();
+            @SuppressWarnings("unchecked")
+            T[] na = (T[])Array.newInstance(c, Array.getLength(array)+1);
+            System.arraycopy(array, 0, na, 1, array.length);
+            na[0]=item;
+            return na;
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param array Any array of object
+     * @return A new <i>modifiable</i> list initialised with the elements from <code>array</code>.
+     */
+    public static<E> List<E> asMutableList(E[] array)
+    {  
+        if (array==null || array.length==0)
+            return new ArrayList<E>();
+        return new ArrayList<E>(Arrays.asList(array));
+    }
+
+    /* ------------------------------------------------------------ */
+    public static <T> T[] removeNulls(T[] array)
+    {
+        for (T t : array)
+        {
+            if (t==null)
+            {
+                List<T> list = new ArrayList<>();
+                for (T t2:array)
+                    if (t2!=null)
+                        list.add(t2);
+                return list.toArray(Arrays.copyOf(array,list.size()));
+            }
+        }
+        return array;
+    }
+    
+}
+
diff --git a/lib/jetty/org/eclipse/jetty/util/Atomics.java b/lib/jetty/org/eclipse/jetty/util/Atomics.java
new file mode 100644 (file)
index 0000000..42fb489
--- /dev/null
@@ -0,0 +1,73 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util;
+
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicLong;
+
+public class Atomics
+{
+    private Atomics()
+    {
+    }
+
+    public static void updateMin(AtomicLong currentMin, long newValue)
+    {
+        long oldValue = currentMin.get();
+        while (newValue < oldValue)
+        {
+            if (currentMin.compareAndSet(oldValue, newValue))
+                break;
+            oldValue = currentMin.get();
+        }
+    }
+
+    public static void updateMax(AtomicLong currentMax, long newValue)
+    {
+        long oldValue = currentMax.get();
+        while (newValue > oldValue)
+        {
+            if (currentMax.compareAndSet(oldValue, newValue))
+                break;
+            oldValue = currentMax.get();
+        }
+    }
+
+    public static void updateMin(AtomicInteger currentMin, int newValue)
+    {
+        int oldValue = currentMin.get();
+        while (newValue < oldValue)
+        {
+            if (currentMin.compareAndSet(oldValue, newValue))
+                break;
+            oldValue = currentMin.get();
+        }
+    }
+
+    public static void updateMax(AtomicInteger currentMax, int newValue)
+    {
+        int oldValue = currentMax.get();
+        while (newValue > oldValue)
+        {
+            if (currentMax.compareAndSet(oldValue, newValue))
+                break;
+            oldValue = currentMax.get();
+        }
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/util/Attributes.java b/lib/jetty/org/eclipse/jetty/util/Attributes.java
new file mode 100644 (file)
index 0000000..c65abc2
--- /dev/null
@@ -0,0 +1,36 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util;
+
+import java.util.Enumeration;
+
+/* ------------------------------------------------------------ */
+/** Attributes.
+ * Interface commonly used for storing attributes.
+ * 
+ *
+ */
+public interface Attributes
+{
+    public void removeAttribute(String name);
+    public void setAttribute(String name, Object attribute);
+    public Object getAttribute(String name);
+    public Enumeration<String> getAttributeNames();
+    public void clearAttributes();
+}
diff --git a/lib/jetty/org/eclipse/jetty/util/AttributesMap.java b/lib/jetty/org/eclipse/jetty/util/AttributesMap.java
new file mode 100644 (file)
index 0000000..c605e09
--- /dev/null
@@ -0,0 +1,151 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.atomic.AtomicReference;
+
+public class AttributesMap implements Attributes
+{
+    private final AtomicReference<ConcurrentMap<String, Object>> _map = new AtomicReference<>();
+
+    public AttributesMap()
+    {
+    }
+
+    public AttributesMap(AttributesMap attributes)
+    {
+        ConcurrentMap<String, Object> map = attributes.map();
+        if (map != null)
+            _map.set(new ConcurrentHashMap<>(map));
+    }
+
+    private ConcurrentMap<String, Object> map()
+    {
+        return _map.get();
+    }
+
+    private ConcurrentMap<String, Object> ensureMap()
+    {
+        while (true)
+        {
+            ConcurrentMap<String, Object> map = map();
+            if (map != null)
+                return map;
+            map = new ConcurrentHashMap<>();
+            if (_map.compareAndSet(null, map))
+                return map;
+        }
+    }
+
+    @Override
+    public void removeAttribute(String name)
+    {
+        Map<String, Object> map = map();
+        if (map != null)
+            map.remove(name);
+    }
+
+    @Override
+    public void setAttribute(String name, Object attribute)
+    {
+        if (attribute == null)
+            removeAttribute(name);
+        else
+            ensureMap().put(name, attribute);
+    }
+
+    @Override
+    public Object getAttribute(String name)
+    {
+        Map<String, Object> map = map();
+        return map == null ? null : map.get(name);
+    }
+
+    @Override
+    public Enumeration<String> getAttributeNames()
+    {
+        return Collections.enumeration(getAttributeNameSet());
+    }
+
+    public Set<String> getAttributeNameSet()
+    {
+        return keySet();
+    }
+
+    public Set<Map.Entry<String, Object>> getAttributeEntrySet()
+    {
+        Map<String, Object> map = map();
+        return map == null ? Collections.<Map.Entry<String, Object>>emptySet() : map.entrySet();
+    }
+
+    public static Enumeration<String> getAttributeNamesCopy(Attributes attrs)
+    {
+        if (attrs instanceof AttributesMap)
+            return Collections.enumeration(((AttributesMap)attrs).keySet());
+
+        List<String> names = new ArrayList<>();
+        names.addAll(Collections.list(attrs.getAttributeNames()));
+        return Collections.enumeration(names);
+    }
+
+    @Override
+    public void clearAttributes()
+    {
+        Map<String, Object> map = map();
+        if (map != null)
+            map.clear();
+    }
+
+    public int size()
+    {
+        Map<String, Object> map = map();
+        return map == null ? 0 : map.size();
+    }
+
+    @Override
+    public String toString()
+    {
+        Map<String, Object> map = map();
+        return map == null ? "{}" : map.toString();
+    }
+
+    private Set<String> keySet()
+    {
+        Map<String, Object> map = map();
+        return map == null ? Collections.<String>emptySet() : map.keySet();
+    }
+
+    public void addAll(Attributes attributes)
+    {
+        Enumeration<String> e = attributes.getAttributeNames();
+        while (e.hasMoreElements())
+        {
+            String name = e.nextElement();
+            setAttribute(name, attributes.getAttribute(name));
+        }
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/util/B64Code.java b/lib/jetty/org/eclipse/jetty/util/B64Code.java
new file mode 100644 (file)
index 0000000..7fbbb18
--- /dev/null
@@ -0,0 +1,464 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+import java.nio.charset.UnsupportedCharsetException;
+
+
+/** Fast B64 Encoder/Decoder as described in RFC 1421.
+ * <p>Does not insert or interpret whitespace as described in RFC
+ * 1521. If you require this you must pre/post process your data.
+ * <p> Note that in a web context the usual case is to not want
+ * linebreaks or other white space in the encoded output.
+ *
+ */
+public class B64Code
+{
+    private static final char __pad='=';
+    private static final char[] __rfc1421alphabet=
+            {
+                'A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P',
+                'Q','R','S','T','U','V','W','X','Y','Z','a','b','c','d','e','f',
+                'g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v',
+                'w','x','y','z','0','1','2','3','4','5','6','7','8','9','+','/'
+            };
+
+    private static final byte[] __rfc1421nibbles;
+    static
+    {
+        __rfc1421nibbles=new byte[256];
+        for (int i=0;i<256;i++)
+            __rfc1421nibbles[i]=-1;
+        for (byte b=0;b<64;b++)
+            __rfc1421nibbles[(byte)__rfc1421alphabet[b]]=b;
+        __rfc1421nibbles[(byte)__pad]=0;
+    }
+
+    private B64Code()
+    {
+    }
+
+    /**
+     * Base 64 encode as described in RFC 1421.
+     * <p>Does not insert whitespace as described in RFC 1521.
+     * @param s String to encode.
+     * @return String containing the encoded form of the input.
+     */
+    public static String encode(String s)
+    {
+        return encode(s, (Charset)null);
+    }
+
+    /**
+     * Base 64 encode as described in RFC 1421.
+     * <p>Does not insert whitespace as described in RFC 1521.
+     * @param s String to encode.
+     * @param charEncoding String representing the name of
+     *        the character encoding of the provided input String.
+     * @return String containing the encoded form of the input.
+     */
+    public static String encode(String s,String charEncoding)
+    {
+        byte[] bytes;
+        if (charEncoding==null)
+            bytes=s.getBytes(StandardCharsets.ISO_8859_1);
+        else
+            bytes=s.getBytes(Charset.forName(charEncoding));
+        return new String(encode(bytes));
+    }
+
+    /**
+     * Base 64 encode as described in RFC 1421.
+     * <p>Does not insert whitespace as described in RFC 1521.
+     * @param s String to encode.
+     * @param charEncoding The character encoding of the provided input String.
+     * @return String containing the encoded form of the input.
+     */
+    public static String encode(String s, Charset charEncoding)
+    {
+        byte[] bytes=s.getBytes(charEncoding==null ? StandardCharsets.ISO_8859_1 : charEncoding);
+        return new String(encode(bytes));
+    }
+
+    /**
+     * Fast Base 64 encode as described in RFC 1421.
+     * <p>Does not insert whitespace as described in RFC 1521.
+     * <p> Avoids creating extra copies of the input/output.
+     * @param b byte array to encode.
+     * @return char array containing the encoded form of the input.
+     */
+    public static char[] encode(byte[] b)
+    {
+        if (b==null)
+            return null;
+
+        int bLen=b.length;
+        int cLen=((bLen+2)/3)*4;
+        char c[]=new char[cLen];
+        int ci=0;
+        int bi=0;
+        byte b0, b1, b2;
+        int stop=(bLen/3)*3;
+        while (bi<stop)
+        {
+            b0=b[bi++];
+            b1=b[bi++];
+            b2=b[bi++];
+            c[ci++]=__rfc1421alphabet[(b0>>>2)&0x3f];
+            c[ci++]=__rfc1421alphabet[(b0<<4)&0x3f|(b1>>>4)&0x0f];
+            c[ci++]=__rfc1421alphabet[(b1<<2)&0x3f|(b2>>>6)&0x03];
+            c[ci++]=__rfc1421alphabet[b2&0x3f];
+        }
+
+        if (bLen!=bi)
+        {
+            switch (bLen%3)
+            {
+                case 2:
+                    b0=b[bi++];
+                    b1=b[bi++];
+                    c[ci++]=__rfc1421alphabet[(b0>>>2)&0x3f];
+                    c[ci++]=__rfc1421alphabet[(b0<<4)&0x3f|(b1>>>4)&0x0f];
+                    c[ci++]=__rfc1421alphabet[(b1<<2)&0x3f];
+                    c[ci++]=__pad;
+                    break;
+
+                case 1:
+                    b0=b[bi++];
+                    c[ci++]=__rfc1421alphabet[(b0>>>2)&0x3f];
+                    c[ci++]=__rfc1421alphabet[(b0<<4)&0x3f];
+                    c[ci++]=__pad;
+                    c[ci++]=__pad;
+                    break;
+
+                default:
+                    break;
+            }
+        }
+
+        return c;
+    }
+
+    /**
+     * Fast Base 64 encode as described in RFC 1421 and RFC2045
+     * <p>Does not insert whitespace as described in RFC 1521, unless rfc2045 is passed as true.
+     * <p> Avoids creating extra copies of the input/output.
+     * @param b byte array to encode.
+     * @param rfc2045 If true, break lines at 76 characters with CRLF
+     * @return char array containing the encoded form of the input.
+     */
+    public static char[] encode(byte[] b, boolean rfc2045)
+    {
+        if (b==null)
+            return null;
+        if (!rfc2045)
+            return encode(b);
+
+        int bLen=b.length;
+        int cLen=((bLen+2)/3)*4;
+        cLen+=2+2*(cLen/76);
+        char c[]=new char[cLen];
+        int ci=0;
+        int bi=0;
+        byte b0, b1, b2;
+        int stop=(bLen/3)*3;
+        int l=0;
+        while (bi<stop)
+        {
+            b0=b[bi++];
+            b1=b[bi++];
+            b2=b[bi++];
+            c[ci++]=__rfc1421alphabet[(b0>>>2)&0x3f];
+            c[ci++]=__rfc1421alphabet[(b0<<4)&0x3f|(b1>>>4)&0x0f];
+            c[ci++]=__rfc1421alphabet[(b1<<2)&0x3f|(b2>>>6)&0x03];
+            c[ci++]=__rfc1421alphabet[b2&0x3f];
+            l+=4;
+            if (l%76==0)
+            {
+                c[ci++]=13;
+                c[ci++]=10;
+            }
+        }
+
+        if (bLen!=bi)
+        {
+            switch (bLen%3)
+            {
+                case 2:
+                    b0=b[bi++];
+                    b1=b[bi++];
+                    c[ci++]=__rfc1421alphabet[(b0>>>2)&0x3f];
+                    c[ci++]=__rfc1421alphabet[(b0<<4)&0x3f|(b1>>>4)&0x0f];
+                    c[ci++]=__rfc1421alphabet[(b1<<2)&0x3f];
+                    c[ci++]=__pad;
+                    break;
+
+                case 1:
+                    b0=b[bi++];
+                    c[ci++]=__rfc1421alphabet[(b0>>>2)&0x3f];
+                    c[ci++]=__rfc1421alphabet[(b0<<4)&0x3f];
+                    c[ci++]=__pad;
+                    c[ci++]=__pad;
+                    break;
+
+                default:
+                    break;
+            }
+        }
+
+        c[ci++]=13;
+        c[ci++]=10;
+        return c;
+    }
+
+    /**
+     * Base 64 decode as described in RFC 2045.
+     * <p>Unlike {@link #decode(char[])}, extra whitespace is ignored.
+     * @param encoded String to decode.
+     * @param charEncoding String representing the character encoding
+     *        used to map the decoded bytes into a String.
+     * @return String decoded byte array.
+     * @throws UnsupportedCharsetException if the encoding is not supported
+     * @throws IllegalArgumentException if the input is not a valid
+     *         B64 encoding.
+     */
+    public static String decode(String encoded,String charEncoding)
+    {
+        byte[] decoded=decode(encoded);
+        if (charEncoding==null)
+            return new String(decoded);
+        return new String(decoded,Charset.forName(charEncoding));
+    }
+
+    /**
+     * Base 64 decode as described in RFC 2045.
+     * <p>Unlike {@link #decode(char[])}, extra whitespace is ignored.
+     * @param encoded String to decode.
+     * @param charEncoding Character encoding
+     *        used to map the decoded bytes into a String.
+     * @return String decoded byte array.
+     * @throws IllegalArgumentException if the input is not a valid
+     *         B64 encoding.
+     */
+    public static String decode(String encoded, Charset charEncoding)
+    {
+        byte[] decoded=decode(encoded);
+        if (charEncoding==null)
+            return new String(decoded);
+        return new String(decoded, charEncoding);
+    }
+
+    /**
+     * Fast Base 64 decode as described in RFC 1421.
+     *
+     * <p>Unlike other decode methods, this does not attempt to
+     * cope with extra whitespace as described in RFC 1521/2045.
+     * <p> Avoids creating extra copies of the input/output.
+     * <p> Note this code has been flattened for performance.
+     * @param b char array to decode.
+     * @return byte array containing the decoded form of the input.
+     * @throws IllegalArgumentException if the input is not a valid
+     *         B64 encoding.
+     */
+    public static byte[] decode(char[] b)
+    {
+        if (b==null)
+            return null;
+
+        int bLen=b.length;
+        if (bLen%4!=0)
+            throw new IllegalArgumentException("Input block size is not 4");
+
+        int li=bLen-1;
+        while (li>=0 && b[li]==(byte)__pad)
+            li--;
+
+        if (li<0)
+            return new byte[0];
+
+        // Create result array of exact required size.
+        int rLen=((li+1)*3)/4;
+        byte r[]=new byte[rLen];
+        int ri=0;
+        int bi=0;
+        int stop=(rLen/3)*3;
+        byte b0,b1,b2,b3;
+        try
+        {
+            while (ri<stop)
+            {
+                b0=__rfc1421nibbles[b[bi++]];
+                b1=__rfc1421nibbles[b[bi++]];
+                b2=__rfc1421nibbles[b[bi++]];
+                b3=__rfc1421nibbles[b[bi++]];
+                if (b0<0 || b1<0 || b2<0 || b3<0)
+                    throw new IllegalArgumentException("Not B64 encoded");
+
+                r[ri++]=(byte)(b0<<2|b1>>>4);
+                r[ri++]=(byte)(b1<<4|b2>>>2);
+                r[ri++]=(byte)(b2<<6|b3);
+            }
+
+            if (rLen!=ri)
+            {
+                switch (rLen%3)
+                {
+                    case 2:
+                        b0=__rfc1421nibbles[b[bi++]];
+                        b1=__rfc1421nibbles[b[bi++]];
+                        b2=__rfc1421nibbles[b[bi++]];
+                        if (b0<0 || b1<0 || b2<0)
+                            throw new IllegalArgumentException("Not B64 encoded");
+                        r[ri++]=(byte)(b0<<2|b1>>>4);
+                        r[ri++]=(byte)(b1<<4|b2>>>2);
+                        break;
+
+                    case 1:
+                        b0=__rfc1421nibbles[b[bi++]];
+                        b1=__rfc1421nibbles[b[bi++]];
+                        if (b0<0 || b1<0)
+                            throw new IllegalArgumentException("Not B64 encoded");
+                        r[ri++]=(byte)(b0<<2|b1>>>4);
+                        break;
+
+                    default:
+                        break;
+                }
+            }
+        }
+        catch (IndexOutOfBoundsException e)
+        {
+            throw new IllegalArgumentException("char "+bi
+                    +" was not B64 encoded");
+        }
+
+        return r;
+    }
+
+    /**
+     * Base 64 decode as described in RFC 2045.
+     * <p>Unlike {@link #decode(char[])}, extra whitespace is ignored.
+     * @param encoded String to decode.
+     * @return byte array containing the decoded form of the input.
+     * @throws IllegalArgumentException if the input is not a valid
+     *         B64 encoding.
+     */
+    public static byte[] decode(String encoded)
+    {
+        if (encoded==null)
+            return null;
+
+        ByteArrayOutputStream bout = new ByteArrayOutputStream(4*encoded.length()/3);        
+        decode(encoded, bout);
+        return bout.toByteArray();
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * Base 64 decode as described in RFC 2045.
+     * <p>Unlike {@link #decode(char[])}, extra whitespace is ignored.
+     * @param encoded String to decode.
+     * @param bout stream for decoded bytes
+     * @throws IllegalArgumentException if the input is not a valid
+     *         B64 encoding.
+     */
+    static public void decode (String encoded, ByteArrayOutputStream bout)
+    {
+        if (encoded==null)
+            return;
+        
+        if (bout == null)
+            throw new IllegalArgumentException("No outputstream for decoded bytes");
+        
+        int ci=0;
+        byte nibbles[] = new byte[4];
+        int s=0;
+  
+        while (ci<encoded.length())
+        {
+            char c=encoded.charAt(ci++);
+
+            if (c==__pad)
+                break;
+
+            if (Character.isWhitespace(c))
+                continue;
+
+            byte nibble=__rfc1421nibbles[c];
+            if (nibble<0)
+                throw new IllegalArgumentException("Not B64 encoded");
+
+            nibbles[s++]=__rfc1421nibbles[c];
+
+            switch(s)
+            {
+                case 1:
+                    break;
+                case 2:
+                    bout.write(nibbles[0]<<2|nibbles[1]>>>4);
+                    break;
+                case 3:
+                    bout.write(nibbles[1]<<4|nibbles[2]>>>2);
+                    break;
+                case 4:
+                    bout.write(nibbles[2]<<6|nibbles[3]);
+                    s=0;
+                    break;
+            }
+
+        }
+
+        return;
+    }
+    
+
+    public static void encode(int value,Appendable buf) throws IOException
+    {
+        buf.append(__rfc1421alphabet[0x3f&((0xFC000000&value)>>26)]);
+        buf.append(__rfc1421alphabet[0x3f&((0x03F00000&value)>>20)]);
+        buf.append(__rfc1421alphabet[0x3f&((0x000FC000&value)>>14)]);
+        buf.append(__rfc1421alphabet[0x3f&((0x00003F00&value)>>8)]);
+        buf.append(__rfc1421alphabet[0x3f&((0x000000FC&value)>>2)]);
+        buf.append(__rfc1421alphabet[0x3f&((0x00000003&value)<<4)]);
+        buf.append('=');
+    }
+
+    public static void encode(long lvalue,Appendable buf) throws IOException
+    {
+        int value=(int)(0xFFFFFFFC&(lvalue>>32));
+        buf.append(__rfc1421alphabet[0x3f&((0xFC000000&value)>>26)]);
+        buf.append(__rfc1421alphabet[0x3f&((0x03F00000&value)>>20)]);
+        buf.append(__rfc1421alphabet[0x3f&((0x000FC000&value)>>14)]);
+        buf.append(__rfc1421alphabet[0x3f&((0x00003F00&value)>>8)]);
+        buf.append(__rfc1421alphabet[0x3f&((0x000000FC&value)>>2)]);
+
+        buf.append(__rfc1421alphabet[0x3f&((0x00000003&value)<<4) + (0xf&(int)(lvalue>>28))]);
+
+        value=0x0FFFFFFF&(int)lvalue;
+        buf.append(__rfc1421alphabet[0x3f&((0x0FC00000&value)>>22)]);
+        buf.append(__rfc1421alphabet[0x3f&((0x003F0000&value)>>16)]);
+        buf.append(__rfc1421alphabet[0x3f&((0x0000FC00&value)>>10)]);
+        buf.append(__rfc1421alphabet[0x3f&((0x000003F0&value)>>4)]);
+        buf.append(__rfc1421alphabet[0x3f&((0x0000000F&value)<<2)]);
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/util/BlockingArrayQueue.java b/lib/jetty/org/eclipse/jetty/util/BlockingArrayQueue.java
new file mode 100644 (file)
index 0000000..7acbdaf
--- /dev/null
@@ -0,0 +1,880 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util;
+
+import java.util.AbstractList;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.ListIterator;
+import java.util.NoSuchElementException;
+import java.util.Objects;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.locks.Condition;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+
+/**
+ * A BlockingQueue backed by a circular array capable or growing.
+ * <p/>
+ * This queue is uses a variant of the two lock queue algorithm to provide an efficient queue or list backed by a growable circular array.
+ * <p/>
+ * Unlike {@link java.util.concurrent.ArrayBlockingQueue}, this class is able to grow and provides a blocking put call.
+ * <p/>
+ * The queue has both a capacity (the size of the array currently allocated) and a max capacity (the maximum size that may be allocated), which defaults to
+ * {@link Integer#MAX_VALUE}.
+ * 
+ * @param <E>
+ *            The element type
+ */
+public class BlockingArrayQueue<E> extends AbstractList<E> implements BlockingQueue<E>
+{
+    /**
+     * The head offset in the {@link #_indexes} array, displaced by 15 slots to avoid false sharing with the array length (stored before the first element of
+     * the array itself).
+     */
+    private static final int HEAD_OFFSET = MemoryUtils.getIntegersPerCacheLine() - 1;
+    /**
+     * The tail offset in the {@link #_indexes} array, displaced by 16 slots from the head to avoid false sharing with it.
+     */
+    private static final int TAIL_OFFSET = HEAD_OFFSET + MemoryUtils.getIntegersPerCacheLine();
+    /**
+     * Default initial capacity, 128.
+     */
+    public static final int DEFAULT_CAPACITY = 128;
+    /**
+     * Default growth factor, 64.
+     */
+    public static final int DEFAULT_GROWTH = 64;
+
+    private final int _maxCapacity;
+    private final int _growCapacity;
+    /**
+     * Array that holds the head and tail indexes, separated by a cache line to avoid false sharing
+     */
+    private final int[] _indexes = new int[TAIL_OFFSET + 1];
+    private final Lock _tailLock = new ReentrantLock();
+    private final AtomicInteger _size = new AtomicInteger();
+    private final Lock _headLock = new ReentrantLock();
+    private final Condition _notEmpty = _headLock.newCondition();
+    private Object[] _elements;
+
+    /**
+     * Creates an unbounded {@link BlockingArrayQueue} with default initial capacity and grow factor.
+     * 
+     * @see #DEFAULT_CAPACITY
+     * @see #DEFAULT_GROWTH
+     */
+    public BlockingArrayQueue()
+    {
+        _elements = new Object[DEFAULT_CAPACITY];
+        _growCapacity = DEFAULT_GROWTH;
+        _maxCapacity = Integer.MAX_VALUE;
+    }
+
+    /**
+     * Creates a bounded {@link BlockingArrayQueue} that does not grow. The capacity of the queue is fixed and equal to the given parameter.
+     * 
+     * @param maxCapacity
+     *            the maximum capacity
+     */
+    public BlockingArrayQueue(int maxCapacity)
+    {
+        _elements = new Object[maxCapacity];
+        _growCapacity = -1;
+        _maxCapacity = maxCapacity;
+    }
+
+    /**
+     * Creates an unbounded {@link BlockingArrayQueue} that grows by the given parameter.
+     * 
+     * @param capacity
+     *            the initial capacity
+     * @param growBy
+     *            the growth factor
+     */
+    public BlockingArrayQueue(int capacity, int growBy)
+    {
+        _elements = new Object[capacity];
+        _growCapacity = growBy;
+        _maxCapacity = Integer.MAX_VALUE;
+    }
+
+    /**
+     * Create a bounded {@link BlockingArrayQueue} that grows by the given parameter.
+     * 
+     * @param capacity
+     *            the initial capacity
+     * @param growBy
+     *            the growth factor
+     * @param maxCapacity
+     *            the maximum capacity
+     */
+    public BlockingArrayQueue(int capacity, int growBy, int maxCapacity)
+    {
+        if (capacity > maxCapacity)
+            throw new IllegalArgumentException();
+        _elements = new Object[capacity];
+        _growCapacity = growBy;
+        _maxCapacity = maxCapacity;
+    }
+
+    /*----------------------------------------------------------------------------*/
+    /* Collection methods */
+    /*----------------------------------------------------------------------------*/
+
+    @Override
+    public void clear()
+    {
+
+        _tailLock.lock();
+        try
+        {
+
+            _headLock.lock();
+            try
+            {
+                _indexes[HEAD_OFFSET] = 0;
+                _indexes[TAIL_OFFSET] = 0;
+                _size.set(0);
+            }
+            finally
+            {
+                _headLock.unlock();
+            }
+        }
+        finally
+        {
+            _tailLock.unlock();
+        }
+    }
+
+    @Override
+    public int size()
+    {
+        return _size.get();
+    }
+
+    @Override
+    public Iterator<E> iterator()
+    {
+        return listIterator();
+    }
+
+    /*----------------------------------------------------------------------------*/
+    /* Queue methods */
+    /*----------------------------------------------------------------------------*/
+
+    @SuppressWarnings("unchecked")
+    @Override
+    public E poll()
+    {
+        if (_size.get() == 0)
+            return null;
+
+        E e = null;
+
+        _headLock.lock(); // Size cannot shrink
+        try
+        {
+            if (_size.get() > 0)
+            {
+                final int head = _indexes[HEAD_OFFSET];
+                e = (E)_elements[head];
+                _elements[head] = null;
+                _indexes[HEAD_OFFSET] = (head + 1) % _elements.length;
+                if (_size.decrementAndGet() > 0)
+                    _notEmpty.signal();
+            }
+        }
+        finally
+        {
+            _headLock.unlock();
+        }
+        return e;
+    }
+
+    @SuppressWarnings("unchecked")
+    @Override
+    public E peek()
+    {
+        if (_size.get() == 0)
+            return null;
+
+        E e = null;
+
+        _headLock.lock(); // Size cannot shrink
+        try
+        {
+            if (_size.get() > 0)
+                e = (E)_elements[_indexes[HEAD_OFFSET]];
+        }
+        finally
+        {
+            _headLock.unlock();
+        }
+        return e;
+    }
+
+    @Override
+    public E remove()
+    {
+        E e = poll();
+        if (e == null)
+            throw new NoSuchElementException();
+        return e;
+    }
+
+    @Override
+    public E element()
+    {
+        E e = peek();
+        if (e == null)
+            throw new NoSuchElementException();
+        return e;
+    }
+
+    /*----------------------------------------------------------------------------*/
+    /* BlockingQueue methods */
+    /*----------------------------------------------------------------------------*/
+
+    @Override
+    public boolean offer(E e)
+    {
+        Objects.requireNonNull(e);
+
+        boolean notEmpty = false;
+        _tailLock.lock(); // Size cannot grow... only shrink
+        try
+        {
+            int size = _size.get();
+            if (size >= _maxCapacity)
+                return false;
+
+            // Should we expand array?
+            if (size == _elements.length)
+            {
+                _headLock.lock();
+                try
+                {
+                    if (!grow())
+                        return false;
+                }
+                finally
+                {
+                    _headLock.unlock();
+                }
+            }
+
+            // Re-read head and tail after a possible grow
+            int tail = _indexes[TAIL_OFFSET];
+            _elements[tail] = e;
+            _indexes[TAIL_OFFSET] = (tail + 1) % _elements.length;
+            notEmpty = _size.getAndIncrement() == 0;
+        }
+        finally
+        {
+            _tailLock.unlock();
+        }
+
+        if (notEmpty)
+        {
+            _headLock.lock();
+            try
+            {
+                _notEmpty.signal();
+            }
+            finally
+            {
+                _headLock.unlock();
+            }
+        }
+
+        return true;
+    }
+
+    @Override
+    public boolean add(E e)
+    {
+        if (offer(e))
+            return true;
+        else
+            throw new IllegalStateException();
+    }
+
+    @Override
+    public void put(E o) throws InterruptedException
+    {
+        // The mechanism to await and signal when the queue is full is not implemented
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public boolean offer(E o, long timeout, TimeUnit unit) throws InterruptedException
+    {
+        // The mechanism to await and signal when the queue is full is not implemented
+        throw new UnsupportedOperationException();
+    }
+
+    @SuppressWarnings("unchecked")
+    @Override
+    public E take() throws InterruptedException
+    {
+        E e = null;
+
+        _headLock.lockInterruptibly(); // Size cannot shrink
+        try
+        {
+            try
+            {
+                while (_size.get() == 0)
+                {
+                    _notEmpty.await();
+                }
+            }
+            catch (InterruptedException ie)
+            {
+                _notEmpty.signal();
+                throw ie;
+            }
+
+            final int head = _indexes[HEAD_OFFSET];
+            e = (E)_elements[head];
+            _elements[head] = null;
+            _indexes[HEAD_OFFSET] = (head + 1) % _elements.length;
+
+            if (_size.decrementAndGet() > 0)
+                _notEmpty.signal();
+        }
+        finally
+        {
+            _headLock.unlock();
+        }
+        return e;
+    }
+
+    @SuppressWarnings("unchecked")
+    @Override
+    public E poll(long time, TimeUnit unit) throws InterruptedException
+    {
+        long nanos = unit.toNanos(time);
+        E e = null;
+
+        _headLock.lockInterruptibly(); // Size cannot shrink
+        try
+        {
+            try
+            {
+                while (_size.get() == 0)
+                {
+                    if (nanos <= 0)
+                        return null;
+                    nanos = _notEmpty.awaitNanos(nanos);
+                }
+            }
+            catch (InterruptedException x)
+            {
+                _notEmpty.signal();
+                throw x;
+            }
+
+            int head = _indexes[HEAD_OFFSET];
+            e = (E)_elements[head];
+            _elements[head] = null;
+            _indexes[HEAD_OFFSET] = (head + 1) % _elements.length;
+
+            if (_size.decrementAndGet() > 0)
+                _notEmpty.signal();
+        }
+        finally
+        {
+            _headLock.unlock();
+        }
+        return e;
+    }
+
+    @Override
+    public boolean remove(Object o)
+    {
+
+        _tailLock.lock();
+        try
+        {
+
+            _headLock.lock();
+            try
+            {
+                if (isEmpty())
+                    return false;
+
+                final int head = _indexes[HEAD_OFFSET];
+                final int tail = _indexes[TAIL_OFFSET];
+                final int capacity = _elements.length;
+
+                int i = head;
+                while (true)
+                {
+                    if (Objects.equals(_elements[i],o))
+                    {
+                        remove(i >= head?i - head:capacity - head + i);
+                        return true;
+                    }
+                    ++i;
+                    if (i == capacity)
+                        i = 0;
+                    if (i == tail)
+                        return false;
+                }
+            }
+            finally
+            {
+                _headLock.unlock();
+            }
+        }
+        finally
+        {
+            _tailLock.unlock();
+        }
+    }
+
+    @Override
+    public int remainingCapacity()
+    {
+
+        _tailLock.lock();
+        try
+        {
+
+            _headLock.lock();
+            try
+            {
+                return getCapacity() - size();
+            }
+            finally
+            {
+                _headLock.unlock();
+            }
+        }
+        finally
+        {
+            _tailLock.unlock();
+        }
+    }
+
+    @Override
+    public int drainTo(Collection<? super E> c)
+    {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public int drainTo(Collection<? super E> c, int maxElements)
+    {
+        throw new UnsupportedOperationException();
+    }
+
+    /*----------------------------------------------------------------------------*/
+    /* List methods */
+    /*----------------------------------------------------------------------------*/
+
+    @SuppressWarnings("unchecked")
+    @Override
+    public E get(int index)
+    {
+
+        _tailLock.lock();
+        try
+        {
+
+            _headLock.lock();
+            try
+            {
+                if (index < 0 || index >= _size.get())
+                    throw new IndexOutOfBoundsException("!(" + 0 + "<" + index + "<=" + _size + ")");
+                int i = _indexes[HEAD_OFFSET] + index;
+                int capacity = _elements.length;
+                if (i >= capacity)
+                    i -= capacity;
+                return (E)_elements[i];
+            }
+            finally
+            {
+                _headLock.unlock();
+            }
+        }
+        finally
+        {
+            _tailLock.unlock();
+        }
+    }
+
+    @Override
+    public void add(int index, E e)
+    {
+        if (e == null)
+            throw new NullPointerException();
+
+        _tailLock.lock();
+        try
+        {
+
+            _headLock.lock();
+            try
+            {
+                final int size = _size.get();
+
+                if (index < 0 || index > size)
+                    throw new IndexOutOfBoundsException("!(" + 0 + "<" + index + "<=" + _size + ")");
+
+                if (index == size)
+                {
+                    add(e);
+                }
+                else
+                {
+                    if (_indexes[TAIL_OFFSET] == _indexes[HEAD_OFFSET])
+                        if (!grow())
+                            throw new IllegalStateException("full");
+
+                    // Re-read head and tail after a possible grow
+                    int i = _indexes[HEAD_OFFSET] + index;
+                    int capacity = _elements.length;
+
+                    if (i >= capacity)
+                        i -= capacity;
+
+                    _size.incrementAndGet();
+                    int tail = _indexes[TAIL_OFFSET];
+                    _indexes[TAIL_OFFSET] = tail = (tail + 1) % capacity;
+
+                    if (i < tail)
+                    {
+                        System.arraycopy(_elements,i,_elements,i + 1,tail - i);
+                        _elements[i] = e;
+                    }
+                    else
+                    {
+                        if (tail > 0)
+                        {
+                            System.arraycopy(_elements,0,_elements,1,tail);
+                            _elements[0] = _elements[capacity - 1];
+                        }
+
+                        System.arraycopy(_elements,i,_elements,i + 1,capacity - i - 1);
+                        _elements[i] = e;
+                    }
+                }
+            }
+            finally
+            {
+                _headLock.unlock();
+            }
+        }
+        finally
+        {
+            _tailLock.unlock();
+        }
+    }
+
+    @SuppressWarnings("unchecked")
+    @Override
+    public E set(int index, E e)
+    {
+        Objects.requireNonNull(e);
+
+        _tailLock.lock();
+        try
+        {
+
+            _headLock.lock();
+            try
+            {
+                if (index < 0 || index >= _size.get())
+                    throw new IndexOutOfBoundsException("!(" + 0 + "<" + index + "<=" + _size + ")");
+
+                int i = _indexes[HEAD_OFFSET] + index;
+                int capacity = _elements.length;
+                if (i >= capacity)
+                    i -= capacity;
+                E old = (E)_elements[i];
+                _elements[i] = e;
+                return old;
+            }
+            finally
+            {
+                _headLock.unlock();
+            }
+        }
+        finally
+        {
+            _tailLock.unlock();
+        }
+    }
+
+    @SuppressWarnings("unchecked")
+    @Override
+    public E remove(int index)
+    {
+
+        _tailLock.lock();
+        try
+        {
+
+            _headLock.lock();
+            try
+            {
+                if (index < 0 || index >= _size.get())
+                    throw new IndexOutOfBoundsException("!(" + 0 + "<" + index + "<=" + _size + ")");
+
+                int i = _indexes[HEAD_OFFSET] + index;
+                int capacity = _elements.length;
+                if (i >= capacity)
+                    i -= capacity;
+                E old = (E)_elements[i];
+
+                int tail = _indexes[TAIL_OFFSET];
+                if (i < tail)
+                {
+                    System.arraycopy(_elements,i + 1,_elements,i,tail - i);
+                    --_indexes[TAIL_OFFSET];
+                }
+                else
+                {
+                    System.arraycopy(_elements,i + 1,_elements,i,capacity - i - 1);
+                    _elements[capacity - 1] = _elements[0];
+                    if (tail > 0)
+                    {
+                        System.arraycopy(_elements,1,_elements,0,tail);
+                        --_indexes[TAIL_OFFSET];
+                    }
+                    else
+                    {
+                        _indexes[TAIL_OFFSET] = capacity - 1;
+                    }
+                    _elements[_indexes[TAIL_OFFSET]] = null;
+                }
+
+                _size.decrementAndGet();
+
+                return old;
+            }
+            finally
+            {
+                _headLock.unlock();
+            }
+        }
+        finally
+        {
+            _tailLock.unlock();
+        }
+    }
+
+    @Override
+    public ListIterator<E> listIterator(int index)
+    {
+
+        _tailLock.lock();
+        try
+        {
+
+            _headLock.lock();
+            try
+            {
+                Object[] elements = new Object[size()];
+                if (size() > 0)
+                {
+                    int head = _indexes[HEAD_OFFSET];
+                    int tail = _indexes[TAIL_OFFSET];
+                    if (head < tail)
+                    {
+                        System.arraycopy(_elements,head,elements,0,tail - head);
+                    }
+                    else
+                    {
+                        int chunk = _elements.length - head;
+                        System.arraycopy(_elements,head,elements,0,chunk);
+                        System.arraycopy(_elements,0,elements,chunk,tail);
+                    }
+                }
+                return new Itr(elements,index);
+            }
+            finally
+            {
+                _headLock.unlock();
+            }
+        }
+        finally
+        {
+            _tailLock.unlock();
+        }
+    }
+
+    /*----------------------------------------------------------------------------*/
+    /* Additional methods */
+    /*----------------------------------------------------------------------------*/
+
+    /**
+     * @return the current capacity of this queue
+     */
+    public int getCapacity()
+    {
+        _tailLock.lock();
+        try
+        {
+            return _elements.length;
+        }
+        finally
+        {
+            _tailLock.unlock();
+        }
+    }
+
+    /**
+     * @return the max capacity of this queue, or -1 if this queue is unbounded
+     */
+    public int getMaxCapacity()
+    {
+        return _maxCapacity;
+    }
+
+    /*----------------------------------------------------------------------------*/
+    /* Implementation methods */
+    /*----------------------------------------------------------------------------*/
+
+    private boolean grow()
+    {
+        if (_growCapacity <= 0)
+            return false;
+
+        _tailLock.lock();
+        try
+        {
+
+            _headLock.lock();
+            try
+            {
+                final int head = _indexes[HEAD_OFFSET];
+                final int tail = _indexes[TAIL_OFFSET];
+                final int newTail;
+                final int capacity = _elements.length;
+
+                Object[] elements = new Object[capacity + _growCapacity];
+
+                if (head < tail)
+                {
+                    newTail = tail - head;
+                    System.arraycopy(_elements,head,elements,0,newTail);
+                }
+                else if (head > tail || _size.get() > 0)
+                {
+                    newTail = capacity + tail - head;
+                    int cut = capacity - head;
+                    System.arraycopy(_elements,head,elements,0,cut);
+                    System.arraycopy(_elements,0,elements,cut,tail);
+                }
+                else
+                {
+                    newTail = 0;
+                }
+
+                _elements = elements;
+                _indexes[HEAD_OFFSET] = 0;
+                _indexes[TAIL_OFFSET] = newTail;
+                return true;
+            }
+            finally
+            {
+                _headLock.unlock();
+            }
+        }
+        finally
+        {
+            _tailLock.unlock();
+        }
+    }
+
+    private class Itr implements ListIterator<E>
+    {
+        private final Object[] _elements;
+        private int _cursor;
+
+        public Itr(Object[] elements, int offset)
+        {
+            _elements = elements;
+            _cursor = offset;
+        }
+
+        @Override
+        public boolean hasNext()
+        {
+            return _cursor < _elements.length;
+        }
+
+        @SuppressWarnings("unchecked")
+        @Override
+        public E next()
+        {
+            return (E)_elements[_cursor++];
+        }
+
+        @Override
+        public boolean hasPrevious()
+        {
+            return _cursor > 0;
+        }
+
+        @SuppressWarnings("unchecked")
+        @Override
+        public E previous()
+        {
+            return (E)_elements[--_cursor];
+        }
+
+        @Override
+        public int nextIndex()
+        {
+            return _cursor + 1;
+        }
+
+        @Override
+        public int previousIndex()
+        {
+            return _cursor - 1;
+        }
+
+        @Override
+        public void remove()
+        {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public void set(E e)
+        {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public void add(E e)
+        {
+            throw new UnsupportedOperationException();
+        }
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/util/BlockingCallback.java b/lib/jetty/org/eclipse/jetty/util/BlockingCallback.java
new file mode 100644 (file)
index 0000000..a3cf9da
--- /dev/null
@@ -0,0 +1,105 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util;
+
+import java.io.IOException;
+import java.io.InterruptedIOException;
+import java.util.concurrent.CancellationException;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.atomic.AtomicReference;
+
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.thread.NonBlockingThread;
+
+/* ------------------------------------------------------------ */
+/**
+ * An implementation of Callback that blocks until success or failure.
+ */
+public class BlockingCallback implements Callback
+{
+    private static final Logger LOG = Log.getLogger(BlockingCallback.class);
+    
+    private static Throwable SUCCEEDED=new Throwable()
+    {
+        @Override
+        public String toString() { return "SUCCEEDED"; }
+    };
+    
+    private final CountDownLatch _latch = new CountDownLatch(1);
+    private final AtomicReference<Throwable> _state = new AtomicReference<>();
+    
+    public BlockingCallback()
+    {}
+
+    @Override
+    public void succeeded()
+    {
+        if (_state.compareAndSet(null,SUCCEEDED))
+            _latch.countDown();
+    }
+
+    @Override
+    public void failed(Throwable cause)
+    {
+        if (_state.compareAndSet(null,cause))
+            _latch.countDown();
+    }
+
+    /** Block until the Callback has succeeded or failed and 
+     * after the return leave in the state to allow reuse.
+     * This is useful for code that wants to repeatable use a FutureCallback to convert
+     * an asynchronous API to a blocking API. 
+     * @throws IOException if exception was caught during blocking, or callback was cancelled 
+     */
+    public void block() throws IOException
+    {
+        if (NonBlockingThread.isNonBlockingThread())
+            LOG.warn("Blocking a NonBlockingThread: ",new Throwable());
+        
+        try
+        {
+            _latch.await();
+            Throwable state=_state.get();
+            if (state==SUCCEEDED)
+                return;
+            if (state instanceof IOException)
+                throw (IOException) state;
+            if (state instanceof CancellationException)
+                throw (CancellationException) state;
+            throw new IOException(state);
+        }
+        catch (final InterruptedException e)
+        {
+            throw new InterruptedIOException(){{initCause(e);}};
+        }
+        finally
+        {
+            _state.set(null);
+        }
+    }
+    
+    
+    @Override
+    public String toString()
+    {
+        return String.format("%s@%x{%s}",BlockingCallback.class.getSimpleName(),hashCode(),_state.get());
+    }
+
+}
diff --git a/lib/jetty/org/eclipse/jetty/util/BufferUtil.java b/lib/jetty/org/eclipse/jetty/util/BufferUtil.java
new file mode 100644 (file)
index 0000000..bd7cb06
--- /dev/null
@@ -0,0 +1,1045 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.RandomAccessFile;
+import java.nio.Buffer;
+import java.nio.BufferOverflowException;
+import java.nio.ByteBuffer;
+import java.nio.channels.FileChannel;
+import java.nio.channels.FileChannel.MapMode;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+
+import org.eclipse.jetty.util.resource.Resource;
+
+
+/* ------------------------------------------------------------------------------- */
+/**
+ * Buffer utility methods.
+ * <p>The standard JVM {@link ByteBuffer} can exist in two modes: In fill mode the valid
+ * data is between 0 and pos; In flush mode the valid data is between the pos and the limit.
+ * The various ByteBuffer methods assume a mode and some of them will switch or enforce a mode:
+ * Allocate and clear set fill mode; flip and compact switch modes; read and write assume fill 
+ * and flush modes.    This duality can result in confusing code such as:
+ * <pre>
+ *     buffer.clear();
+ *     channel.write(buffer);
+ * </pre>
+ * Which looks as if it should write no data, but in fact writes the buffer worth of garbage.
+ * </p>
+ * <p>
+ * The BufferUtil class provides a set of utilities that operate on the convention that ByteBuffers
+ * will always be left, passed in an API or returned from a method in the flush mode - ie with
+ * valid data between the pos and limit.    This convention is adopted so as to avoid confusion as to
+ * what state a buffer is in and to avoid excessive copying of data that can result with the usage 
+ * of compress.</p> 
+ * <p>
+ * Thus this class provides alternate implementations of {@link #allocate(int)}, 
+ * {@link #allocateDirect(int)} and {@link #clear(ByteBuffer)} that leave the buffer
+ * in flush mode.   Thus the following tests will pass:<pre>
+ *     ByteBuffer buffer = BufferUtil.allocate(1024);
+ *     assert(buffer.remaining()==0);
+ *     BufferUtil.clear(buffer);
+ *     assert(buffer.remaining()==0);
+ * </pre>
+ * </p>
+ * <p>If the BufferUtil methods {@link #fill(ByteBuffer, byte[], int, int)}, 
+ * {@link #append(ByteBuffer, byte[], int, int)} or {@link #put(ByteBuffer, ByteBuffer)} are used,
+ * then the caller does not need to explicitly switch the buffer to fill mode.    
+ * If the caller wishes to use other ByteBuffer bases libraries to fill a buffer, 
+ * then they can use explicit calls of #flipToFill(ByteBuffer) and #flipToFlush(ByteBuffer, int)
+ * to change modes.  Note because this convention attempts to avoid the copies of compact, the position
+ * is not set to zero on each fill cycle and so its value must be remembered:
+ * <pre>
+ *      int pos = BufferUtil.flipToFill(buffer);
+ *      try
+ *      {
+ *          buffer.put(data);
+ *      }
+ *      finally
+ *      {
+ *          flipToFlush(buffer, pos);
+ *      }
+ * </pre>
+ * The flipToFill method will effectively clear the buffer if it is emtpy and will compact the buffer if there is no space.
+ * 
+ */
+public class BufferUtil
+{
+    static final int TEMP_BUFFER_SIZE = 4096;
+    static final byte SPACE = 0x20;
+    static final byte MINUS = '-';
+    static final byte[] DIGIT =
+            {(byte)'0', (byte)'1', (byte)'2', (byte)'3', (byte)'4', (byte)'5', (byte)'6', (byte)'7', (byte)'8', (byte)'9', (byte)'A', (byte)'B', (byte)'C', (byte)'D',
+                    (byte)'E', (byte)'F'};
+
+    public static final ByteBuffer EMPTY_BUFFER = ByteBuffer.wrap(new byte[0]);
+
+    /* ------------------------------------------------------------ */
+    /** Allocate ByteBuffer in flush mode.
+     * The position and limit will both be zero, indicating that the buffer is
+     * empty and must be flipped before any data is put to it.
+     * @param capacity capacity of the allocated ByteBuffer
+     * @return Buffer
+     */
+    public static ByteBuffer allocate(int capacity)
+    {
+        ByteBuffer buf = ByteBuffer.allocate(capacity);
+        buf.limit(0);
+        return buf;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Allocate ByteBuffer in flush mode.
+     * The position and limit will both be zero, indicating that the buffer is
+     * empty and in flush mode.
+     * @param capacity capacity of the allocated ByteBuffer
+     * @return Buffer
+     */
+    public static ByteBuffer allocateDirect(int capacity)
+    {
+        ByteBuffer buf = ByteBuffer.allocateDirect(capacity);
+        buf.limit(0);
+        return buf;
+    }
+
+
+    /* ------------------------------------------------------------ */
+    /** Clear the buffer to be empty in flush mode.
+     * The position and limit are set to 0;
+     * @param buffer The buffer to clear.
+     */
+    public static void clear(ByteBuffer buffer)
+    {
+        if (buffer != null)
+        {
+            buffer.position(0);
+            buffer.limit(0);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Clear the buffer to be empty in fill mode.
+     * The position is set to 0 and the limit is set to the capacity.
+     * @param buffer The buffer to clear.
+     */
+    public static void clearToFill(ByteBuffer buffer)
+    {
+        if (buffer != null)
+        {
+            buffer.position(0);
+            buffer.limit(buffer.capacity());
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Flip the buffer to fill mode.
+     * The position is set to the first unused position in the buffer
+     * (the old limit) and the limit is set to the capacity.
+     * If the buffer is empty, then this call is effectively {@link #clearToFill(ByteBuffer)}.
+     * If there is no unused space to fill, a {@link ByteBuffer#compact()} is done to attempt
+     * to create space.
+     * <p>
+     * This method is used as a replacement to {@link ByteBuffer#compact()}.
+     *
+     * @param buffer The buffer to flip
+     * @return The position of the valid data before the flipped position. This value should be
+     * passed to a subsequent call to {@link #flipToFlush(ByteBuffer, int)}
+     */
+    public static int flipToFill(ByteBuffer buffer)
+    {
+        int position = buffer.position();
+        int limit = buffer.limit();
+        if (position == limit)
+        {
+            buffer.position(0);
+            buffer.limit(buffer.capacity());
+            return 0;
+        }
+
+        int capacity = buffer.capacity();
+        if (limit == capacity)
+        {
+            buffer.compact();
+            return 0;
+        }
+
+        buffer.position(limit);
+        buffer.limit(capacity);
+        return position;
+    }
+
+
+    /* ------------------------------------------------------------ */
+    /** Flip the buffer to Flush mode.
+     * The limit is set to the first unused byte(the old position) and
+     * the position is set to the passed position.
+     * <p>
+     * This method is used as a replacement of {@link Buffer#flip()}.
+     * @param buffer   the buffer to be flipped
+     * @param position The position of valid data to flip to. This should
+     * be the return value of the previous call to {@link #flipToFill(ByteBuffer)}
+     */
+    public static void flipToFlush(ByteBuffer buffer, int position)
+    {
+        buffer.limit(buffer.position());
+        buffer.position(position);
+    }
+
+
+    /* ------------------------------------------------------------ */
+    /** Convert a ByteBuffer to a byte array.
+     * @param buffer The buffer to convert in flush mode. The buffer is not altered.
+     * @return An array of bytes duplicated from the buffer.
+     */
+    public static byte[] toArray(ByteBuffer buffer)
+    {
+        if (buffer.hasArray())
+        {
+            byte[] array = buffer.array();
+            int from=buffer.arrayOffset() + buffer.position();
+            return Arrays.copyOfRange(array,from,from+buffer.remaining());
+        }
+        else
+        {
+            byte[] to = new byte[buffer.remaining()];
+            buffer.slice().get(to);
+            return to;
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Check for an empty or null buffer.
+     * @param buf the buffer to check
+     * @return true if the buffer is null or empty.
+     */
+    public static boolean isEmpty(ByteBuffer buf)
+    {
+        return buf == null || buf.remaining() == 0;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Check for a non null and non empty buffer.
+     * @param buf the buffer to check
+     * @return true if the buffer is not null and not empty.
+     */
+    public static boolean hasContent(ByteBuffer buf)
+    {
+        return buf != null && buf.remaining() > 0;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Check for a non null and full buffer.
+     * @param buf the buffer to check
+     * @return true if the buffer is not null and the limit equals the capacity.
+     */
+    public static boolean isFull(ByteBuffer buf)
+    {
+        return buf != null && buf.limit() == buf.capacity();
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Get remaining from null checked buffer
+     * @param buffer The buffer to get the remaining from, in flush mode.
+     * @return 0 if the buffer is null, else the bytes remaining in the buffer.
+     */
+    public static int length(ByteBuffer buffer)
+    {
+        return buffer == null ? 0 : buffer.remaining();
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Get the space from the limit to the capacity
+     * @param buffer the buffer to get the space from
+     * @return space
+     */
+    public static int space(ByteBuffer buffer)
+    {
+        if (buffer == null)
+            return 0;
+        return buffer.capacity() - buffer.limit();
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Compact the buffer
+     * @param buffer the buffer to compact
+     * @return true if the compact made a full buffer have space
+     */
+    public static boolean compact(ByteBuffer buffer)
+    {
+        if (buffer.position()==0)
+            return false;
+        boolean full = buffer.limit() == buffer.capacity();
+        buffer.compact().flip();
+        return full && buffer.limit() < buffer.capacity();
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Put data from one buffer into another, avoiding over/under flows
+     * @param from Buffer to take bytes from in flush mode
+     * @param to   Buffer to put bytes to in fill mode.
+     * @return number of bytes moved
+     */
+    public static int put(ByteBuffer from, ByteBuffer to)
+    {
+        int put;
+        int remaining = from.remaining();
+        if (remaining > 0)
+        {
+            if (remaining <= to.remaining())
+            {
+                to.put(from);
+                put = remaining;
+                from.position(0);
+                from.limit(0);
+            }
+            else if (from.hasArray())
+            {
+                put = to.remaining();
+                to.put(from.array(), from.arrayOffset() + from.position(), put);
+                from.position(from.position() + put);
+            }
+            else
+            {
+                put = to.remaining();
+                ByteBuffer slice = from.slice();
+                slice.limit(put);
+                to.put(slice);
+                from.position(from.position() + put);
+            }
+        }
+        else
+            put = 0;
+
+        return put;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Put data from one buffer into another, avoiding over/under flows
+     * @param from Buffer to take bytes from in flush mode
+     * @param to   Buffer to put bytes to in flush mode. The buffer is flipToFill before the put and flipToFlush after.
+     * @return number of bytes moved
+     * @deprecated use {@link #append(ByteBuffer, ByteBuffer)}
+     */
+    public static int flipPutFlip(ByteBuffer from, ByteBuffer to)
+    {
+        return append(to,from);
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Append bytes to a buffer.
+     * @param to Buffer is flush mode
+     * @param b bytes to append
+     * @param off offset into byte
+     * @param len length to append
+     * @throws BufferOverflowException
+     */
+    public static void append(ByteBuffer to, byte[] b, int off, int len) throws BufferOverflowException
+    {
+        int pos = flipToFill(to);
+        try
+        {
+            to.put(b, off, len);
+        }
+        finally
+        {
+            flipToFlush(to, pos);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Appends a byte to a buffer
+     * @param to Buffer is flush mode
+     * @param b byte to append
+     */
+    public static void append(ByteBuffer to, byte b)
+    {
+        int pos = flipToFill(to);
+        try
+        {
+            to.put(b);
+        }
+        finally
+        {
+            flipToFlush(to, pos);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Appends a byte to a buffer
+     * @param to Buffer is flush mode
+     * @param b bytes to append
+     */
+    public static int append(ByteBuffer to, ByteBuffer b)
+    {
+        int pos = flipToFill(to);
+        try
+        {
+            return put(b, to);
+        }
+        finally
+        {
+            flipToFlush(to, pos);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Like append, but does not throw {@link BufferOverflowException}
+     * @param to Buffer is flush mode
+     * @param b bytes to fill
+     * @param off offset into byte
+     * @param len length to fill
+     */
+    public static int fill(ByteBuffer to, byte[] b, int off, int len)
+    {
+        int pos = flipToFill(to);
+        try
+        {
+            int remaining = to.remaining();
+            int take = remaining < len ? remaining : len;
+            to.put(b, off, take);
+            return take;
+        }
+        finally
+        {
+            flipToFlush(to, pos);
+        }
+    }
+
+
+    /* ------------------------------------------------------------ */
+    public static void readFrom(File file, ByteBuffer buffer) throws IOException
+    {
+        try(RandomAccessFile raf = new RandomAccessFile(file,"r"))
+        {
+            FileChannel channel = raf.getChannel();
+            long needed=raf.length();
+
+            while (needed>0 && buffer.hasRemaining())
+                needed=needed-channel.read(buffer);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    public static void readFrom(InputStream is, int needed, ByteBuffer buffer) throws IOException
+    {
+        ByteBuffer tmp = allocate(8192);
+
+        while (needed > 0 && buffer.hasRemaining())
+        {
+            int l = is.read(tmp.array(), 0, 8192);
+            if (l < 0)
+                break;
+            tmp.position(0);
+            tmp.limit(l);
+            buffer.put(tmp);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    public static void writeTo(ByteBuffer buffer, OutputStream out) throws IOException
+    {
+        if (buffer.hasArray())
+            out.write(buffer.array(), buffer.arrayOffset() + buffer.position(), buffer.remaining());
+        else
+        {
+            byte[] bytes = new byte[TEMP_BUFFER_SIZE];
+            while(buffer.hasRemaining()){
+                int byteCountToWrite = Math.min(buffer.remaining(), TEMP_BUFFER_SIZE);
+                buffer.get(bytes, 0, byteCountToWrite);
+                out.write(bytes,0 , byteCountToWrite);
+            }
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Convert the buffer to an ISO-8859-1 String
+     * @param buffer The buffer to convert in flush mode. The buffer is unchanged
+     * @return The buffer as a string.
+     */
+    public static String toString(ByteBuffer buffer)
+    {
+        return toString(buffer, StandardCharsets.ISO_8859_1);
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Convert the buffer to an UTF-8 String
+     * @param buffer The buffer to convert in flush mode. The buffer is unchanged
+     * @return The buffer as a string.
+     */
+    public static String toUTF8String(ByteBuffer buffer)
+    {
+        return toString(buffer, StandardCharsets.UTF_8);
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Convert the buffer to an ISO-8859-1 String
+     * @param buffer  The buffer to convert in flush mode. The buffer is unchanged
+     * @param charset The {@link Charset} to use to convert the bytes
+     * @return The buffer as a string.
+     */
+    public static String toString(ByteBuffer buffer, Charset charset)
+    {
+        if (buffer == null)
+            return null;
+        byte[] array = buffer.hasArray() ? buffer.array() : null;
+        if (array == null)
+        {
+            byte[] to = new byte[buffer.remaining()];
+            buffer.slice().get(to);
+            return new String(to, 0, to.length, charset);
+        }
+        return new String(array, buffer.arrayOffset() + buffer.position(), buffer.remaining(), charset);
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Convert a partial buffer to an ISO-8859-1 String
+     * @param buffer  The buffer to convert in flush mode. The buffer is unchanged
+     * @param charset The {@link Charset} to use to convert the bytes
+     * @return The buffer as a string.
+     */
+    public static String toString(ByteBuffer buffer, int position, int length, Charset charset)
+    {
+        if (buffer == null)
+            return null;
+        byte[] array = buffer.hasArray() ? buffer.array() : null;
+        if (array == null)
+        {
+            ByteBuffer ro = buffer.asReadOnlyBuffer();
+            ro.position(position);
+            ro.limit(position + length);
+            byte[] to = new byte[length];
+            ro.get(to);
+            return new String(to, 0, to.length, charset);
+        }
+        return new String(array, buffer.arrayOffset() + position, length, charset);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Convert buffer to an integer. Parses up to the first non-numeric character. If no number is found an IllegalArgumentException is thrown
+     *
+     * @param buffer
+     *            A buffer containing an integer in flush mode. The position is not changed.
+     * @return an int
+     */
+    public static int toInt(ByteBuffer buffer)
+    {
+        int val = 0;
+        boolean started = false;
+        boolean minus = false;
+
+        for (int i = buffer.position(); i < buffer.limit(); i++)
+        {
+            byte b = buffer.get(i);
+            if (b <= SPACE)
+            {
+                if (started)
+                    break;
+            }
+            else if (b >= '0' && b <= '9')
+            {
+                val = val * 10 + (b - '0');
+                started = true;
+            }
+            else if (b == MINUS && !started)
+            {
+                minus = true;
+            }
+            else
+                break;
+        }
+
+        if (started)
+            return minus ? (-val) : val;
+        throw new NumberFormatException(toString(buffer));
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * Convert buffer to an integer. Parses up to the first non-numeric character. If no number is found an IllegalArgumentException is thrown
+     *
+     * @param buffer
+     *            A buffer containing an integer in flush mode. The position is updated.
+     * @return an int
+     */
+    public static int takeInt(ByteBuffer buffer)
+    {
+        int val = 0;
+        boolean started = false;
+        boolean minus = false;
+        int i;
+        for (i = buffer.position(); i < buffer.limit(); i++)
+        {
+            byte b = buffer.get(i);
+            if (b <= SPACE)
+            {
+                if (started)
+                    break;
+            }
+            else if (b >= '0' && b <= '9')
+            {
+                val = val * 10 + (b - '0');
+                started = true;
+            }
+            else if (b == MINUS && !started)
+            {
+                minus = true;
+            }
+            else
+                break;
+        }
+
+        if (started)
+        {
+            buffer.position(i);
+            return minus ? (-val) : val;
+        }
+        throw new NumberFormatException(toString(buffer));
+    }
+
+    /**
+     * Convert buffer to an long. Parses up to the first non-numeric character. If no number is found an IllegalArgumentException is thrown
+     *
+     * @param buffer
+     *            A buffer containing an integer in flush mode. The position is not changed.
+     * @return an int
+     */
+    public static long toLong(ByteBuffer buffer)
+    {
+        long val = 0;
+        boolean started = false;
+        boolean minus = false;
+
+        for (int i = buffer.position(); i < buffer.limit(); i++)
+        {
+            byte b = buffer.get(i);
+            if (b <= SPACE)
+            {
+                if (started)
+                    break;
+            }
+            else if (b >= '0' && b <= '9')
+            {
+                val = val * 10L + (b - '0');
+                started = true;
+            }
+            else if (b == MINUS && !started)
+            {
+                minus = true;
+            }
+            else
+                break;
+        }
+
+        if (started)
+            return minus ? (-val) : val;
+        throw new NumberFormatException(toString(buffer));
+    }
+
+    public static void putHexInt(ByteBuffer buffer, int n)
+    {
+        if (n < 0)
+        {
+            buffer.put((byte)'-');
+
+            if (n == Integer.MIN_VALUE)
+            {
+                buffer.put((byte)(0x7f & '8'));
+                buffer.put((byte)(0x7f & '0'));
+                buffer.put((byte)(0x7f & '0'));
+                buffer.put((byte)(0x7f & '0'));
+                buffer.put((byte)(0x7f & '0'));
+                buffer.put((byte)(0x7f & '0'));
+                buffer.put((byte)(0x7f & '0'));
+                buffer.put((byte)(0x7f & '0'));
+
+                return;
+            }
+            n = -n;
+        }
+
+        if (n < 0x10)
+        {
+            buffer.put(DIGIT[n]);
+        }
+        else
+        {
+            boolean started = false;
+            // This assumes constant time int arithmatic
+            for (int hexDivisor : hexDivisors)
+            {
+                if (n < hexDivisor)
+                {
+                    if (started)
+                        buffer.put((byte)'0');
+                    continue;
+                }
+
+                started = true;
+                int d = n / hexDivisor;
+                buffer.put(DIGIT[d]);
+                n = n - d * hexDivisor;
+            }
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    public static void putDecInt(ByteBuffer buffer, int n)
+    {
+        if (n < 0)
+        {
+            buffer.put((byte)'-');
+
+            if (n == Integer.MIN_VALUE)
+            {
+                buffer.put((byte)'2');
+                n = 147483648;
+            }
+            else
+                n = -n;
+        }
+
+        if (n < 10)
+        {
+            buffer.put(DIGIT[n]);
+        }
+        else
+        {
+            boolean started = false;
+            // This assumes constant time int arithmatic
+            for (int decDivisor : decDivisors)
+            {
+                if (n < decDivisor)
+                {
+                    if (started)
+                        buffer.put((byte)'0');
+                    continue;
+                }
+
+                started = true;
+                int d = n / decDivisor;
+                buffer.put(DIGIT[d]);
+                n = n - d * decDivisor;
+            }
+        }
+    }
+
+    public static void putDecLong(ByteBuffer buffer, long n)
+    {
+        if (n < 0)
+        {
+            buffer.put((byte)'-');
+
+            if (n == Long.MIN_VALUE)
+            {
+                buffer.put((byte)'9');
+                n = 223372036854775808L;
+            }
+            else
+                n = -n;
+        }
+
+        if (n < 10)
+        {
+            buffer.put(DIGIT[(int)n]);
+        }
+        else
+        {
+            boolean started = false;
+            // This assumes constant time int arithmatic
+            for (long aDecDivisorsL : decDivisorsL)
+            {
+                if (n < aDecDivisorsL)
+                {
+                    if (started)
+                        buffer.put((byte)'0');
+                    continue;
+                }
+
+                started = true;
+                long d = n / aDecDivisorsL;
+                buffer.put(DIGIT[(int)d]);
+                n = n - d * aDecDivisorsL;
+            }
+        }
+    }
+
+    public static ByteBuffer toBuffer(int value)
+    {
+        ByteBuffer buf = ByteBuffer.allocate(32);
+        putDecInt(buf, value);
+        return buf;
+    }
+
+    public static ByteBuffer toBuffer(long value)
+    {
+        ByteBuffer buf = ByteBuffer.allocate(32);
+        putDecLong(buf, value);
+        return buf;
+    }
+
+    public static ByteBuffer toBuffer(String s)
+    {
+        return ByteBuffer.wrap(s.getBytes(StandardCharsets.ISO_8859_1));
+    }
+
+    public static ByteBuffer toDirectBuffer(String s)
+    {
+        byte[] bytes = s.getBytes(StandardCharsets.ISO_8859_1);
+        ByteBuffer buf = ByteBuffer.allocateDirect(bytes.length);
+        buf.put(bytes);
+        buf.flip();
+        return buf;
+    }
+
+    public static ByteBuffer toBuffer(String s, Charset charset)
+    {
+        return ByteBuffer.wrap(s.getBytes(charset));
+    }
+
+    public static ByteBuffer toDirectBuffer(String s, Charset charset)
+    {
+        byte[] bytes = s.getBytes(charset);
+        ByteBuffer buf = ByteBuffer.allocateDirect(bytes.length);
+        buf.put(bytes);
+        buf.flip();
+        return buf;
+    }
+
+    /**
+     * Create a new ByteBuffer using provided byte array.
+     *
+     * @param array
+     *            the byte array to back buffer with.
+     * @return ByteBuffer with provided byte array, in flush mode
+     */
+    public static ByteBuffer toBuffer(byte array[])
+    {
+        return ByteBuffer.wrap(array);
+    }
+
+    /**
+     * Create a new ByteBuffer using the provided byte array.
+     *
+     * @param array
+     *            the byte array to use.
+     * @param offset
+     *            the offset within the byte array to use from
+     * @param length
+     *            the length in bytes of the array to use
+     * @return ByteBuffer with provided byte array, in flush mode
+     */
+    public static ByteBuffer toBuffer(byte array[], int offset, int length)
+    {
+        return ByteBuffer.wrap(array, offset, length);
+    }
+
+    public static ByteBuffer toMappedBuffer(File file) throws IOException
+    {
+        try (RandomAccessFile raf = new RandomAccessFile(file, "r"))
+        {
+            return raf.getChannel().map(MapMode.READ_ONLY, 0, raf.length());
+        }
+    }
+
+    public static ByteBuffer toBuffer(Resource resource,boolean direct) throws IOException
+    {
+        int len=(int)resource.length();
+        if (len<0)
+            throw new IllegalArgumentException("invalid resource: "+String.valueOf(resource)+" len="+len);
+        
+        ByteBuffer buffer = direct?BufferUtil.allocateDirect(len):BufferUtil.allocate(len);
+
+        int pos=BufferUtil.flipToFill(buffer);
+        if (resource.getFile()!=null)
+            BufferUtil.readFrom(resource.getFile(),buffer);
+        else
+        {
+            try (InputStream is = resource.getInputStream();)
+            {
+                BufferUtil.readFrom(is,len,buffer);
+            }
+        }
+        BufferUtil.flipToFlush(buffer,pos);
+        
+        return buffer;
+    }
+
+    public static String toSummaryString(ByteBuffer buffer)
+    {
+        if (buffer == null)
+            return "null";
+        StringBuilder buf = new StringBuilder();
+        buf.append("[p=");
+        buf.append(buffer.position());
+        buf.append(",l=");
+        buf.append(buffer.limit());
+        buf.append(",c=");
+        buf.append(buffer.capacity());
+        buf.append(",r=");
+        buf.append(buffer.remaining());
+        buf.append("]");
+        return buf.toString();
+    }
+
+    public static String toDetailString(ByteBuffer[] buffer)
+    {
+        StringBuilder builder = new StringBuilder();
+        builder.append('[');
+        for (int i = 0; i < buffer.length; i++)
+        {
+            if (i > 0) builder.append(',');
+            builder.append(toDetailString(buffer[i]));
+        }
+        builder.append(']');
+        return builder.toString();
+    }
+
+    public static String toDetailString(ByteBuffer buffer)
+    {
+        if (buffer == null)
+            return "null";
+
+        StringBuilder buf = new StringBuilder();
+        buf.append(buffer.getClass().getSimpleName());
+        buf.append("@");
+        if (buffer.hasArray())
+            buf.append(Integer.toHexString(((Object)buffer.array()).hashCode()));
+        else
+            buf.append(Integer.toHexString(buf.hashCode()));
+        buf.append("[p=");
+        buf.append(buffer.position());
+        buf.append(",l=");
+        buf.append(buffer.limit());
+        buf.append(",c=");
+        buf.append(buffer.capacity());
+        buf.append(",r=");
+        buf.append(buffer.remaining());
+        buf.append("]={");
+
+        for (int i = 0; i < buffer.position(); i++)
+        {
+            char c = (char)buffer.get(i);
+            if (c >= ' ' && c <= 127)
+                buf.append(c);
+            else if (c == '\r' || c == '\n')
+                buf.append('|');
+            else
+                buf.append('\ufffd');
+            if (i == 16 && buffer.position() > 32)
+            {
+                buf.append("...");
+                i = buffer.position() - 16;
+            }
+        }
+        buf.append("<<<");
+        for (int i = buffer.position(); i < buffer.limit(); i++)
+        {
+            char c = (char)buffer.get(i);
+            if (c >= ' ' && c <= 127)
+                buf.append(c);
+            else if (c == '\r' || c == '\n')
+                buf.append('|');
+            else
+                buf.append('\ufffd');
+            if (i == buffer.position() + 16 && buffer.limit() > buffer.position() + 32)
+            {
+                buf.append("...");
+                i = buffer.limit() - 16;
+            }
+        }
+        buf.append(">>>");
+        int limit = buffer.limit();
+        buffer.limit(buffer.capacity());
+        for (int i = limit; i < buffer.capacity(); i++)
+        {
+            char c = (char)buffer.get(i);
+            if (c >= ' ' && c <= 127)
+                buf.append(c);
+            else if (c == '\r' || c == '\n')
+                buf.append('|');
+            else
+                buf.append('\ufffd');
+            if (i == limit + 16 && buffer.capacity() > limit + 32)
+            {
+                buf.append("...");
+                i = buffer.capacity() - 16;
+            }
+        }
+        buffer.limit(limit);
+        buf.append("}");
+
+        return buf.toString();
+    }
+
+
+    private final static int[] decDivisors =
+            {1000000000, 100000000, 10000000, 1000000, 100000, 10000, 1000, 100, 10, 1};
+
+    private final static int[] hexDivisors =
+            {0x10000000, 0x1000000, 0x100000, 0x10000, 0x1000, 0x100, 0x10, 0x1};
+
+    private final static long[] decDivisorsL =
+            {1000000000000000000L, 100000000000000000L, 10000000000000000L, 1000000000000000L, 100000000000000L, 10000000000000L, 1000000000000L, 100000000000L,
+                    10000000000L, 1000000000L, 100000000L, 10000000L, 1000000L, 100000L, 10000L, 1000L, 100L, 10L, 1L};
+
+    public static void putCRLF(ByteBuffer buffer)
+    {
+        buffer.put((byte)13);
+        buffer.put((byte)10);
+    }
+
+    public static boolean isPrefix(ByteBuffer prefix, ByteBuffer buffer)
+    {
+        if (prefix.remaining() > buffer.remaining())
+            return false;
+        int bi = buffer.position();
+        for (int i = prefix.position(); i < prefix.limit(); i++)
+            if (prefix.get(i) != buffer.get(bi++))
+                return false;
+        return true;
+    }
+
+    public static ByteBuffer ensureCapacity(ByteBuffer buffer, int capacity)
+    {
+        if (buffer==null)
+            return allocate(capacity);
+        
+        if (buffer.capacity()>=capacity)
+            return buffer;
+        
+        if (buffer.hasArray())
+            return ByteBuffer.wrap(Arrays.copyOfRange(buffer.array(), buffer.arrayOffset(), buffer.arrayOffset()+capacity),buffer.position(),buffer.remaining());
+        
+        throw new UnsupportedOperationException();
+    }
+
+
+
+}
diff --git a/lib/jetty/org/eclipse/jetty/util/ByteArrayISO8859Writer.java b/lib/jetty/org/eclipse/jetty/util/ByteArrayISO8859Writer.java
new file mode 100644 (file)
index 0000000..80df881
--- /dev/null
@@ -0,0 +1,269 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.Writer;
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+
+
+/* ------------------------------------------------------------ */
+/** Byte Array ISO 8859 writer. 
+ * This class combines the features of a OutputStreamWriter for
+ * ISO8859 encoding with that of a ByteArrayOutputStream.  It avoids
+ * many inefficiencies associated with these standard library classes.
+ * It has been optimized for standard ASCII characters.
+ * 
+ * 
+ */
+public class ByteArrayISO8859Writer extends Writer
+{
+    private byte[] _buf;
+    private int _size;
+    private ByteArrayOutputStream2 _bout=null;
+    private OutputStreamWriter _writer=null;
+    private boolean _fixed=false;
+
+    /* ------------------------------------------------------------ */
+    /** Constructor. 
+     */
+    public ByteArrayISO8859Writer()
+    {
+        _buf=new byte[2048];
+    } 
+    
+    /* ------------------------------------------------------------ */
+    /** Constructor. 
+     * @param capacity Buffer capacity
+     */
+    public ByteArrayISO8859Writer(int capacity)
+    {
+        _buf=new byte[capacity];
+    }
+    
+    /* ------------------------------------------------------------ */
+    public ByteArrayISO8859Writer(byte[] buf)
+    {
+        _buf=buf;
+        _fixed=true;
+    }
+
+    /* ------------------------------------------------------------ */
+    public Object getLock()
+    {
+        return lock;
+    }
+    
+    /* ------------------------------------------------------------ */
+    public int size()
+    {
+        return _size;
+    }
+    
+    /* ------------------------------------------------------------ */
+    public int capacity()
+    {
+        return _buf.length;
+    }
+
+    /* ------------------------------------------------------------ */
+    public int spareCapacity()
+    {
+        return _buf.length-_size;
+    }
+    
+    /* ------------------------------------------------------------ */
+    public void setLength(int l)
+    {
+        _size=l;
+    }
+
+    /* ------------------------------------------------------------ */
+    public byte[] getBuf()
+    {
+        return _buf;
+    }
+    
+    /* ------------------------------------------------------------ */
+    public void writeTo(OutputStream out)
+        throws IOException
+    {
+        out.write(_buf,0,_size);
+    }
+
+    /* ------------------------------------------------------------ */
+    public void write(char c)
+        throws IOException
+    {
+        ensureSpareCapacity(1);
+        if (c>=0&&c<=0x7f)
+            _buf[_size++]=(byte)c;
+        else
+        {
+            char[] ca ={c};
+            writeEncoded(ca,0,1);
+        }
+    }
+    
+    /* ------------------------------------------------------------ */
+    @Override
+    public void write(char[] ca)
+        throws IOException
+    {
+        ensureSpareCapacity(ca.length);
+        for (int i=0;i<ca.length;i++)
+        {
+            char c=ca[i];
+            if (c>=0&&c<=0x7f)
+                _buf[_size++]=(byte)c;
+            else
+            {
+                writeEncoded(ca,i,ca.length-i);
+                break;
+            }
+        }
+    }
+    
+    /* ------------------------------------------------------------ */
+    @Override
+    public void write(char[] ca,int offset, int length)
+        throws IOException
+    {
+        ensureSpareCapacity(length);
+        for (int i=0;i<length;i++)
+        {
+            char c=ca[offset+i];
+            if (c>=0&&c<=0x7f)
+                _buf[_size++]=(byte)c;
+            else
+            {
+                writeEncoded(ca,offset+i,length-i);
+                break;
+            }
+        }
+    }
+    
+    /* ------------------------------------------------------------ */
+    @Override
+    public void write(String s)
+        throws IOException
+    {
+        if (s==null)
+        {
+            write("null",0,4);
+            return;
+        }
+        
+        int length=s.length();
+        ensureSpareCapacity(length);
+        for (int i=0;i<length;i++)
+        {
+            char c=s.charAt(i);
+            if (c>=0x0&&c<=0x7f)
+                _buf[_size++]=(byte)c;
+            else
+            {
+                writeEncoded(s.toCharArray(),i,length-i);
+                break;
+            }
+        }
+    }
+    
+    /* ------------------------------------------------------------ */
+    @Override
+    public void write(String s,int offset, int length)
+        throws IOException
+    {
+        ensureSpareCapacity(length);
+        for (int i=0;i<length;i++)
+        {
+            char c=s.charAt(offset+i);
+            if (c>=0&&c<=0x7f)
+                _buf[_size++]=(byte)c;
+            else
+            {
+                writeEncoded(s.toCharArray(),offset+i,length-i);
+                break;
+            }
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    private void writeEncoded(char[] ca,int offset, int length)
+        throws IOException
+    {
+        if (_bout==null)
+        {
+            _bout = new ByteArrayOutputStream2(2*length);
+            _writer = new OutputStreamWriter(_bout,StandardCharsets.ISO_8859_1);
+        }
+        else
+            _bout.reset();
+        _writer.write(ca,offset,length);
+        _writer.flush();
+        ensureSpareCapacity(_bout.getCount());
+        System.arraycopy(_bout.getBuf(),0,_buf,_size,_bout.getCount());
+        _size+=_bout.getCount();
+    }
+    
+    /* ------------------------------------------------------------ */
+    @Override
+    public void flush()
+    {}
+
+    /* ------------------------------------------------------------ */
+    public void resetWriter()
+    {
+        _size=0;
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public void close()
+    {}
+
+    /* ------------------------------------------------------------ */
+    public void destroy()
+    {
+        _buf=null;
+    }
+    
+    /* ------------------------------------------------------------ */
+    public void ensureSpareCapacity(int n)
+        throws IOException
+    {
+        if (_size+n>_buf.length)
+        {
+            if (_fixed)
+                throw new IOException("Buffer overflow: "+_buf.length);
+            _buf=Arrays.copyOf(_buf,(_buf.length+n)*4/3);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    public byte[] getByteArray()
+    {
+        return Arrays.copyOf(_buf,_size);
+    }
+    
+}
+    
+    
diff --git a/lib/jetty/org/eclipse/jetty/util/ByteArrayOutputStream2.java b/lib/jetty/org/eclipse/jetty/util/ByteArrayOutputStream2.java
new file mode 100644 (file)
index 0000000..bfde018
--- /dev/null
@@ -0,0 +1,54 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util;
+import java.io.ByteArrayOutputStream;
+import java.nio.charset.Charset;
+
+/* ------------------------------------------------------------ */
+/** ByteArrayOutputStream with public internals
+
+ * 
+ */
+public class ByteArrayOutputStream2 extends ByteArrayOutputStream
+{
+    public ByteArrayOutputStream2(){super();}
+    public ByteArrayOutputStream2(int size){super(size);}
+    public byte[] getBuf(){return buf;}
+    public int getCount(){return count;}
+    public void setCount(int count){this.count = count;}
+
+    public void reset(int minSize)
+    {
+        reset();
+        if (buf.length<minSize)
+        {
+            buf=new byte[minSize];
+        }
+    }
+    
+    public void writeUnchecked(int b)
+    {
+        buf[count++]=(byte)b;
+    }
+    
+    public String toString(Charset charset)
+    {
+        return new String(buf, 0, count, charset);
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/util/Callback.java b/lib/jetty/org/eclipse/jetty/util/Callback.java
new file mode 100644 (file)
index 0000000..973d941
--- /dev/null
@@ -0,0 +1,79 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+/*
+ * Copyright (c) 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.eclipse.jetty.util;
+
+/**
+ * <p>A callback abstraction that handles completed/failed events of asynchronous operations.</p>
+ *
+ * <p>Semantically this is equivalent to an optimise Promise&lt;Void&gt;, but callback is a more meaningful 
+ * name than EmptyPromise</p>
+ */
+public interface Callback
+{
+    /**
+     * <p>Callback invoked when the operation completes.</p>
+     *
+     * @see #failed(Throwable)
+     */
+    public abstract void succeeded();
+
+    /**
+     * <p>Callback invoked when the operation fails.</p>
+     * @param x the reason for the operation failure
+     */
+    public void failed(Throwable x);
+
+    /**
+     * <p>Empty implementation of {@link Callback}</p>
+     */
+    public static class Adapter implements Callback
+    {
+        /**
+         * Instance of Adapter that can be used when the callback methods need an empty
+         * implementation without incurring in the cost of allocating a new Adapter object.
+         */
+        public static final Adapter INSTANCE = new Adapter();
+
+        @Override
+        public void succeeded()
+        {
+        }
+
+        @Override
+        public void failed(Throwable x)
+        {
+        }
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/util/ClassLoadingObjectInputStream.java b/lib/jetty/org/eclipse/jetty/util/ClassLoadingObjectInputStream.java
new file mode 100644 (file)
index 0000000..5b734db
--- /dev/null
@@ -0,0 +1,105 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+
+package org.eclipse.jetty.util;
+
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.lang.reflect.Modifier;
+import java.lang.reflect.Proxy;
+
+
+/**
+ * ClassLoadingObjectInputStream
+ *
+ * For re-inflating serialized objects, this class uses the thread context classloader
+ * rather than the jvm's default classloader selection.
+ * 
+ */
+public class ClassLoadingObjectInputStream extends ObjectInputStream
+{
+    /* ------------------------------------------------------------ */
+    public ClassLoadingObjectInputStream(java.io.InputStream in) throws IOException
+    {
+        super(in);
+    }
+
+    /* ------------------------------------------------------------ */
+    public ClassLoadingObjectInputStream () throws IOException
+    {
+        super();
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public Class<?> resolveClass (java.io.ObjectStreamClass cl) throws IOException, ClassNotFoundException
+    {
+        try
+        {
+            return Class.forName(cl.getName(), false, Thread.currentThread().getContextClassLoader());
+        }
+        catch (ClassNotFoundException e)
+        {
+            return super.resolveClass(cl);
+        }
+    }
+    
+    /* ------------------------------------------------------------ */
+    @Override
+    protected Class<?> resolveProxyClass(String[] interfaces)
+            throws IOException, ClassNotFoundException
+    {
+        ClassLoader loader = Thread.currentThread().getContextClassLoader();
+
+        ClassLoader nonPublicLoader = null;
+        boolean hasNonPublicInterface = false;
+
+        // define proxy in class loader of non-public interface(s), if any
+        Class[] classObjs = new Class[interfaces.length];
+        for (int i = 0; i < interfaces.length; i++) 
+        {
+            Class cl = Class.forName(interfaces[i], false, loader);
+            if ((cl.getModifiers() & Modifier.PUBLIC) == 0) 
+            {
+                if (hasNonPublicInterface) 
+                {
+                    if (nonPublicLoader != cl.getClassLoader()) 
+                    {
+                        throw new IllegalAccessError(
+                                "conflicting non-public interface class loaders");
+                    }
+                } 
+                else 
+                {
+                    nonPublicLoader = cl.getClassLoader();
+                    hasNonPublicInterface = true;
+                }
+            }
+            classObjs[i] = cl;
+        }
+        try 
+        {
+            return Proxy.getProxyClass(hasNonPublicInterface ? nonPublicLoader : loader,classObjs);
+        } 
+        catch (IllegalArgumentException e) 
+        {
+            throw new ClassNotFoundException(null, e);
+        }    
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/util/CompletableCallback.java b/lib/jetty/org/eclipse/jetty/util/CompletableCallback.java
new file mode 100644 (file)
index 0000000..ec5a30a
--- /dev/null
@@ -0,0 +1,100 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util;
+
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * A callback to be used by driver code that needs to know whether the callback has been
+ * succeeded or failed (that is, completed) just after the asynchronous operation or not,
+ * typically because further processing depends on the callback being completed.
+ * The driver code competes with the asynchronous operation to complete the callback.
+ * <p />
+ * If the callback is already completed, the driver code continues the processing,
+ * otherwise it suspends it. If it is suspended, the callback will be completed some time
+ * later, and {@link #resume()} or {@link #abort(Throwable)} will be called to allow the
+ * application to resume the processing.
+ * <p />
+ * Typical usage:
+ * <pre>
+ * CompletableCallback callback = new CompletableCallback()
+ * {
+ *     &#64;Override
+ *     public void resume()
+ *     {
+ *         // continue processing
+ *     }
+ *
+ *     &#64;Override
+ *     public void abort(Throwable failure)
+ *     {
+ *         // abort processing
+ *     }
+ * }
+ * asyncOperation(callback);
+ * boolean completed = callback.tryComplete();
+ * if (completed)
+ *     // suspend processing, async operation not done yet
+ * else
+ *     // continue processing, async operation already done
+ * </pre>
+ */
+public abstract class CompletableCallback implements Callback
+{
+    private final AtomicBoolean completed = new AtomicBoolean();
+
+    @Override
+    public void succeeded()
+    {
+        if (!tryComplete())
+            resume();
+    }
+
+    @Override
+    public void failed(Throwable x)
+    {
+        if (!tryComplete())
+            abort(x);
+    }
+
+    /**
+     * Callback method invoked when this callback is succeeded
+     * <em>after</em> a first call to {@link #tryComplete()}.
+     */
+    public abstract void resume();
+
+    /**
+     * Callback method invoked when this callback is failed
+     * <em>after</em> a first call to {@link #tryComplete()}.
+     */
+    public abstract void abort(Throwable failure);
+
+    /**
+     * Tries to complete this callback; driver code should call
+     * this method once <em>after</em> the asynchronous operation
+     * to detect whether the asynchronous operation has already
+     * completed or not.
+     *
+     * @return whether the attempt to complete was successful.
+     */
+    public boolean tryComplete()
+    {
+        return completed.compareAndSet(false, true);
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/util/ConcurrentArrayQueue.java b/lib/jetty/org/eclipse/jetty/util/ConcurrentArrayQueue.java
new file mode 100644 (file)
index 0000000..fc1326d
--- /dev/null
@@ -0,0 +1,570 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util;
+
+import java.util.AbstractQueue;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+import java.util.NoSuchElementException;
+import java.util.Objects;
+import java.util.Queue;
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.atomic.AtomicIntegerArray;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.concurrent.atomic.AtomicReferenceArray;
+
+/**
+ * A concurrent, unbounded implementation of {@link Queue} that uses singly-linked array blocks
+ * to store elements.
+ * <p/>
+ * This class is a drop-in replacement for {@link ConcurrentLinkedQueue}, with similar performance
+ * but producing less garbage because arrays are used to store elements rather than nodes.
+ * <p/>
+ * The algorithm used is a variation of the algorithm from Gidenstam, Sundell and Tsigas
+ * (http://www.adm.hb.se/~AGD/Presentations/CacheAwareQueue_OPODIS.pdf).
+ *
+ * @param <T>
+ */
+public class ConcurrentArrayQueue<T> extends AbstractQueue<T>
+{
+    public static final int DEFAULT_BLOCK_SIZE = 512;
+    public static final Object REMOVED_ELEMENT = new Object()
+    {
+        @Override
+        public String toString()
+        {
+            return "X";
+        }
+    };
+
+    private static final int HEAD_OFFSET = MemoryUtils.getIntegersPerCacheLine() - 1;
+    private static final int TAIL_OFFSET = MemoryUtils.getIntegersPerCacheLine()*2 -1;
+
+    private final AtomicReferenceArray<Block<T>> _blocks = new AtomicReferenceArray<>(TAIL_OFFSET + 1);
+    private final int _blockSize;
+
+    public ConcurrentArrayQueue()
+    {
+        this(DEFAULT_BLOCK_SIZE);
+    }
+
+    public ConcurrentArrayQueue(int blockSize)
+    {
+        _blockSize = blockSize;
+        Block<T> block = newBlock();
+        _blocks.set(HEAD_OFFSET,block);
+        _blocks.set(TAIL_OFFSET,block);
+    }
+
+    public int getBlockSize()
+    {
+        return _blockSize;
+    }
+
+    protected Block<T> getHeadBlock()
+    {
+        return _blocks.get(HEAD_OFFSET);
+    }
+
+    protected Block<T> getTailBlock()
+    {
+        return _blocks.get(TAIL_OFFSET);
+    }
+
+    @Override
+    public boolean offer(T item)
+    {
+        item = Objects.requireNonNull(item);
+
+        final Block<T> initialTailBlock = getTailBlock();
+        Block<T> currentTailBlock = initialTailBlock;
+        int tail = currentTailBlock.tail();
+        while (true)
+        {
+            if (tail == getBlockSize())
+            {
+                Block<T> nextTailBlock = currentTailBlock.next();
+                if (nextTailBlock == null)
+                {
+                    nextTailBlock = newBlock();
+                    if (currentTailBlock.link(nextTailBlock))
+                    {
+                        // Linking succeeded, loop
+                        currentTailBlock = nextTailBlock;
+                    }
+                    else
+                    {
+                        // Concurrent linking, use other block and loop
+                        currentTailBlock = currentTailBlock.next();
+                    }
+                }
+                else
+                {
+                    // Not at last block, loop
+                    currentTailBlock = nextTailBlock;
+                }
+                tail = currentTailBlock.tail();
+            }
+            else
+            {
+                if (currentTailBlock.peek(tail) == null)
+                {
+                    if (currentTailBlock.store(tail, item))
+                    {
+                        // Item stored
+                        break;
+                    }
+                    else
+                    {
+                        // Concurrent store, try next index
+                        ++tail;
+                    }
+                }
+                else
+                {
+                    // Not free, try next index
+                    ++tail;
+                }
+            }
+        }
+
+        updateTailBlock(initialTailBlock, currentTailBlock);
+
+        return true;
+    }
+
+    private void updateTailBlock(Block<T> oldTailBlock, Block<T> newTailBlock)
+    {
+        // Update the tail block pointer if needs to
+        if (oldTailBlock != newTailBlock)
+        {
+            // The tail block pointer is allowed to lag behind.
+            // If this update fails, it means that other threads
+            // have filled this block and installed a new one.
+            casTailBlock(oldTailBlock, newTailBlock);
+        }
+    }
+
+    protected boolean casTailBlock(Block<T> current, Block<T> update)
+    {
+        return _blocks.compareAndSet(TAIL_OFFSET,current,update);
+    }
+
+    @Override
+    public T poll()
+    {
+        final Block<T> initialHeadBlock = getHeadBlock();
+        Block<T> currentHeadBlock = initialHeadBlock;
+        int head = currentHeadBlock.head();
+        T result = null;
+        while (true)
+        {
+            if (head == getBlockSize())
+            {
+                Block<T> nextHeadBlock = currentHeadBlock.next();
+                if (nextHeadBlock == null)
+                {
+                    // We could have read that the next head block was null
+                    // but another thread allocated a new block and stored a
+                    // new item. This thread could not detect this, but that
+                    // is ok, otherwise we would not be able to exit this loop.
+
+                    // Queue is empty
+                    break;
+                }
+                else
+                {
+                    // Use next block and loop
+                    currentHeadBlock = nextHeadBlock;
+                    head = currentHeadBlock.head();
+                }
+            }
+            else
+            {
+                Object element = currentHeadBlock.peek(head);
+                if (element == REMOVED_ELEMENT)
+                {
+                    // Already removed, try next index
+                    ++head;
+                }
+                else
+                {
+                    result = (T)element;
+                    if (result != null)
+                    {
+                        if (currentHeadBlock.remove(head, result, true))
+                        {
+                            // Item removed
+                            break;
+                        }
+                        else
+                        {
+                            // Concurrent remove, try next index
+                            ++head;
+                        }
+                    }
+                    else
+                    {
+                        // Queue is empty
+                        break;
+                    }
+                }
+            }
+        }
+
+        updateHeadBlock(initialHeadBlock, currentHeadBlock);
+
+        return result;
+    }
+
+    private void updateHeadBlock(Block<T> oldHeadBlock, Block<T> newHeadBlock)
+    {
+        // Update the head block pointer if needs to
+        if (oldHeadBlock != newHeadBlock)
+        {
+            // The head block pointer lagged behind.
+            // If this update fails, it means that other threads
+            // have emptied this block and pointed to a new one.
+            casHeadBlock(oldHeadBlock, newHeadBlock);
+        }
+    }
+
+    protected boolean casHeadBlock(Block<T> current, Block<T> update)
+    {
+        return _blocks.compareAndSet(HEAD_OFFSET,current,update);
+    }
+
+    @Override
+    public T peek()
+    {
+        Block<T> currentHeadBlock = getHeadBlock();
+        int head = currentHeadBlock.head();
+        while (true)
+        {
+            if (head == getBlockSize())
+            {
+                Block<T> nextHeadBlock = currentHeadBlock.next();
+                if (nextHeadBlock == null)
+                {
+                    // Queue is empty
+                    return null;
+                }
+                else
+                {
+                    // Use next block and loop
+                    currentHeadBlock = nextHeadBlock;
+                    head = currentHeadBlock.head();
+                }
+            }
+            else
+            {
+                Object element = currentHeadBlock.peek(head);
+                if (element == REMOVED_ELEMENT)
+                {
+                    // Already removed, try next index
+                    ++head;
+                }
+                else
+                {
+                    return (T)element;
+                }
+            }
+        }
+    }
+
+    @Override
+    public boolean remove(Object o)
+    {
+        Block<T> currentHeadBlock = getHeadBlock();
+        int head = currentHeadBlock.head();
+        boolean result = false;
+        while (true)
+        {
+            if (head == getBlockSize())
+            {
+                Block<T> nextHeadBlock = currentHeadBlock.next();
+                if (nextHeadBlock == null)
+                {
+                    // Not found
+                    break;
+                }
+                else
+                {
+                    // Use next block and loop
+                    currentHeadBlock = nextHeadBlock;
+                    head = currentHeadBlock.head();
+                }
+            }
+            else
+            {
+                Object element = currentHeadBlock.peek(head);
+                if (element == REMOVED_ELEMENT)
+                {
+                    // Removed, try next index
+                    ++head;
+                }
+                else
+                {
+                    if (element == null)
+                    {
+                        // Not found
+                        break;
+                    }
+                    else
+                    {
+                        if (element.equals(o))
+                        {
+                            // Found
+                            if (currentHeadBlock.remove(head, o, false))
+                            {
+                                result = true;
+                                break;
+                            }
+                            else
+                            {
+                                ++head;
+                            }
+                        }
+                        else
+                        {
+                            // Not the one we're looking for
+                            ++head;
+                        }
+                    }
+                }
+            }
+        }
+
+        return result;
+    }
+
+    @Override
+    public boolean removeAll(Collection<?> c)
+    {
+        // TODO: super invocations are based on iterator.remove(), which throws
+        return super.removeAll(c);
+    }
+
+    @Override
+    public boolean retainAll(Collection<?> c)
+    {
+        // TODO: super invocations are based on iterator.remove(), which throws
+        return super.retainAll(c);
+    }
+
+    @Override
+    public Iterator<T> iterator()
+    {
+        final List<Object[]> blocks = new ArrayList<>();
+        Block<T> currentHeadBlock = getHeadBlock();
+        while (currentHeadBlock != null)
+        {
+            Object[] elements = currentHeadBlock.arrayCopy();
+            blocks.add(elements);
+            currentHeadBlock = currentHeadBlock.next();
+        }
+        return new Iterator<T>()
+        {
+            private int blockIndex;
+            private int index;
+
+            @Override
+            public boolean hasNext()
+            {
+                while (true)
+                {
+                    if (blockIndex == blocks.size())
+                        return false;
+
+                    Object element = blocks.get(blockIndex)[index];
+
+                    if (element == null)
+                        return false;
+
+                    if (element != REMOVED_ELEMENT)
+                        return true;
+
+                    advance();
+                }
+            }
+
+            @Override
+            public T next()
+            {
+                while (true)
+                {
+                    if (blockIndex == blocks.size())
+                        throw new NoSuchElementException();
+
+                    Object element = blocks.get(blockIndex)[index];
+
+                    if (element == null)
+                        throw new NoSuchElementException();
+
+                    advance();
+
+                    if (element != REMOVED_ELEMENT)
+                        return (T)element;
+                }
+            }
+
+            private void advance()
+            {
+                if (++index == getBlockSize())
+                {
+                    index = 0;
+                    ++blockIndex;
+                }
+            }
+
+            @Override
+            public void remove()
+            {
+                throw new UnsupportedOperationException();
+            }
+        };
+    }
+
+    @Override
+    public int size()
+    {
+        Block<T> currentHeadBlock = getHeadBlock();
+        int head = currentHeadBlock.head();
+        int size = 0;
+        while (true)
+        {
+            if (head == getBlockSize())
+            {
+                Block<T> nextHeadBlock = currentHeadBlock.next();
+                if (nextHeadBlock == null)
+                {
+                    break;
+                }
+                else
+                {
+                    // Use next block and loop
+                    currentHeadBlock = nextHeadBlock;
+                    head = currentHeadBlock.head();
+                }
+            }
+            else
+            {
+                Object element = currentHeadBlock.peek(head);
+                if (element == REMOVED_ELEMENT)
+                {
+                    // Already removed, try next index
+                    ++head;
+                }
+                else if (element != null)
+                {
+                    ++size;
+                    ++head;
+                }
+                else
+                {
+                    break;
+                }
+            }
+        }
+        return size;
+    }
+
+    protected Block<T> newBlock()
+    {
+        return new Block<>(getBlockSize());
+    }
+
+    protected int getBlockCount()
+    {
+        int result = 0;
+        Block<T> headBlock = getHeadBlock();
+        while (headBlock != null)
+        {
+            ++result;
+            headBlock = headBlock.next();
+        }
+        return result;
+    }
+
+    protected static final class Block<E>
+    {
+        private static final int headOffset = MemoryUtils.getIntegersPerCacheLine()-1;
+        private static final int tailOffset = MemoryUtils.getIntegersPerCacheLine()*2-1;
+
+        private final AtomicReferenceArray<Object> elements;
+        private final AtomicReference<Block<E>> next = new AtomicReference<>();
+        private final AtomicIntegerArray indexes = new AtomicIntegerArray(TAIL_OFFSET+1);
+
+        protected Block(int blockSize)
+        {
+            elements = new AtomicReferenceArray<>(blockSize);
+        }
+
+        public Object peek(int index)
+        {
+            return elements.get(index);
+        }
+
+        public boolean store(int index, E item)
+        {
+            boolean result = elements.compareAndSet(index, null, item);
+            if (result)
+                indexes.incrementAndGet(tailOffset);
+            return result;
+        }
+
+        public boolean remove(int index, Object item, boolean updateHead)
+        {
+            boolean result = elements.compareAndSet(index, item, REMOVED_ELEMENT);
+            if (result && updateHead)
+                indexes.incrementAndGet(headOffset);
+            return result;
+        }
+
+        public Block<E> next()
+        {
+            return next.get();
+        }
+
+        public boolean link(Block<E> nextBlock)
+        {
+            return next.compareAndSet(null, nextBlock);
+        }
+
+        public int head()
+        {
+            return indexes.get(headOffset);
+        }
+
+        public int tail()
+        {
+            return indexes.get(tailOffset);
+        }
+
+        public Object[] arrayCopy()
+        {
+            Object[] result = new Object[elements.length()];
+            for (int i = 0; i < result.length; ++i)
+                result[i] = elements.get(i);
+            return result;
+        }
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/util/ConcurrentHashSet.java b/lib/jetty/org/eclipse/jetty/util/ConcurrentHashSet.java
new file mode 100644 (file)
index 0000000..4a4c8e6
--- /dev/null
@@ -0,0 +1,126 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util;
+
+import java.util.AbstractSet;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+
+public class ConcurrentHashSet<E> extends AbstractSet<E> implements Set<E>
+{
+    private final Map<E, Boolean> _map = new ConcurrentHashMap<E, Boolean>();
+    private transient Set<E> _keys = _map.keySet();
+
+    public ConcurrentHashSet()
+    {
+    }
+
+    @Override
+    public boolean add(E e)
+    {
+        return _map.put(e,Boolean.TRUE) == null;
+    }
+
+    @Override
+    public void clear()
+    {
+        _map.clear();
+    }
+
+    @Override
+    public boolean contains(Object o)
+    {
+        return _map.containsKey(o);
+    }
+
+    @Override
+    public boolean containsAll(Collection<?> c)
+    {
+        return _keys.containsAll(c);
+    }
+
+    @Override
+    public boolean equals(Object o)
+    {
+        return o == this || _keys.equals(o);
+    }
+
+    @Override
+    public int hashCode()
+    {
+        return _keys.hashCode();
+    }
+
+    @Override
+    public boolean isEmpty()
+    {
+        return _map.isEmpty();
+    }
+
+    @Override
+    public Iterator<E> iterator()
+    {
+        return _keys.iterator();
+    }
+
+    @Override
+    public boolean remove(Object o)
+    {
+        return _map.remove(o) != null;
+    }
+
+    @Override
+    public boolean removeAll(Collection<?> c)
+    {
+        return _keys.removeAll(c);
+    }
+
+    @Override
+    public boolean retainAll(Collection<?> c)
+    {
+        return _keys.retainAll(c);
+    }
+
+    @Override
+    public int size()
+    {
+        return _map.size();
+    }
+
+    @Override
+    public Object[] toArray()
+    {
+        return _keys.toArray();
+    }
+
+    @Override
+    public <T> T[] toArray(T[] a)
+    {
+        return _keys.toArray(a);
+    }
+
+    @Override
+    public String toString()
+    {
+        return _keys.toString();
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/util/DateCache.java b/lib/jetty/org/eclipse/jetty/util/DateCache.java
new file mode 100644 (file)
index 0000000..31097ee
--- /dev/null
@@ -0,0 +1,268 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util;
+
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Locale;
+import java.util.TimeZone;
+
+/* ------------------------------------------------------------ */
+/**  Date Format Cache.
+ * Computes String representations of Dates and caches
+ * the results so that subsequent requests within the same second
+ * will be fast.
+ *
+ * Only format strings that contain either "ss".  Sub second formatting is 
+ * not handled.
+ *
+ * The timezone of the date may be included as an ID with the "zzz"
+ * format string or as an offset with the "ZZZ" format string.
+ *
+ * If consecutive calls are frequently very different, then this
+ * may be a little slower than a normal DateFormat.
+ *
+ */
+
+public class DateCache
+{
+    public static final String DEFAULT_FORMAT="EEE MMM dd HH:mm:ss zzz yyyy";
+    
+    private final String _formatString;
+    private final String _tzFormatString;
+    private final SimpleDateFormat _tzFormat;
+    private final Locale _locale ;
+    
+    private volatile Tick _tick;
+
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    public static class Tick
+    {
+        final long _seconds;
+        final String _string;
+        public Tick(long seconds, String string)
+        {
+            _seconds = seconds;
+            _string = string;
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Constructor.
+     * Make a DateCache that will use a default format. The default format
+     * generates the same results as Date.toString().
+     */
+    public DateCache()
+    {
+        this(DEFAULT_FORMAT);
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** Constructor.
+     * Make a DateCache that will use the given format
+     */
+    public DateCache(String format)
+    {
+        this(format,null,TimeZone.getDefault());
+    }
+    
+    /* ------------------------------------------------------------ */
+    public DateCache(String format,Locale l)
+    {
+        this(format,l,TimeZone.getDefault());
+    }
+
+    /* ------------------------------------------------------------ */
+    public DateCache(String format,Locale l,String tz)
+    {
+        this(format,l,TimeZone.getTimeZone(tz));
+    }
+    
+    /* ------------------------------------------------------------ */
+    public DateCache(String format,Locale l,TimeZone tz)
+    {
+        _formatString=format;
+        _locale = l;
+        
+
+        int zIndex = _formatString.indexOf( "ZZZ" );
+        if( zIndex >= 0 )
+        {
+            String ss1 = _formatString.substring( 0, zIndex );
+            String ss2 = _formatString.substring( zIndex+3 );
+            int tzOffset = tz.getRawOffset();
+            
+            StringBuilder sb = new StringBuilder(_formatString.length()+10);
+            sb.append(ss1);
+            sb.append("'");
+            if( tzOffset >= 0 )
+                sb.append( '+' );
+            else
+            {
+                tzOffset = -tzOffset;
+                sb.append( '-' );
+            }
+            
+            int raw = tzOffset / (1000*60);             // Convert to seconds
+            int hr = raw / 60;
+            int min = raw % 60;
+            
+            if( hr < 10 )
+                sb.append( '0' );
+            sb.append( hr );
+            if( min < 10 )
+                sb.append( '0' );
+            sb.append( min );
+            sb.append( '\'' );
+            
+            sb.append(ss2);
+            _tzFormatString=sb.toString();            
+        }
+        else
+            _tzFormatString=_formatString;
+   
+        if( _locale != null ) 
+        {
+            _tzFormat=new SimpleDateFormat(_tzFormatString,_locale);
+        }
+        else 
+        {
+            _tzFormat=new SimpleDateFormat(_tzFormatString);
+        }
+        _tzFormat.setTimeZone(tz);
+        
+        _tick=null;
+    }
+    
+
+    /* ------------------------------------------------------------ */
+    public TimeZone getTimeZone()
+    {
+        return _tzFormat.getTimeZone();
+    }
+
+
+    /* ------------------------------------------------------------ */
+    /** Format a date according to our stored formatter.
+     * @param inDate 
+     * @return Formatted date
+     */
+    public String format(Date inDate)
+    {
+        long seconds = inDate.getTime() / 1000;
+
+        Tick tick=_tick;
+        
+        // Is this the cached time
+        if (tick==null || seconds!=tick._seconds)
+        {
+            // It's a cache miss
+            synchronized (this)
+            {
+                return _tzFormat.format(inDate);
+            }
+        }
+        
+        return tick._string;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** Format a date according to our stored formatter.
+     * If it happens to be in the same second as the last formatNow
+     * call, then the format is reused.
+     * @param inDate 
+     * @return Formatted date
+     */
+    public String format(long inDate)
+    {
+        long seconds = inDate / 1000;
+
+        Tick tick=_tick;
+        
+        // Is this the cached time
+        if (tick==null || seconds!=tick._seconds)
+        {
+            // It's a cache miss
+            Date d = new Date(inDate);
+            synchronized (this)
+            {
+                return _tzFormat.format(d);
+            }
+        }
+        
+        return tick._string;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** Format a date according to our stored formatter.
+     * The passed time is expected to be close to the current time, so it is 
+     * compared to the last value passed and if it is within the same second,
+     * the format is reused.  Otherwise a new cached format is created.
+     * @param now 
+     * @return Formatted date
+     */
+    public String formatNow(long now)
+    {
+        long seconds = now / 1000;
+
+        Tick tick=_tick;
+        
+        // Is this the cached time
+        if (tick!=null && tick._seconds==seconds)
+            return tick._string;
+        return formatTick(now)._string;
+    }
+    
+    /* ------------------------------------------------------------ */
+    public String now()
+    {
+        return formatNow(System.currentTimeMillis());
+    }
+    
+    /* ------------------------------------------------------------ */
+    public Tick tick()
+    {
+        return formatTick(System.currentTimeMillis());
+    }
+    
+    /* ------------------------------------------------------------ */
+    protected Tick formatTick(long now)
+    {
+        long seconds = now / 1000;
+
+        // Synchronize to protect _tzFormat
+        synchronized (this)
+        {
+            // recheck the tick, to save multiple formats
+            if (_tick==null || _tick._seconds!=seconds)
+            {
+                String s= _tzFormat.format(new Date(now));
+                return _tick=new Tick(seconds,s);
+            }
+            return _tick;
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    public String getFormatString()
+    {
+        return _formatString;
+    }    
+}
diff --git a/lib/jetty/org/eclipse/jetty/util/Fields.java b/lib/jetty/org/eclipse/jetty/util/Fields.java
new file mode 100644 (file)
index 0000000..152934d
--- /dev/null
@@ -0,0 +1,336 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * <p>A container for name/value pairs, known as fields.</p>
+ * <p>A {@link Field} is composed of a name string that can be case-sensitive
+ * or case-insensitive (by specifying the option at the constructor) and
+ * of a case-sensitive set of value strings.</p>
+ * <p>The implementation of this class is not thread safe.</p>
+ */
+public class Fields implements Iterable<Fields.Field>
+{
+    private final boolean caseSensitive;
+    private final Map<String, Field> fields;
+
+    /**
+     * <p>Creates an empty, modifiable, case insensitive {@link Fields} instance.</p>
+     * @see #Fields(Fields, boolean)
+     */
+    public Fields()
+    {
+        this(false);
+    }
+
+    /**
+     * <p>Creates an empty, modifiable, case insensitive {@link Fields} instance.</p>
+     * @param caseSensitive whether this {@link Fields} instance must be case sensitive
+     * @see #Fields(Fields, boolean)
+     */
+    public Fields(boolean caseSensitive)
+    {
+        this.caseSensitive = caseSensitive;
+        fields = new LinkedHashMap<>();
+    }
+
+    /**
+     * <p>Creates a {@link Fields} instance by copying the fields from the given
+     * {@link Fields} and making it (im)mutable depending on the given {@code immutable} parameter</p>
+     *
+     * @param original the {@link Fields} to copy fields from
+     * @param immutable whether this instance is immutable
+     */
+    public Fields(Fields original, boolean immutable)
+    {
+        this.caseSensitive = original.caseSensitive;
+        Map<String, Field> copy = new LinkedHashMap<>();
+        copy.putAll(original.fields);
+        fields = immutable ? Collections.unmodifiableMap(copy) : copy;
+    }
+
+    @Override
+    public boolean equals(Object obj)
+    {
+        if (this == obj)
+            return true;
+        if (obj == null || getClass() != obj.getClass())
+            return false;
+        Fields that = (Fields)obj;
+        if (getSize() != that.getSize())
+            return false;
+        if (caseSensitive != that.caseSensitive)
+            return false;
+        for (Map.Entry<String, Field> entry : fields.entrySet())
+        {
+            String name = entry.getKey();
+            Field value = entry.getValue();
+            if (!value.equals(that.get(name), caseSensitive))
+                return false;
+        }
+        return true;
+    }
+
+    @Override
+    public int hashCode()
+    {
+        return fields.hashCode();
+    }
+
+    /**
+     * @return a set of field names
+     */
+    public Set<String> getNames()
+    {
+        Set<String> result = new LinkedHashSet<>();
+        for (Field field : fields.values())
+            result.add(field.getName());
+        return result;
+    }
+
+    private String normalizeName(String name)
+    {
+        return caseSensitive ? name : name.toLowerCase(Locale.ENGLISH);
+    }
+
+    /**
+     * @param name the field name
+     * @return the {@link Field} with the given name, or null if no such field exists
+     */
+    public Field get(String name)
+    {
+        return fields.get(normalizeName(name));
+    }
+
+    /**
+     * <p>Inserts or replaces the given name/value pair as a single-valued {@link Field}.</p>
+     *
+     * @param name the field name
+     * @param value the field value
+     */
+    public void put(String name, String value)
+    {
+        // Preserve the case for the field name
+        Field field = new Field(name, value);
+        fields.put(normalizeName(name), field);
+    }
+
+    /**
+     * <p>Inserts or replaces the given {@link Field}, mapped to the {@link Field#getName() field's name}</p>
+     *
+     * @param field the field to put
+     */
+    public void put(Field field)
+    {
+        if (field != null)
+            fields.put(normalizeName(field.getName()), field);
+    }
+
+    /**
+     * <p>Adds the given value to a field with the given name,
+     * creating a {@link Field} is none exists for the given name.</p>
+     *
+     * @param name the field name
+     * @param value the field value to add
+     */
+    public void add(String name, String value)
+    {
+        String key = normalizeName(name);
+        Field field = fields.get(key);
+        if (field == null)
+        {
+            // Preserve the case for the field name
+            field = new Field(name, value);
+            fields.put(key, field);
+        }
+        else
+        {
+            field = new Field(field.getName(), field.getValues(), value);
+            fields.put(key, field);
+        }
+    }
+
+    /**
+     * <p>Removes the {@link Field} with the given name</p>
+     *
+     * @param name the name of the field to remove
+     * @return the removed field, or null if no such field existed
+     */
+    public Field remove(String name)
+    {
+        return fields.remove(normalizeName(name));
+    }
+
+    /**
+     * <p>Empties this {@link Fields} instance from all fields</p>
+     * @see #isEmpty()
+     */
+    public void clear()
+    {
+        fields.clear();
+    }
+
+    /**
+     * @return whether this {@link Fields} instance is empty
+     */
+    public boolean isEmpty()
+    {
+        return fields.isEmpty();
+    }
+
+    /**
+     * @return the number of fields
+     */
+    public int getSize()
+    {
+        return fields.size();
+    }
+
+    /**
+     * @return an iterator over the {@link Field}s present in this instance
+     */
+    @Override
+    public Iterator<Field> iterator()
+    {
+        return fields.values().iterator();
+    }
+
+    @Override
+    public String toString()
+    {
+        return fields.toString();
+    }
+
+    /**
+     * <p>A named list of string values.</p>
+     * <p>The name is case-sensitive and there must be at least one value.</p>
+     */
+    public static class Field
+    {
+        private final String name;
+        private final List<String> values;
+
+        public Field(String name, String value)
+        {
+            this(name, Collections.singletonList(value));
+        }
+
+        private Field(String name, List<String> values, String... moreValues)
+        {
+            this.name = name;
+            List<String> list = new ArrayList<>(values.size() + moreValues.length);
+            list.addAll(values);
+            list.addAll(Arrays.asList(moreValues));
+            this.values = Collections.unmodifiableList(list);
+        }
+
+        public boolean equals(Field that, boolean caseSensitive)
+        {
+            if (this == that)
+                return true;
+            if (that == null)
+                return false;
+            if (caseSensitive)
+                return equals(that);
+            return name.equalsIgnoreCase(that.name) && values.equals(that.values);
+        }
+
+        @Override
+        public boolean equals(Object obj)
+        {
+            if (this == obj)
+                return true;
+            if (obj == null || getClass() != obj.getClass())
+                return false;
+            Field that = (Field)obj;
+            return name.equals(that.name) && values.equals(that.values);
+        }
+
+        @Override
+        public int hashCode()
+        {
+            int result = name.hashCode();
+            result = 31 * result + values.hashCode();
+            return result;
+        }
+
+        /**
+         * @return the field's name
+         */
+        public String getName()
+        {
+            return name;
+        }
+
+        /**
+         * @return the first field's value
+         */
+        public String getValue()
+        {
+            return values.get(0);
+        }
+
+        /**
+         * <p>Attempts to convert the result of {@link #getValue()} to an integer,
+         * returning it if the conversion is successful; returns null if the
+         * result of {@link #getValue()} is null.</p>
+         *
+         * @return the result of {@link #getValue()} converted to an integer, or null
+         * @throws NumberFormatException if the conversion fails
+         */
+        public Integer getValueAsInt()
+        {
+            final String value = getValue();
+            return value == null ? null : Integer.valueOf(value);
+        }
+
+        /**
+         * @return the field's values
+         */
+        public List<String> getValues()
+        {
+            return values;
+        }
+
+        /**
+         * @return whether the field has multiple values
+         */
+        public boolean hasMultipleValues()
+        {
+            return values.size() > 1;
+        }
+
+        @Override
+        public String toString()
+        {
+            return String.format("%s=%s", name, values);
+        }
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/util/ForkInvoker.java b/lib/jetty/org/eclipse/jetty/util/ForkInvoker.java
new file mode 100644 (file)
index 0000000..c15b313
--- /dev/null
@@ -0,0 +1,135 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util;
+
+/**
+ * Utility class that splits calls to {@link #invoke(Object)} into calls to {@link #fork(Object)} or {@link #call(Object)}
+ * depending on the max number of reentrant calls to {@link #invoke(Object)}.
+ * <p/>
+ * This class prevents {@link StackOverflowError}s in case of methods that end up invoking themselves,
+ * such is common for {@link Callback#succeeded()}.
+ * <p/>
+ * Typical use case is:
+ * <pre>
+ * public void reentrantMethod(Object param)
+ * {
+ *     if (condition || tooManyReenters)
+ *         fork(param)
+ *     else
+ *         call(param)
+ * }
+ * </pre>
+ * Calculating {@code tooManyReenters} usually involves using a {@link ThreadLocal} and algebra on the
+ * number of reentrant invocations, which is factored out in this class for convenience.
+ * <p />
+ * The same code using this class becomes:
+ * <pre>
+ * private final ForkInvoker invoker = ...;
+ *
+ * public void reentrantMethod(Object param)
+ * {
+ *     invoker.invoke(param);
+ * }
+ * </pre>
+ *
+ */
+public abstract class ForkInvoker<T>
+{
+    private static final ThreadLocal<Integer> __invocations = new ThreadLocal<Integer>()
+    {
+        @Override
+        protected Integer initialValue()
+        {
+            return 0;
+        }
+    };
+    private final int _maxInvocations;
+
+    /**
+     * Creates an instance with the given max number of reentrant calls to {@link #invoke(Object)}
+     * <p/>
+     * If {@code maxInvocations} is zero or negative, it is interpreted
+     * as if the max number of reentrant calls is infinite.
+     *
+     * @param maxInvocations the max number of reentrant calls to {@link #invoke(Object)}
+     */
+    public ForkInvoker(int maxInvocations)
+    {
+        _maxInvocations = maxInvocations;
+    }
+
+    /**
+     * Invokes either {@link #fork(Object)} or {@link #call(Object)}.
+     * If {@link #condition()} returns true, {@link #fork(Object)} is invoked.
+     * Otherwise, if the max number of reentrant calls is positive and the
+     * actual number of reentrant invocations exceeds it, {@link #fork(Object)} is invoked.
+     * Otherwise, {@link #call(Object)} is invoked.
+     * @param arg TODO
+     *
+     * @return true if {@link #fork(Object)} has been called, false otherwise
+     */
+    public boolean invoke(T arg)
+    {
+        boolean countInvocations = _maxInvocations > 0;
+        int invocations = __invocations.get();
+        if (condition() || countInvocations && invocations > _maxInvocations)
+        {
+            fork(arg);
+            return true;
+        }
+        else
+        {
+            if (countInvocations)
+                __invocations.set(invocations + 1);
+            try
+            {
+                call(arg);
+                return false;
+            }
+            finally
+            {
+                if (countInvocations)
+                    __invocations.set(invocations);
+            }
+        }
+    }
+
+    /**
+     * Subclasses should override this method returning true if they want
+     * {@link #invoke(Object)} to call {@link #fork(Object)}.
+     *
+     * @return true if {@link #invoke(Object)} should call {@link #fork(Object)}, false otherwise
+     */
+    protected boolean condition()
+    {
+        return false;
+    }
+
+    /**
+     * Executes the forked invocation
+     * @param arg TODO
+     */
+    public abstract void fork(T arg);
+
+    /**
+     * Executes the direct, non-forked, invocation
+     * @param arg TODO
+     */
+    public abstract void call(T arg);
+}
diff --git a/lib/jetty/org/eclipse/jetty/util/FutureCallback.java b/lib/jetty/org/eclipse/jetty/util/FutureCallback.java
new file mode 100644 (file)
index 0000000..5bad6b2
--- /dev/null
@@ -0,0 +1,157 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util;
+
+import java.io.IOException;
+import java.util.concurrent.CancellationException;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+public class FutureCallback implements Future<Void>,Callback
+{
+    private static Throwable COMPLETED=new Throwable();
+    private final AtomicBoolean _done=new AtomicBoolean(false);
+    private final CountDownLatch _latch=new CountDownLatch(1);
+    private Throwable _cause;
+    
+    public FutureCallback()
+    {}
+
+    public FutureCallback(boolean completed)
+    {
+        if (completed)
+        {
+            _cause=COMPLETED;
+            _done.set(true);
+            _latch.countDown();
+        }
+    }
+
+    public FutureCallback(Throwable failed)
+    {
+        _cause=failed;
+        _done.set(true);
+        _latch.countDown();
+    }
+
+    @Override
+    public void succeeded()
+    {
+        if (_done.compareAndSet(false,true))
+        {
+            _cause=COMPLETED;
+            _latch.countDown();
+        }
+    }
+
+    @Override
+    public void failed(Throwable cause)
+    {
+        if (_done.compareAndSet(false,true))
+        {
+            _cause=cause;
+            _latch.countDown();
+        }
+    }
+
+    @Override
+    public boolean cancel(boolean mayInterruptIfRunning)
+    {
+        if (_done.compareAndSet(false,true))
+        {
+            _cause=new CancellationException();
+            _latch.countDown();
+            return true;
+        }
+        return false;
+    }
+
+    @Override
+    public boolean isCancelled()
+    {
+        if (_done.get())
+        {
+            try
+            {
+                _latch.await();
+            }
+            catch (InterruptedException e)
+            {
+                throw new RuntimeException(e);
+            }
+            return _cause instanceof CancellationException;
+        }
+        return false;
+    }
+
+    @Override
+    public boolean isDone()
+    {
+        return _done.get() && _latch.getCount()==0;
+    }
+
+    @Override
+    public Void get() throws InterruptedException, ExecutionException
+    {
+        _latch.await();
+        if (_cause==COMPLETED)
+            return null;
+        if (_cause instanceof CancellationException)
+            throw (CancellationException) new CancellationException().initCause(_cause);
+        throw new ExecutionException(_cause);
+    }
+
+    @Override
+    public Void get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException
+    {
+        if (!_latch.await(timeout,unit))
+            throw new TimeoutException();
+
+        if (_cause==COMPLETED)
+            return null;
+        if (_cause instanceof TimeoutException)
+            throw (TimeoutException)_cause;
+        if (_cause instanceof CancellationException)
+            throw (CancellationException) new CancellationException().initCause(_cause);
+        throw new ExecutionException(_cause);
+    }
+
+    public static void rethrow(ExecutionException e) throws IOException
+    {
+        Throwable cause=e.getCause();
+        if (cause instanceof IOException)
+            throw (IOException)cause;
+        if (cause instanceof Error)
+            throw (Error)cause;
+        if (cause instanceof RuntimeException)
+            throw (RuntimeException)cause;
+        throw new RuntimeException(cause);
+    }
+    
+    @Override
+    public String toString()
+    {
+        return String.format("FutureCallback@%x{%b,%b}",hashCode(),_done,_cause==COMPLETED);
+    }
+    
+}
diff --git a/lib/jetty/org/eclipse/jetty/util/FuturePromise.java b/lib/jetty/org/eclipse/jetty/util/FuturePromise.java
new file mode 100644 (file)
index 0000000..f781c85
--- /dev/null
@@ -0,0 +1,159 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util;
+
+import java.io.IOException;
+import java.util.concurrent.CancellationException;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+public class FuturePromise<C> implements Future<C>,Promise<C>
+{
+    private static Throwable COMPLETED=new Throwable();
+    private final AtomicBoolean _done=new AtomicBoolean(false);
+    private final CountDownLatch _latch=new CountDownLatch(1);
+    private Throwable _cause;
+    private C _result;
+    
+    public FuturePromise()
+    {}
+
+    public FuturePromise(C result)
+    {
+        _cause=COMPLETED;
+        _result=result;
+        _done.set(true);
+        _latch.countDown();
+    }
+
+    public FuturePromise(C ctx, Throwable failed)
+    {
+        _result=ctx;
+        _cause=failed;
+        _done.set(true);
+        _latch.countDown();
+    }
+
+    @Override
+    public void succeeded(C result)
+    {
+        if (_done.compareAndSet(false,true))
+        {
+            _result=result;
+            _cause=COMPLETED;
+            _latch.countDown();
+        }
+    }
+
+    @Override
+    public void failed(Throwable cause)
+    {
+        if (_done.compareAndSet(false,true))
+        {
+            _cause=cause;
+            _latch.countDown();
+        }
+    }
+
+    @Override
+    public boolean cancel(boolean mayInterruptIfRunning)
+    {
+        if (_done.compareAndSet(false,true))
+        {
+            _result=null;
+            _cause=new CancellationException();
+            _latch.countDown();
+            return true;
+        }
+        return false;
+    }
+
+    @Override
+    public boolean isCancelled()
+    {
+        if (_done.get())
+        {
+            try
+            {
+                _latch.await();
+            }
+            catch (InterruptedException e)
+            {
+                throw new RuntimeException(e);
+            }
+            return _cause instanceof CancellationException;
+        }
+        return false;
+    }
+
+    @Override
+    public boolean isDone()
+    {
+        return _done.get() && _latch.getCount()==0;
+    }
+
+    @Override
+    public C get() throws InterruptedException, ExecutionException
+    {
+        _latch.await();
+        if (_cause==COMPLETED)
+            return _result;
+        if (_cause instanceof CancellationException)
+            throw (CancellationException) new CancellationException().initCause(_cause);
+        throw new ExecutionException(_cause);
+    }
+
+    @Override
+    public C get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException
+    {
+        if (!_latch.await(timeout,unit))
+            throw new TimeoutException();
+
+        if (_cause==COMPLETED)
+            return _result;
+        if (_cause instanceof TimeoutException)
+            throw (TimeoutException)_cause;
+        if (_cause instanceof CancellationException)
+            throw (CancellationException) new CancellationException().initCause(_cause);
+        throw new ExecutionException(_cause);
+    }
+
+    public static void rethrow(ExecutionException e) throws IOException
+    {
+        Throwable cause=e.getCause();
+        if (cause instanceof IOException)
+            throw (IOException)cause;
+        if (cause instanceof Error)
+            throw (Error)cause;
+        if (cause instanceof RuntimeException)
+            throw (RuntimeException)cause;
+        throw new RuntimeException(cause);
+    }
+    
+    @Override
+    public String toString()
+    {
+        return String.format("FutureCallback@%x{%b,%b,%s}",hashCode(),_done,_cause==COMPLETED,_result);
+    }
+    
+}
diff --git a/lib/jetty/org/eclipse/jetty/util/HostMap.java b/lib/jetty/org/eclipse/jetty/util/HostMap.java
new file mode 100644 (file)
index 0000000..23e10bb
--- /dev/null
@@ -0,0 +1,108 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+
+package org.eclipse.jetty.util;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+
+/* ------------------------------------------------------------ */
+/**
+ */
+@SuppressWarnings("serial")
+public class HostMap<TYPE> extends HashMap<String, TYPE>
+{
+
+    /* --------------------------------------------------------------- */
+    /** Construct empty HostMap.
+     */
+    public HostMap()
+    {
+        super(11);
+    }
+   
+    /* --------------------------------------------------------------- */
+    /** Construct empty HostMap.
+     * 
+     * @param capacity initial capacity
+     */
+    public HostMap(int capacity)
+    {
+        super (capacity);
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * @see java.util.HashMap#put(java.lang.Object, java.lang.Object)
+     */
+    @Override
+    public TYPE put(String host, TYPE object)
+        throws IllegalArgumentException
+    {
+        return super.put(host, object);
+    }
+        
+    /* ------------------------------------------------------------ */
+    /**
+     * @see java.util.HashMap#get(java.lang.Object)
+     */
+    @Override
+    public TYPE get(Object key)
+    {
+        return super.get(key);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Retrieve a lazy list of map entries associated with specified
+     * hostname by taking into account the domain suffix matches.
+     * 
+     * @param host hostname
+     * @return lazy list of map entries
+     */
+    public Object getLazyMatches(String host)
+    {
+        if (host == null)
+            return LazyList.getList(super.entrySet());
+        
+        int idx = 0;
+        String domain = host.trim();
+        HashSet<String> domains = new HashSet<String>();
+        do {
+            domains.add(domain);
+            if ((idx = domain.indexOf('.')) > 0)
+            {
+                domain = domain.substring(idx+1);
+            }
+        } while (idx > 0);
+        
+        Object entries = null;
+        for(Map.Entry<String, TYPE> entry: super.entrySet())
+        {
+            if (domains.contains(entry.getKey()))
+            {
+                entries = LazyList.add(entries,entry);
+            }
+        }
+       
+        return entries;        
+    }
+
+}
diff --git a/lib/jetty/org/eclipse/jetty/util/HttpCookieStore.java b/lib/jetty/org/eclipse/jetty/util/HttpCookieStore.java
new file mode 100644 (file)
index 0000000..99aa742
--- /dev/null
@@ -0,0 +1,114 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util;
+
+import java.net.CookieManager;
+import java.net.CookieStore;
+import java.net.HttpCookie;
+import java.net.URI;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Implementation of {@link CookieStore} that delegates to an instance created by {@link CookieManager}
+ * via {@link CookieManager#getCookieStore()}.
+ */
+public class HttpCookieStore implements CookieStore
+{
+    private final CookieStore delegate;
+
+    public HttpCookieStore()
+    {
+        delegate = new CookieManager().getCookieStore();
+    }
+
+    @Override
+    public void add(URI uri, HttpCookie cookie)
+    {
+        delegate.add(uri, cookie);
+    }
+
+    @Override
+    public List<HttpCookie> get(URI uri)
+    {
+        return delegate.get(uri);
+    }
+
+    @Override
+    public List<HttpCookie> getCookies()
+    {
+        return delegate.getCookies();
+    }
+
+    @Override
+    public List<URI> getURIs()
+    {
+        return delegate.getURIs();
+    }
+
+    @Override
+    public boolean remove(URI uri, HttpCookie cookie)
+    {
+        return delegate.remove(uri, cookie);
+    }
+
+    @Override
+    public boolean removeAll()
+    {
+        return delegate.removeAll();
+    }
+
+    public static class Empty implements CookieStore
+    {
+        @Override
+        public void add(URI uri, HttpCookie cookie)
+        {
+        }
+
+        @Override
+        public List<HttpCookie> get(URI uri)
+        {
+            return Collections.emptyList();
+        }
+
+        @Override
+        public List<HttpCookie> getCookies()
+        {
+            return Collections.emptyList();
+        }
+
+        @Override
+        public List<URI> getURIs()
+        {
+            return Collections.emptyList();
+        }
+
+        @Override
+        public boolean remove(URI uri, HttpCookie cookie)
+        {
+            return false;
+        }
+
+        @Override
+        public boolean removeAll()
+        {
+            return false;
+        }
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/util/IO.java b/lib/jetty/org/eclipse/jetty/util/IO.java
new file mode 100644 (file)
index 0000000..51fbed9
--- /dev/null
@@ -0,0 +1,499 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.io.PrintWriter;
+import java.io.Reader;
+import java.io.StringWriter;
+import java.io.Writer;
+import java.nio.charset.Charset;
+
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/* ======================================================================== */
+/** IO Utilities.
+ * Provides stream handling utilities in
+ * singleton Threadpool implementation accessed by static members.
+ */
+public class IO 
+{
+    private static final Logger LOG = Log.getLogger(IO.class);
+    
+    /* ------------------------------------------------------------------- */
+    public final static String
+        CRLF      = "\015\012";
+
+    /* ------------------------------------------------------------------- */
+    public final static byte[]
+        CRLF_BYTES    = {(byte)'\015',(byte)'\012'};
+
+    /* ------------------------------------------------------------------- */
+    public static final int bufferSize = 64*1024;
+
+    /* ------------------------------------------------------------------- */
+    static class Job implements Runnable
+    {
+        InputStream in;
+        OutputStream out;
+        Reader read;
+        Writer write;
+
+        Job(InputStream in,OutputStream out)
+        {
+            this.in=in;
+            this.out=out;
+            this.read=null;
+            this.write=null;
+        }
+        Job(Reader read,Writer write)
+        {
+            this.in=null;
+            this.out=null;
+            this.read=read;
+            this.write=write;
+        }
+        
+        /* ------------------------------------------------------------ */
+        /* 
+         * @see java.lang.Runnable#run()
+         */
+        public void run()
+        {
+            try {
+                if (in!=null)
+                    copy(in,out,-1);
+                else
+                    copy(read,write,-1);
+            }
+            catch(IOException e)
+            {
+                LOG.ignore(e);
+                try{
+                    if (out!=null)
+                        out.close();
+                    if (write!=null)
+                        write.close();
+                }
+                catch(IOException e2)
+                {
+                    LOG.ignore(e2);
+                }
+            }
+        }
+    }
+    
+    /* ------------------------------------------------------------------- */
+    /** Copy Stream in to Stream out until EOF or exception.
+     */
+    public static void copy(InputStream in, OutputStream out)
+         throws IOException
+    {
+        copy(in,out,-1);
+    }
+    
+    /* ------------------------------------------------------------------- */
+    /** Copy Reader to Writer out until EOF or exception.
+     */
+    public static void copy(Reader in, Writer out)
+         throws IOException
+    {
+        copy(in,out,-1);
+    }
+    
+    /* ------------------------------------------------------------------- */
+    /** Copy Stream in to Stream for byteCount bytes or until EOF or exception.
+     */
+    public static void copy(InputStream in,
+                            OutputStream out,
+                            long byteCount)
+         throws IOException
+    {     
+        byte buffer[] = new byte[bufferSize];
+        int len=bufferSize;
+        
+        if (byteCount>=0)
+        {
+            while (byteCount>0)
+            {
+                int max = byteCount<bufferSize?(int)byteCount:bufferSize;
+                len=in.read(buffer,0,max);
+                
+                if (len==-1)
+                    break;
+                
+                byteCount -= len;
+                out.write(buffer,0,len);
+            }
+        }
+        else
+        {
+            while (true)
+            {
+                len=in.read(buffer,0,bufferSize);
+                if (len<0 )
+                    break;
+                out.write(buffer,0,len);
+            }
+        }
+    }  
+    
+    /* ------------------------------------------------------------------- */
+    /** Copy Reader to Writer for byteCount bytes or until EOF or exception.
+     */
+    public static void copy(Reader in,
+                            Writer out,
+                            long byteCount)
+         throws IOException
+    {  
+        char buffer[] = new char[bufferSize];
+        int len=bufferSize;
+        
+        if (byteCount>=0)
+        {
+            while (byteCount>0)
+            {
+                if (byteCount<bufferSize)
+                    len=in.read(buffer,0,(int)byteCount);
+                else
+                    len=in.read(buffer,0,bufferSize);                   
+                
+                if (len==-1)
+                    break;
+                
+                byteCount -= len;
+                out.write(buffer,0,len);
+            }
+        }
+        else if (out instanceof PrintWriter)
+        {
+            PrintWriter pout=(PrintWriter)out;
+            while (!pout.checkError())
+            {
+                len=in.read(buffer,0,bufferSize);
+                if (len==-1)
+                    break;
+                out.write(buffer,0,len);
+            }
+        }
+        else
+        {
+            while (true)
+            {
+                len=in.read(buffer,0,bufferSize);
+                if (len==-1)
+                    break;
+                out.write(buffer,0,len);
+            }
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Copy files or directories
+     * @param from
+     * @param to
+     * @throws IOException
+     */
+    public static void copy(File from,File to) throws IOException
+    {
+        if (from.isDirectory())
+            copyDir(from,to);
+        else
+            copyFile(from,to);
+    }
+
+    /* ------------------------------------------------------------ */
+    public static void copyDir(File from,File to) throws IOException
+    {
+        if (to.exists())
+        {
+            if (!to.isDirectory())
+                throw new IllegalArgumentException(to.toString());
+        }
+        else
+            to.mkdirs();
+        
+        File[] files = from.listFiles();
+        if (files!=null)
+        {
+            for (int i=0;i<files.length;i++)
+            {
+                String name = files[i].getName();
+                if (".".equals(name) || "..".equals(name))
+                    continue;
+                copy(files[i],new File(to,name));
+            }
+        }
+    }
+    
+    /* ------------------------------------------------------------ */
+    public static void copyFile(File from,File to) throws IOException
+    {
+        try (InputStream in=new FileInputStream(from);
+                OutputStream out=new FileOutputStream(to))
+        {
+            copy(in,out);
+        }
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** Read input stream to string.
+     */
+    public static String toString(InputStream in)
+        throws IOException
+    {
+        return toString(in,(Charset)null);
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** Read input stream to string.
+     */
+    public static String toString(InputStream in,String encoding)
+        throws IOException
+    {
+        return toString(in, encoding==null?null:Charset.forName(encoding));
+    }
+
+    /** Read input stream to string.
+     */
+    public static String toString(InputStream in, Charset encoding)
+            throws IOException
+    {
+        StringWriter writer=new StringWriter();
+        InputStreamReader reader = encoding==null?new InputStreamReader(in):new InputStreamReader(in,encoding);
+
+        copy(reader,writer);
+        return writer.toString();
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Read input stream to string.
+     */
+    public static String toString(Reader in)
+        throws IOException
+    {
+        StringWriter writer=new StringWriter();
+        copy(in,writer);
+        return writer.toString();
+    }
+
+
+    /* ------------------------------------------------------------ */
+    /** Delete File.
+     * This delete will recursively delete directories - BE CAREFULL
+     * @param file The file to be deleted.
+     */
+    public static boolean delete(File file)
+    {
+        if (!file.exists())
+            return false;
+        if (file.isDirectory())
+        {
+            File[] files = file.listFiles();
+            for (int i=0;files!=null && i<files.length;i++)
+                delete(files[i]);
+        }
+        return file.delete();
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * closes an input stream, and logs exceptions
+     *
+     * @param is the input stream to close
+     */
+    public static void close(InputStream is)
+    {
+        try
+        {
+            if (is != null)
+                is.close();
+        }
+        catch (IOException e)
+        {
+            LOG.ignore(e);
+        }
+    }
+
+    /**
+     * closes a reader, and logs exceptions
+     * 
+     * @param reader the reader to close
+     */
+    public static void close(Reader reader)
+    {
+        try
+        {
+            if (reader != null)
+                reader.close();
+        } catch (IOException e)
+        {
+            LOG.ignore(e);
+        }
+    }
+
+    /**
+     * closes a writer, and logs exceptions
+     * 
+     * @param writer the writer to close
+     */
+    public static void close(Writer writer)
+    {
+        try
+        {
+            if (writer != null)
+                writer.close();
+        } catch (IOException e)
+        {
+            LOG.ignore(e);
+        }
+    }
+    
+    /* ------------------------------------------------------------ */
+    public static byte[] readBytes(InputStream in)
+        throws IOException
+    {
+        ByteArrayOutputStream bout = new ByteArrayOutputStream();
+        copy(in,bout);
+        return bout.toByteArray();
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * closes an output stream, and logs exceptions
+     *
+     * @param os the output stream to close
+     */
+    public static void close(OutputStream os)
+    {
+        try
+        {
+            if (os != null)
+                os.close();
+        }
+        catch (IOException e)
+        {
+            LOG.ignore(e);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /** 
+     * @return An outputstream to nowhere
+     */
+    public static OutputStream getNullStream()
+    {
+        return __nullStream;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** 
+     * @return An outputstream to nowhere
+     */
+    public static InputStream getClosedStream()
+    {
+        return __closedStream;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    private static class NullOS extends OutputStream                                    
+    {
+        @Override
+        public void close(){}
+        @Override
+        public void flush(){}
+        @Override
+        public void write(byte[]b){}
+        @Override
+        public void write(byte[]b,int i,int l){}
+        @Override
+        public void write(int b){}
+    }
+    private static NullOS __nullStream = new NullOS();
+
+    
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    private static class ClosedIS extends InputStream                                    
+    {
+        @Override
+        public int read() throws IOException
+        {
+            return -1;
+        }
+    }
+    private static ClosedIS __closedStream = new ClosedIS();
+    
+    /* ------------------------------------------------------------ */
+    /** 
+     * @return An writer to nowhere
+     */
+    public static Writer getNullWriter()
+    {
+        return __nullWriter;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** 
+     * @return An writer to nowhere
+     */
+    public static PrintWriter getNullPrintWriter()
+    {
+        return __nullPrintWriter;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    private static class NullWrite extends Writer                                    
+    {
+        @Override
+        public void close(){}
+        @Override
+        public void flush(){}
+        @Override
+        public void write(char[]b){}
+        @Override
+        public void write(char[]b,int o,int l){}
+        @Override
+        public void write(int b){}
+        @Override
+        public void write(String s){}
+        @Override
+        public void write(String s,int o,int l){}
+    }
+    private static NullWrite __nullWriter = new NullWrite();
+    private static PrintWriter __nullPrintWriter = new PrintWriter(__nullWriter);
+}
+
+
+
+
+
+
+
+
+
diff --git a/lib/jetty/org/eclipse/jetty/util/IPAddressMap.java b/lib/jetty/org/eclipse/jetty/util/IPAddressMap.java
new file mode 100644 (file)
index 0000000..7cbbcab
--- /dev/null
@@ -0,0 +1,364 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util;
+
+import java.util.BitSet;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.StringTokenizer;
+
+
+/* ------------------------------------------------------------ */
+/**
+ * Internet address map to object
+ * <p>
+ * Internet addresses may be specified as absolute address or as a combination of 
+ * four octet wildcard specifications (a.b.c.d) that are defined as follows.
+ * </p>
+ * <pre>
+ * nnn - an absolute value (0-255)
+ * mmm-nnn - an inclusive range of absolute values, 
+ *           with following shorthand notations:
+ *           nnn- => nnn-255
+ *           -nnn => 0-nnn
+ *           -    => 0-255
+ * a,b,... - a list of wildcard specifications
+ * </pre>
+ */
+@SuppressWarnings("serial")
+public class IPAddressMap<TYPE> extends HashMap<String, TYPE>
+{
+    private final HashMap<String,IPAddrPattern> _patterns = new HashMap<String,IPAddrPattern>();
+
+    /* --------------------------------------------------------------- */
+    /** Construct empty IPAddressMap.
+     */
+    public IPAddressMap()
+    {
+        super(11);
+    }
+   
+    /* --------------------------------------------------------------- */
+    /** Construct empty IPAddressMap.
+     * 
+     * @param capacity initial capacity
+     */
+    public IPAddressMap(int capacity)
+    {
+        super (capacity);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Insert a new internet address into map
+     * 
+     * @see java.util.HashMap#put(java.lang.Object, java.lang.Object)
+     */
+    @Override
+    public TYPE put(String addrSpec, TYPE object)
+        throws IllegalArgumentException
+    {
+        if (addrSpec == null || addrSpec.trim().length() == 0)
+            throw new IllegalArgumentException("Invalid IP address pattern: "+addrSpec);
+        
+        String spec = addrSpec.trim();
+        if (_patterns.get(spec) == null)
+            _patterns.put(spec,new IPAddrPattern(spec));
+        
+        return super.put(spec, object);
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * Retrieve the object mapped to the specified internet address literal
+     * 
+     * @see java.util.HashMap#get(java.lang.Object)
+     */
+    @Override
+    public TYPE get(Object key)
+    {
+        return super.get(key);
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * Retrieve the first object that is associated with the specified 
+     * internet address by taking into account the wildcard specifications.
+     * 
+     * @param addr internet address
+     * @return associated object
+     */
+    public TYPE match(String addr)
+    {
+        Map.Entry<String, TYPE> entry = getMatch(addr);
+        return entry==null ? null : entry.getValue();
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * Retrieve the first map entry that is associated with the specified 
+     * internet address by taking into account the wildcard specifications.
+     * 
+     * @param addr internet address
+     * @return map entry associated
+     */
+    public Map.Entry<String, TYPE> getMatch(String addr)
+    {
+        if (addr != null)
+        {
+            for(Map.Entry<String, TYPE> entry: super.entrySet())
+            {
+                if (_patterns.get(entry.getKey()).match(addr))
+                {
+                    return entry;
+                }
+            }
+        }
+        return null;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * Retrieve a lazy list of map entries associated with specified
+     * internet address by taking into account the wildcard specifications.
+     * 
+     * @param addr  internet address
+     * @return lazy list of map entries
+     */
+    public Object getLazyMatches(String addr)
+    {
+        if (addr == null)
+            return LazyList.getList(super.entrySet());
+        
+        Object entries = null;
+        for(Map.Entry<String, TYPE> entry: super.entrySet())
+        {
+            if (_patterns.get(entry.getKey()).match(addr))
+            {
+                entries = LazyList.add(entries,entry);
+            }
+        }
+        return entries;        
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * IPAddrPattern
+     * 
+     * Represents internet address wildcard. 
+     * Matches the wildcard to provided internet address.
+     */
+    private static class IPAddrPattern
+    {
+        private final OctetPattern[] _octets = new OctetPattern[4];
+        /* ------------------------------------------------------------ */
+        /**
+         * Create new IPAddrPattern
+         * 
+         * @param value internet address wildcard specification
+         * @throws IllegalArgumentException if wildcard specification is invalid
+         */
+        public IPAddrPattern(String value)
+            throws IllegalArgumentException
+        {
+            if (value == null || value.trim().length() == 0)
+                throw new IllegalArgumentException("Invalid IP address pattern: "+value);
+                
+            try
+            {
+                StringTokenizer parts = new StringTokenizer(value, ".");
+                
+                String part;
+                for (int idx=0; idx<4; idx++)
+                {
+                    part = parts.hasMoreTokens() ? parts.nextToken().trim() : "0-255";
+                    
+                    int len = part.length();
+                    if (len == 0 && parts.hasMoreTokens())
+                        throw new IllegalArgumentException("Invalid IP address pattern: "+value);
+                    
+                    _octets[idx] = new OctetPattern(len==0 ? "0-255" : part);
+                }
+            }
+            catch (IllegalArgumentException ex)
+            {
+                throw new IllegalArgumentException("Invalid IP address pattern: "+value, ex);
+            }
+        }
+        
+        /* ------------------------------------------------------------ */
+        /**
+         * Match the specified internet address against the wildcard
+         * 
+         * @param value internet address
+         * @return true if specified internet address matches wildcard specification
+         * 
+         * @throws IllegalArgumentException if specified internet address is invalid
+         */
+        public boolean match(String value)
+            throws IllegalArgumentException
+        {
+            if (value == null || value.trim().length() == 0)
+                throw new IllegalArgumentException("Invalid IP address: "+value);
+            
+            try
+            {
+                StringTokenizer parts = new StringTokenizer(value, ".");
+                
+                boolean result = true;
+                for (int idx=0; idx<4; idx++)
+                {
+                    if (!parts.hasMoreTokens())
+                        throw new IllegalArgumentException("Invalid IP address: "+value);
+                        
+                    if (!(result &= _octets[idx].match(parts.nextToken())))
+                        break;
+                }
+                return result;
+            }
+            catch (IllegalArgumentException ex)
+            {
+                throw new IllegalArgumentException("Invalid IP address: "+value, ex);
+            }
+        }
+    }
+        
+    /* ------------------------------------------------------------ */
+    /**
+     * OctetPattern
+     * 
+     * Represents a single octet wildcard.
+     * Matches the wildcard to the specified octet value.
+     */
+    private static class OctetPattern extends BitSet
+    {
+        private final BitSet _mask = new BitSet(256);
+        
+        /* ------------------------------------------------------------ */
+        /**
+         * Create new OctetPattern
+         * 
+         * @param octetSpec octet wildcard specification
+         * @throws IllegalArgumentException if wildcard specification is invalid
+         */
+        public OctetPattern(String octetSpec)
+            throws IllegalArgumentException
+        {
+            try
+            {
+                if (octetSpec != null)
+                {
+                    String spec = octetSpec.trim();
+                    if(spec.length() == 0)
+                    {
+                        _mask.set(0,255);
+                    }
+                    else
+                    {
+                        StringTokenizer parts = new StringTokenizer(spec,",");
+                        while (parts.hasMoreTokens())
+                        {
+                            String part = parts.nextToken().trim();
+                            if (part.length() > 0)
+                            {
+                                if (part.indexOf('-') < 0)
+                                {
+                                    Integer value = Integer.valueOf(part);
+                                    _mask.set(value);
+                                }
+                                else
+                                {
+                                    int low = 0, high = 255;
+                                    
+                                    String[] bounds = part.split("-",-2);
+                                    if (bounds.length != 2)
+                                    {
+                                        throw new IllegalArgumentException("Invalid octet spec: "+octetSpec);
+                                    }
+                                    
+                                    if (bounds[0].length() > 0)
+                                    {
+                                        low = Integer.parseInt(bounds[0]);
+                                    }
+                                    if (bounds[1].length() > 0)
+                                    {
+                                        high = Integer.parseInt(bounds[1]);
+                                    }
+                                    
+                                    if (low > high)
+                                    {
+                                        throw new IllegalArgumentException("Invalid octet spec: "+octetSpec);
+                                    }
+                                    
+                                    _mask.set(low, high+1);
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+            catch (NumberFormatException ex)
+            {
+                throw new IllegalArgumentException("Invalid octet spec: "+octetSpec, ex);
+            }
+        }
+        
+        /* ------------------------------------------------------------ */
+        /**
+         * Match specified octet value against the wildcard
+         * 
+         * @param value octet value
+         * @return true if specified octet value matches the wildcard
+         * @throws IllegalArgumentException if specified octet value is invalid
+         */
+        public boolean match(String value)
+            throws IllegalArgumentException
+        {
+            if (value == null || value.trim().length() == 0)
+                throw new IllegalArgumentException("Invalid octet: "+value);
+
+            try
+            {
+                int number = Integer.parseInt(value);
+                return match(number);
+            }
+            catch (NumberFormatException ex)
+            {
+                throw new IllegalArgumentException("Invalid octet: "+value);
+            }
+        }
+        
+        /* ------------------------------------------------------------ */
+        /**
+         * Match specified octet value against the wildcard
+         * 
+         * @param number octet value
+         * @return true if specified octet value matches the wildcard
+         * @throws IllegalArgumentException if specified octet value is invalid
+         */
+        public boolean match(int number)
+            throws IllegalArgumentException
+        {
+            if (number < 0 || number > 255)
+                throw new IllegalArgumentException("Invalid octet: "+number);
+            
+            return _mask.get(number);
+        }
+    }   
+}
diff --git a/lib/jetty/org/eclipse/jetty/util/IntrospectionUtil.java b/lib/jetty/org/eclipse/jetty/util/IntrospectionUtil.java
new file mode 100644 (file)
index 0000000..8588675
--- /dev/null
@@ -0,0 +1,300 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Member;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * IntrospectionUtil
+ *
+ *
+ */
+public class IntrospectionUtil
+{
+    
+    public static boolean isJavaBeanCompliantSetter (Method method)
+    {
+        if (method == null)
+            return false;
+        
+        if (method.getReturnType() != Void.TYPE)
+            return false;
+        
+        if (!method.getName().startsWith("set"))
+            return false;
+        
+        if (method.getParameterTypes().length != 1)
+            return false;
+        
+        return true;
+    }
+    
+    public static Method findMethod (Class<?> clazz, String methodName, Class<?>[] args, boolean checkInheritance, boolean strictArgs)
+    throws NoSuchMethodException
+    {
+        if (clazz == null)
+            throw new NoSuchMethodException("No class");
+        if (methodName==null || methodName.trim().equals(""))
+            throw new NoSuchMethodException("No method name");
+        
+        Method method = null;
+        Method[] methods = clazz.getDeclaredMethods();
+        for (int i=0;i<methods.length && method==null;i++)
+        {
+            if (methods[i].getName().equals(methodName) && checkParams(methods[i].getParameterTypes(), (args==null?new Class[] {}:args), strictArgs))
+            {
+                method = methods[i];
+            }
+            
+        }
+        if (method!=null)
+        {
+            return method;
+        }
+        else if (checkInheritance)
+                return findInheritedMethod(clazz.getPackage(), clazz.getSuperclass(), methodName, args, strictArgs);
+        else
+            throw new NoSuchMethodException("No such method "+methodName+" on class "+clazz.getName());
+
+    }
+    
+    
+    
+    
+
+    public static Field findField (Class<?> clazz, String targetName, Class<?> targetType, boolean checkInheritance, boolean strictType)
+    throws NoSuchFieldException
+    {
+        if (clazz == null)
+            throw new NoSuchFieldException("No class");
+        if (targetName==null)
+            throw new NoSuchFieldException("No field name");
+        
+        try
+        {
+            Field field = clazz.getDeclaredField(targetName);
+            if (strictType)
+            {
+                if (field.getType().equals(targetType))
+                    return field;
+            }
+            else
+            {
+                if (field.getType().isAssignableFrom(targetType))
+                    return field;
+            }
+            if (checkInheritance)
+            {
+                    return findInheritedField(clazz.getPackage(), clazz.getSuperclass(), targetName, targetType, strictType);
+            }
+            else
+                throw new NoSuchFieldException("No field with name "+targetName+" in class "+clazz.getName()+" of type "+targetType);
+        }
+        catch (NoSuchFieldException e)
+        {
+            return findInheritedField(clazz.getPackage(),clazz.getSuperclass(), targetName,targetType,strictType);
+        }
+    }
+    
+    
+    
+    
+    
+    public static boolean isInheritable (Package pack, Member member)
+    {
+        if (pack==null)
+            return false;
+        if (member==null)
+            return false;
+        
+        int modifiers = member.getModifiers();
+        if (Modifier.isPublic(modifiers))
+            return true;
+        if (Modifier.isProtected(modifiers))
+            return true;
+        if (!Modifier.isPrivate(modifiers) && pack.equals(member.getDeclaringClass().getPackage()))
+            return true;
+       
+        return false;
+    }
+    
+   
+    
+    
+    public static boolean checkParams (Class<?>[] formalParams, Class<?>[] actualParams, boolean strict)
+    {
+        if (formalParams==null)
+            return actualParams==null;
+        if (actualParams==null)
+            return false;
+
+        if (formalParams.length!=actualParams.length)
+            return false;
+
+        if (formalParams.length==0)
+            return true; 
+        
+        int j=0;
+        if (strict)
+        {
+            while (j<formalParams.length && formalParams[j].equals(actualParams[j]))
+                j++;
+        }
+        else
+        { 
+            while ((j<formalParams.length) && (formalParams[j].isAssignableFrom(actualParams[j])))
+            {
+                j++;
+            }
+        }
+
+        if (j!=formalParams.length)
+        {
+            return false;
+        }
+
+        return true;
+    }
+    
+    
+    public static boolean isSameSignature (Method methodA, Method methodB)
+    {
+        if (methodA==null)
+            return false;
+        if (methodB==null)
+            return false;
+        
+        List<Class<?>> parameterTypesA = Arrays.asList(methodA.getParameterTypes());
+        List<Class<?>> parameterTypesB = Arrays.asList(methodB.getParameterTypes());
+       
+        if (methodA.getName().equals(methodB.getName())
+            &&
+            parameterTypesA.containsAll(parameterTypesB))
+            return true;
+        
+        return false;
+    }
+    
+    public static boolean isTypeCompatible (Class<?> formalType, Class<?> actualType, boolean strict)
+    {
+        if (formalType==null)
+            return actualType==null;
+        if (actualType==null)
+            return false;
+        
+        if (strict)
+            return formalType.equals(actualType);
+        else
+            return formalType.isAssignableFrom(actualType);
+    }
+
+    
+    
+    
+    public static boolean containsSameMethodSignature (Method method, Class<?> c, boolean checkPackage)
+    {
+        if (checkPackage)
+        {
+            if (!c.getPackage().equals(method.getDeclaringClass().getPackage()))
+                return false;
+        }
+        
+        boolean samesig = false;
+        Method[] methods = c.getDeclaredMethods();
+        for (int i=0; i<methods.length && !samesig; i++)
+        {
+            if (IntrospectionUtil.isSameSignature(method, methods[i]))
+                samesig = true;
+        }
+        return samesig;
+    }
+    
+    
+    public static boolean containsSameFieldName(Field field, Class<?> c, boolean checkPackage)
+    {
+        if (checkPackage)
+        {
+            if (!c.getPackage().equals(field.getDeclaringClass().getPackage()))
+                return false;
+        }
+        
+        boolean sameName = false;
+        Field[] fields = c.getDeclaredFields();
+        for (int i=0;i<fields.length && !sameName; i++)
+        {
+            if (fields[i].getName().equals(field.getName()))
+                sameName = true;
+        }
+        return sameName;
+    }
+    
+    
+    
+    protected static Method findInheritedMethod (Package pack, Class<?> clazz, String methodName, Class<?>[] args, boolean strictArgs)
+    throws NoSuchMethodException
+    {
+        if (clazz==null)
+            throw new NoSuchMethodException("No class");
+        if (methodName==null)
+            throw new NoSuchMethodException("No method name");
+        
+        Method method = null;
+        Method[] methods = clazz.getDeclaredMethods();
+        for (int i=0;i<methods.length && method==null;i++)
+        {
+            if (methods[i].getName().equals(methodName) 
+                    && isInheritable(pack,methods[i])
+                    && checkParams(methods[i].getParameterTypes(), args, strictArgs))
+                method = methods[i];
+        }
+        if (method!=null)
+        {
+            return method;
+        }
+        else
+            return findInheritedMethod(clazz.getPackage(), clazz.getSuperclass(), methodName, args, strictArgs);
+    }
+    
+    protected static Field findInheritedField (Package pack, Class<?> clazz, String fieldName, Class<?> fieldType, boolean strictType)
+    throws NoSuchFieldException
+    {
+        if (clazz==null)
+            throw new NoSuchFieldException ("No class");
+        if (fieldName==null)
+            throw new NoSuchFieldException ("No field name");
+        try
+        {
+            Field field = clazz.getDeclaredField(fieldName);
+            if (isInheritable(pack, field) && isTypeCompatible(fieldType, field.getType(), strictType))
+                return field;
+            else
+                return findInheritedField(clazz.getPackage(), clazz.getSuperclass(),fieldName, fieldType, strictType);
+        }
+        catch (NoSuchFieldException e)
+        {
+            return findInheritedField(clazz.getPackage(), clazz.getSuperclass(),fieldName, fieldType, strictType); 
+        }
+    }
+    
+}
diff --git a/lib/jetty/org/eclipse/jetty/util/IteratingCallback.java b/lib/jetty/org/eclipse/jetty/util/IteratingCallback.java
new file mode 100644 (file)
index 0000000..1c74d5e
--- /dev/null
@@ -0,0 +1,374 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util;
+
+import java.util.concurrent.atomic.AtomicReference;
+
+/**
+ * This specialized callback implements a pattern that allows
+ * a large job to be broken into smaller tasks using iteration
+ * rather than recursion.
+ * <p/>
+ * A typical example is the write of a large content to a socket,
+ * divided in chunks. Chunk C1 is written by thread T1, which
+ * also invokes the callback, which writes chunk C2, which invokes
+ * the callback again, which writes chunk C3, and so forth.
+ * <p/>
+ * The problem with the example is that if the callback thread
+ * is the same that performs the I/O operation, then the process
+ * is recursive and may result in a stack overflow.
+ * To avoid the stack overflow, a thread dispatch must be performed,
+ * causing context switching and cache misses, affecting performance.
+ * <p/>
+ * To avoid this issue, this callback uses an AtomicReference to
+ * record whether success callback has been called during the processing
+ * of a sub task, and if so then the processing iterates rather than
+ * recurring.
+ * <p/>
+ * Subclasses must implement method {@link #process()} where the sub
+ * task is executed and a suitable {@link IteratingCallback.Action} is
+ * returned to this callback to indicate the overall progress of the job.
+ * This callback is passed to the asynchronous execution of each sub
+ * task and a call the {@link #succeeded()} on this callback represents
+ * the completion of the sub task.
+ */
+public abstract class IteratingCallback implements Callback
+{
+    /**
+     * The indication of the overall progress of the overall job that
+     * implementations of {@link #process()} must return.
+     */
+    protected enum Action
+    {
+        /**
+         * Indicates that {@link #process()} has no more work to do,
+         * but the overall job is not completed yet, probably waiting
+         * for additional events to trigger more work.
+         */
+        IDLE,
+        /**
+         * Indicates that {@link #process()} is executing asynchronously
+         * a sub task, where the execution has started but the callback
+         * may have not yet been invoked.
+         */
+        SCHEDULED,
+        /**
+         * Indicates that {@link #process()} has completed the overall job.
+         */
+        SUCCEEDED,
+        /**
+         * Indicates that {@link #process()} has failed the overall job.
+         */
+        FAILED
+    }
+
+    private final AtomicReference<State> _state = new AtomicReference<>(State.INACTIVE);
+
+    /**
+     * Method called by {@link #iterate()} to process the sub task.
+     * <p/>
+     * Implementations must start the asynchronous execution of the sub task
+     * (if any) and return an appropriate action:
+     * <ul>
+     * <li>{@link Action#IDLE} when no sub tasks are available for execution
+     * but the overall job is not completed yet</li>
+     * <li>{@link Action#SCHEDULED} when the sub task asynchronous execution
+     * has been started</li>
+     * <li>{@link Action#SUCCEEDED} when the overall job is completed</li>
+     * <li>{@link Action#FAILED} when the overall job cannot be completed</li>
+     * </ul>
+     *
+     * @throws Exception if the sub task processing throws
+     */
+    protected abstract Action process() throws Exception;
+
+    /**
+     * Invoked when the overall task has completed successfully.
+     */
+    protected abstract void completed();
+
+    /**
+     * This method must be invoked by applications to start the processing
+     * of sub tasks.
+     * <p/>
+     * If {@link #process()} returns {@link Action#IDLE}, then this method
+     * should be called again to restart processing.
+     * It is safe to call iterate multiple times from multiple threads since only
+     * the first thread to move the state out of INACTIVE will actually do any iteration
+     * and processing.
+     */
+    public void iterate()
+    {
+        try
+        {
+            while (true)
+            {
+                switch (_state.get())
+                {
+                    case INACTIVE:
+                    {
+                        if (processIterations())
+                            return;
+                        break;
+                    }
+                    case ITERATING:
+                    {
+                        if (_state.compareAndSet(State.ITERATING, State.ITERATE_AGAIN))
+                            return;
+                        break;
+                    }
+                    default:
+                    {
+                        return;
+                    }
+                }
+            }
+        }
+        catch (Throwable x)
+        {
+            failed(x);
+        }
+    }
+
+    private boolean processIterations() throws Exception
+    {
+        // Keeps iterating as long as succeeded() is called during process().
+        // If we are in INACTIVE state, either this is the first iteration or
+        // succeeded()/failed() were called already.
+        while (_state.compareAndSet(State.INACTIVE, State.ITERATING))
+        {
+            // Method process() can only be called by one thread at a time because
+            // it is guarded by the CaS above. However, the case blocks below may
+            // be executed concurrently in this case: T1 calls process() which
+            // executes the asynchronous sub task, which calls succeeded(), which
+            // moves the state into INACTIVE, then returns SCHEDULED; T2 calls
+            // iterate(), state is now INACTIVE and process() is called again and
+            // returns another action. Now we have 2 threads that may execute the
+            // action case blocks below concurrently; therefore each case block
+            // has to be prepared to fail the CaS it's doing.
+
+            Action action = process();
+            switch (action)
+            {
+                case IDLE:
+                {
+                    // No more progress can be made.
+                    if (_state.compareAndSet(State.ITERATING, State.INACTIVE))
+                        return true;
+
+                    // Was iterate() called again since we already decided to go INACTIVE ?
+                    // If so, try another iteration as more work may have been added
+                    // while the previous call to process() was returning.
+                    if (_state.compareAndSet(State.ITERATE_AGAIN, State.INACTIVE))
+                        continue;
+
+                    // State may have changed concurrently, try again.
+                    continue;
+                }
+                case SCHEDULED:
+                {
+                    // The sub task is executing, and the callback for it may or
+                    // may not have already been called yet, which we figure out below.
+                    // Can double CaS here because state never changes directly ITERATING_AGAIN --> ITERATE.
+                    if (_state.compareAndSet(State.ITERATING, State.ACTIVE) ||
+                            _state.compareAndSet(State.ITERATE_AGAIN, State.ACTIVE))
+                        // Not called back yet, so wait.
+                        return true;
+                    // Call back must have happened, so iterate.
+                    continue;
+                }
+                case SUCCEEDED:
+                {
+                    // The overall job has completed.
+                    if (completeSuccess())
+                        completed();
+                    return true;
+                }
+                case FAILED:
+                {
+                    completeFailure();
+                    return true;
+                }
+                default:
+                {
+                    throw new IllegalStateException(toString());
+                }
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Invoked when the sub task succeeds.
+     * Subclasses that override this method must always remember to call
+     * {@code super.succeeded()}.
+     */
+    @Override
+    public void succeeded()
+    {
+        while (true)
+        {
+            State current = _state.get();
+            switch (current)
+            {
+                case ITERATE_AGAIN:
+                case ITERATING:
+                {
+                    if (_state.compareAndSet(current, State.INACTIVE))
+                        return;
+                    continue;
+                }
+                case ACTIVE:
+                {
+                    // If we can move from ACTIVE to INACTIVE
+                    // then we are responsible to call iterate().
+                    if (_state.compareAndSet(current, State.INACTIVE))
+                        iterate();
+                    // If we can't CaS, then failed() must have been
+                    // called, and we just return.
+                    return;
+                }
+                case INACTIVE:
+                {
+                    // Support the case where the callback is scheduled
+                    // externally without a call to iterate().
+                    iterate();
+                    return;
+                }
+                default:
+                {
+                    throw new IllegalStateException(toString());
+                }
+            }
+        }
+    }
+
+    /**
+     * Invoked when the sub task fails.
+     * Subclasses that override this method must always remember to call
+     * {@code super.failed(Throwable)}.
+     */
+    @Override
+    public void failed(Throwable x)
+    {
+        completeFailure();
+    }
+
+    private boolean completeSuccess()
+    {
+        while (true)
+        {
+            State current = _state.get();
+            if (current == State.FAILED)
+            {
+                // Success arrived too late, sorry.
+                return false;
+            }
+            else
+            {
+                if (_state.compareAndSet(current, State.SUCCEEDED))
+                    return true;
+            }
+        }
+    }
+
+    private void completeFailure()
+    {
+        while (true)
+        {
+            State current = _state.get();
+            if (current == State.SUCCEEDED)
+            {
+                // Failed arrived too late, sorry.
+                return;
+            }
+            else
+            {
+                if (_state.compareAndSet(current, State.FAILED))
+                    break;
+            }
+        }
+    }
+
+    /**
+     * @return whether this callback is idle and {@link #iterate()} needs to be called
+     */
+    public boolean isIdle()
+    {
+        return _state.get() == State.INACTIVE;
+    }
+
+    /**
+     * @return whether this callback has failed
+     */
+    public boolean isFailed()
+    {
+        return _state.get() == State.FAILED;
+    }
+
+    /**
+     * @return whether this callback has succeeded
+     */
+    public boolean isSucceeded()
+    {
+        return _state.get() == State.SUCCEEDED;
+    }
+
+    @Override
+    public String toString()
+    {
+        return String.format("%s[%s]", super.toString(), _state);
+    }
+
+    /**
+     * The internal states of this callback
+     */
+    private enum State
+    {
+        /**
+         * This callback is inactive, ready to iterate.
+         */
+        INACTIVE,
+        /**
+         * This callback is iterating and {@link #process()} has scheduled an
+         * asynchronous operation by returning {@link Action#SCHEDULED}, but
+         * the operation is still undergoing.
+         */
+        ACTIVE,
+        /**
+         * This callback is iterating and {@link #process()} has been called
+         * but not returned yet.
+         */
+        ITERATING,
+        /**
+         * While this callback was iterating, another request for iteration
+         * has been issued, so the iteration must continue even if a previous
+         * call to {@link #process()} returned {@link Action#IDLE}.
+         */
+        ITERATE_AGAIN,
+        /**
+         * The overall job has succeeded.
+         */
+        SUCCEEDED,
+        /**
+         * The overall job has failed.
+         */
+        FAILED
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/util/IteratingNestedCallback.java b/lib/jetty/org/eclipse/jetty/util/IteratingNestedCallback.java
new file mode 100644 (file)
index 0000000..e018f43
--- /dev/null
@@ -0,0 +1,68 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util;
+
+
+/* ------------------------------------------------------------ */
+/** Iterating Nested Callback.
+ * <p>This specialized callback is used when breaking up an
+ * asynchronous task into smaller asynchronous tasks.  A typical pattern
+ * is that a successful callback is used to schedule the next sub task, but 
+ * if that task completes quickly and uses the calling thread to callback
+ * the success notification, this can result in a growing stack depth.
+ * </p>
+ * <p>To avoid this issue, this callback uses an AtomicBoolean to note 
+ * if the success callback has been called during the processing of a 
+ * sub task, and if so then the processing iterates rather than recurses.
+ * </p>
+ * <p>This callback is passed to the asynchronous handling of each sub
+ * task and a call the {@link #succeeded()} on this call back represents
+ * completion of the subtask.  Only once all the subtasks are completed is 
+ * the {@link Callback#succeeded()} method called on the {@link Callback} instance
+ * passed the the {@link #IteratingNestedCallback(Callback)} constructor.</p>
+ *  
+ */
+public abstract class IteratingNestedCallback extends IteratingCallback
+{
+    final Callback _callback;
+    
+    public IteratingNestedCallback(Callback callback)
+    {
+        _callback=callback;
+    }
+    
+    @Override
+    protected void completed()
+    {
+        _callback.succeeded();
+    }
+
+    @Override
+    public void failed(Throwable x)
+    {
+        super.failed(x);
+        _callback.failed(x);
+    }
+
+    @Override
+    public String toString()
+    {
+        return String.format("%s@%x",getClass().getSimpleName(),hashCode());
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/util/Jetty.java b/lib/jetty/org/eclipse/jetty/util/Jetty.java
new file mode 100644 (file)
index 0000000..5a9b257
--- /dev/null
@@ -0,0 +1,39 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util;
+
+public class Jetty
+{
+    public static final String VERSION;
+
+    static
+    {
+        Package pkg = Jetty.class.getPackage();
+        if (pkg != null &&
+                "Eclipse.org - Jetty".equals(pkg.getImplementationVendor()) &&
+                pkg.getImplementationVersion() != null)
+            VERSION = pkg.getImplementationVersion();
+        else
+            VERSION = System.getProperty("jetty.version", "9.2.z-SNAPSHOT");
+    }
+
+    private Jetty()
+    {
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/util/LazyList.java b/lib/jetty/org/eclipse/jetty/util/LazyList.java
new file mode 100644 (file)
index 0000000..6ff416e
--- /dev/null
@@ -0,0 +1,451 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util;
+
+import java.io.Serializable;
+import java.lang.reflect.Array;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.ListIterator;
+
+/* ------------------------------------------------------------ */
+/** Lazy List creation.
+ * A List helper class that attempts to avoid unnecessary List
+ * creation.   If a method needs to create a List to return, but it is
+ * expected that this will either be empty or frequently contain a
+ * single item, then using LazyList will avoid additional object
+ * creations by using {@link Collections#EMPTY_LIST} or
+ * {@link Collections#singletonList(Object)} where possible.
+ * <p>
+ * LazyList works by passing an opaque representation of the list in
+ * and out of all the LazyList methods.  This opaque object is either
+ * null for an empty list, an Object for a list with a single entry
+ * or an {@link ArrayList} for a list of items.
+ *
+ * <p><h4>Usage</h4>
+ * <pre>
+ *   Object lazylist =null;
+ *   while(loopCondition)
+ *   {
+ *     Object item = getItem();
+ *     if (item.isToBeAdded())
+ *         lazylist = LazyList.add(lazylist,item);
+ *   }
+ *   return LazyList.getList(lazylist);
+ * </pre>
+ *
+ * An ArrayList of default size is used as the initial LazyList.
+ *
+ * @see java.util.List
+ */
+@SuppressWarnings("serial")
+public class LazyList
+    implements Cloneable, Serializable
+{
+    private static final String[] __EMTPY_STRING_ARRAY = new String[0];
+    
+    /* ------------------------------------------------------------ */
+    private LazyList()
+    {}
+    
+    /* ------------------------------------------------------------ */
+    /** Add an item to a LazyList 
+     * @param list The list to add to or null if none yet created.
+     * @param item The item to add.
+     * @return The lazylist created or added to.
+     */
+    @SuppressWarnings("unchecked")
+    public static Object add(Object list, Object item)
+    {
+        if (list==null)
+        {
+            if (item instanceof List || item==null)
+            {
+                List<Object> l = new ArrayList<Object>();
+                l.add(item);
+                return l;
+            }
+
+            return item;
+        }
+
+        if (list instanceof List)
+        {
+            ((List<Object>)list).add(item);
+            return list;
+        }
+
+        List<Object> l=new ArrayList<Object>();
+        l.add(list);
+        l.add(item);
+        return l;    
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Add an item to a LazyList 
+     * @param list The list to add to or null if none yet created.
+     * @param index The index to add the item at.
+     * @param item The item to add.
+     * @return The lazylist created or added to.
+     */
+    @SuppressWarnings("unchecked")
+    public static Object add(Object list, int index, Object item)
+    {
+        if (list==null)
+        {
+            if (index>0 || item instanceof List || item==null)
+            {
+                List<Object> l = new ArrayList<Object>();
+                l.add(index,item);
+                return l;
+            }
+            return item;
+        }
+
+        if (list instanceof List)
+        {
+            ((List<Object>)list).add(index,item);
+            return list;
+        }
+
+        List<Object> l=new ArrayList<Object>();
+        l.add(list);
+        l.add(index,item);
+        return l;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** Add the contents of a Collection to a LazyList
+     * @param list The list to add to or null if none yet created.
+     * @param collection The Collection whose contents should be added.
+     * @return The lazylist created or added to.
+     */
+    public static Object addCollection(Object list, Collection<?> collection)
+    {
+        Iterator<?> i=collection.iterator();
+        while(i.hasNext())
+            list=LazyList.add(list,i.next());
+        return list;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** Add the contents of an array to a LazyList
+     * @param list The list to add to or null if none yet created.
+     * @param array The array whose contents should be added.
+     * @return The lazylist created or added to.
+     */
+    public static Object addArray(Object list, Object[] array)
+    {
+        for(int i=0;array!=null && i<array.length;i++)
+            list=LazyList.add(list,array[i]);
+        return list;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Ensure the capacity of the underlying list.
+     * 
+     */
+    public static Object ensureSize(Object list, int initialSize)
+    {
+        if (list==null)
+            return new ArrayList<Object>(initialSize);
+        if (list instanceof ArrayList)
+        {
+            ArrayList<?> ol=(ArrayList<?>)list;
+            if (ol.size()>initialSize)
+                return ol;
+            ArrayList<Object> nl = new ArrayList<Object>(initialSize);
+            nl.addAll(ol);
+            return nl;
+        }
+        List<Object> l= new ArrayList<Object>(initialSize);
+        l.add(list);
+        return l;    
+    }
+
+    /* ------------------------------------------------------------ */
+    public static Object remove(Object list, Object o)
+    {
+        if (list==null)
+            return null;
+
+        if (list instanceof List)
+        {
+            List<?> l = (List<?>)list;
+            l.remove(o);
+            if (l.size()==0)
+                return null;
+            return list;
+        }
+
+        if (list.equals(o))
+            return null;
+        return list;
+    }
+    
+    /* ------------------------------------------------------------ */
+    public static Object remove(Object list, int i)
+    {
+        if (list==null)
+            return null;
+
+        if (list instanceof List)
+        {
+            List<?> l = (List<?>)list;
+            l.remove(i);
+            if (l.size()==0)
+                return null;
+            return list;
+        }
+
+        if (i==0)
+            return null;
+        return list;
+    }
+    
+    
+    
+    /* ------------------------------------------------------------ */
+    /** Get the real List from a LazyList.
+     * 
+     * @param list A LazyList returned from LazyList.add(Object)
+     * @return The List of added items, which may be an EMPTY_LIST
+     * or a SingletonList.
+     */
+    public static<E> List<E> getList(Object list)
+    {
+        return getList(list,false);
+    }
+    
+
+    /* ------------------------------------------------------------ */
+    /** Get the real List from a LazyList.
+     * 
+     * @param list A LazyList returned from LazyList.add(Object) or null
+     * @param nullForEmpty If true, null is returned instead of an
+     * empty list.
+     * @return The List of added items, which may be null, an EMPTY_LIST
+     * or a SingletonList.
+     */
+    @SuppressWarnings("unchecked")
+    public static<E> List<E> getList(Object list, boolean nullForEmpty)
+    {
+        if (list==null)
+        {
+            if (nullForEmpty)
+                return null;
+            return Collections.emptyList();
+        }
+        if (list instanceof List)
+            return (List<E>)list;
+        
+        return (List<E>)Collections.singletonList(list);
+    }
+    
+    /**
+     * Simple utility method to test if List has at least 1 entry.
+     * 
+     * @param list
+     *            a LazyList, {@link List} or {@link Object}
+     * @return true if not-null and is not empty
+     */
+    public static boolean hasEntry(Object list)
+    {
+        if (list == null)
+            return false;
+        if (list instanceof List)
+            return !((List<?>)list).isEmpty();
+        return true;
+    }
+    
+    /**
+     * Simple utility method to test if List is empty
+     * 
+     * @param list
+     *            a LazyList, {@link List} or {@link Object}
+     * @return true if null or is empty
+     */
+    public static boolean isEmpty(Object list)
+    {
+        if (list == null)
+            return true;
+        if (list instanceof List)
+            return ((List<?>)list).isEmpty();
+        return false;
+    }
+
+    
+    /* ------------------------------------------------------------ */
+    public static String[] toStringArray(Object list)
+    {
+        if (list==null)
+            return __EMTPY_STRING_ARRAY;
+        
+        if (list instanceof List)
+        {
+            List<?> l = (List<?>)list;
+            String[] a = new String[l.size()];
+            for (int i=l.size();i-->0;)
+            {
+                Object o=l.get(i);
+                if (o!=null)
+                    a[i]=o.toString();
+            }
+            return a;
+        }
+        
+        return new String[] {list.toString()};
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Convert a lazylist to an array
+     * @param list The list to convert
+     * @param clazz The class of the array, which may be a primitive type
+     * @return array of the lazylist entries passed in
+     */
+    public static Object toArray(Object list,Class<?> clazz)
+    {
+        if (list==null)
+            return Array.newInstance(clazz,0);
+        
+        if (list instanceof List)
+        {
+            List<?> l = (List<?>)list;
+            if (clazz.isPrimitive())
+            {
+                Object a = Array.newInstance(clazz,l.size());
+                for (int i=0;i<l.size();i++)
+                    Array.set(a,i,l.get(i));
+                return a;
+            }
+            return l.toArray((Object[])Array.newInstance(clazz,l.size()));
+            
+        }
+        
+        Object a = Array.newInstance(clazz,1);
+        Array.set(a,0,list);
+        return a;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** The size of a lazy List 
+     * @param list  A LazyList returned from LazyList.add(Object) or null
+     * @return the size of the list.
+     */
+    public static int size(Object list)
+    {
+        if (list==null)
+            return 0;
+        if (list instanceof List)
+            return ((List<?>)list).size();
+        return 1;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** Get item from the list 
+     * @param list  A LazyList returned from LazyList.add(Object) or null
+     * @param i int index
+     * @return the item from the list.
+     */
+    @SuppressWarnings("unchecked")
+    public static <E> E get(Object list, int i)
+    {
+        if (list==null)
+            throw new IndexOutOfBoundsException();
+       
+        if (list instanceof List)
+            return (E)((List<?>)list).get(i);
+
+        if (i==0)
+            return (E)list;
+        
+        throw new IndexOutOfBoundsException();
+    }
+    
+    /* ------------------------------------------------------------ */
+    public static boolean contains(Object list,Object item)
+    {
+        if (list==null)
+            return false;
+        
+        if (list instanceof List)
+            return ((List<?>)list).contains(item);
+
+        return list.equals(item);
+    }
+    
+
+    /* ------------------------------------------------------------ */
+    public static Object clone(Object list)
+    {
+        if (list==null)
+            return null;
+        if (list instanceof List)
+            return new ArrayList<Object>((List<?>)list);
+        return list;
+    }
+    
+    /* ------------------------------------------------------------ */
+    public static String toString(Object list)
+    {
+        if (list==null)
+            return "[]";
+        if (list instanceof List)
+            return list.toString();
+        return "["+list+"]";
+    }
+
+    /* ------------------------------------------------------------ */
+    @SuppressWarnings("unchecked")
+    public static<E> Iterator<E> iterator(Object list)
+    {
+        if (list==null)
+        {
+            List<E> empty=Collections.emptyList();
+            return empty.iterator();
+        }
+        if (list instanceof List)
+        {
+            return ((List<E>)list).iterator();
+        }
+        List<E> l=getList(list);
+        return l.iterator();
+    }
+    
+    /* ------------------------------------------------------------ */
+    @SuppressWarnings("unchecked")
+    public static<E> ListIterator<E> listIterator(Object list)
+    {
+        if (list==null)
+        {
+            List<E> empty=Collections.emptyList();
+            return empty.listIterator();
+        }
+        if (list instanceof List)
+            return ((List<E>)list).listIterator();
+
+        List<E> l=getList(list);
+        return l.listIterator();
+    }
+    
+}
+
diff --git a/lib/jetty/org/eclipse/jetty/util/LeakDetector.java b/lib/jetty/org/eclipse/jetty/util/LeakDetector.java
new file mode 100644 (file)
index 0000000..b0ac94e
--- /dev/null
@@ -0,0 +1,201 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util;
+
+import java.lang.ref.PhantomReference;
+import java.lang.ref.ReferenceQueue;
+import java.lang.ref.WeakReference;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+import org.eclipse.jetty.util.component.AbstractLifeCycle;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/**
+ * A facility to detect improper usage of resource pools.
+ * <p>
+ * Resource pools usually have a method to acquire a pooled resource
+ * and a method to released it back to the pool.
+ * <p>
+ * To detect if client code acquires a resource but never releases it,
+ * the resource pool can be modified to use a {@link LeakDetector}.
+ * The modified resource pool should call {@link #acquired(Object)} every time
+ * the method to acquire a resource is called, and {@link #released(Object)}
+ * every time the method to release the resource is called.
+ * {@link LeakDetector} keeps track of these resources and invokes method
+ * {@link #leaked(org.eclipse.jetty.util.LeakDetector.LeakInfo)} when it detects that a resource
+ * has been leaked (that is, acquired but never released).
+ * <p>
+ * To detect whether client code releases a resource without having
+ * acquired it, the resource pool can be modified to check the return value
+ * of {@link #released(Object)}: if false, it means that the resource was
+ * not acquired.
+ * <p>
+ * IMPLEMENTATION NOTES
+ * <p>
+ * This class relies on {@link System#identityHashCode(Object)} to create
+ * a unique id for each resource passed to {@link #acquired(Object)} and
+ * {@link #released(Object)}. {@link System#identityHashCode(Object)} does
+ * not guarantee that it will not generate the same number for different
+ * objects, but in practice the chance of collision is rare.
+ * <p>
+ * {@link LeakDetector} uses {@link PhantomReference}s to detect leaks.
+ * {@link PhantomReference}s are enqueued in their {@link ReferenceQueue}
+ * <em>after</em> they have been garbage collected (differently from
+ * {@link WeakReference}s that are enqueued <em>before</em>).
+ * Since the resource is now garbage collected, {@link LeakDetector} checks
+ * whether it has been released and if not, it reports a leak.
+ * Using {@link PhantomReference}s is better than overriding {@link #finalize()}
+ * and works also in those cases where {@link #finalize()} is not
+ * overridable.
+ *
+ * @param <T> the resource type.
+ */
+public class LeakDetector<T> extends AbstractLifeCycle implements Runnable
+{
+    private static final Logger LOG = Log.getLogger(LeakDetector.class);
+
+    private final ReferenceQueue<T> queue = new ReferenceQueue<>();
+    private final ConcurrentMap<String, LeakInfo> resources = new ConcurrentHashMap<>();
+    private Thread thread;
+
+    /**
+     * Tracks the resource as been acquired.
+     *
+     * @param resource the resource that has been acquired
+     * @return whether the resource has been tracked
+     * @see #released(Object)
+     */
+    public boolean acquired(T resource)
+    {
+        String id = id(resource);
+        return resources.putIfAbsent(id, new LeakInfo(resource, id)) == null;
+    }
+
+    /**
+     * Tracks the resource as been released.
+     *
+     * @param resource the resource that has been released
+     * @return whether the resource has been acquired
+     * @see #acquired(Object)
+     */
+    public boolean released(T resource)
+    {
+        String id = id(resource);
+        return resources.remove(id) != null;
+    }
+
+    /**
+     * Generates a unique ID for the given resource.
+     *
+     * @param resource the resource to generate the unique ID for
+     * @return the unique ID of the given resource
+     */
+    protected String id(T resource)
+    {
+        return String.valueOf(System.identityHashCode(resource));
+    }
+
+    @Override
+    protected void doStart() throws Exception
+    {
+        super.doStart();
+        thread = new Thread(this, getClass().getSimpleName());
+        thread.setDaemon(true);
+        thread.start();
+    }
+
+    @Override
+    protected void doStop() throws Exception
+    {
+        thread.interrupt();
+        super.doStop();
+    }
+
+    @Override
+    public void run()
+    {
+        try
+        {
+            while (isRunning())
+            {
+                @SuppressWarnings("unchecked")
+                LeakInfo leakInfo = (LeakInfo)queue.remove();
+                LOG.debug("Resource GC'ed: {}", leakInfo);
+                if (resources.remove(leakInfo.id) != null)
+                    leaked(leakInfo);
+            }
+        }
+        catch (InterruptedException x)
+        {
+            // Exit
+        }
+    }
+
+    /**
+     * Callback method invoked by {@link LeakDetector} when it detects that a resource has been leaked.
+     *
+     * @param leakInfo the information about the leak
+     */
+    protected void leaked(LeakInfo leakInfo)
+    {
+        LOG.warn("Resource leaked: " + leakInfo.description, leakInfo.stackFrames);
+    }
+
+    /**
+     * Information about the leak of a resource.
+     */
+    public class LeakInfo extends PhantomReference<T>
+    {
+        private final String id;
+        private final String description;
+        private final Throwable stackFrames;
+
+        private LeakInfo(T referent, String id)
+        {
+            super(referent, queue);
+            this.id = id;
+            this.description = referent.toString();
+            this.stackFrames = new Throwable();
+        }
+
+        /**
+         * @return the resource description as provided by the resource's {@link Object#toString()} method.
+         */
+        public String getResourceDescription()
+        {
+            return description;
+        }
+
+        /**
+         * @return a Throwable instance that contains the stack frames at the time of resource acquisition.
+         */
+        public Throwable getStackFrames()
+        {
+            return stackFrames;
+        }
+
+        @Override
+        public String toString()
+        {
+            return description;
+        }
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/util/Loader.java b/lib/jetty/org/eclipse/jetty/util/Loader.java
new file mode 100644 (file)
index 0000000..dfb87fd
--- /dev/null
@@ -0,0 +1,183 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util;
+
+import java.io.File;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.util.Locale;
+import java.util.MissingResourceException;
+import java.util.ResourceBundle;
+
+import org.eclipse.jetty.util.resource.Resource;
+
+/* ------------------------------------------------------------ */
+/** ClassLoader Helper.
+ * This helper class allows classes to be loaded either from the
+ * Thread's ContextClassLoader, the classloader of the derived class
+ * or the system ClassLoader.
+ *
+ * <B>Usage:</B><PRE>
+ * public class MyClass {
+ *     void myMethod() {
+ *          ...
+ *          Class c=Loader.loadClass(this.getClass(),classname);
+ *          ...
+ *     }
+ * </PRE>          
+ * 
+ */
+public class Loader
+{
+    /* ------------------------------------------------------------ */
+    public static URL getResource(Class<?> loadClass,String name)
+    {
+        URL url =null;
+        ClassLoader context_loader=Thread.currentThread().getContextClassLoader();
+        if (context_loader!=null)
+            url=context_loader.getResource(name); 
+        
+        if (url==null && loadClass!=null)
+        {
+            ClassLoader load_loader=loadClass.getClassLoader();
+            if (load_loader!=null && load_loader!=context_loader)
+                url=load_loader.getResource(name);
+        }
+
+        if (url==null)
+            url=ClassLoader.getSystemResource(name);
+
+        return url;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Load a class.
+     * 
+     * @param loadClass
+     * @param name
+     * @return Class
+     * @throws ClassNotFoundException
+     */
+    @SuppressWarnings("rawtypes")
+    public static Class loadClass(Class loadClass,String name)
+        throws ClassNotFoundException
+    {
+        ClassNotFoundException ex=null;
+        Class<?> c =null;
+        ClassLoader context_loader=Thread.currentThread().getContextClassLoader();
+        if (context_loader!=null )
+        {
+            try { c=context_loader.loadClass(name); }
+            catch (ClassNotFoundException e) {ex=e;}
+        }    
+        
+        if (c==null && loadClass!=null)
+        {
+            ClassLoader load_loader=loadClass.getClassLoader();
+            if (load_loader!=null && load_loader!=context_loader)
+            {
+                try { c=load_loader.loadClass(name); }
+                catch (ClassNotFoundException e) {if(ex==null)ex=e;}
+            }
+        }
+
+        if (c==null)
+        {
+            try { c=Class.forName(name); }
+            catch (ClassNotFoundException e) 
+            {
+                if(ex!=null)
+                    throw ex;
+                throw e;
+            }
+        }   
+
+        return c;
+    }
+    
+    
+    
+    /* ------------------------------------------------------------ */
+    public static ResourceBundle getResourceBundle(Class<?> loadClass,String name,boolean checkParents, Locale locale)
+        throws MissingResourceException
+    {
+        MissingResourceException ex=null;
+        ResourceBundle bundle =null;
+        ClassLoader loader=Thread.currentThread().getContextClassLoader();
+        while (bundle==null && loader!=null )
+        {
+            try { bundle=ResourceBundle.getBundle(name, locale, loader); }
+            catch (MissingResourceException e) {if(ex==null)ex=e;}
+            loader=(bundle==null&&checkParents)?loader.getParent():null;
+        }      
+        
+        loader=loadClass==null?null:loadClass.getClassLoader();
+        while (bundle==null && loader!=null )
+        {
+            try { bundle=ResourceBundle.getBundle(name, locale, loader); }
+            catch (MissingResourceException e) {if(ex==null)ex=e;}
+            loader=(bundle==null&&checkParents)?loader.getParent():null;
+        }       
+
+        if (bundle==null)
+        {
+            try { bundle=ResourceBundle.getBundle(name, locale); }
+            catch (MissingResourceException e) {if(ex==null)ex=e;}
+        }   
+
+        if (bundle!=null)
+            return bundle;
+        throw ex;
+    }
+    
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * Generate the classpath (as a string) of all classloaders
+     * above the given classloader.
+     * 
+     * This is primarily used for jasper.
+     * @return the system class path
+     */
+    public static String getClassPath(ClassLoader loader) throws Exception
+    {
+        StringBuilder classpath=new StringBuilder();
+        while (loader != null && (loader instanceof URLClassLoader))
+        {
+            URL[] urls = ((URLClassLoader)loader).getURLs();
+            if (urls != null)
+            {     
+                for (int i=0;i<urls.length;i++)
+                {
+                    Resource resource = Resource.newResource(urls[i]);
+                    File file=resource.getFile();
+                    if (file!=null && file.exists())
+                    {
+                        if (classpath.length()>0)
+                            classpath.append(File.pathSeparatorChar);
+                        classpath.append(file.getAbsolutePath());
+                    }
+                }
+            }
+            loader = loader.getParent();
+        }
+        return classpath.toString();
+    }
+}
+
diff --git a/lib/jetty/org/eclipse/jetty/util/MemoryUtils.java b/lib/jetty/org/eclipse/jetty/util/MemoryUtils.java
new file mode 100644 (file)
index 0000000..764ad60
--- /dev/null
@@ -0,0 +1,71 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util;
+
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+
+/**
+ * {@link MemoryUtils} provides an abstraction over memory properties and operations.
+ * <p />
+ */
+public class MemoryUtils
+{
+    private static final int cacheLineBytes;
+    static
+    {
+        final int defaultValue = 64;
+        int value = defaultValue;
+        try
+        {
+            value = Integer.parseInt(AccessController.doPrivileged(new PrivilegedAction<String>()
+            {
+                @Override
+                public String run()
+                {
+                    return System.getProperty("org.eclipse.jetty.util.cacheLineBytes", String.valueOf(defaultValue));
+                }
+            }));
+        }
+        catch (Exception ignored)
+        {
+        }
+        cacheLineBytes = value;
+    }
+
+    private MemoryUtils()
+    {
+    }
+
+    public static int getCacheLineBytes()
+    {
+        return cacheLineBytes;
+    }
+
+    public static int getIntegersPerCacheLine()
+    {
+        return getCacheLineBytes() >> 2;
+    }
+
+    public static int getLongsPerCacheLine()
+    {
+        return getCacheLineBytes() >> 3;
+    }
+
+}
diff --git a/lib/jetty/org/eclipse/jetty/util/MultiException.java b/lib/jetty/org/eclipse/jetty/util/MultiException.java
new file mode 100644 (file)
index 0000000..2e71f3c
--- /dev/null
@@ -0,0 +1,214 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util;
+
+import java.io.PrintStream;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/** 
+ * Wraps multiple exceptions.
+ *
+ * Allows multiple exceptions to be thrown as a single exception.
+ */
+@SuppressWarnings("serial")
+public class MultiException extends Exception
+{
+    private List<Throwable> nested;
+
+    /* ------------------------------------------------------------ */
+    public MultiException()
+    {
+        super("Multiple exceptions");
+    }
+
+    /* ------------------------------------------------------------ */
+    public void add(Throwable e)
+    {
+        if(nested == null)
+        {
+            nested = new ArrayList<>();
+        }
+        
+        if (e instanceof MultiException)
+        {
+            MultiException me = (MultiException)e;
+            nested.addAll(me.nested);
+        }
+        else
+            nested.add(e);
+    }
+
+    /* ------------------------------------------------------------ */
+    public int size()
+    {
+        return (nested ==null)?0:nested.size();
+    }
+    
+    /* ------------------------------------------------------------ */
+    public List<Throwable> getThrowables()
+    {
+        if(nested == null) {
+            return Collections.emptyList();
+        }
+        return nested;
+    }
+    
+    /* ------------------------------------------------------------ */
+    public Throwable getThrowable(int i)
+    {
+        return nested.get(i);
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Throw a multiexception.
+     * If this multi exception is empty then no action is taken. If it
+     * contains a single exception that is thrown, otherwise the this
+     * multi exception is thrown. 
+     * @exception Exception 
+     */
+    public void ifExceptionThrow()
+        throws Exception
+    {
+        if(nested == null)
+            return;
+        
+        switch (nested.size())
+        {
+          case 0:
+              break;
+          case 1:
+              Throwable th=nested.get(0);
+              if (th instanceof Error)
+                  throw (Error)th;
+              if (th instanceof Exception)
+                  throw (Exception)th;
+          default:
+              throw this;
+        }
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** Throw a Runtime exception.
+     * If this multi exception is empty then no action is taken. If it
+     * contains a single error or runtime exception that is thrown, otherwise the this
+     * multi exception is thrown, wrapped in a runtime exception. 
+     * @exception Error If this exception contains exactly 1 {@link Error} 
+     * @exception RuntimeException If this exception contains 1 {@link Throwable} but it is not an error,
+     *                             or it contains more than 1 {@link Throwable} of any type.
+     */
+    public void ifExceptionThrowRuntime()
+        throws Error
+    {
+        if(nested == null)
+            return;
+        
+        switch (nested.size())
+        {
+          case 0:
+              break;
+          case 1:
+              Throwable th=nested.get(0);
+              if (th instanceof Error)
+                  throw (Error)th;
+              else if (th instanceof RuntimeException)
+                  throw (RuntimeException)th;
+              else
+                  throw new RuntimeException(th);
+          default:
+              throw new RuntimeException(this);
+        }
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** Throw a multiexception.
+     * If this multi exception is empty then no action is taken. If it
+     * contains a any exceptions then this
+     * multi exception is thrown. 
+     */
+    public void ifExceptionThrowMulti()
+        throws MultiException
+    {
+        if(nested == null)
+            return;
+        
+        if (nested.size()>0)
+            throw this;
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public String toString()
+    {
+        StringBuilder str = new StringBuilder();
+        str.append(MultiException.class.getSimpleName());
+        if((nested == null) || (nested.size()<=0)) {
+            str.append("[]");
+        } else {
+            str.append(nested);
+        }
+        return str.toString();
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public void printStackTrace()
+    {
+        super.printStackTrace();
+        if(nested != null) {
+            for(Throwable t: nested) {
+                t.printStackTrace();
+            }
+        }
+    }
+   
+
+    /* ------------------------------------------------------------------------------- */
+    /**
+     * @see java.lang.Throwable#printStackTrace(java.io.PrintStream)
+     */
+    @Override
+    public void printStackTrace(PrintStream out)
+    {
+        super.printStackTrace(out);
+        if(nested != null) {
+            for(Throwable t: nested) {
+                t.printStackTrace(out);
+            }
+        }
+    }
+
+    /* ------------------------------------------------------------------------------- */
+    /**
+     * @see java.lang.Throwable#printStackTrace(java.io.PrintWriter)
+     */
+    @Override
+    public void printStackTrace(PrintWriter out)
+    {
+        super.printStackTrace(out);
+        if(nested != null) {
+            for(Throwable t: nested) {
+                t.printStackTrace(out);
+            }
+        }
+    }
+
+}
diff --git a/lib/jetty/org/eclipse/jetty/util/MultiMap.java b/lib/jetty/org/eclipse/jetty/util/MultiMap.java
new file mode 100644 (file)
index 0000000..a3c905a
--- /dev/null
@@ -0,0 +1,373 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+
+/** 
+ * A multi valued Map.
+ */
+@SuppressWarnings("serial")
+public class MultiMap<V> extends HashMap<String,List<V>>
+{
+    public MultiMap()
+    {
+        super();
+    }
+
+    public MultiMap(Map<String,List<V>> map)
+    {
+        super(map);
+    }
+
+    public MultiMap(MultiMap<V> map)
+    {
+        super(map);
+    }
+
+
+    /* ------------------------------------------------------------ */
+    /** Get multiple values.
+     * Single valued entries are converted to singleton lists.
+     * @param name The entry key. 
+     * @return Unmodifieable List of values.
+     */
+    public List<V> getValues(String name)
+    {
+        List<V> vals = super.get(name);
+        if((vals == null) || vals.isEmpty()) {
+            return null;
+        }
+        return vals;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** Get a value from a multiple value.
+     * If the value is not a multivalue, then index 0 retrieves the
+     * value or null.
+     * @param name The entry key.
+     * @param i Index of element to get.
+     * @return Unmodifieable List of values.
+     */
+    public V getValue(String name,int i)
+    {
+        List<V> vals = getValues(name);
+        if(vals == null) {
+            return null;
+        }
+        if (i==0 && vals.isEmpty()) {
+            return null;
+        }
+        return vals.get(i);
+    }
+    
+    
+    /* ------------------------------------------------------------ */
+    /** Get value as String.
+     * Single valued items are converted to a String with the toString()
+     * Object method. Multi valued entries are converted to a comma separated
+     * List.  No quoting of commas within values is performed.
+     * @param name The entry key. 
+     * @return String value.
+     */
+    public String getString(String name)
+    {
+        List<V> vals =get(name);
+        if ((vals == null) || (vals.isEmpty()))
+        {
+            return null;
+        }
+        
+        if (vals.size() == 1)
+        {
+            // simple form.
+            return vals.get(0).toString();
+        }
+        
+        // delimited form
+        StringBuilder values=new StringBuilder(128);
+        for (V e : vals)
+        {
+            if (e != null)
+            {
+                if (values.length() > 0)
+                    values.append(',');
+                values.append(e.toString());
+            }
+        }   
+        return values.toString();
+    }
+    
+    /** 
+     * Put multi valued entry.
+     * @param name The entry key. 
+     * @param value The simple value
+     * @return The previous value or null.
+     */
+    public List<V> put(String name, V value) 
+    {
+        if(value == null) {
+            return super.put(name, null);
+        }
+        List<V> vals = new ArrayList<>();
+        vals.add(value);
+        return put(name,vals);
+    }
+
+    /**
+     * Shorthand version of putAll
+     * @param input the input map
+     */
+    public void putAllValues(Map<String, V> input)
+    {
+        for(Map.Entry<String,V> entry: input.entrySet())
+        {
+            put(entry.getKey(), entry.getValue());
+        }
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** Put multi valued entry.
+     * @param name The entry key. 
+     * @param values The List of multiple values.
+     * @return The previous value or null.
+     */
+    public List<V> putValues(String name, List<V> values) 
+    {
+        return super.put(name,values);
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** Put multi valued entry.
+     * @param name The entry key. 
+     * @param values The array of multiple values.
+     * @return The previous value or null.
+     */
+    @SafeVarargs
+    public final List<V> putValues(String name, V... values) 
+    {
+        List<V> list = new ArrayList<>();
+        list.addAll(Arrays.asList(values));
+        return super.put(name,list);
+    }
+    
+    
+    /* ------------------------------------------------------------ */
+    /** Add value to multi valued entry.
+     * If the entry is single valued, it is converted to the first
+     * value of a multi valued entry.
+     * @param name The entry key. 
+     * @param value The entry value.
+     */
+    public void add(String name, V value) 
+    {
+        List<V> lo = get(name);
+        if(lo == null) {
+            lo = new ArrayList<>();
+        }
+        lo.add(value);
+        super.put(name,lo);
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Add values to multi valued entry.
+     * If the entry is single valued, it is converted to the first
+     * value of a multi valued entry.
+     * @param name The entry key. 
+     * @param values The List of multiple values.
+     */
+    public void addValues(String name, List<V> values) 
+    {
+        List<V> lo = get(name);
+        if(lo == null) {
+            lo = new ArrayList<>();
+        }
+        lo.addAll(values);
+        put(name,lo);
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** Add values to multi valued entry.
+     * If the entry is single valued, it is converted to the first
+     * value of a multi valued entry.
+     * @param name The entry key. 
+     * @param values The String array of multiple values.
+     */
+    public void addValues(String name, V[] values) 
+    {
+        List<V> lo = get(name);
+        if(lo == null) {
+            lo = new ArrayList<>();
+        }
+        lo.addAll(Arrays.asList(values));
+        put(name,lo);
+    }
+    
+    /**
+     * Merge values.
+     * 
+     * @param map
+     *            the map to overlay on top of this one, merging together values if needed.
+     * @return true if an existing key was merged with potentially new values, false if either no change was made, or there were only new keys.
+     */
+    public boolean addAllValues(MultiMap<V> map)
+    {
+        boolean merged = false;
+
+        if ((map == null) || (map.isEmpty()))
+        {
+            // done
+            return merged;
+        }
+
+        for (Map.Entry<String, List<V>> entry : map.entrySet())
+        {
+            String name = entry.getKey();
+            List<V> values = entry.getValue();
+
+            if (this.containsKey(name))
+            {
+                merged = true;
+            }
+
+            this.addValues(name,values);
+        }
+
+        return merged;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** Remove value.
+     * @param name The entry key. 
+     * @param value The entry value. 
+     * @return true if it was removed.
+     */
+    public boolean removeValue(String name,V value)
+    {
+        List<V> lo = get(name);
+        if((lo == null)||(lo.isEmpty())) {
+            return false;
+        }
+        boolean ret = lo.remove(value);
+        if(lo.isEmpty()) {
+            remove(name);
+        } else {
+            put(name,lo);
+        }
+        return ret;
+    }
+    
+    /**
+     * Test for a specific single value in the map.
+     * <p>
+     * NOTE: This is a SLOW operation, and is actively discouraged.
+     * @param value
+     * @return true if contains simple value
+     */
+    public boolean containsSimpleValue(V value)
+    {
+        for (List<V> vals : values())
+        {
+            if ((vals.size() == 1) && vals.contains(value))
+            {
+                return true;
+            }
+        }
+        return false;
+    }
+    
+    @Override
+    public String toString()
+    {
+        Iterator<Entry<String, List<V>>> iter = entrySet().iterator();
+        StringBuilder sb = new StringBuilder();
+        sb.append('{');
+        boolean delim = false;
+        while (iter.hasNext())
+        {
+            Entry<String, List<V>> e = iter.next();
+            if (delim)
+            {
+                sb.append(", ");
+            }
+            String key = e.getKey();
+            List<V> vals = e.getValue();
+            sb.append(key);
+            sb.append('=');
+            if (vals.size() == 1)
+            {
+                sb.append(vals.get(0));
+            }
+            else
+            {
+                sb.append(vals);
+            }
+            delim = true;
+        }
+        sb.append('}');
+        return sb.toString();
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** 
+     * @return Map of String arrays
+     */
+    public Map<String,String[]> toStringArrayMap()
+    {
+        HashMap<String,String[]> map = new HashMap<String,String[]>(size()*3/2)
+        {
+            @Override
+            public String toString()
+            {
+                StringBuilder b=new StringBuilder();
+                b.append('{');
+                for (String k:super.keySet())
+                {
+                    if(b.length()>1)
+                        b.append(',');
+                    b.append(k);
+                    b.append('=');
+                    b.append(Arrays.asList(super.get(k)));
+                }
+
+                b.append('}');
+                return b.toString();
+            }
+        };
+        
+        for(Map.Entry<String,List<V>> entry: entrySet())
+        {
+            String[] a = null;
+            if (entry.getValue() != null)
+            {
+                a = new String[entry.getValue().size()];
+                a = entry.getValue().toArray(a);
+            }
+            map.put(entry.getKey(),a);
+        }
+        return map;
+    }
+
+}
diff --git a/lib/jetty/org/eclipse/jetty/util/MultiPartInputStreamParser.java b/lib/jetty/org/eclipse/jetty/util/MultiPartInputStreamParser.java
new file mode 100644 (file)
index 0000000..441d648
--- /dev/null
@@ -0,0 +1,835 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.FilterInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Locale;
+
+import javax.servlet.MultipartConfigElement;
+import javax.servlet.ServletException;
+import javax.servlet.http.Part;
+
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+
+
+/**
+ * MultiPartInputStream
+ *
+ * Handle a MultiPart Mime input stream, breaking it up on the boundary into files and strings.
+ */
+public class MultiPartInputStreamParser
+{
+    private static final Logger LOG = Log.getLogger(MultiPartInputStreamParser.class);
+    public static final MultipartConfigElement  __DEFAULT_MULTIPART_CONFIG = new MultipartConfigElement(System.getProperty("java.io.tmpdir"));
+    protected InputStream _in;
+    protected MultipartConfigElement _config;
+    protected String _contentType;
+    protected MultiMap _parts;
+    protected File _tmpDir;
+    protected File _contextTmpDir;
+    protected boolean _deleteOnExit;
+
+
+
+    public class MultiPart implements Part
+    {
+        protected String _name;
+        protected String _filename;
+        protected File _file;
+        protected OutputStream _out;
+        protected ByteArrayOutputStream2 _bout;
+        protected String _contentType;
+        protected MultiMap _headers;
+        protected long _size = 0;
+        protected boolean _temporary = true;
+
+        public MultiPart (String name, String filename)
+        throws IOException
+        {
+            _name = name;
+            _filename = filename;
+        }
+
+        protected void setContentType (String contentType)
+        {
+            _contentType = contentType;
+        }
+
+
+        protected void open()
+        throws IOException
+        {
+            //We will either be writing to a file, if it has a filename on the content-disposition
+            //and otherwise a byte-array-input-stream, OR if we exceed the getFileSizeThreshold, we
+            //will need to change to write to a file.
+            if (_filename != null && _filename.trim().length() > 0)
+            {
+                createFile();
+            }
+            else
+            {
+                //Write to a buffer in memory until we discover we've exceed the
+                //MultipartConfig fileSizeThreshold
+                _out = _bout= new ByteArrayOutputStream2();
+            }
+        }
+
+        protected void close()
+        throws IOException
+        {
+            _out.close();
+        }
+
+
+        protected void write (int b)
+        throws IOException
+        {
+            if (MultiPartInputStreamParser.this._config.getMaxFileSize() > 0 && _size + 1 > MultiPartInputStreamParser.this._config.getMaxFileSize())
+                throw new IllegalStateException ("Multipart Mime part "+_name+" exceeds max filesize");
+
+            if (MultiPartInputStreamParser.this._config.getFileSizeThreshold() > 0 && _size + 1 > MultiPartInputStreamParser.this._config.getFileSizeThreshold() && _file==null)
+                createFile();
+            _out.write(b);
+            _size ++;
+        }
+
+        protected void write (byte[] bytes, int offset, int length)
+        throws IOException
+        {
+            if (MultiPartInputStreamParser.this._config.getMaxFileSize() > 0 && _size + length > MultiPartInputStreamParser.this._config.getMaxFileSize())
+                throw new IllegalStateException ("Multipart Mime part "+_name+" exceeds max filesize");
+
+            if (MultiPartInputStreamParser.this._config.getFileSizeThreshold() > 0 && _size + length > MultiPartInputStreamParser.this._config.getFileSizeThreshold() && _file==null)
+                createFile();
+
+            _out.write(bytes, offset, length);
+            _size += length;
+        }
+
+        protected void createFile ()
+        throws IOException
+        {
+            _file = File.createTempFile("MultiPart", "", MultiPartInputStreamParser.this._tmpDir);
+            if (_deleteOnExit)
+                _file.deleteOnExit();
+            FileOutputStream fos = new FileOutputStream(_file);
+            BufferedOutputStream bos = new BufferedOutputStream(fos);
+
+            if (_size > 0 && _out != null)
+            {
+                //already written some bytes, so need to copy them into the file
+                _out.flush();
+                _bout.writeTo(bos);
+                _out.close();
+                _bout = null;
+            }
+            _out = bos;
+        }
+
+
+
+        protected void setHeaders(MultiMap headers)
+        {
+            _headers = headers;
+        }
+
+        /**
+         * @see javax.servlet.http.Part#getContentType()
+         */
+        public String getContentType()
+        {
+            return _contentType;
+        }
+
+        /**
+         * @see javax.servlet.http.Part#getHeader(java.lang.String)
+         */
+        public String getHeader(String name)
+        {
+            if (name == null)
+                return null;
+            return (String)_headers.getValue(name.toLowerCase(Locale.ENGLISH), 0);
+        }
+
+        /**
+         * @see javax.servlet.http.Part#getHeaderNames()
+         */
+        public Collection<String> getHeaderNames()
+        {
+            return _headers.keySet();
+        }
+
+        /**
+         * @see javax.servlet.http.Part#getHeaders(java.lang.String)
+         */
+        public Collection<String> getHeaders(String name)
+        {
+           return _headers.getValues(name);
+        }
+
+        /**
+         * @see javax.servlet.http.Part#getInputStream()
+         */
+        public InputStream getInputStream() throws IOException
+        {
+           if (_file != null)
+           {
+               //written to a file, whether temporary or not
+               return new BufferedInputStream (new FileInputStream(_file));
+           }
+           else
+           {
+               //part content is in memory
+               return new ByteArrayInputStream(_bout.getBuf(),0,_bout.size());
+           }
+        }
+
+        
+        /** 
+         * @see javax.servlet.http.Part#getSubmittedFileName()
+         */
+        @Override
+        public String getSubmittedFileName()
+        {
+            return getContentDispositionFilename();
+        }
+
+        public byte[] getBytes()
+        {
+            if (_bout!=null)
+                return _bout.toByteArray();
+            return null;
+        }
+
+        /**
+         * @see javax.servlet.http.Part#getName()
+         */
+        public String getName()
+        {
+           return _name;
+        }
+
+        /**
+         * @see javax.servlet.http.Part#getSize()
+         */
+        public long getSize()
+        {
+            return _size;         
+        }
+
+        /**
+         * @see javax.servlet.http.Part#write(java.lang.String)
+         */
+        public void write(String fileName) throws IOException
+        {
+            if (_file == null)
+            {
+                _temporary = false;
+                
+                //part data is only in the ByteArrayOutputStream and never been written to disk
+                _file = new File (_tmpDir, fileName);
+
+                BufferedOutputStream bos = null;
+                try
+                {
+                    bos = new BufferedOutputStream(new FileOutputStream(_file));
+                    _bout.writeTo(bos);
+                    bos.flush();
+                }
+                finally
+                {
+                    if (bos != null)
+                        bos.close();
+                    _bout = null;
+                }
+            }
+            else
+            {
+                //the part data is already written to a temporary file, just rename it
+                _temporary = false;
+                
+                File f = new File(_tmpDir, fileName);
+                if (_file.renameTo(f))
+                    _file = f;
+            }
+        }
+
+        /**
+         * Remove the file, whether or not Part.write() was called on it
+         * (ie no longer temporary)
+         * @see javax.servlet.http.Part#delete()
+         */
+        public void delete() throws IOException
+        {
+            if (_file != null && _file.exists())
+                _file.delete();     
+        }
+        
+        /**
+         * Only remove tmp files.
+         * 
+         * @throws IOException
+         */
+        public void cleanUp() throws IOException
+        {
+            if (_temporary && _file != null && _file.exists())
+                _file.delete();
+        }
+
+
+        /**
+         * Get the file, if any, the data has been written to.
+         */
+        public File getFile ()
+        {
+            return _file;
+        }
+
+
+        /**
+         * Get the filename from the content-disposition.
+         * @return null or the filename
+         */
+        public String getContentDispositionFilename ()
+        {
+            return _filename;
+        }
+    }
+
+
+
+
+    /**
+     * @param in Request input stream
+     * @param contentType Content-Type header
+     * @param config MultipartConfigElement
+     * @param contextTmpDir javax.servlet.context.tempdir
+     */
+    public MultiPartInputStreamParser (InputStream in, String contentType, MultipartConfigElement config, File contextTmpDir)
+    {
+        _in = new ReadLineInputStream(in);
+       _contentType = contentType;
+       _config = config;
+       _contextTmpDir = contextTmpDir;
+       if (_contextTmpDir == null)
+           _contextTmpDir = new File (System.getProperty("java.io.tmpdir"));
+       
+       if (_config == null)
+           _config = new MultipartConfigElement(_contextTmpDir.getAbsolutePath());
+    }
+
+    /**
+     * Get the already parsed parts.
+     */
+    public Collection<Part> getParsedParts()
+    {
+        if (_parts == null)
+            return Collections.emptyList();
+
+        Collection<Object> values = _parts.values();
+        List<Part> parts = new ArrayList<Part>();
+        for (Object o: values)
+        {
+            List<Part> asList = LazyList.getList(o, false);
+            parts.addAll(asList);
+        }
+        return parts;
+    }
+
+    /**
+     * Delete any tmp storage for parts, and clear out the parts list.
+     * 
+     * @throws MultiException
+     */
+    public void deleteParts ()
+    throws MultiException
+    {
+        Collection<Part> parts = getParsedParts();
+        MultiException err = new MultiException();
+        for (Part p:parts)
+        {
+            try
+            {
+                ((MultiPartInputStreamParser.MultiPart)p).cleanUp();
+            } 
+            catch(Exception e)
+            {     
+                err.add(e); 
+            }
+        }
+        _parts.clear();
+        
+        err.ifExceptionThrowMulti();
+    }
+
+   
+    /**
+     * Parse, if necessary, the multipart data and return the list of Parts.
+     * 
+     * @throws IOException
+     * @throws ServletException
+     */
+    public Collection<Part> getParts()
+    throws IOException, ServletException
+    {
+        parse();
+        Collection<Object> values = _parts.values();
+        List<Part> parts = new ArrayList<Part>();
+        for (Object o: values)
+        {
+            List<Part> asList = LazyList.getList(o, false);
+            parts.addAll(asList);
+        }
+        return parts;
+    }
+
+
+    /**
+     * Get the named Part.
+     * 
+     * @param name
+     * @throws IOException
+     * @throws ServletException
+     */
+    public Part getPart(String name)
+    throws IOException, ServletException
+    {
+        parse();
+        return (Part)_parts.getValue(name, 0);
+    }
+
+
+    /**
+     * Parse, if necessary, the multipart stream.
+     * 
+     * @throws IOException
+     * @throws ServletException
+     */
+    protected void parse ()
+    throws IOException, ServletException
+    {
+        //have we already parsed the input?
+        if (_parts != null)
+            return;
+
+        //initialize
+        long total = 0; //keep running total of size of bytes read from input and throw an exception if exceeds MultipartConfigElement._maxRequestSize
+        _parts = new MultiMap();
+
+        //if its not a multipart request, don't parse it
+        if (_contentType == null || !_contentType.startsWith("multipart/form-data"))
+            return;
+
+        //sort out the location to which to write the files
+
+        if (_config.getLocation() == null)
+            _tmpDir = _contextTmpDir;
+        else if ("".equals(_config.getLocation()))
+            _tmpDir = _contextTmpDir;
+        else
+        {
+            File f = new File (_config.getLocation());
+            if (f.isAbsolute())
+                _tmpDir = f;
+            else
+                _tmpDir = new File (_contextTmpDir, _config.getLocation());
+        }
+
+        if (!_tmpDir.exists())
+            _tmpDir.mkdirs();
+
+        String contentTypeBoundary = "";
+        int bstart = _contentType.indexOf("boundary=");
+        if (bstart >= 0)
+        {
+            int bend = _contentType.indexOf(";", bstart);
+            bend = (bend < 0? _contentType.length(): bend);
+            contentTypeBoundary = QuotedStringTokenizer.unquote(value(_contentType.substring(bstart,bend)).trim());
+        }
+        
+        String boundary="--"+contentTypeBoundary;
+        byte[] byteBoundary=(boundary+"--").getBytes(StandardCharsets.ISO_8859_1);
+
+        // Get first boundary
+        String line = null;
+        try
+        {
+            line=((ReadLineInputStream)_in).readLine();  
+        }
+        catch (IOException e)
+        {
+            LOG.warn("Badly formatted multipart request");
+            throw e;
+        }
+        
+        if (line == null)
+            throw new IOException("Missing content for multipart request");
+        
+        boolean badFormatLogged = false;
+        line=line.trim();
+        while (line != null && !line.equals(boundary))
+        {
+            if (!badFormatLogged)
+            {
+                LOG.warn("Badly formatted multipart request");
+                badFormatLogged = true;
+            }
+            line=((ReadLineInputStream)_in).readLine();
+            line=(line==null?line:line.trim());
+        }
+
+        if (line == null)
+            throw new IOException("Missing initial multi part boundary");
+
+        // Read each part
+        boolean lastPart=false;
+
+        outer:while(!lastPart)
+        {
+            String contentDisposition=null;
+            String contentType=null;
+            String contentTransferEncoding=null;
+            
+            MultiMap headers = new MultiMap();
+            while(true)
+            {
+                line=((ReadLineInputStream)_in).readLine();
+                
+                //No more input
+                if(line==null)
+                    break outer;
+                
+                //end of headers:
+                if("".equals(line))
+                    break;
+           
+                total += line.length();
+                if (_config.getMaxRequestSize() > 0 && total > _config.getMaxRequestSize())
+                    throw new IllegalStateException ("Request exceeds maxRequestSize ("+_config.getMaxRequestSize()+")");
+
+                //get content-disposition and content-type
+                int c=line.indexOf(':',0);
+                if(c>0)
+                {
+                    String key=line.substring(0,c).trim().toLowerCase(Locale.ENGLISH);
+                    String value=line.substring(c+1,line.length()).trim();
+                    headers.put(key, value);
+                    if (key.equalsIgnoreCase("content-disposition"))
+                        contentDisposition=value;
+                    if (key.equalsIgnoreCase("content-type"))
+                        contentType = value;
+                    if(key.equals("content-transfer-encoding"))
+                        contentTransferEncoding=value;
+                }
+            }
+
+            // Extract content-disposition
+            boolean form_data=false;
+            if(contentDisposition==null)
+            {
+                throw new IOException("Missing content-disposition");
+            }
+
+            QuotedStringTokenizer tok=new QuotedStringTokenizer(contentDisposition,";", false, true);
+            String name=null;
+            String filename=null;
+            while(tok.hasMoreTokens())
+            {
+                String t=tok.nextToken().trim();
+                String tl=t.toLowerCase(Locale.ENGLISH);
+                if(t.startsWith("form-data"))
+                    form_data=true;
+                else if(tl.startsWith("name="))
+                    name=value(t);
+                else if(tl.startsWith("filename="))
+                    filename=filenameValue(t);
+            }
+
+            // Check disposition
+            if(!form_data)
+            {
+                continue;
+            }
+            //It is valid for reset and submit buttons to have an empty name.
+            //If no name is supplied, the browser skips sending the info for that field.
+            //However, if you supply the empty string as the name, the browser sends the
+            //field, with name as the empty string. So, only continue this loop if we
+            //have not yet seen a name field.
+            if(name==null)
+            {
+                continue;
+            }
+
+            //Have a new Part
+            MultiPart part = new MultiPart(name, filename);
+            part.setHeaders(headers);
+            part.setContentType(contentType);
+            _parts.add(name, part);
+            part.open();
+            
+            InputStream partInput = null;
+            if ("base64".equalsIgnoreCase(contentTransferEncoding))
+            {
+                partInput = new Base64InputStream((ReadLineInputStream)_in);
+            }
+            else if ("quoted-printable".equalsIgnoreCase(contentTransferEncoding))
+            {
+                partInput = new FilterInputStream(_in)
+                {
+                    @Override
+                    public int read() throws IOException
+                    {
+                        int c = in.read();
+                        if (c >= 0 && c == '=')
+                        {
+                            int hi = in.read();
+                            int lo = in.read();
+                            if (hi < 0 || lo < 0)
+                            {
+                                throw new IOException("Unexpected end to quoted-printable byte");
+                            }
+                            char[] chars = new char[] { (char)hi, (char)lo };
+                            c = Integer.parseInt(new String(chars),16);
+                        }
+                        return c;
+                    }
+                };
+            }
+            else
+                partInput = _in;
+
+            
+            try
+            {
+                int state=-2;
+                int c;
+                boolean cr=false;
+                boolean lf=false;
+
+                // loop for all lines
+                while(true)
+                {
+                    int b=0;
+                    while((c=(state!=-2)?state:partInput.read())!=-1)
+                    {
+                        total ++;
+                        if (_config.getMaxRequestSize() > 0 && total > _config.getMaxRequestSize())
+                            throw new IllegalStateException("Request exceeds maxRequestSize ("+_config.getMaxRequestSize()+")");
+
+                        state=-2;
+                        
+                        // look for CR and/or LF
+                        if(c==13||c==10)
+                        {
+                            if(c==13)
+                            {
+                                partInput.mark(1);
+                                int tmp=partInput.read();
+                                if (tmp!=10)
+                                    partInput.reset();
+                                else
+                                    state=tmp;
+                            }
+                            break;
+                        }
+                        
+                        // Look for boundary
+                        if(b>=0&&b<byteBoundary.length&&c==byteBoundary[b])
+                        {
+                            b++;
+                        }
+                        else
+                        {
+                            // Got a character not part of the boundary, so we don't have the boundary marker.
+                            // Write out as many chars as we matched, then the char we're looking at.
+                            if(cr)
+                                part.write(13);
+
+                            if(lf)
+                                part.write(10);
+
+                            cr=lf=false;
+                            if(b>0)
+                                part.write(byteBoundary,0,b);
+
+                            b=-1;
+                            part.write(c);
+                        }
+                    }
+                    
+                    // Check for incomplete boundary match, writing out the chars we matched along the way
+                    if((b>0&&b<byteBoundary.length-2)||(b==byteBoundary.length-1))
+                    {
+                        if(cr)
+                            part.write(13);
+
+                        if(lf)
+                            part.write(10);
+
+                        cr=lf=false;
+                        part.write(byteBoundary,0,b);
+                        b=-1;
+                    }
+                    
+                    // Boundary match. If we've run out of input or we matched the entire final boundary marker, then this is the last part.
+                    if(b>0||c==-1)
+                    {
+                       
+                        if(b==byteBoundary.length)
+                            lastPart=true;
+                        if(state==10)
+                            state=-2;
+                        break;
+                    }
+                    
+                    // handle CR LF
+                    if(cr)
+                        part.write(13);
+
+                    if(lf)
+                        part.write(10);
+
+                    cr=(c==13);
+                    lf=(c==10||state==10);
+                    if(state==10)
+                        state=-2;
+                }
+            }
+            finally
+            {
+
+                part.close();
+            }
+        }
+        if (!lastPart)
+            throw new IOException("Incomplete parts");
+    }
+    
+    public void setDeleteOnExit(boolean deleteOnExit)
+    {
+        _deleteOnExit = deleteOnExit;
+    }
+
+
+    public boolean isDeleteOnExit()
+    {
+        return _deleteOnExit;
+    }
+
+
+    /* ------------------------------------------------------------ */
+    private String value(String nameEqualsValue)
+    {
+        int idx = nameEqualsValue.indexOf('=');
+        String value = nameEqualsValue.substring(idx+1).trim();
+        return QuotedStringTokenizer.unquoteOnly(value);
+    }
+    
+    
+    /* ------------------------------------------------------------ */
+    private String filenameValue(String nameEqualsValue)
+    {
+        int idx = nameEqualsValue.indexOf('=');
+        String value = nameEqualsValue.substring(idx+1).trim();
+
+        if (value.matches(".??[a-z,A-Z]\\:\\\\[^\\\\].*"))
+        {
+            //incorrectly escaped IE filenames that have the whole path
+            //we just strip any leading & trailing quotes and leave it as is
+            char first=value.charAt(0);
+            if (first=='"' || first=='\'')
+                value=value.substring(1);
+            char last=value.charAt(value.length()-1);
+            if (last=='"' || last=='\'')
+                value = value.substring(0,value.length()-1);
+
+            return value;
+        }
+        else
+            //unquote the string, but allow any backslashes that don't
+            //form a valid escape sequence to remain as many browsers
+            //even on *nix systems will not escape a filename containing
+            //backslashes
+            return QuotedStringTokenizer.unquoteOnly(value, true);
+    }
+
+    
+
+    private static class Base64InputStream extends InputStream
+    {
+        ReadLineInputStream _in;
+        String _line;
+        byte[] _buffer;
+        int _pos;
+
+    
+        public Base64InputStream(ReadLineInputStream rlis)
+        {
+            _in = rlis;
+        }
+
+        @Override
+        public int read() throws IOException
+        {
+            if (_buffer==null || _pos>= _buffer.length)
+            {
+                //Any CR and LF will be consumed by the readLine() call.
+                //We need to put them back into the bytes returned from this
+                //method because the parsing of the multipart content uses them
+                //as markers to determine when we've reached the end of a part.
+                _line = _in.readLine(); 
+                if (_line==null)
+                    return -1;  //nothing left
+                if (_line.startsWith("--"))
+                    _buffer=(_line+"\r\n").getBytes(); //boundary marking end of part
+                else if (_line.length()==0)
+                    _buffer="\r\n".getBytes(); //blank line
+                else
+                {
+                    ByteArrayOutputStream baos = new ByteArrayOutputStream((4*_line.length()/3)+2);
+                    B64Code.decode(_line, baos);
+                    baos.write(13);
+                    baos.write(10);
+                    _buffer = baos.toByteArray();
+                }
+
+                _pos=0;
+            }
+            
+            return _buffer[_pos++];
+        }
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/util/MultiPartOutputStream.java b/lib/jetty/org/eclipse/jetty/util/MultiPartOutputStream.java
new file mode 100644 (file)
index 0000000..f554e76
--- /dev/null
@@ -0,0 +1,152 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util;
+
+import java.io.FilterOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.nio.charset.StandardCharsets;
+
+
+/* ================================================================ */
+/** Handle a multipart MIME response.
+ *
+ * 
+ * 
+*/
+public class MultiPartOutputStream extends FilterOutputStream
+{
+    /* ------------------------------------------------------------ */
+    private static final byte[] __CRLF={'\r','\n'};
+    private static final byte[] __DASHDASH={'-','-'};
+    
+    public static final String MULTIPART_MIXED="multipart/mixed";
+    public static final String MULTIPART_X_MIXED_REPLACE="multipart/x-mixed-replace";
+    
+    /* ------------------------------------------------------------ */
+    private final String boundary;
+    private final byte[] boundaryBytes;
+
+    /* ------------------------------------------------------------ */
+    private boolean inPart=false;    
+    
+    /* ------------------------------------------------------------ */
+    public MultiPartOutputStream(OutputStream out)
+    throws IOException
+    {
+        super(out);
+
+        boundary = "jetty"+System.identityHashCode(this)+
+        Long.toString(System.currentTimeMillis(),36);
+        boundaryBytes=boundary.getBytes(StandardCharsets.ISO_8859_1);
+    }
+
+    public MultiPartOutputStream(OutputStream out, String boundary)
+         throws IOException
+    {
+        super(out);
+
+        this.boundary = boundary;
+        boundaryBytes=boundary.getBytes(StandardCharsets.ISO_8859_1);
+    }
+
+    /* ------------------------------------------------------------ */
+    /** End the current part.
+     * @exception IOException IOException
+     */
+    @Override
+    public void close()
+         throws IOException
+    {
+        try
+        {
+            if (inPart)
+                out.write(__CRLF);
+            out.write(__DASHDASH);
+            out.write(boundaryBytes);
+            out.write(__DASHDASH);
+            out.write(__CRLF);
+            inPart=false;
+        }
+        finally
+        {
+            super.close();
+        }
+    }
+    
+    /* ------------------------------------------------------------ */
+    public String getBoundary()
+    {
+        return boundary;
+    }
+
+    public OutputStream getOut() {return out;}
+    
+    /* ------------------------------------------------------------ */
+    /** Start creation of the next Content.
+     */
+    public void startPart(String contentType)
+         throws IOException
+    {
+        if (inPart)
+            out.write(__CRLF);
+        inPart=true;
+        out.write(__DASHDASH);
+        out.write(boundaryBytes);
+        out.write(__CRLF);
+        if (contentType != null)
+            out.write(("Content-Type: "+contentType).getBytes(StandardCharsets.ISO_8859_1));
+        out.write(__CRLF);
+        out.write(__CRLF);
+    }
+        
+    /* ------------------------------------------------------------ */
+    /** Start creation of the next Content.
+     */
+    public void startPart(String contentType, String[] headers)
+         throws IOException
+    {
+        if (inPart)
+            out.write(__CRLF);
+        inPart=true;
+        out.write(__DASHDASH);
+        out.write(boundaryBytes);
+        out.write(__CRLF);
+        if (contentType != null)
+            out.write(("Content-Type: "+contentType).getBytes(StandardCharsets.ISO_8859_1));
+        out.write(__CRLF);
+        for (int i=0;headers!=null && i<headers.length;i++)
+        {
+            out.write(headers[i].getBytes(StandardCharsets.ISO_8859_1));
+            out.write(__CRLF);
+        }
+        out.write(__CRLF);
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public void write(byte[] b, int off, int len) throws IOException
+    {
+        out.write(b,off,len);
+    }
+}
+
+
+
+
diff --git a/lib/jetty/org/eclipse/jetty/util/MultiPartWriter.java b/lib/jetty/org/eclipse/jetty/util/MultiPartWriter.java
new file mode 100644 (file)
index 0000000..0021712
--- /dev/null
@@ -0,0 +1,144 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util;
+
+import java.io.FilterWriter;
+import java.io.IOException;
+import java.io.Writer;
+
+
+/* ================================================================ */
+/** Handle a multipart MIME response.
+ *
+ * 
+ * 
+*/
+public class MultiPartWriter extends FilterWriter
+{
+    /* ------------------------------------------------------------ */
+    private final static String __CRLF="\015\012";
+    private final static String __DASHDASH="--";
+    
+    public static final String MULTIPART_MIXED=MultiPartOutputStream.MULTIPART_MIXED;
+    public static final String MULTIPART_X_MIXED_REPLACE=MultiPartOutputStream.MULTIPART_X_MIXED_REPLACE;
+    
+    /* ------------------------------------------------------------ */
+    private String boundary;
+
+    /* ------------------------------------------------------------ */
+    private boolean inPart=false;    
+    
+    /* ------------------------------------------------------------ */
+    public MultiPartWriter(Writer out)
+         throws IOException
+    {
+        super(out);
+        boundary = "jetty"+System.identityHashCode(this)+
+        Long.toString(System.currentTimeMillis(),36);
+        
+        inPart=false;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** End the current part.
+     * @exception IOException IOException
+     */
+    @Override
+    public void close()
+         throws IOException
+    {
+        try
+        {
+            if (inPart)
+                out.write(__CRLF);
+            out.write(__DASHDASH);
+            out.write(boundary);
+            out.write(__DASHDASH);
+            out.write(__CRLF);
+            inPart=false;
+        }
+        finally
+        {
+            super.close();
+        }
+    }
+    
+    /* ------------------------------------------------------------ */
+    public String getBoundary()
+    {
+        return boundary;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** Start creation of the next Content.
+     */
+    public void startPart(String contentType)
+         throws IOException
+    {
+        if (inPart)
+            out.write(__CRLF);
+        out.write(__DASHDASH);
+        out.write(boundary);
+        out.write(__CRLF);
+        out.write("Content-Type: ");
+        out.write(contentType);
+        out.write(__CRLF);
+        out.write(__CRLF);
+        inPart=true;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** end creation of the next Content.
+     */
+    public void endPart()
+         throws IOException
+    {
+        if (inPart)
+            out.write(__CRLF);
+        inPart=false;
+    }
+        
+    /* ------------------------------------------------------------ */
+    /** Start creation of the next Content.
+     */
+    public void startPart(String contentType, String[] headers)
+         throws IOException
+    {
+        if (inPart)
+            out.write(__CRLF);
+        out.write(__DASHDASH);
+        out.write(boundary);
+        out.write(__CRLF);
+        out.write("Content-Type: ");
+        out.write(contentType);
+        out.write(__CRLF);
+        for (int i=0;headers!=null && i<headers.length;i++)
+        {
+            out.write(headers[i]);
+            out.write(__CRLF);
+        }
+        out.write(__CRLF);
+        inPart=true;
+    }
+    
+}
+
+
+
+
diff --git a/lib/jetty/org/eclipse/jetty/util/PatternMatcher.java b/lib/jetty/org/eclipse/jetty/util/PatternMatcher.java
new file mode 100644 (file)
index 0000000..0afda65
--- /dev/null
@@ -0,0 +1,104 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util;
+
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.regex.Pattern;
+
+public abstract class PatternMatcher
+{
+    public abstract void matched (URI uri) throws Exception;
+    
+    
+    /**
+     * Find jar names from the provided list matching a pattern.
+     * 
+     * If the pattern is null and isNullInclusive is true, then
+     * all jar names will match.
+     * 
+     * A pattern is a set of acceptable jar names. Each acceptable
+     * jar name is a regex. Each regex can be separated by either a
+     * "," or a "|". If you use a "|" this or's together the jar
+     * name patterns. This means that ordering of the matches is
+     * unimportant to you. If instead, you want to match particular
+     * jar names, and you want to match them in order, you should
+     * separate the regexs with "," instead. 
+     * 
+     * Eg "aaa-.*\\.jar|bbb-.*\\.jar"
+     * Will iterate over the jar names and match
+     * in any order.
+     * 
+     * Eg "aaa-*\\.jar,bbb-.*\\.jar"
+     * Will iterate over the jar names, matching
+     * all those starting with "aaa-" first, then "bbb-".
+     *
+     * @param pattern the pattern
+     * @param uris the uris to test the pattern against
+     * @param isNullInclusive if true, an empty pattern means all names match, if false, none match
+     * @throws Exception
+     */
+    public void match (Pattern pattern, URI[] uris, boolean isNullInclusive)
+    throws Exception
+    {
+        if (uris!=null)
+        {
+            String[] patterns = (pattern==null?null:pattern.pattern().split(","));
+
+            List<Pattern> subPatterns = new ArrayList<Pattern>();
+            for (int i=0; patterns!=null && i<patterns.length;i++)
+            {
+                subPatterns.add(Pattern.compile(patterns[i]));
+            }
+            if (subPatterns.isEmpty())
+                subPatterns.add(pattern);
+
+            if (subPatterns.isEmpty())
+            {
+                matchPatterns(null, uris, isNullInclusive);
+            }
+            else
+            {
+                //for each subpattern, iterate over all the urls, processing those that match
+                for (Pattern p : subPatterns)
+                {
+                    matchPatterns(p, uris, isNullInclusive);
+                }
+            }
+        }
+    }
+
+
+    public void matchPatterns (Pattern pattern, URI[] uris, boolean isNullInclusive)
+    throws Exception
+    {
+        for (int i=0; i<uris.length;i++)
+        {
+            URI uri = uris[i];
+            String s = uri.toString();
+            if ((pattern == null && isNullInclusive)
+                    ||
+                    (pattern!=null && pattern.matcher(s).matches()))
+            {
+                matched(uris[i]);
+            }
+        }
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/util/Promise.java b/lib/jetty/org/eclipse/jetty/util/Promise.java
new file mode 100644 (file)
index 0000000..6727fc4
--- /dev/null
@@ -0,0 +1,65 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util;
+
+import org.eclipse.jetty.util.log.Log;
+
+/**
+ * <p>A callback abstraction that handles completed/failed events of asynchronous operations.</p>
+ *
+ * @param <C> the type of the context object
+ */
+public interface Promise<C>
+{
+    /**
+     * <p>Callback invoked when the operation completes.</p>
+     *
+     * @param result the context
+     * @see #failed(Throwable)
+     */
+    public abstract void succeeded(C result);
+
+    /**
+     * <p>Callback invoked when the operation fails.</p>
+     *
+     * @param x the reason for the operation failure
+     */
+    public void failed(Throwable x);
+    
+
+    /**
+     * <p>Empty implementation of {@link Promise}</p>
+     *
+     * @param <C> the type of the context object
+     */
+    public static class Adapter<C> implements Promise<C>
+    {
+        @Override
+        public void succeeded(C result)
+        {
+        }
+
+        @Override
+        public void failed(Throwable x)
+        {
+            Log.getLogger(this.getClass()).warn(x);
+        }
+    }
+
+}
diff --git a/lib/jetty/org/eclipse/jetty/util/QuotedStringTokenizer.java b/lib/jetty/org/eclipse/jetty/util/QuotedStringTokenizer.java
new file mode 100644 (file)
index 0000000..3a4d518
--- /dev/null
@@ -0,0 +1,609 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.NoSuchElementException;
+import java.util.StringTokenizer;
+
+/* ------------------------------------------------------------ */
+/** StringTokenizer with Quoting support.
+ *
+ * This class is a copy of the java.util.StringTokenizer API and
+ * the behaviour is the same, except that single and double quoted
+ * string values are recognised.
+ * Delimiters within quotes are not considered delimiters.
+ * Quotes can be escaped with '\'.
+ *
+ * @see java.util.StringTokenizer
+ *
+ */
+public class QuotedStringTokenizer
+    extends StringTokenizer
+{
+    private final static String __delim="\t\n\r";
+    private String _string;
+    private String _delim = __delim;
+    private boolean _returnQuotes=false;
+    private boolean _returnDelimiters=false;
+    private StringBuffer _token;
+    private boolean _hasToken=false;
+    private int _i=0;
+    private int _lastStart=0;
+    private boolean _double=true;
+    private boolean _single=true;
+
+    /* ------------------------------------------------------------ */
+    public QuotedStringTokenizer(String str,
+                                 String delim,
+                                 boolean returnDelimiters,
+                                 boolean returnQuotes)
+    {
+        super("");
+        _string=str;
+        if (delim!=null)
+            _delim=delim;
+        _returnDelimiters=returnDelimiters;
+        _returnQuotes=returnQuotes;
+
+        if (_delim.indexOf('\'')>=0 ||
+            _delim.indexOf('"')>=0)
+            throw new Error("Can't use quotes as delimiters: "+_delim);
+
+        _token=new StringBuffer(_string.length()>1024?512:_string.length()/2);
+    }
+
+    /* ------------------------------------------------------------ */
+    public QuotedStringTokenizer(String str,
+                                 String delim,
+                                 boolean returnDelimiters)
+    {
+        this(str,delim,returnDelimiters,false);
+    }
+
+    /* ------------------------------------------------------------ */
+    public QuotedStringTokenizer(String str,
+                                 String delim)
+    {
+        this(str,delim,false,false);
+    }
+
+    /* ------------------------------------------------------------ */
+    public QuotedStringTokenizer(String str)
+    {
+        this(str,null,false,false);
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public boolean hasMoreTokens()
+    {
+        // Already found a token
+        if (_hasToken)
+            return true;
+
+        _lastStart=_i;
+
+        int state=0;
+        boolean escape=false;
+        while (_i<_string.length())
+        {
+            char c=_string.charAt(_i++);
+
+            switch (state)
+            {
+              case 0: // Start
+                  if(_delim.indexOf(c)>=0)
+                  {
+                      if (_returnDelimiters)
+                      {
+                          _token.append(c);
+                          return _hasToken=true;
+                      }
+                  }
+                  else if (c=='\'' && _single)
+                  {
+                      if (_returnQuotes)
+                          _token.append(c);
+                      state=2;
+                  }
+                  else if (c=='\"' && _double)
+                  {
+                      if (_returnQuotes)
+                          _token.append(c);
+                      state=3;
+                  }
+                  else
+                  {
+                      _token.append(c);
+                      _hasToken=true;
+                      state=1;
+                  }
+                  break;
+
+              case 1: // Token
+                  _hasToken=true;
+                  if(_delim.indexOf(c)>=0)
+                  {
+                      if (_returnDelimiters)
+                          _i--;
+                      return _hasToken;
+                  }
+                  else if (c=='\'' && _single)
+                  {
+                      if (_returnQuotes)
+                          _token.append(c);
+                      state=2;
+                  }
+                  else if (c=='\"' && _double)
+                  {
+                      if (_returnQuotes)
+                          _token.append(c);
+                      state=3;
+                  }
+                  else
+                  {
+                      _token.append(c);
+                  }
+                  break;
+
+              case 2: // Single Quote
+                  _hasToken=true;
+                  if (escape)
+                  {
+                      escape=false;
+                      _token.append(c);
+                  }
+                  else if (c=='\'')
+                  {
+                      if (_returnQuotes)
+                          _token.append(c);
+                      state=1;
+                  }
+                  else if (c=='\\')
+                  {
+                      if (_returnQuotes)
+                          _token.append(c);
+                      escape=true;
+                  }
+                  else
+                  {
+                      _token.append(c);
+                  }
+                  break;
+
+              case 3: // Double Quote
+                  _hasToken=true;
+                  if (escape)
+                  {
+                      escape=false;
+                      _token.append(c);
+                  }
+                  else if (c=='\"')
+                  {
+                      if (_returnQuotes)
+                          _token.append(c);
+                      state=1;
+                  }
+                  else if (c=='\\')
+                  {
+                      if (_returnQuotes)
+                          _token.append(c);
+                      escape=true;
+                  }
+                  else
+                  {
+                      _token.append(c);
+                  }
+                  break;
+            }
+        }
+
+        return _hasToken;
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public String nextToken()
+        throws NoSuchElementException
+    {
+        if (!hasMoreTokens() || _token==null)
+            throw new NoSuchElementException();
+        String t=_token.toString();
+        _token.setLength(0);
+        _hasToken=false;
+        return t;
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public String nextToken(String delim)
+        throws NoSuchElementException
+    {
+        _delim=delim;
+        _i=_lastStart;
+        _token.setLength(0);
+        _hasToken=false;
+        return nextToken();
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public boolean hasMoreElements()
+    {
+        return hasMoreTokens();
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public Object nextElement()
+        throws NoSuchElementException
+    {
+        return nextToken();
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Not implemented.
+     */
+    @Override
+    public int countTokens()
+    {
+        return -1;
+    }
+
+
+    /* ------------------------------------------------------------ */
+    /** Quote a string.
+     * The string is quoted only if quoting is required due to
+     * embedded delimiters, quote characters or the
+     * empty string.
+     * @param s The string to quote.
+     * @param delim the delimiter to use to quote the string
+     * @return quoted string
+     */
+    public static String quoteIfNeeded(String s, String delim)
+    {
+        if (s==null)
+            return null;
+        if (s.length()==0)
+            return "\"\"";
+
+
+        for (int i=0;i<s.length();i++)
+        {
+            char c = s.charAt(i);
+            if (c=='\\' || c=='"' || c=='\'' || Character.isWhitespace(c) || delim.indexOf(c)>=0)
+            {
+                StringBuffer b=new StringBuffer(s.length()+8);
+                quote(b,s);
+                return b.toString();
+            }
+        }
+
+        return s;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Quote a string.
+     * The string is quoted only if quoting is required due to
+     * embeded delimiters, quote characters or the
+     * empty string.
+     * @param s The string to quote.
+     * @return quoted string
+     */
+    public static String quote(String s)
+    {
+        if (s==null)
+            return null;
+        if (s.length()==0)
+            return "\"\"";
+
+        StringBuffer b=new StringBuffer(s.length()+8);
+        quote(b,s);
+        return b.toString();
+
+    }
+
+    private static final char[] escapes = new char[32];
+    static
+    {
+        Arrays.fill(escapes, (char)0xFFFF);
+        escapes['\b'] = 'b';
+        escapes['\t'] = 't';
+        escapes['\n'] = 'n';
+        escapes['\f'] = 'f';
+        escapes['\r'] = 'r';
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Quote a string into an Appendable.
+     * Only quotes and backslash are escaped.
+     * @param buffer The Appendable
+     * @param input The String to quote.
+     */
+    public static void quoteOnly(Appendable buffer, String input)
+    {
+        if(input==null)
+            return;
+
+        try
+        {
+            buffer.append('"');
+            for (int i = 0; i < input.length(); ++i)
+            {
+                char c = input.charAt(i);
+                if (c == '"' || c == '\\')
+                    buffer.append('\\');
+                buffer.append(c);
+            }
+            buffer.append('"');
+        }
+        catch (IOException x)
+        {
+            throw new RuntimeException(x);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Quote a string into an Appendable.
+     * The characters ", \, \n, \r, \t, \f and \b are escaped
+     * @param buffer The Appendable
+     * @param input The String to quote.
+     */
+    public static void quote(Appendable buffer, String input)
+    {
+        if(input==null)
+            return;
+
+        try
+        {
+            buffer.append('"');
+            for (int i = 0; i < input.length(); ++i)
+            {
+                char c = input.charAt(i);
+                if (c >= 32)
+                {
+                    if (c == '"' || c == '\\')
+                        buffer.append('\\');
+                    buffer.append(c);
+                }
+                else
+                {
+                    char escape = escapes[c];
+                    if (escape == 0xFFFF)
+                    {
+                        // Unicode escape
+                        buffer.append('\\').append('u').append('0').append('0');
+                        if (c < 0x10)
+                            buffer.append('0');
+                        buffer.append(Integer.toString(c, 16));
+                    }
+                    else
+                    {
+                        buffer.append('\\').append(escape);
+                    }
+                }
+            }
+            buffer.append('"');
+        }
+        catch (IOException x)
+        {
+            throw new RuntimeException(x);
+        }
+    }
+
+
+    /* ------------------------------------------------------------ */
+    public static String unquoteOnly(String s)
+    {
+        return unquoteOnly(s, false);
+    }
+
+
+    /* ------------------------------------------------------------ */
+    /** Unquote a string, NOT converting unicode sequences
+     * @param s The string to unquote.
+     * @param lenient if true, will leave in backslashes that aren't valid escapes
+     * @return quoted string
+     */
+    public static String unquoteOnly(String s, boolean lenient)
+    {
+        if (s==null)
+            return null;
+        if (s.length()<2)
+            return s;
+
+        char first=s.charAt(0);
+        char last=s.charAt(s.length()-1);
+        if (first!=last || (first!='"' && first!='\''))
+            return s;
+
+        StringBuilder b = new StringBuilder(s.length() - 2);
+        boolean escape=false;
+        for (int i=1;i<s.length()-1;i++)
+        {
+            char c = s.charAt(i);
+
+            if (escape)
+            {
+                escape=false;
+                if (lenient && !isValidEscaping(c))
+                {
+                    b.append('\\');
+                }
+                b.append(c);
+            }
+            else if (c=='\\')
+            {
+                escape=true;
+            }
+            else
+            {
+                b.append(c);
+            }
+        }
+
+        return b.toString();
+    }
+
+    /* ------------------------------------------------------------ */
+    public static String unquote(String s)
+    {
+        return unquote(s,false);
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Unquote a string.
+     * @param s The string to unquote.
+     * @return quoted string
+     */
+    public static String unquote(String s, boolean lenient)
+    {
+        if (s==null)
+            return null;
+        if (s.length()<2)
+            return s;
+
+        char first=s.charAt(0);
+        char last=s.charAt(s.length()-1);
+        if (first!=last || (first!='"' && first!='\''))
+            return s;
+
+        StringBuilder b = new StringBuilder(s.length() - 2);
+        boolean escape=false;
+        for (int i=1;i<s.length()-1;i++)
+        {
+            char c = s.charAt(i);
+
+            if (escape)
+            {
+                escape=false;
+                switch (c)
+                {
+                    case 'n':
+                        b.append('\n');
+                        break;
+                    case 'r':
+                        b.append('\r');
+                        break;
+                    case 't':
+                        b.append('\t');
+                        break;
+                    case 'f':
+                        b.append('\f');
+                        break;
+                    case 'b':
+                        b.append('\b');
+                        break;
+                    case '\\':
+                        b.append('\\');
+                        break;
+                    case '/':
+                        b.append('/');
+                        break;
+                    case '"':
+                        b.append('"');
+                        break;
+                    case 'u':
+                        b.append((char)(
+                                (TypeUtil.convertHexDigit((byte)s.charAt(i++))<<24)+
+                                (TypeUtil.convertHexDigit((byte)s.charAt(i++))<<16)+
+                                (TypeUtil.convertHexDigit((byte)s.charAt(i++))<<8)+
+                                (TypeUtil.convertHexDigit((byte)s.charAt(i++)))
+                                )
+                        );
+                        break;
+                    default:
+                        if (lenient && !isValidEscaping(c))
+                        {
+                            b.append('\\');
+                        }
+                        b.append(c);
+                }
+            }
+            else if (c=='\\')
+            {
+                escape=true;
+            }
+            else
+            {
+                b.append(c);
+            }
+        }
+
+        return b.toString();
+    }
+
+
+    /* ------------------------------------------------------------ */
+    /** Check that char c (which is preceded by a backslash) is a valid
+     * escape sequence.
+     * @param c
+     * @return
+     */
+    private static boolean isValidEscaping(char c)
+    {
+        return ((c == 'n') || (c == 'r') || (c == 't') ||
+                 (c == 'f') || (c == 'b') || (c == '\\') ||
+                 (c == '/') || (c == '"') || (c == 'u'));
+    }
+
+    /* ------------------------------------------------------------ */
+    public static boolean isQuoted(String s)
+    {
+        return s!=null && s.length()>0 && s.charAt(0)=='"' && s.charAt(s.length()-1)=='"';
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return handle double quotes if true
+     */
+    public boolean getDouble()
+    {
+        return _double;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param d handle double quotes if true
+     */
+    public void setDouble(boolean d)
+    {
+        _double=d;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return handle single quotes if true
+     */
+    public boolean getSingle()
+    {
+        return _single;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param single handle single quotes if true
+     */
+    public void setSingle(boolean single)
+    {
+        _single=single;
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/util/ReadLineInputStream.java b/lib/jetty/org/eclipse/jetty/util/ReadLineInputStream.java
new file mode 100644 (file)
index 0000000..067b2c1
--- /dev/null
@@ -0,0 +1,137 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util;
+
+import java.io.BufferedInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+
+/**
+ * ReadLineInputStream
+ *
+ * Read from an input stream, accepting CR/LF, LF or just CR.
+ */
+public class ReadLineInputStream extends BufferedInputStream
+{
+    boolean _seenCRLF;
+    boolean _skipLF;
+    
+    public ReadLineInputStream(InputStream in)
+    {
+        super(in);
+    }
+
+    public ReadLineInputStream(InputStream in, int size)
+    {
+        super(in,size);
+    }
+    
+    public String readLine() throws IOException
+    {
+        mark(buf.length);
+                
+        while (true)
+        {
+            int b=super.read();
+            
+            if (markpos < 0)
+                throw new IOException("Buffer size exceeded: no line terminator");
+            
+            if (b==-1)
+            {
+                int m=markpos;
+                markpos=-1;
+                if (pos>m)
+                    return new String(buf,m,pos-m, StandardCharsets.UTF_8);
+
+                return null;
+            }
+            
+            if (b=='\r')
+            {
+                int p=pos;
+                
+                // if we have seen CRLF before, hungrily consume LF
+                if (_seenCRLF && pos<count)
+                {
+                    if (buf[pos]=='\n')
+                        pos+=1;
+                }
+                else
+                    _skipLF=true;
+                int m=markpos;
+                markpos=-1;
+                return new String(buf,m,p-m-1,StandardCharsets.UTF_8);
+            }
+            
+            if (b=='\n')
+            {
+                if (_skipLF)
+                {
+                    _skipLF=false;
+                    _seenCRLF=true;
+                    markpos++;
+                    continue;
+                }
+                int m=markpos;
+                markpos=-1;
+                return new String(buf,m,pos-m-1,StandardCharsets.UTF_8);
+            }
+        }
+    }
+
+    @Override
+    public synchronized int read() throws IOException
+    {
+        int b = super.read();
+        if (_skipLF)
+        {
+            _skipLF=false;
+            if (_seenCRLF && b=='\n')
+                b=super.read();
+        }
+        return b;
+    }
+
+    @Override
+    public synchronized int read(byte[] buf, int off, int len) throws IOException
+    {
+        if (_skipLF && len>0)
+        {
+            _skipLF=false;
+            if (_seenCRLF)
+            {
+                int b = super.read();
+                if (b==-1)
+                    return -1;
+                
+                if (b!='\n')
+                {
+                    buf[off]=(byte)(0xff&b);
+                    return 1+super.read(buf,off+1,len-1);
+                }
+            }
+        }
+        
+        return super.read(buf,off,len);
+    }
+    
+    
+}
diff --git a/lib/jetty/org/eclipse/jetty/util/RolloverFileOutputStream.java b/lib/jetty/org/eclipse/jetty/util/RolloverFileOutputStream.java
new file mode 100644 (file)
index 0000000..60f5da4
--- /dev/null
@@ -0,0 +1,340 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util; 
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.FilterOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.text.SimpleDateFormat;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.GregorianCalendar;
+import java.util.Locale;
+import java.util.TimeZone;
+import java.util.Timer;
+import java.util.TimerTask;
+
+/** 
+ * RolloverFileOutputStream
+ * 
+ * This output stream puts content in a file that is rolled over every 24 hours.
+ * The filename must include the string "yyyy_mm_dd", which is replaced with the 
+ * actual date when creating and rolling over the file.
+ * 
+ * Old files are retained for a number of days before being deleted.
+ * 
+ * 
+ */
+public class RolloverFileOutputStream extends FilterOutputStream
+{
+    private static Timer __rollover;
+    
+    final static String YYYY_MM_DD="yyyy_mm_dd";
+    final static String ROLLOVER_FILE_DATE_FORMAT = "yyyy_MM_dd";
+    final static String ROLLOVER_FILE_BACKUP_FORMAT = "HHmmssSSS";
+    final static int ROLLOVER_FILE_RETAIN_DAYS = 31;
+
+    private RollTask _rollTask;
+    private SimpleDateFormat _fileBackupFormat;
+    private SimpleDateFormat _fileDateFormat;
+
+    private String _filename;
+    private File _file;
+    private boolean _append;
+    private int _retainDays;
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * @param filename The filename must include the string "yyyy_mm_dd", 
+     * which is replaced with the actual date when creating and rolling over the file.
+     * @throws IOException
+     */
+    public RolloverFileOutputStream(String filename)
+        throws IOException
+    {
+        this(filename,true,ROLLOVER_FILE_RETAIN_DAYS);
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * @param filename The filename must include the string "yyyy_mm_dd", 
+     * which is replaced with the actual date when creating and rolling over the file.
+     * @param append If true, existing files will be appended to.
+     * @throws IOException
+     */
+    public RolloverFileOutputStream(String filename, boolean append)
+        throws IOException
+    {
+        this(filename,append,ROLLOVER_FILE_RETAIN_DAYS);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param filename The filename must include the string "yyyy_mm_dd", 
+     * which is replaced with the actual date when creating and rolling over the file.
+     * @param append If true, existing files will be appended to.
+     * @param retainDays The number of days to retain files before deleting them.  0 to retain forever.
+     * @throws IOException
+     */
+    public RolloverFileOutputStream(String filename,
+                                    boolean append,
+                                    int retainDays)
+        throws IOException
+    {
+        this(filename,append,retainDays,TimeZone.getDefault());
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param filename The filename must include the string "yyyy_mm_dd", 
+     * which is replaced with the actual date when creating and rolling over the file.
+     * @param append If true, existing files will be appended to.
+     * @param retainDays The number of days to retain files before deleting them. 0 to retain forever.
+     * @throws IOException
+     */
+    public RolloverFileOutputStream(String filename,
+                                    boolean append,
+                                    int retainDays,
+                                    TimeZone zone)
+        throws IOException
+    {
+
+         this(filename,append,retainDays,zone,null,null);
+    }
+     
+    /* ------------------------------------------------------------ */
+    /**
+     * @param filename The filename must include the string "yyyy_mm_dd", 
+     * which is replaced with the actual date when creating and rolling over the file.
+     * @param append If true, existing files will be appended to.
+     * @param retainDays The number of days to retain files before deleting them. 0 to retain forever.
+     * @param dateFormat The format for the date file substitution. The default is "yyyy_MM_dd". 
+     * @param backupFormat The format for the file extension of backup files. The default is "HHmmssSSS". 
+     * @throws IOException
+     */
+    public RolloverFileOutputStream(String filename,
+                                    boolean append,
+                                    int retainDays,
+                                    TimeZone zone,
+                                    String dateFormat,
+                                    String backupFormat)
+        throws IOException
+    {
+        super(null);
+
+        if (dateFormat==null)
+            dateFormat=ROLLOVER_FILE_DATE_FORMAT;
+        _fileDateFormat = new SimpleDateFormat(dateFormat);
+        
+        if (backupFormat==null)
+            backupFormat=ROLLOVER_FILE_BACKUP_FORMAT;
+        _fileBackupFormat = new SimpleDateFormat(backupFormat);
+        
+        _fileBackupFormat.setTimeZone(zone);
+        _fileDateFormat.setTimeZone(zone);
+        
+        if (filename!=null)
+        {
+            filename=filename.trim();
+            if (filename.length()==0)
+                filename=null;
+        }
+        if (filename==null)
+            throw new IllegalArgumentException("Invalid filename");
+
+        _filename=filename;
+        _append=append;
+        _retainDays=retainDays;
+        setFile();
+        
+        synchronized(RolloverFileOutputStream.class)
+        {
+            if (__rollover==null)
+                __rollover=new Timer(RolloverFileOutputStream.class.getName(),true);
+            
+            _rollTask=new RollTask();
+
+             Calendar now = Calendar.getInstance();
+             now.setTimeZone(zone);
+
+             GregorianCalendar midnight =
+                 new GregorianCalendar(now.get(Calendar.YEAR),
+                         now.get(Calendar.MONTH),
+                         now.get(Calendar.DAY_OF_MONTH),
+                         23,0);
+             midnight.setTimeZone(zone);
+             midnight.add(Calendar.HOUR,1);
+             __rollover.scheduleAtFixedRate(_rollTask,midnight.getTime(),1000L*60*60*24);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    public String getFilename()
+    {
+        return _filename;
+    }
+    
+    /* ------------------------------------------------------------ */
+    public String getDatedFilename()
+    {
+        if (_file==null)
+            return null;
+        return _file.toString();
+    }
+    
+    /* ------------------------------------------------------------ */
+    public int getRetainDays()
+    {
+        return _retainDays;
+    }
+
+    /* ------------------------------------------------------------ */
+    private synchronized void setFile()
+        throws IOException
+    {
+        // Check directory
+        File file = new File(_filename);
+        _filename=file.getCanonicalPath();
+        file=new File(_filename);
+        File dir= new File(file.getParent());
+        if (!dir.isDirectory() || !dir.canWrite())
+            throw new IOException("Cannot write log directory "+dir);
+            
+        Date now=new Date();
+        
+        // Is this a rollover file?
+        String filename=file.getName();
+        int i=filename.toLowerCase(Locale.ENGLISH).indexOf(YYYY_MM_DD);
+        if (i>=0)
+        {
+            file=new File(dir,
+                          filename.substring(0,i)+
+                          _fileDateFormat.format(now)+
+                          filename.substring(i+YYYY_MM_DD.length()));
+        }
+            
+        if (file.exists()&&!file.canWrite())
+            throw new IOException("Cannot write log file "+file);
+
+        // Do we need to change the output stream?
+        if (out==null || !file.equals(_file))
+        {
+            // Yep
+            _file=file;
+            if (!_append && file.exists())
+                file.renameTo(new File(file.toString()+"."+_fileBackupFormat.format(now)));
+            OutputStream oldOut=out;
+            out=new FileOutputStream(file.toString(),_append);
+            if (oldOut!=null)
+                oldOut.close();
+            //if(log.isDebugEnabled())log.debug("Opened "+_file);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    private void removeOldFiles()
+    {
+        if (_retainDays>0)
+        {
+            long now = System.currentTimeMillis();
+            
+            File file= new File(_filename);
+            File dir = new File(file.getParent());
+            String fn=file.getName();
+            int s=fn.toLowerCase(Locale.ENGLISH).indexOf(YYYY_MM_DD);
+            if (s<0)
+                return;
+            String prefix=fn.substring(0,s);
+            String suffix=fn.substring(s+YYYY_MM_DD.length());
+
+            String[] logList=dir.list();
+            for (int i=0;i<logList.length;i++)
+            {
+                fn = logList[i];
+                if(fn.startsWith(prefix)&&fn.indexOf(suffix,prefix.length())>=0)
+                {        
+                    File f = new File(dir,fn);
+                    long date = f.lastModified();
+                    if ( ((now-date)/(1000*60*60*24))>_retainDays)
+                        f.delete();   
+                }
+            }
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public void write (byte[] buf)
+            throws IOException
+     {
+            out.write (buf);
+     }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public void write (byte[] buf, int off, int len)
+            throws IOException
+     {
+            out.write (buf, off, len);
+     }
+    
+    /* ------------------------------------------------------------ */
+    /** 
+     */
+    @Override
+    public void close()
+        throws IOException
+    {
+        synchronized(RolloverFileOutputStream.class)
+        {
+            try{super.close();}
+            finally
+            {
+                out=null;
+                _file=null;
+            }
+
+            _rollTask.cancel(); 
+        }
+    }
+    
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    private class RollTask extends TimerTask
+    {
+        @Override
+        public void run()
+        {
+            try
+            {
+                RolloverFileOutputStream.this.setFile();
+                RolloverFileOutputStream.this.removeOldFiles();
+
+            }
+            catch(IOException e)
+            {
+                // Cannot log this exception to a LOG, as RolloverFOS can be used by logging
+                e.printStackTrace();
+            }
+        }
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/util/Scanner.java b/lib/jetty/org/eclipse/jetty/util/Scanner.java
new file mode 100644 (file)
index 0000000..f29db3d
--- /dev/null
@@ -0,0 +1,736 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+
+package org.eclipse.jetty.util;
+
+import java.io.File;
+import java.io.FilenameFilter;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+import java.util.Timer;
+import java.util.TimerTask;
+
+import org.eclipse.jetty.util.component.AbstractLifeCycle;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+
+/**
+ * Scanner
+ * 
+ * Utility for scanning a directory for added, removed and changed
+ * files and reporting these events via registered Listeners.
+ *
+ */
+public class Scanner extends AbstractLifeCycle
+{
+    private static final Logger LOG = Log.getLogger(Scanner.class);
+    private static int __scannerId=0;
+    private int _scanInterval;
+    private int _scanCount = 0;
+    private final List<Listener> _listeners = new ArrayList<Listener>();
+    private final Map<String,TimeNSize> _prevScan = new HashMap<String,TimeNSize> ();
+    private final Map<String,TimeNSize> _currentScan = new HashMap<String,TimeNSize> ();
+    private FilenameFilter _filter;
+    private final List<File> _scanDirs = new ArrayList<File>();
+    private volatile boolean _running = false;
+    private boolean _reportExisting = true;
+    private boolean _reportDirs = true;
+    private Timer _timer;
+    private TimerTask _task;
+    private int _scanDepth=0;
+    
+    public enum Notification { ADDED, CHANGED, REMOVED };
+    private final Map<String,Notification> _notifications = new HashMap<String,Notification>();
+
+    static class TimeNSize
+    {
+        final long _lastModified;
+        final long _size;
+        
+        public TimeNSize(long lastModified, long size)
+        {
+            _lastModified = lastModified;
+            _size = size;
+        }
+        
+        @Override
+        public int hashCode()
+        {
+            return (int)_lastModified^(int)_size;
+        }
+        
+        @Override
+        public boolean equals(Object o)
+        {
+            if (o instanceof TimeNSize)
+            {
+                TimeNSize tns = (TimeNSize)o;
+                return tns._lastModified==_lastModified && tns._size==_size;
+            }
+            return false;
+        }
+        
+        @Override
+        public String toString()
+        {
+            return "[lm="+_lastModified+",s="+_size+"]";
+        }
+    }
+    
+    /**
+     * Listener
+     * 
+     * Marker for notifications re file changes.
+     */
+    public interface Listener
+    {
+    }
+
+    public interface ScanListener extends Listener
+    {
+        public void scan();
+    }
+    
+    public interface DiscreteListener extends Listener
+    {
+        public void fileChanged (String filename) throws Exception;
+        public void fileAdded (String filename) throws Exception;
+        public void fileRemoved (String filename) throws Exception;
+    }
+    
+    
+    public interface BulkListener extends Listener
+    {
+        public void filesChanged (List<String> filenames) throws Exception;
+    }
+
+    /**
+     * Listener that notifies when a scan has started and when it has ended.
+     */
+    public interface ScanCycleListener extends Listener
+    {
+        public void scanStarted(int cycle) throws Exception;
+        public void scanEnded(int cycle) throws Exception;
+    }
+
+    /**
+     * 
+     */
+    public Scanner ()
+    {       
+    }
+
+    /**
+     * Get the scan interval
+     * @return interval between scans in seconds
+     */
+    public synchronized int getScanInterval()
+    {
+        return _scanInterval;
+    }
+
+    /**
+     * Set the scan interval
+     * @param scanInterval pause between scans in seconds, or 0 for no scan after the initial scan.
+     */
+    public synchronized void setScanInterval(int scanInterval)
+    {
+        _scanInterval = scanInterval;
+        schedule();
+    }
+
+    public void setScanDirs (List<File> dirs)
+    {
+        _scanDirs.clear(); 
+        _scanDirs.addAll(dirs);
+    }
+    
+    public synchronized void addScanDir( File dir )
+    {
+        _scanDirs.add( dir );
+    }
+    
+    public List<File> getScanDirs ()
+    {
+        return Collections.unmodifiableList(_scanDirs);
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * @param recursive True if scanning is recursive
+     * @see  #setScanDepth(int)
+     */
+    public void setRecursive (boolean recursive)
+    {
+        _scanDepth=recursive?-1:0;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * @return True if scanning is fully recursive (scandepth==-1)
+     * @see #getScanDepth()
+     */
+    public boolean getRecursive ()
+    {
+        return _scanDepth==-1;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** Get the scanDepth.
+     * @return the scanDepth
+     */
+    public int getScanDepth()
+    {
+        return _scanDepth;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Set the scanDepth.
+     * @param scanDepth the scanDepth to set
+     */
+    public void setScanDepth(int scanDepth)
+    {
+        _scanDepth = scanDepth;
+    }
+
+    /**
+     * Apply a filter to files found in the scan directory.
+     * Only files matching the filter will be reported as added/changed/removed.
+     * @param filter
+     */
+    public void setFilenameFilter (FilenameFilter filter)
+    {
+        _filter = filter;
+    }
+
+    /**
+     * Get any filter applied to files in the scan dir.
+     * @return the filename filter
+     */
+    public FilenameFilter getFilenameFilter ()
+    {
+        return _filter;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Whether or not an initial scan will report all files as being
+     * added.
+     * @param reportExisting if true, all files found on initial scan will be 
+     * reported as being added, otherwise not
+     */
+    public void setReportExistingFilesOnStartup (boolean reportExisting)
+    {
+        _reportExisting = reportExisting;
+    }
+
+    /* ------------------------------------------------------------ */
+    public boolean getReportExistingFilesOnStartup()
+    {
+        return _reportExisting;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** Set if found directories should be reported.
+     * @param dirs
+     */
+    public void setReportDirs(boolean dirs)
+    {
+        _reportDirs=dirs;
+    }
+    
+    /* ------------------------------------------------------------ */
+    public boolean getReportDirs()
+    {
+        return _reportDirs;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * Add an added/removed/changed listener
+     * @param listener
+     */
+    public synchronized void addListener (Listener listener)
+    {
+        if (listener == null)
+            return;
+        _listeners.add(listener);   
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Remove a registered listener
+     * @param listener the Listener to be removed
+     */
+    public synchronized void removeListener (Listener listener)
+    {
+        if (listener == null)
+            return;
+        _listeners.remove(listener);    
+    }
+
+
+    /**
+     * Start the scanning action.
+     */
+    @Override
+    public synchronized void doStart()
+    {
+        if (_running)
+            return;
+
+        _running = true;
+
+        if (_reportExisting)
+        {
+            // if files exist at startup, report them
+            scan();
+            scan(); // scan twice so files reported as stable
+        }
+        else
+        {
+            //just register the list of existing files and only report changes
+            scanFiles();
+            _prevScan.putAll(_currentScan);
+        }
+        schedule();
+    }
+
+    public TimerTask newTimerTask ()
+    {
+        return new TimerTask()
+        {
+            @Override
+            public void run() { scan(); }
+        };
+    }
+
+    public Timer newTimer ()
+    {
+        return new Timer("Scanner-"+__scannerId++, true);
+    }
+    
+    public void schedule ()
+    {  
+        if (_running)
+        {
+            if (_timer!=null)
+                _timer.cancel();
+            if (_task!=null)
+                _task.cancel();
+            if (getScanInterval() > 0)
+            {
+                _timer = newTimer();
+                _task = newTimerTask();
+                _timer.schedule(_task, 1010L*getScanInterval(),1010L*getScanInterval());
+            }
+        }
+    }
+    /**
+     * Stop the scanning.
+     */
+    @Override
+    public synchronized void doStop()
+    {
+        if (_running)
+        {
+            _running = false; 
+            if (_timer!=null)
+                _timer.cancel();
+            if (_task!=null)
+                _task.cancel();
+            _task=null;
+            _timer=null;
+        }
+    }
+
+    /**
+     * @return true if the path exists in one of the scandirs
+     */
+    public boolean exists(String path)
+    {
+        for (File dir : _scanDirs)
+            if (new File(dir,path).exists())
+                return true;
+        return false;
+    }
+    
+    
+    /**
+     * Perform a pass of the scanner and report changes
+     */
+    public synchronized void scan ()
+    {
+        reportScanStart(++_scanCount);
+        scanFiles();
+        reportDifferences(_currentScan, _prevScan);
+        _prevScan.clear();
+        _prevScan.putAll(_currentScan);
+        reportScanEnd(_scanCount);
+        
+        for (Listener l : _listeners)
+        {
+            try
+            {
+                if (l instanceof ScanListener)
+                    ((ScanListener)l).scan();
+            }
+            catch (Exception e)
+            {
+                LOG.warn(e);
+            }
+            catch (Error e)
+            {
+                LOG.warn(e);
+            }
+        }
+    }
+
+    /**
+     * Recursively scan all files in the designated directories.
+     */
+    public synchronized void scanFiles ()
+    {
+        if (_scanDirs==null)
+            return;
+        
+        _currentScan.clear();
+        Iterator<File> itor = _scanDirs.iterator();
+        while (itor.hasNext())
+        {
+            File dir = itor.next();
+            
+            if ((dir != null) && (dir.exists()))
+                try
+                {
+                    scanFile(dir.getCanonicalFile(), _currentScan,0);
+                }
+                catch (IOException e)
+                {
+                    LOG.warn("Error scanning files.", e);
+                }
+        }
+    }
+
+
+    /**
+     * Report the adds/changes/removes to the registered listeners
+     * 
+     * @param currentScan the info from the most recent pass
+     * @param oldScan info from the previous pass
+     */
+    public synchronized void reportDifferences (Map<String,TimeNSize> currentScan, Map<String,TimeNSize> oldScan) 
+    {
+        // scan the differences and add what was found to the map of notifications:
+
+        Set<String> oldScanKeys = new HashSet<String>(oldScan.keySet());
+        
+        // Look for new and changed files
+        for (Map.Entry<String, TimeNSize> entry: currentScan.entrySet())
+        {
+            String file = entry.getKey(); 
+            if (!oldScanKeys.contains(file))
+            {
+                Notification old=_notifications.put(file,Notification.ADDED);
+                if (old!=null)
+                { 
+                    switch(old)
+                    {
+                        case REMOVED: 
+                        case CHANGED:
+                            _notifications.put(file,Notification.CHANGED);
+                    }
+                }
+            }
+            else if (!oldScan.get(file).equals(currentScan.get(file)))
+            {
+                Notification old=_notifications.put(file,Notification.CHANGED);
+                if (old!=null)
+                {
+                    switch(old)
+                    {
+                        case ADDED:
+                            _notifications.put(file,Notification.ADDED);
+                    }
+                }
+            }
+        }
+        
+        // Look for deleted files
+        for (String file : oldScan.keySet())
+        {
+            if (!currentScan.containsKey(file))
+            {
+                Notification old=_notifications.put(file,Notification.REMOVED);
+                if (old!=null)
+                {
+                    switch(old)
+                    {
+                        case ADDED:
+                            _notifications.remove(file);
+                    }
+                }
+            }
+        }
+        
+        if (LOG.isDebugEnabled())
+            LOG.debug("scanned "+_scanDirs+": "+_notifications);
+                
+        // Process notifications
+        // Only process notifications that are for stable files (ie same in old and current scan).
+        List<String> bulkChanges = new ArrayList<String>();
+        for (Iterator<Entry<String,Notification>> iter = _notifications.entrySet().iterator();iter.hasNext();)
+        {
+            Entry<String,Notification> entry=iter.next();
+            String file=entry.getKey();
+            
+            // Is the file stable?
+            if (oldScan.containsKey(file))
+            {
+                if (!oldScan.get(file).equals(currentScan.get(file)))
+                    continue;
+            }
+            else if (currentScan.containsKey(file))
+                continue;
+                            
+            // File is stable so notify
+            Notification notification=entry.getValue();
+            iter.remove();
+            bulkChanges.add(file);
+            switch(notification)
+            {
+                case ADDED:
+                    reportAddition(file);
+                    break;
+                case CHANGED:
+                    reportChange(file);
+                    break;
+                case REMOVED:
+                    reportRemoval(file);
+                    break;
+            }
+        }
+        if (!bulkChanges.isEmpty())
+            reportBulkChanges(bulkChanges);
+    }
+
+
+    /**
+     * Get last modified time on a single file or recurse if
+     * the file is a directory. 
+     * @param f file or directory
+     * @param scanInfoMap map of filenames to last modified times
+     */
+    private void scanFile (File f, Map<String,TimeNSize> scanInfoMap, int depth)
+    {
+        try
+        {
+            if (!f.exists())
+                return;
+
+            if (f.isFile() || depth>0&& _reportDirs && f.isDirectory())
+            {
+                if ((_filter == null) || ((_filter != null) && _filter.accept(f.getParentFile(), f.getName())))
+                {
+                    LOG.debug("scan accepted {}",f);
+                    String name = f.getCanonicalPath();
+                    scanInfoMap.put(name, new TimeNSize(f.lastModified(),f.length()));
+                }
+                else
+                    LOG.debug("scan rejected {}",f);
+            }
+            
+            // If it is a directory, scan if it is a known directory or the depth is OK.
+            if (f.isDirectory() && (depth<_scanDepth || _scanDepth==-1 || _scanDirs.contains(f)))
+            {
+                File[] files = f.listFiles();
+                if (files != null)
+                {
+                    for (int i=0;i<files.length;i++)
+                        scanFile(files[i], scanInfoMap,depth+1);
+                }
+                else
+                    LOG.warn("Error listing files in directory {}", f);
+            }
+        }
+        catch (IOException e)
+        {
+            LOG.warn("Error scanning watched files", e);
+        }
+    }
+
+    private void warn(Object listener,String filename,Throwable th)
+    {
+        LOG.warn(listener+" failed on '"+filename, th);
+    }
+
+    /**
+     * Report a file addition to the registered FileAddedListeners
+     * @param filename
+     */
+    private void reportAddition (String filename)
+    {
+        Iterator<Listener> itor = _listeners.iterator();
+        while (itor.hasNext())
+        {
+            Listener l = itor.next();
+            try
+            {
+                if (l instanceof DiscreteListener)
+                    ((DiscreteListener)l).fileAdded(filename);
+            }
+            catch (Exception e)
+            {
+                warn(l,filename,e);
+            }
+            catch (Error e)
+            {
+                warn(l,filename,e);
+            }
+        }
+    }
+
+
+    /**
+     * Report a file removal to the FileRemovedListeners
+     * @param filename
+     */
+    private void reportRemoval (String filename)
+    {
+        Iterator<Listener> itor = _listeners.iterator();
+        while (itor.hasNext())
+        {
+            Object l = itor.next();
+            try
+            {
+                if (l instanceof DiscreteListener)
+                    ((DiscreteListener)l).fileRemoved(filename);
+            }
+            catch (Exception e)
+            {
+                warn(l,filename,e);
+            }
+            catch (Error e)
+            {
+                warn(l,filename,e);
+            }
+        }
+    }
+
+
+    /**
+     * Report a file change to the FileChangedListeners
+     * @param filename
+     */
+    private void reportChange (String filename)
+    {
+        Iterator<Listener> itor = _listeners.iterator();
+        while (itor.hasNext())
+        {
+            Listener l = itor.next();
+            try
+            {
+                if (l instanceof DiscreteListener)
+                    ((DiscreteListener)l).fileChanged(filename);
+            }
+            catch (Exception e)
+            {
+                warn(l,filename,e);
+            }
+            catch (Error e)
+            {
+                warn(l,filename,e);
+            }
+        }
+    }
+    
+    private void reportBulkChanges (List<String> filenames)
+    {
+        Iterator<Listener> itor = _listeners.iterator();
+        while (itor.hasNext())
+        {
+            Listener l = itor.next();
+            try
+            {
+                if (l instanceof BulkListener)
+                    ((BulkListener)l).filesChanged(filenames);
+            }
+            catch (Exception e)
+            {
+                warn(l,filenames.toString(),e);
+            }
+            catch (Error e)
+            {
+                warn(l,filenames.toString(),e);
+            }
+        }
+    }
+    
+    /**
+     * signal any scan cycle listeners that a scan has started
+     */
+    private void reportScanStart(int cycle)
+    {
+        for (Listener listener : _listeners)
+        {
+            try
+            {
+                if (listener instanceof ScanCycleListener)
+                {
+                    ((ScanCycleListener)listener).scanStarted(cycle);
+                }
+            }
+            catch (Exception e)
+            {
+                LOG.warn(listener + " failed on scan start for cycle " + cycle, e);
+            }
+        }
+    }
+
+    /**
+     * sign
+     */
+    private void reportScanEnd(int cycle)
+    {
+        for (Listener listener : _listeners)
+        {
+            try
+            {
+                if (listener instanceof ScanCycleListener)
+                {
+                    ((ScanCycleListener)listener).scanEnded(cycle);
+                }
+            }
+            catch (Exception e)
+            {
+                LOG.warn(listener + " failed on scan end for cycle " + cycle, e);
+            }
+        }
+    }
+
+}
diff --git a/lib/jetty/org/eclipse/jetty/util/SharedBlockingCallback.java b/lib/jetty/org/eclipse/jetty/util/SharedBlockingCallback.java
new file mode 100644 (file)
index 0000000..3085a68
--- /dev/null
@@ -0,0 +1,272 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.io.InterruptedIOException;
+import java.util.concurrent.CancellationException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import java.util.concurrent.locks.Condition;
+import java.util.concurrent.locks.ReentrantLock;
+
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.thread.NonBlockingThread;
+
+
+/* ------------------------------------------------------------ */
+/** Provides a reusable BlockingCallback.
+ * A typical usage pattern is:
+ * <pre>
+ * void someBlockingCall(Object... args) throws IOException
+ * {
+ *   try(Blocker blocker=sharedBlockingCallback.acquire())
+ *   {
+ *     someAsyncCall(args,blocker);
+ *     blocker.block();
+ *   }
+ * }
+ * </pre>
+ */
+public class SharedBlockingCallback
+{
+    private static final Logger LOG = Log.getLogger(SharedBlockingCallback.class);
+
+    
+    private static Throwable IDLE = new Throwable()
+    {
+        @Override
+        public String toString()
+        {
+            return "IDLE";
+        }
+    };
+
+    private static Throwable SUCCEEDED = new Throwable()
+    {
+        @Override
+        public String toString()
+        {
+            return "SUCCEEDED";
+        }
+    };
+    
+    private static Throwable FAILED = new Throwable()
+    {
+        @Override
+        public String toString()
+        {
+            return "FAILED";
+        }
+    };
+
+    final Blocker _blocker;
+    
+    public SharedBlockingCallback()
+    {
+        this(new Blocker());
+    }
+    
+    protected SharedBlockingCallback(Blocker blocker)
+    {
+        _blocker=blocker;
+    }
+    
+    public Blocker acquire() throws IOException
+    {
+        _blocker._lock.lock();
+        try
+        {
+            while (_blocker._state != IDLE)
+                _blocker._idle.await();
+            _blocker._state = null;
+        }
+        catch (final InterruptedException e)
+        {
+            throw new InterruptedIOException()
+            {
+                {
+                    initCause(e);
+                }
+            };
+        }
+        finally
+        {
+            _blocker._lock.unlock();
+        }
+        return _blocker;
+    }
+
+    
+    /* ------------------------------------------------------------ */
+    /** A Closeable Callback.
+     * Uses the auto close mechanism to check block has been called OK.
+     */
+    public static class Blocker implements Callback, Closeable
+    {
+        final ReentrantLock _lock = new ReentrantLock();
+        final Condition _idle = _lock.newCondition();
+        final Condition _complete = _lock.newCondition();
+        Throwable _state = IDLE;
+
+        public Blocker()
+        {
+        }
+
+        @Override
+        public void succeeded()
+        {
+            _lock.lock();
+            try
+            {
+                if (_state == null)
+                {
+                    _state = SUCCEEDED;
+                    _complete.signalAll();
+                }
+                else if (_state == IDLE)
+                    throw new IllegalStateException("IDLE");
+            }
+            finally
+            {
+                _lock.unlock();
+            }
+        }
+
+        @Override
+        public void failed(Throwable cause)
+        {
+            _lock.lock();
+            try
+            {
+                if (_state == null)
+                {
+                    // TODO remove when feedback received on 435322
+                    if (cause==null)
+                        LOG.warn("null failed cause (please report stack trace) ",new Throwable());
+                    _state = cause==null?FAILED:cause;
+                    _complete.signalAll();
+                }
+                else if (_state == IDLE)
+                    throw new IllegalStateException("IDLE");
+            }
+            finally
+            {
+                _lock.unlock();
+            }
+        }
+
+        /**
+         * Block until the Callback has succeeded or failed and after the return leave in the state to allow reuse. This is useful for code that wants to
+         * repeatable use a FutureCallback to convert an asynchronous API to a blocking API.
+         * 
+         * @throws IOException
+         *             if exception was caught during blocking, or callback was cancelled
+         */
+        public void block() throws IOException
+        {
+            if (NonBlockingThread.isNonBlockingThread())
+                LOG.warn("Blocking a NonBlockingThread: ",new Throwable());
+            
+            _lock.lock();
+            try
+            {
+                while (_state == null)
+                {
+                    // TODO remove this debug timout!
+                    // This is here to help debug 435322,
+                    if (!_complete.await(10,TimeUnit.MINUTES))
+                    {
+                        IOException x = new IOException("DEBUG timeout");
+                        LOG.warn("Blocked too long (please report!!!) "+this, x);
+                        _state=x;
+                    }
+                }
+
+                if (_state == SUCCEEDED)
+                    return;
+                if (_state == IDLE)
+                    throw new IllegalStateException("IDLE");
+                if (_state instanceof IOException)
+                    throw (IOException)_state;
+                if (_state instanceof CancellationException)
+                    throw (CancellationException)_state;
+                if (_state instanceof RuntimeException)
+                    throw (RuntimeException)_state;
+                if (_state instanceof Error)
+                    throw (Error)_state;
+                throw new IOException(_state);
+            }
+            catch (final InterruptedException e)
+            {
+                throw new InterruptedIOException()
+                {
+                    {
+                        initCause(e);
+                    }
+                };
+            }
+            finally
+            {
+                _lock.unlock();
+            }
+        }
+        
+        /**
+         * Check the Callback has succeeded or failed and after the return leave in the state to allow reuse.
+         * 
+         * @throws IOException
+         *             if exception was caught during blocking, or callback was cancelled
+         */
+        @Override
+        public void close() throws IOException
+        {
+            _lock.lock();
+            try
+            {
+                if (_state == IDLE)
+                    throw new IllegalStateException("IDLE");
+                if (_state == null)
+                    LOG.debug("Blocker not complete",new Throwable());
+            }
+            finally
+            {
+                _state = IDLE;
+                _idle.signalAll();
+                _lock.unlock();
+            }
+        }
+
+        @Override
+        public String toString()
+        {
+            _lock.lock();
+            try
+            {
+                return String.format("%s@%x{%s}",SharedBlockingCallback.class.getSimpleName(),hashCode(),_state);
+            }
+            finally
+            {
+                _lock.unlock();
+            }
+        }
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/util/SocketAddressResolver.java b/lib/jetty/org/eclipse/jetty/util/SocketAddressResolver.java
new file mode 100644 (file)
index 0000000..e75abfc
--- /dev/null
@@ -0,0 +1,175 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util;
+
+import java.net.InetSocketAddress;
+import java.net.SocketAddress;
+import java.nio.channels.UnresolvedAddressException;
+import java.util.concurrent.Executor;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.thread.Scheduler;
+
+/**
+ * Creates asynchronously {@link SocketAddress} instances, returning them through a {@link Promise},
+ * in order to avoid blocking on DNS lookup.
+ * <p />
+ * {@link InetSocketAddress#InetSocketAddress(String, int)} attempts to perform a DNS resolution of
+ * the host name, and this may block for several seconds.
+ * This class creates the {@link InetSocketAddress} in a separate thread and provides the result
+ * through a {@link Promise}, with the possibility to specify a timeout for the operation.
+ * <p />
+ * Example usage:
+ * <pre>
+ * SocketAddressResolver resolver = new SocketAddressResolver(executor, scheduler);
+ * resolver.resolve("www.google.com", 80, new Promise&lt;SocketAddress&gt;()
+ * {
+ *     public void succeeded(SocketAddress result)
+ *     {
+ *         // The address was resolved
+ *     }
+ *
+ *     public void failed(Throwable failure)
+ *     {
+ *         // The address resolution failed
+ *     }
+ * });
+ * </pre>
+ */
+public class SocketAddressResolver
+{
+    private static final Logger LOG = Log.getLogger(SocketAddressResolver.class);
+
+    private final Executor executor;
+    private final Scheduler scheduler;
+    private final long timeout;
+
+    /**
+     * Creates a new instance with the given executor (to perform DNS resolution in a separate thread),
+     * the given scheduler (to cancel the operation if it takes too long) and the given timeout, in milliseconds.
+     *
+     * @param executor the thread pool to use to perform DNS resolution in pooled threads
+     * @param scheduler the scheduler to schedule tasks to cancel DNS resolution if it takes too long
+     * @param timeout the timeout, in milliseconds, for the DNS resolution to complete
+     */
+    public SocketAddressResolver(Executor executor, Scheduler scheduler, long timeout)
+    {
+        this.executor = executor;
+        this.scheduler = scheduler;
+        this.timeout = timeout;
+    }
+
+    public Executor getExecutor()
+    {
+        return executor;
+    }
+
+    public Scheduler getScheduler()
+    {
+        return scheduler;
+    }
+
+    public long getTimeout()
+    {
+        return timeout;
+    }
+
+    /**
+     * Resolves the given host and port, returning a {@link SocketAddress} through the given {@link Promise}
+     * with the default timeout.
+     *
+     * @param host the host to resolve
+     * @param port the port of the resulting socket address
+     * @param promise the callback invoked when the resolution succeeds or fails
+     * @see #resolve(String, int, long, Promise)
+     */
+    public void resolve(String host, int port, Promise<SocketAddress> promise)
+    {
+        resolve(host, port, timeout, promise);
+    }
+
+    /**
+     * Resolves the given host and port, returning a {@link SocketAddress} through the given {@link Promise}
+     * with the given timeout.
+     *
+     * @param host the host to resolve
+     * @param port the port of the resulting socket address
+     * @param timeout the timeout, in milliseconds, for the DNS resolution to complete
+     * @param promise the callback invoked when the resolution succeeds or fails
+     */
+    protected void resolve(final String host, final int port, final long timeout, final Promise<SocketAddress> promise)
+    {
+        executor.execute(new Runnable()
+        {
+            @Override
+            public void run()
+            {
+                Scheduler.Task task = null;
+                final AtomicBoolean complete = new AtomicBoolean();
+                if (timeout > 0)
+                {
+                    final Thread thread = Thread.currentThread();
+                    task = scheduler.schedule(new Runnable()
+                    {
+                        @Override
+                        public void run()
+                        {
+                            if (complete.compareAndSet(false, true))
+                            {
+                                promise.failed(new TimeoutException());
+                                thread.interrupt();
+                            }
+                        }
+                    }, timeout, TimeUnit.MILLISECONDS);
+                }
+
+                try
+                {
+                    long start = System.nanoTime();
+                    InetSocketAddress result = new InetSocketAddress(host, port);
+                    long elapsed = System.nanoTime() - start;
+                    LOG.debug("Resolved {} in {} ms", host, TimeUnit.NANOSECONDS.toMillis(elapsed));
+                    if (complete.compareAndSet(false, true))
+                    {
+                        if (result.isUnresolved())
+                            promise.failed(new UnresolvedAddressException());
+                        else
+                            promise.succeeded(result);
+                    }
+                }
+                catch (Throwable x)
+                {
+                    if (complete.compareAndSet(false, true))
+                        promise.failed(x);
+                }
+                finally
+                {
+                    if (task != null)
+                        task.cancel();
+                    // Reset the interrupted status before releasing the thread to the pool
+                    Thread.interrupted();
+                }
+            }
+        });
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/util/StringUtil.java b/lib/jetty/org/eclipse/jetty/util/StringUtil.java
new file mode 100644 (file)
index 0000000..55868ad
--- /dev/null
@@ -0,0 +1,735 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util;
+
+import java.io.UnsupportedEncodingException;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/** Fast String Utilities.
+ *
+ * These string utilities provide both convenience methods and
+ * performance improvements over most standard library versions. The
+ * main aim of the optimizations is to avoid object creation unless
+ * absolutely required.
+ *
+ * 
+ */
+public class StringUtil
+{
+    private static final Logger LOG = Log.getLogger(StringUtil.class);
+    
+    
+    private final static Trie<String> CHARSETS= new ArrayTrie<>(256);
+    
+    public static final String ALL_INTERFACES="0.0.0.0";
+    public static final String CRLF="\015\012";
+    public static final String __LINE_SEPARATOR=
+        System.getProperty("line.separator","\n");
+       
+    public static final String __ISO_8859_1="ISO-8859-1";
+    public final static String __UTF8="UTF-8";
+    public final static String __UTF16="UTF-16";
+
+    /**
+     * @deprecated Use {@link StandardCharsets#UTF_8}
+     */
+    @Deprecated
+    public final static Charset __UTF8_CHARSET=StandardCharsets.UTF_8;
+    /**
+     * @deprecated Use {@link StandardCharsets#ISO_8859_1}
+     */
+    @Deprecated
+    public final static Charset __ISO_8859_1_CHARSET=StandardCharsets.ISO_8859_1;
+    /**
+     * @deprecated Use {@link StandardCharsets#UTF_16}
+     */
+    @Deprecated
+    public final static Charset __UTF16_CHARSET=StandardCharsets.UTF_16;
+    /**
+     * @deprecated Use {@link StandardCharsets#US_ASCII}
+     */
+    @Deprecated
+    public final static Charset __US_ASCII_CHARSET=StandardCharsets.US_ASCII;
+    
+    static
+    {
+        CHARSETS.put("UTF-8",__UTF8);
+        CHARSETS.put("UTF8",__UTF8);
+        CHARSETS.put("UTF-16",__UTF16);
+        CHARSETS.put("UTF16",__UTF16);
+        CHARSETS.put("ISO-8859-1",__ISO_8859_1);
+        CHARSETS.put("ISO_8859_1",__ISO_8859_1);
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** Convert alternate charset names (eg utf8) to normalized
+     * name (eg UTF-8).
+     */
+    public static String normalizeCharset(String s)
+    {
+        String n=CHARSETS.get(s);
+        return (n==null)?s:n;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** Convert alternate charset names (eg utf8) to normalized
+     * name (eg UTF-8).
+     */
+    public static String normalizeCharset(String s,int offset,int length)
+    {
+        String n=CHARSETS.get(s,offset,length);       
+        return (n==null)?s.substring(offset,offset+length):n;
+    }
+    
+
+    /* ------------------------------------------------------------ */
+    public static final char[] lowercases = {
+          '\000','\001','\002','\003','\004','\005','\006','\007',
+          '\010','\011','\012','\013','\014','\015','\016','\017',
+          '\020','\021','\022','\023','\024','\025','\026','\027',
+          '\030','\031','\032','\033','\034','\035','\036','\037',
+          '\040','\041','\042','\043','\044','\045','\046','\047',
+          '\050','\051','\052','\053','\054','\055','\056','\057',
+          '\060','\061','\062','\063','\064','\065','\066','\067',
+          '\070','\071','\072','\073','\074','\075','\076','\077',
+          '\100','\141','\142','\143','\144','\145','\146','\147',
+          '\150','\151','\152','\153','\154','\155','\156','\157',
+          '\160','\161','\162','\163','\164','\165','\166','\167',
+          '\170','\171','\172','\133','\134','\135','\136','\137',
+          '\140','\141','\142','\143','\144','\145','\146','\147',
+          '\150','\151','\152','\153','\154','\155','\156','\157',
+          '\160','\161','\162','\163','\164','\165','\166','\167',
+          '\170','\171','\172','\173','\174','\175','\176','\177' };
+
+    /* ------------------------------------------------------------ */
+    /**
+     * fast lower case conversion. Only works on ascii (not unicode)
+     * @param s the string to convert
+     * @return a lower case version of s
+     */
+    public static String asciiToLowerCase(String s)
+    {
+        char[] c = null;
+        int i=s.length();
+
+        // look for first conversion
+        while (i-->0)
+        {
+            char c1=s.charAt(i);
+            if (c1<=127)
+            {
+                char c2=lowercases[c1];
+                if (c1!=c2)
+                {
+                    c=s.toCharArray();
+                    c[i]=c2;
+                    break;
+                }
+            }
+        }
+
+        while (i-->0)
+        {
+            if(c[i]<=127)
+                c[i] = lowercases[c[i]];
+        }
+        
+        return c==null?s:new String(c);
+    }
+
+
+    /* ------------------------------------------------------------ */
+    public static boolean startsWithIgnoreCase(String s,String w)
+    {
+        if (w==null)
+            return true;
+        
+        if (s==null || s.length()<w.length())
+            return false;
+        
+        for (int i=0;i<w.length();i++)
+        {
+            char c1=s.charAt(i);
+            char c2=w.charAt(i);
+            if (c1!=c2)
+            {
+                if (c1<=127)
+                    c1=lowercases[c1];
+                if (c2<=127)
+                    c2=lowercases[c2];
+                if (c1!=c2)
+                    return false;
+            }
+        }
+        return true;
+    }
+    
+    /* ------------------------------------------------------------ */
+    public static boolean endsWithIgnoreCase(String s,String w)
+    {
+        if (w==null)
+            return true;
+
+        if (s==null)
+            return false;
+            
+        int sl=s.length();
+        int wl=w.length();
+        
+        if (sl<wl)
+            return false;
+        
+        for (int i=wl;i-->0;)
+        {
+            char c1=s.charAt(--sl);
+            char c2=w.charAt(i);
+            if (c1!=c2)
+            {
+                if (c1<=127)
+                    c1=lowercases[c1];
+                if (c2<=127)
+                    c2=lowercases[c2];
+                if (c1!=c2)
+                    return false;
+            }
+        }
+        return true;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * returns the next index of a character from the chars string
+     */
+    public static int indexFrom(String s,String chars)
+    {
+        for (int i=0;i<s.length();i++)
+           if (chars.indexOf(s.charAt(i))>=0)
+              return i;
+        return -1;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * replace substrings within string.
+     */
+    public static String replace(String s, String sub, String with)
+    {
+        int c=0;
+        int i=s.indexOf(sub,c);
+        if (i == -1)
+            return s;
+    
+        StringBuilder buf = new StringBuilder(s.length()+with.length());
+
+        do
+        {
+            buf.append(s.substring(c,i));
+            buf.append(with);
+            c=i+sub.length();
+        } while ((i=s.indexOf(sub,c))!=-1);
+
+        if (c<s.length())
+            buf.append(s.substring(c,s.length()));
+
+        return buf.toString();
+        
+    }
+
+
+    /* ------------------------------------------------------------ */
+    /** Remove single or double quotes.
+     */
+    public static String unquote(String s)
+    {
+        return QuotedStringTokenizer.unquote(s);
+    }
+
+
+    /* ------------------------------------------------------------ */
+    /** Append substring to StringBuilder 
+     * @param buf StringBuilder to append to
+     * @param s String to append from
+     * @param offset The offset of the substring
+     * @param length The length of the substring
+     */
+    public static void append(StringBuilder buf,
+                              String s,
+                              int offset,
+                              int length)
+    {
+        synchronized(buf)
+        {
+            int end=offset+length;
+            for (int i=offset; i<end;i++)
+            {
+                if (i>=s.length())
+                    break;
+                buf.append(s.charAt(i));
+            }
+        }
+    }
+
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * append hex digit
+     * 
+     */
+    public static void append(StringBuilder buf,byte b,int base)
+    {
+        int bi=0xff&b;
+        int c='0'+(bi/base)%base;
+        if (c>'9')
+            c= 'a'+(c-'0'-10);
+        buf.append((char)c);
+        c='0'+bi%base;
+        if (c>'9')
+            c= 'a'+(c-'0'-10);
+        buf.append((char)c);
+    }
+
+    /* ------------------------------------------------------------ */
+    public static void append2digits(StringBuffer buf,int i)
+    {
+        if (i<100)
+        {
+            buf.append((char)(i/10+'0'));
+            buf.append((char)(i%10+'0'));
+        }
+    }
+    
+    /* ------------------------------------------------------------ */
+    public static void append2digits(StringBuilder buf,int i)
+    {
+        if (i<100)
+        {
+            buf.append((char)(i/10+'0'));
+            buf.append((char)(i%10+'0'));
+        }
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** Return a non null string.
+     * @param s String
+     * @return The string passed in or empty string if it is null. 
+     */
+    public static String nonNull(String s)
+    {
+        if (s==null)
+            return "";
+        return s;
+    }
+    
+    /* ------------------------------------------------------------ */
+    public static boolean equals(String s,char[] buf, int offset, int length)
+    {
+        if (s.length()!=length)
+            return false;
+        for (int i=0;i<length;i++)
+            if (buf[offset+i]!=s.charAt(i))
+                return false;
+        return true;
+    }
+
+    /* ------------------------------------------------------------ */
+    public static String toUTF8String(byte[] b,int offset,int length)
+    {
+        return new String(b,offset,length,StandardCharsets.UTF_8);
+    }
+
+    /* ------------------------------------------------------------ */
+    public static String toString(byte[] b,int offset,int length,String charset)
+    {
+        try
+        {
+            return new String(b,offset,length,charset);
+        }
+        catch (UnsupportedEncodingException e)
+        {
+            throw new IllegalArgumentException(e);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Test if a string is null or only has whitespace characters in it.
+     * <p>
+     * Note: uses codepoint version of {@link Character#isWhitespace(int)} to support Unicode better.
+     * 
+     * <pre>
+     *   isBlank(null)   == true
+     *   isBlank("")     == true
+     *   isBlank("\r\n") == true
+     *   isBlank("\t")   == true
+     *   isBlank("   ")  == true
+     *   isBlank("a")    == false
+     *   isBlank(".")    == false
+     *   isBlank(";\n")  == false
+     * </pre>
+     * 
+     * @param str
+     *            the string to test.
+     * @return true if string is null or only whitespace characters, false if non-whitespace characters encountered.
+     */
+    public static boolean isBlank(String str)
+    {
+        if (str == null)
+        {
+            return true;
+        }
+        int len = str.length();
+        for (int i = 0; i < len; i++)
+        {
+            if (!Character.isWhitespace(str.codePointAt(i)))
+            {
+                // found a non-whitespace, we can stop searching  now
+                return false;
+            }
+        }
+        // only whitespace
+        return true;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * Test if a string is not null and contains at least 1 non-whitespace characters in it.
+     * <p>
+     * Note: uses codepoint version of {@link Character#isWhitespace(int)} to support Unicode better.
+     * 
+     * <pre>
+     *   isNotBlank(null)   == false
+     *   isNotBlank("")     == false
+     *   isNotBlank("\r\n") == false
+     *   isNotBlank("\t")   == false
+     *   isNotBlank("   ")  == false
+     *   isNotBlank("a")    == true
+     *   isNotBlank(".")    == true
+     *   isNotBlank(";\n")  == true
+     * </pre>
+     * 
+     * @param str
+     *            the string to test.
+     * @return true if string is not null and has at least 1 non-whitespace character, false if null or all-whitespace characters.
+     */
+    public static boolean isNotBlank(String str)
+    {
+        if (str == null)
+        {
+            return false;
+        }
+        int len = str.length();
+        for (int i = 0; i < len; i++)
+        {
+            if (!Character.isWhitespace(str.codePointAt(i)))
+            {
+                // found a non-whitespace, we can stop searching  now
+                return true;
+            }
+        }
+        // only whitespace
+        return false;
+    }
+
+    /* ------------------------------------------------------------ */
+    public static boolean isUTF8(String charset)
+    {
+        return __UTF8.equalsIgnoreCase(charset)||__UTF8.equalsIgnoreCase(normalizeCharset(charset));
+    }
+
+
+    /* ------------------------------------------------------------ */
+    public static String printable(String name)
+    {
+        if (name==null)
+            return null;
+        StringBuilder buf = new StringBuilder(name.length());
+        for (int i=0;i<name.length();i++)
+        {
+            char c=name.charAt(i);
+            if (!Character.isISOControl(c))
+                buf.append(c);
+        }
+        return buf.toString();
+    }
+    
+    /* ------------------------------------------------------------ */
+    public static String printable(byte[] b)
+    {
+        StringBuilder buf = new StringBuilder();
+        for (int i=0;i<b.length;i++)
+        {
+            char c=(char)b[i];
+            if (Character.isWhitespace(c)|| c>' ' && c<0x7f)
+                buf.append(c);
+            else 
+            {
+                buf.append("0x");
+                TypeUtil.toHex(b[i],buf);
+            }
+        }
+        return buf.toString();
+    }
+    
+    public static byte[] getBytes(String s)
+    {
+        return s.getBytes(StandardCharsets.ISO_8859_1);
+    }
+    
+    public static byte[] getUtf8Bytes(String s)
+    {
+        return s.getBytes(StandardCharsets.UTF_8);
+    }
+    
+    public static byte[] getBytes(String s,String charset)
+    {
+        try
+        {
+            return s.getBytes(charset);
+        }
+        catch(Exception e)
+        {
+            LOG.warn(e);
+            return s.getBytes();
+        }
+    }
+    
+    
+    
+    /**
+     * Converts a binary SID to a string SID
+     * 
+     * http://en.wikipedia.org/wiki/Security_Identifier
+     * 
+     * S-1-IdentifierAuthority-SubAuthority1-SubAuthority2-...-SubAuthorityn
+     */
+    public static String sidBytesToString(byte[] sidBytes)
+    {
+        StringBuilder sidString = new StringBuilder();
+        
+        // Identify this as a SID
+        sidString.append("S-");
+        
+        // Add SID revision level (expect 1 but may change someday)
+        sidString.append(Byte.toString(sidBytes[0])).append('-');
+        
+        StringBuilder tmpBuilder = new StringBuilder();
+        
+        // crunch the six bytes of issuing authority value
+        for (int i = 2; i <= 7; ++i)
+        {
+            tmpBuilder.append(Integer.toHexString(sidBytes[i] & 0xFF));
+        }
+        
+        sidString.append(Long.parseLong(tmpBuilder.toString(), 16)); // '-' is in the subauth loop
+   
+        // the number of subAuthorities we need to attach
+        int subAuthorityCount = sidBytes[1];
+
+        // attach each of the subAuthorities
+        for (int i = 0; i < subAuthorityCount; ++i)
+        {
+            int offset = i * 4;
+            tmpBuilder.setLength(0);
+            // these need to be zero padded hex and little endian
+            tmpBuilder.append(String.format("%02X%02X%02X%02X", 
+                    (sidBytes[11 + offset] & 0xFF),
+                    (sidBytes[10 + offset] & 0xFF),
+                    (sidBytes[9 + offset] & 0xFF),
+                    (sidBytes[8 + offset] & 0xFF)));  
+            sidString.append('-').append(Long.parseLong(tmpBuilder.toString(), 16));
+        }
+        
+        return sidString.toString();
+    }
+    
+    /**
+     * Converts a string SID to a binary SID
+     * 
+     * http://en.wikipedia.org/wiki/Security_Identifier
+     * 
+     * S-1-IdentifierAuthority-SubAuthority1-SubAuthority2-...-SubAuthorityn
+     */
+    public static byte[] sidStringToBytes( String sidString )
+    {
+        String[] sidTokens = sidString.split("-");
+        
+        int subAuthorityCount = sidTokens.length - 3; // S-Rev-IdAuth-
+        
+        int byteCount = 0;
+        byte[] sidBytes = new byte[1 + 1 + 6 + (4 * subAuthorityCount)];
+        
+        // the revision byte
+        sidBytes[byteCount++] = (byte)Integer.parseInt(sidTokens[1]);
+
+        // the # of sub authorities byte
+        sidBytes[byteCount++] = (byte)subAuthorityCount;
+
+        // the certAuthority
+        String hexStr = Long.toHexString(Long.parseLong(sidTokens[2]));
+        
+        while( hexStr.length() < 12) // pad to 12 characters
+        {
+            hexStr = "0" + hexStr;
+        }
+
+        // place the certAuthority 6 bytes
+        for ( int i = 0 ; i < hexStr.length(); i = i + 2)
+        {
+            sidBytes[byteCount++] = (byte)Integer.parseInt(hexStr.substring(i, i + 2),16);
+        }
+                
+        
+        for ( int i = 3; i < sidTokens.length ; ++i)
+        {
+            hexStr = Long.toHexString(Long.parseLong(sidTokens[i]));
+            
+            while( hexStr.length() < 8) // pad to 8 characters
+            {
+                hexStr = "0" + hexStr;
+            }     
+            
+            // place the inverted sub authorities, 4 bytes each
+            for ( int j = hexStr.length(); j > 0; j = j - 2)
+            {          
+                sidBytes[byteCount++] = (byte)Integer.parseInt(hexStr.substring(j-2, j),16);
+            }
+        }
+      
+        return sidBytes;
+    }
+    
+
+    /**
+     * Convert String to an integer. Parses up to the first non-numeric character. If no number is found an IllegalArgumentException is thrown
+     * 
+     * @param string
+     *            A String containing an integer.
+     * @return an int
+     */
+    public static int toInt(String string)
+    {
+        int val = 0;
+        boolean started = false;
+        boolean minus = false;
+
+        for (int i = 0; i < string.length(); i++)
+        {
+            char b = string.charAt(i);
+            if (b <= ' ')
+            {
+                if (started)
+                    break;
+            }
+            else if (b >= '0' && b <= '9')
+            {
+                val = val * 10 + (b - '0');
+                started = true;
+            }
+            else if (b == '-' && !started)
+            {
+                minus = true;
+            }
+            else
+                break;
+        }
+
+        if (started)
+            return minus?(-val):val;
+        throw new NumberFormatException(string);
+    }
+
+    /**
+     * Convert String to an long. Parses up to the first non-numeric character. If no number is found an IllegalArgumentException is thrown
+     * 
+     * @param string
+     *            A String containing an integer.
+     * @return an int
+     */
+    public static long toLong(String string)
+    {
+        long val = 0;
+        boolean started = false;
+        boolean minus = false;
+
+        for (int i = 0; i < string.length(); i++)
+        {
+            char b = string.charAt(i);
+            if (b <= ' ')
+            {
+                if (started)
+                    break;
+            }
+            else if (b >= '0' && b <= '9')
+            {
+                val = val * 10L + (b - '0');
+                started = true;
+            }
+            else if (b == '-' && !started)
+            {
+                minus = true;
+            }
+            else
+                break;
+        }
+
+        if (started)
+            return minus?(-val):val;
+        throw new NumberFormatException(string);
+    }
+    
+    /**
+     * Truncate a string to a max size.
+     * 
+     * @param str the string to possibly truncate
+     * @param maxSize the maximum size of the string
+     * @return the truncated string.  if <code>str</code> param is null, then the returned string will also be null.
+     */
+    public static String truncate(String str, int maxSize)
+    {
+        if (str == null)
+        {
+            return null;
+        }
+
+        if (str.length() <= maxSize)
+        {
+            return str;
+        }
+
+        return str.substring(0,maxSize);
+    }
+
+    public static String[] arrayFromString(String s) 
+    {
+        if (s==null)
+            return new String[]{};
+
+        if (!s.startsWith("[") || !s.endsWith("]"))
+            throw new IllegalArgumentException();
+        if (s.length()==2)
+            return new String[]{};
+
+        return s.substring(1,s.length()-1).split(" *, *");
+    }
+
+}
diff --git a/lib/jetty/org/eclipse/jetty/util/TreeTrie.java b/lib/jetty/org/eclipse/jetty/util/TreeTrie.java
new file mode 100644 (file)
index 0000000..42f3bdc
--- /dev/null
@@ -0,0 +1,349 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/* ------------------------------------------------------------ */
+/** A Trie String lookup data structure using a tree
+ * <p>This implementation is always case insensitive and is optimal for
+ * a variable number of fixed strings with few special characters.
+ * </p>
+ * @param <V>
+ */
+public class TreeTrie<V> extends AbstractTrie<V>
+{
+    private static final int[] __lookup = 
+    { // 0   1   2   3   4   5   6   7   8   9   A   B   C   D   E   F
+   /*0*/-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 
+   /*1*/-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 30, 
+   /*2*/31, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 26, -1, 27, -1, -1,
+   /*3*/-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 28, 29, -1, -1, -1, -1,
+   /*4*/-1,  0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14,
+   /*5*/15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1,
+   /*6*/-1,  0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14,
+   /*7*/15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1,
+    };
+    private static final int INDEX = 32;
+    private final TreeTrie<V>[]  _nextIndex;
+    private final List<TreeTrie<V>> _nextOther=new ArrayList<>();
+    private final char _c;
+    private String _key;
+    private V _value;
+
+    public TreeTrie()
+    {
+        super(true);
+        _nextIndex = new TreeTrie[INDEX];
+        _c=0;
+    }
+    
+    private TreeTrie(char c)
+    {
+        super(true);
+        _nextIndex = new TreeTrie[INDEX];
+        this._c=c;
+    }
+
+    @Override
+    public boolean put(String s, V v)
+    {
+        TreeTrie<V> t = this;
+        int limit = s.length();
+        for(int k=0; k < limit; k++)
+        {
+            char c=s.charAt(k);
+            
+            int index=c>=0&&c<0x7f?__lookup[c]:-1;
+            if (index>=0)
+            {
+                if (t._nextIndex[index] == null)
+                    t._nextIndex[index] = new TreeTrie<V>(c);
+                t = t._nextIndex[index];
+            }
+            else
+            {
+                TreeTrie<V> n=null;
+                for (int i=t._nextOther.size();i-->0;)
+                {
+                    n=t._nextOther.get(i);
+                    if (n._c==c)
+                        break;
+                    n=null;
+                }
+                if (n==null)
+                {
+                    n=new TreeTrie<V>(c);
+                    t._nextOther.add(n);
+                }
+                t=n;
+            }
+        }
+        t._key=v==null?null:s;
+        t._value = v;
+        return true;
+    }
+
+    @Override
+    public V get(String s,int offset, int len)
+    {
+        TreeTrie<V> t = this;
+        for(int i=0; i < len; i++)
+        {
+            char c=s.charAt(offset+i);
+            int index=c>=0&&c<0x7f?__lookup[c]:-1;
+            if (index>=0)
+            {
+                if (t._nextIndex[index] == null) 
+                    return null;
+                t = t._nextIndex[index];
+            }
+            else
+            {
+                TreeTrie<V> n=null;
+                for (int j=t._nextOther.size();j-->0;)
+                {
+                    n=t._nextOther.get(j);
+                    if (n._c==c)
+                        break;
+                    n=null;
+                }
+                if (n==null)
+                    return null;
+                t=n;
+            }
+        }
+        return t._value;
+    }
+
+    @Override
+    public V get(ByteBuffer b,int offset,int len)
+    {
+        TreeTrie<V> t = this;
+        for(int i=0; i < len; i++)
+        {
+            byte c=b.get(offset+i);
+            int index=c>=0&&c<0x7f?__lookup[c]:-1;
+            if (index>=0)
+            {
+                if (t._nextIndex[index] == null) 
+                    return null;
+                t = t._nextIndex[index];
+            }
+            else
+            {
+                TreeTrie<V> n=null;
+                for (int j=t._nextOther.size();j-->0;)
+                {
+                    n=t._nextOther.get(j);
+                    if (n._c==c)
+                        break;
+                    n=null;
+                }
+                if (n==null)
+                    return null;
+                t=n;
+            }
+        }
+        return t._value;
+    }
+
+    @Override
+    public V getBest(byte[] b,int offset,int len)
+    {
+        TreeTrie<V> t = this;
+        for(int i=0; i < len; i++)
+        {
+            byte c=b[offset+i];
+            int index=c>=0&&c<0x7f?__lookup[c]:-1;
+            if (index>=0)
+            {
+                if (t._nextIndex[index] == null) 
+                    break;
+                t = t._nextIndex[index];
+            }
+            else
+            {
+                TreeTrie<V> n=null;
+                for (int j=t._nextOther.size();j-->0;)
+                {
+                    n=t._nextOther.get(j);
+                    if (n._c==c)
+                        break;
+                    n=null;
+                }
+                if (n==null)
+                    break;
+                t=n;
+            }
+            
+            // Is the next Trie is a match
+            if (t._key!=null)
+            {
+                // Recurse so we can remember this possibility
+                V best=t.getBest(b,offset+i+1,len-i-1);
+                if (best!=null)
+                    return best;
+                break;
+            }
+        }
+        return t._value;
+    }
+
+    @Override
+    public V getBest(String s, int offset, int len)
+    {
+        // TODO inefficient
+        byte[] b=s.substring(offset,offset+len).getBytes(StandardCharsets.ISO_8859_1);
+        return getBest(b,0,b.length);
+    }
+    
+    @Override
+    public V getBest(ByteBuffer b,int offset,int len)
+    {
+        if (b.hasArray())
+            return getBest(b.array(),b.arrayOffset()+b.position()+offset,len);
+        return getBestByteBuffer(b,offset,len);
+    }
+    
+    private V getBestByteBuffer(ByteBuffer b,int offset,int len)
+    {
+        TreeTrie<V> t = this;
+        int pos=b.position()+offset;
+        for(int i=0; i < len; i++)
+        {
+            byte c=b.get(pos++);
+            int index=c>=0&&c<0x7f?__lookup[c]:-1;
+            if (index>=0)
+            {
+                if (t._nextIndex[index] == null) 
+                    break;
+                t = t._nextIndex[index];
+            }
+            else
+            {
+                TreeTrie<V> n=null;
+                for (int j=t._nextOther.size();j-->0;)
+                {
+                    n=t._nextOther.get(j);
+                    if (n._c==c)
+                        break;
+                    n=null;
+                }
+                if (n==null)
+                    break;
+                t=n;
+            }
+            
+            // Is the next Trie is a match
+            if (t._key!=null)
+            {
+                // Recurse so we can remember this possibility
+                V best=t.getBest(b,offset+i+1,len-i-1);
+                if (best!=null)
+                    return best;
+                break;
+            }
+        }
+        return t._value;
+    }
+    
+
+    @Override
+    public String toString()
+    {
+        StringBuilder buf = new StringBuilder();
+        toString(buf,this);
+        
+        if (buf.length()==0)
+            return "{}";
+        
+        buf.setCharAt(0,'{');
+        buf.append('}');
+        return buf.toString();
+    }
+
+    private static <V> void toString(Appendable out, TreeTrie<V> t)
+    {
+        if (t != null)
+        {
+            if (t._value!=null)
+            {
+                try
+                {
+                    out.append(',');
+                    out.append(t._key);
+                    out.append('=');
+                    out.append(t._value.toString());
+                }
+                catch (IOException e)
+                {
+                    throw new RuntimeException(e);
+                }
+            }
+           
+            for(int i=0; i < INDEX; i++)
+            {
+                if (t._nextIndex[i] != null)
+                    toString(out,t._nextIndex[i]);
+            }
+            for (int i=t._nextOther.size();i-->0;)
+                toString(out,t._nextOther.get(i));
+        }           
+    }
+
+    @Override
+    public Set<String> keySet()
+    {
+        Set<String> keys = new HashSet<>();
+        keySet(keys,this);
+        return keys;
+    }
+    
+    private static <V> void keySet(Set<String> set, TreeTrie<V> t)
+    {
+        if (t != null)
+        {
+            if (t._key!=null)
+                set.add(t._key);
+           
+            for(int i=0; i < INDEX; i++)
+            {
+                if (t._nextIndex[i] != null)
+                    keySet(set,t._nextIndex[i]);
+            }
+            for (int i=t._nextOther.size();i-->0;)
+                keySet(set,t._nextOther.get(i));
+        }           
+    }
+    
+    @Override
+    public boolean isFull()
+    {
+        return false;
+    }
+
+
+}
diff --git a/lib/jetty/org/eclipse/jetty/util/Trie.java b/lib/jetty/org/eclipse/jetty/util/Trie.java
new file mode 100644 (file)
index 0000000..9c74924
--- /dev/null
@@ -0,0 +1,124 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util;
+
+import java.nio.ByteBuffer;
+import java.util.Set;
+
+
+/* ------------------------------------------------------------ */
+/** A Trie String lookup data structure.
+ * @param <V>
+ */
+public interface Trie<V>
+{
+    /* ------------------------------------------------------------ */
+    /** Put and entry into the Trie
+     * @param s The key for the entry
+     * @param v The value of the entry
+     * @return True if the Trie had capacity to add the field.
+     */
+    public boolean put(String s, V v);
+    
+    /* ------------------------------------------------------------ */
+    /** Put a value as both a key and a value.
+     * @param v The value and key
+     * @return True if the Trie had capacity to add the field.
+     */
+    public boolean put(V v);
+
+    /* ------------------------------------------------------------ */
+    public V remove(String s);
+
+    /* ------------------------------------------------------------ */
+    /** Get and exact match from a String key
+     * @param s The key
+     */
+    public V get(String s);
+
+    /* ------------------------------------------------------------ */
+    /** Get and exact match from a String key
+     * @param s The key
+     * @param offset The offset within the string of the key
+     * @param len the length of the key
+     */
+    public V get(String s,int offset,int len);
+
+    /* ------------------------------------------------------------ */
+    /** Get and exact match from a segment of a ByteBuufer as key
+     * @param b The buffer
+     * @return The value or null if not found
+     */
+    public V get(ByteBuffer b);
+
+    /* ------------------------------------------------------------ */
+    /** Get and exact match from a segment of a ByteBuufer as key
+     * @param b The buffer
+     * @param offset The offset within the buffer of the key
+     * @param len the length of the key
+     * @return The value or null if not found
+     */
+    public V get(ByteBuffer b,int offset,int len);
+    
+    /* ------------------------------------------------------------ */
+    /** Get the best match from key in a String.
+     * @param s The string
+     * @return The value or null if not found
+     */
+    public V getBest(String s);
+    
+    /* ------------------------------------------------------------ */
+    /** Get the best match from key in a String.
+     * @param s The string
+     * @param offset The offset within the string of the key
+     * @param len the length of the key
+     * @return The value or null if not found
+     */
+    public V getBest(String s,int offset,int len); 
+
+    /* ------------------------------------------------------------ */
+    /** Get the best match from key in a byte array.
+     * The key is assumed to by ISO_8859_1 characters.
+     * @param b The buffer
+     * @param offset The offset within the array of the key
+     * @param len the length of the key
+     * @return The value or null if not found
+     */
+    public V getBest(byte[] b,int offset,int len);
+
+    /* ------------------------------------------------------------ */
+    /** Get the best match from key in a byte buffer.
+     * The key is assumed to by ISO_8859_1 characters.
+     * @param b The buffer
+     * @param offset The offset within the buffer of the key
+     * @param len the length of the key
+     * @return The value or null if not found
+     */
+    public V getBest(ByteBuffer b,int offset,int len);
+    
+    /* ------------------------------------------------------------ */
+    public Set<String> keySet();
+
+    /* ------------------------------------------------------------ */
+    public boolean isFull();
+
+    /* ------------------------------------------------------------ */
+    public boolean isCaseInsensitive();
+
+}
diff --git a/lib/jetty/org/eclipse/jetty/util/TypeUtil.java b/lib/jetty/org/eclipse/jetty/util/TypeUtil.java
new file mode 100644 (file)
index 0000000..b96a520
--- /dev/null
@@ -0,0 +1,647 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util;
+
+import java.io.IOException;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.eclipse.jetty.util.annotation.Name;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+
+/* ------------------------------------------------------------ */
+/**
+ * TYPE Utilities.
+ * Provides various static utiltiy methods for manipulating types and their
+ * string representations.
+ *
+ * @since Jetty 4.1
+ */
+public class TypeUtil
+{
+    private static final Logger LOG = Log.getLogger(TypeUtil.class);
+    public static final Class<?>[] NO_ARGS = new Class[]{};
+    public static final int CR = '\015';
+    public static final int LF = '\012';
+
+    /* ------------------------------------------------------------ */
+    private static final HashMap<String, Class<?>> name2Class=new HashMap<>();
+    static
+    {
+        name2Class.put("boolean",java.lang.Boolean.TYPE);
+        name2Class.put("byte",java.lang.Byte.TYPE);
+        name2Class.put("char",java.lang.Character.TYPE);
+        name2Class.put("double",java.lang.Double.TYPE);
+        name2Class.put("float",java.lang.Float.TYPE);
+        name2Class.put("int",java.lang.Integer.TYPE);
+        name2Class.put("long",java.lang.Long.TYPE);
+        name2Class.put("short",java.lang.Short.TYPE);
+        name2Class.put("void",java.lang.Void.TYPE);
+
+        name2Class.put("java.lang.Boolean.TYPE",java.lang.Boolean.TYPE);
+        name2Class.put("java.lang.Byte.TYPE",java.lang.Byte.TYPE);
+        name2Class.put("java.lang.Character.TYPE",java.lang.Character.TYPE);
+        name2Class.put("java.lang.Double.TYPE",java.lang.Double.TYPE);
+        name2Class.put("java.lang.Float.TYPE",java.lang.Float.TYPE);
+        name2Class.put("java.lang.Integer.TYPE",java.lang.Integer.TYPE);
+        name2Class.put("java.lang.Long.TYPE",java.lang.Long.TYPE);
+        name2Class.put("java.lang.Short.TYPE",java.lang.Short.TYPE);
+        name2Class.put("java.lang.Void.TYPE",java.lang.Void.TYPE);
+
+        name2Class.put("java.lang.Boolean",java.lang.Boolean.class);
+        name2Class.put("java.lang.Byte",java.lang.Byte.class);
+        name2Class.put("java.lang.Character",java.lang.Character.class);
+        name2Class.put("java.lang.Double",java.lang.Double.class);
+        name2Class.put("java.lang.Float",java.lang.Float.class);
+        name2Class.put("java.lang.Integer",java.lang.Integer.class);
+        name2Class.put("java.lang.Long",java.lang.Long.class);
+        name2Class.put("java.lang.Short",java.lang.Short.class);
+
+        name2Class.put("Boolean",java.lang.Boolean.class);
+        name2Class.put("Byte",java.lang.Byte.class);
+        name2Class.put("Character",java.lang.Character.class);
+        name2Class.put("Double",java.lang.Double.class);
+        name2Class.put("Float",java.lang.Float.class);
+        name2Class.put("Integer",java.lang.Integer.class);
+        name2Class.put("Long",java.lang.Long.class);
+        name2Class.put("Short",java.lang.Short.class);
+
+        name2Class.put(null,java.lang.Void.TYPE);
+        name2Class.put("string",java.lang.String.class);
+        name2Class.put("String",java.lang.String.class);
+        name2Class.put("java.lang.String",java.lang.String.class);
+    }
+
+    /* ------------------------------------------------------------ */
+    private static final HashMap<Class<?>, String> class2Name=new HashMap<>();
+    static
+    {
+        class2Name.put(java.lang.Boolean.TYPE,"boolean");
+        class2Name.put(java.lang.Byte.TYPE,"byte");
+        class2Name.put(java.lang.Character.TYPE,"char");
+        class2Name.put(java.lang.Double.TYPE,"double");
+        class2Name.put(java.lang.Float.TYPE,"float");
+        class2Name.put(java.lang.Integer.TYPE,"int");
+        class2Name.put(java.lang.Long.TYPE,"long");
+        class2Name.put(java.lang.Short.TYPE,"short");
+        class2Name.put(java.lang.Void.TYPE,"void");
+
+        class2Name.put(java.lang.Boolean.class,"java.lang.Boolean");
+        class2Name.put(java.lang.Byte.class,"java.lang.Byte");
+        class2Name.put(java.lang.Character.class,"java.lang.Character");
+        class2Name.put(java.lang.Double.class,"java.lang.Double");
+        class2Name.put(java.lang.Float.class,"java.lang.Float");
+        class2Name.put(java.lang.Integer.class,"java.lang.Integer");
+        class2Name.put(java.lang.Long.class,"java.lang.Long");
+        class2Name.put(java.lang.Short.class,"java.lang.Short");
+
+        class2Name.put(null,"void");
+        class2Name.put(java.lang.String.class,"java.lang.String");
+    }
+
+    /* ------------------------------------------------------------ */
+    private static final HashMap<Class<?>, Method> class2Value=new HashMap<>();
+    static
+    {
+        try
+        {
+            Class<?>[] s ={java.lang.String.class};
+
+            class2Value.put(java.lang.Boolean.TYPE,
+                           java.lang.Boolean.class.getMethod("valueOf",s));
+            class2Value.put(java.lang.Byte.TYPE,
+                           java.lang.Byte.class.getMethod("valueOf",s));
+            class2Value.put(java.lang.Double.TYPE,
+                           java.lang.Double.class.getMethod("valueOf",s));
+            class2Value.put(java.lang.Float.TYPE,
+                           java.lang.Float.class.getMethod("valueOf",s));
+            class2Value.put(java.lang.Integer.TYPE,
+                           java.lang.Integer.class.getMethod("valueOf",s));
+            class2Value.put(java.lang.Long.TYPE,
+                           java.lang.Long.class.getMethod("valueOf",s));
+            class2Value.put(java.lang.Short.TYPE,
+                           java.lang.Short.class.getMethod("valueOf",s));
+
+            class2Value.put(java.lang.Boolean.class,
+                           java.lang.Boolean.class.getMethod("valueOf",s));
+            class2Value.put(java.lang.Byte.class,
+                           java.lang.Byte.class.getMethod("valueOf",s));
+            class2Value.put(java.lang.Double.class,
+                           java.lang.Double.class.getMethod("valueOf",s));
+            class2Value.put(java.lang.Float.class,
+                           java.lang.Float.class.getMethod("valueOf",s));
+            class2Value.put(java.lang.Integer.class,
+                           java.lang.Integer.class.getMethod("valueOf",s));
+            class2Value.put(java.lang.Long.class,
+                           java.lang.Long.class.getMethod("valueOf",s));
+            class2Value.put(java.lang.Short.class,
+                           java.lang.Short.class.getMethod("valueOf",s));
+        }
+        catch(Exception e)
+        {
+            throw new Error(e);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Array to List.
+     * <p>
+     * Works like {@link Arrays#asList(Object...)}, but handles null arrays.
+     * @return a list backed by the array.
+     */
+    public static <T> List<T> asList(T[] a)
+    {
+        if (a==null)
+            return Collections.emptyList();
+        return Arrays.asList(a);
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Class from a canonical name for a type.
+     * @param name A class or type name.
+     * @return A class , which may be a primitive TYPE field..
+     */
+    public static Class<?> fromName(String name)
+    {
+        return name2Class.get(name);
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Canonical name for a type.
+     * @param type A class , which may be a primitive TYPE field.
+     * @return Canonical name.
+     */
+    public static String toName(Class<?> type)
+    {
+        return class2Name.get(type);
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Convert String value to instance.
+     * @param type The class of the instance, which may be a primitive TYPE field.
+     * @param value The value as a string.
+     * @return The value as an Object.
+     */
+    public static Object valueOf(Class<?> type, String value)
+    {
+        try
+        {
+            if (type.equals(java.lang.String.class))
+                return value;
+
+            Method m = class2Value.get(type);
+            if (m!=null)
+                return m.invoke(null, value);
+
+            if (type.equals(java.lang.Character.TYPE) ||
+                type.equals(java.lang.Character.class))
+                return value.charAt(0);
+
+            Constructor<?> c = type.getConstructor(java.lang.String.class);
+            return c.newInstance(value);
+        }
+        catch (NoSuchMethodException | IllegalAccessException | InstantiationException x)
+        {
+            LOG.ignore(x);
+        }
+        catch (InvocationTargetException x)
+        {
+            if (x.getTargetException() instanceof Error)
+                throw (Error)x.getTargetException();
+            LOG.ignore(x);
+        }
+        return null;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Convert String value to instance.
+     * @param type classname or type (eg int)
+     * @param value The value as a string.
+     * @return The value as an Object.
+     */
+    public static Object valueOf(String type, String value)
+    {
+        return valueOf(fromName(type),value);
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Parse an int from a substring.
+     * Negative numbers are not handled.
+     * @param s String
+     * @param offset Offset within string
+     * @param length Length of integer or -1 for remainder of string
+     * @param base base of the integer
+     * @return the parsed integer
+     * @throws NumberFormatException if the string cannot be parsed
+     */
+    public static int parseInt(String s, int offset, int length, int base)
+        throws NumberFormatException
+    {
+        int value=0;
+
+        if (length<0)
+            length=s.length()-offset;
+
+        for (int i=0;i<length;i++)
+        {
+            char c=s.charAt(offset+i);
+
+            int digit=convertHexDigit((int)c);
+            if (digit<0 || digit>=base)
+                throw new NumberFormatException(s.substring(offset,offset+length));
+            value=value*base+digit;
+        }
+        return value;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Parse an int from a byte array of ascii characters.
+     * Negative numbers are not handled.
+     * @param b byte array
+     * @param offset Offset within string
+     * @param length Length of integer or -1 for remainder of string
+     * @param base base of the integer
+     * @return the parsed integer
+     * @throws NumberFormatException if the array cannot be parsed into an integer
+     */
+    public static int parseInt(byte[] b, int offset, int length, int base)
+        throws NumberFormatException
+    {
+        int value=0;
+
+        if (length<0)
+            length=b.length-offset;
+
+        for (int i=0;i<length;i++)
+        {
+            char c=(char)(0xff&b[offset+i]);
+
+            int digit=c-'0';
+            if (digit<0 || digit>=base || digit>=10)
+            {
+                digit=10+c-'A';
+                if (digit<10 || digit>=base)
+                    digit=10+c-'a';
+            }
+            if (digit<0 || digit>=base)
+                throw new NumberFormatException(new String(b,offset,length));
+            value=value*base+digit;
+        }
+        return value;
+    }
+
+    /* ------------------------------------------------------------ */
+    public static byte[] parseBytes(String s, int base)
+    {
+        byte[] bytes=new byte[s.length()/2];
+        for (int i=0;i<s.length();i+=2)
+            bytes[i/2]=(byte)TypeUtil.parseInt(s,i,2,base);
+        return bytes;
+    }
+
+    /* ------------------------------------------------------------ */
+    public static String toString(byte[] bytes, int base)
+    {
+        StringBuilder buf = new StringBuilder();
+        for (byte b : bytes)
+        {
+            int bi=0xff&b;
+            int c='0'+(bi/base)%base;
+            if (c>'9')
+                c= 'a'+(c-'0'-10);
+            buf.append((char)c);
+            c='0'+bi%base;
+            if (c>'9')
+                c= 'a'+(c-'0'-10);
+            buf.append((char)c);
+        }
+        return buf.toString();
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param c An ASCII encoded character 0-9 a-f A-F
+     * @return The byte value of the character 0-16.
+     */
+    public static byte convertHexDigit( byte c )
+    {
+        byte b = (byte)((c & 0x1f) + ((c >> 6) * 0x19) - 0x10);
+        if (b<0 || b>15)
+            throw new NumberFormatException("!hex "+c);
+        return b;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * @param c An ASCII encoded character 0-9 a-f A-F
+     * @return The byte value of the character 0-16.
+     */
+    public static int convertHexDigit( int c )
+    {
+        int d= ((c & 0x1f) + ((c >> 6) * 0x19) - 0x10);
+        if (d<0 || d>15)
+            throw new NumberFormatException("!hex "+c);
+        return d;
+    }
+
+    /* ------------------------------------------------------------ */
+    public static void toHex(byte b,Appendable buf)
+    {
+        try
+        {
+            int d=0xf&((0xF0&b)>>4);
+            buf.append((char)((d>9?('A'-10):'0')+d));
+            d=0xf&b;
+            buf.append((char)((d>9?('A'-10):'0')+d));
+        }
+        catch(IOException e)
+        {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    public static void toHex(int value,Appendable buf) throws IOException
+    {
+        int d=0xf&((0xF0000000&value)>>28);
+        buf.append((char)((d>9?('A'-10):'0')+d));
+        d=0xf&((0x0F000000&value)>>24);
+        buf.append((char)((d>9?('A'-10):'0')+d));
+        d=0xf&((0x00F00000&value)>>20);
+        buf.append((char)((d>9?('A'-10):'0')+d));
+        d=0xf&((0x000F0000&value)>>16);
+        buf.append((char)((d>9?('A'-10):'0')+d));
+        d=0xf&((0x0000F000&value)>>12);
+        buf.append((char)((d>9?('A'-10):'0')+d));
+        d=0xf&((0x00000F00&value)>>8);
+        buf.append((char)((d>9?('A'-10):'0')+d));
+        d=0xf&((0x000000F0&value)>>4);
+        buf.append((char)((d>9?('A'-10):'0')+d));
+        d=0xf&value;
+        buf.append((char)((d>9?('A'-10):'0')+d));
+    
+        Integer.toString(0,36);
+    }
+    
+    
+    /* ------------------------------------------------------------ */
+    public static void toHex(long value,Appendable buf) throws IOException
+    {
+        toHex((int)(value>>32),buf);
+        toHex((int)value,buf);
+    }
+
+    /* ------------------------------------------------------------ */
+    public static String toHexString(byte b)
+    {
+        return toHexString(new byte[]{b}, 0, 1);
+    }
+
+    /* ------------------------------------------------------------ */
+    public static String toHexString(byte[] b)
+    {
+        return toHexString(b, 0, b.length);
+    }
+
+    /* ------------------------------------------------------------ */
+    public static String toHexString(byte[] b,int offset,int length)
+    {
+        StringBuilder buf = new StringBuilder();
+        for (int i=offset;i<offset+length;i++)
+        {
+            int bi=0xff&b[i];
+            int c='0'+(bi/16)%16;
+            if (c>'9')
+                c= 'A'+(c-'0'-10);
+            buf.append((char)c);
+            c='0'+bi%16;
+            if (c>'9')
+                c= 'a'+(c-'0'-10);
+            buf.append((char)c);
+        }
+        return buf.toString();
+    }
+
+    /* ------------------------------------------------------------ */
+    public static byte[] fromHexString(String s)
+    {
+        if (s.length()%2!=0)
+            throw new IllegalArgumentException(s);
+        byte[] array = new byte[s.length()/2];
+        for (int i=0;i<array.length;i++)
+        {
+            int b = Integer.parseInt(s.substring(i*2,i*2+2),16);
+            array[i]=(byte)(0xff&b);
+        }
+        return array;
+    }
+
+
+    public static void dump(Class<?> c)
+    {
+        System.err.println("Dump: "+c);
+        dump(c.getClassLoader());
+    }
+
+    public static void dump(ClassLoader cl)
+    {
+        System.err.println("Dump Loaders:");
+        while(cl!=null)
+        {
+            System.err.println("  loader "+cl);
+            cl = cl.getParent();
+        }
+    }
+
+
+    public static Object call(Class<?> oClass, String methodName, Object obj, Object[] arg)
+       throws InvocationTargetException, NoSuchMethodException
+    {
+        // Lets just try all methods for now
+        for (Method method : oClass.getMethods())
+        {
+            if (!method.getName().equals(methodName))
+                continue;            
+            if (method.getParameterTypes().length != arg.length)
+                continue;
+            if (Modifier.isStatic(method.getModifiers()) != (obj == null))
+                continue;
+            if ((obj == null) && method.getDeclaringClass() != oClass)
+                continue;
+
+            try
+            {
+                return method.invoke(obj, arg);
+            }
+            catch (IllegalAccessException | IllegalArgumentException e)
+            {
+                LOG.ignore(e);
+            }
+        }
+        
+        // Lets look for a method with optional arguments
+        Object[] args_with_opts=null;
+        
+        for (Method method : oClass.getMethods())
+        {
+            if (!method.getName().equals(methodName))
+                continue;            
+            if (method.getParameterTypes().length != arg.length+1)
+                continue;
+            if (!method.getParameterTypes()[arg.length].isArray())
+                continue;
+            if (Modifier.isStatic(method.getModifiers()) != (obj == null))
+                continue;
+            if ((obj == null) && method.getDeclaringClass() != oClass)
+                continue;
+
+            if (args_with_opts==null)
+                args_with_opts=ArrayUtil.addToArray(arg,new Object[]{},Object.class);
+            try
+            {
+                return method.invoke(obj, args_with_opts);
+            }
+            catch (IllegalAccessException | IllegalArgumentException e)
+            {
+                LOG.ignore(e);
+            }
+        }
+        
+        
+        throw new NoSuchMethodException(methodName);
+    }
+
+    public static Object construct(Class<?> klass, Object[] arguments) throws InvocationTargetException, NoSuchMethodException
+    {
+        for (Constructor<?> constructor : klass.getConstructors())
+        {
+            if (constructor.getParameterTypes().length != arguments.length)
+                continue;
+
+            try
+            {
+                return constructor.newInstance(arguments);
+            }
+            catch (InstantiationException | IllegalAccessException | IllegalArgumentException e)
+            {
+                LOG.ignore(e);
+            }
+        }
+        throw new NoSuchMethodException("<init>");
+    }
+    
+    public static Object construct(Class<?> klass, Object[] arguments, Map<String, Object> namedArgMap) throws InvocationTargetException, NoSuchMethodException
+    {
+        for (Constructor<?> constructor : klass.getConstructors())
+        {
+            if (constructor.getParameterTypes().length != arguments.length)
+                continue;
+
+            try
+            {
+                Annotation[][] parameterAnnotations = constructor.getParameterAnnotations();
+                
+                // target has no annotations
+                if ( parameterAnnotations == null || parameterAnnotations.length == 0 )
+                {                
+                    LOG.debug("Target has no parameter annotations");                   
+                    return constructor.newInstance(arguments);
+                }
+                else
+                {
+                   Object[] swizzled = new Object[arguments.length];
+                   
+                   int count = 0;
+                   for ( Annotation[] annotations : parameterAnnotations )
+                   {
+                       for ( Annotation annotation : annotations)
+                       {
+                           if ( annotation instanceof Name )
+                           {
+                               Name param = (Name)annotation;
+                               
+                               if (namedArgMap.containsKey(param.value()))
+                               {
+                                   LOG.debug("placing named {} in position {}", param.value(), count);
+                                   swizzled[count] = namedArgMap.get(param.value());
+                               }
+                               else
+                               {
+                                   LOG.debug("placing {} in position {}", arguments[count], count);
+                                   swizzled[count] = arguments[count];
+                               }
+                               ++count;
+                           }
+                           else
+                           {
+                               LOG.debug("passing on annotation {}", annotation);
+                           }
+                       }
+                   }
+                   
+                   return constructor.newInstance(swizzled);
+                }
+                
+            }
+            catch (InstantiationException | IllegalAccessException | IllegalArgumentException e)
+            {
+                LOG.ignore(e);
+            }
+        }
+        throw new NoSuchMethodException("<init>");
+    }
+
+    /* ------------------------------------------------------------ */
+    /** 
+     * @param o Object to test for true
+     * @return True if passed object is not null and is either a Boolean with value true or evaluates to a string that evaluates to true.
+     */
+    public static boolean isTrue(Object o)
+    {
+        if (o==null)
+            return false;
+        if (o instanceof Boolean)
+            return ((Boolean)o).booleanValue();
+        return Boolean.parseBoolean(o.toString());
+    }
+
+    /* ------------------------------------------------------------ */
+    /** 
+     * @param o Object to test for false
+     * @return True if passed object is not null and is either a Boolean with value false or evaluates to a string that evaluates to false.
+     */
+    public static boolean isFalse(Object o)
+    {
+        if (o==null)
+            return false;
+        if (o instanceof Boolean)
+            return !((Boolean)o).booleanValue();
+        return "false".equalsIgnoreCase(o.toString());
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/util/URIUtil.java b/lib/jetty/org/eclipse/jetty/util/URIUtil.java
new file mode 100644 (file)
index 0000000..f333fc3
--- /dev/null
@@ -0,0 +1,694 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util;
+
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+
+
+
+/* ------------------------------------------------------------ */
+/** URI Holder.
+ * This class assists with the decoding and encoding or HTTP URI's.
+ * It differs from the java.net.URL class as it does not provide
+ * communications ability, but it does assist with query string
+ * formatting.
+ * <P>UTF-8 encoding is used by default for % encoded characters. This
+ * may be overridden with the org.eclipse.jetty.util.URI.charset system property.
+ * @see UrlEncoded
+ * 
+ */
+public class URIUtil
+    implements Cloneable
+{
+    public static final String SLASH="/";
+    public static final String HTTP="http";
+    public static final String HTTP_COLON="http:";
+    public static final String HTTPS="https";
+    public static final String HTTPS_COLON="https:";
+
+    // Use UTF-8 as per http://www.w3.org/TR/html40/appendix/notes.html#non-ascii-chars
+    public static final Charset __CHARSET;
+
+    static
+    {
+        String charset = System.getProperty("org.eclipse.jetty.util.URI.charset");
+        __CHARSET = charset == null ? StandardCharsets.UTF_8 : Charset.forName(charset);
+    }
+
+    private URIUtil()
+    {}
+    
+    /* ------------------------------------------------------------ */
+    /** Encode a URI path.
+     * This is the same encoding offered by URLEncoder, except that
+     * the '/' character is not encoded.
+     * @param path The path the encode
+     * @return The encoded path
+     */
+    public static String encodePath(String path)
+    {
+        if (path==null || path.length()==0)
+            return path;
+        
+        StringBuilder buf = encodePath(null,path);
+        return buf==null?path:buf.toString();
+    }
+        
+    /* ------------------------------------------------------------ */
+    /** Encode a URI path.
+     * @param path The path the encode
+     * @param buf StringBuilder to encode path into (or null)
+     * @return The StringBuilder or null if no substitutions required.
+     */
+    public static StringBuilder encodePath(StringBuilder buf, String path)
+    {
+        byte[] bytes=null;
+        if (buf==null)
+        {
+        loop:
+            for (int i=0;i<path.length();i++)
+            {
+                char c=path.charAt(i);
+                switch(c)
+                {
+                    case '%':
+                    case '?':
+                    case ';':
+                    case '#':
+                    case '\'':
+                    case '"':
+                    case '<':
+                    case '>':
+                    case ' ':
+                        buf=new StringBuilder(path.length()*2);
+                        break loop;
+                    default:
+                        if (c>127)
+                        {
+                            bytes=path.getBytes(URIUtil.__CHARSET);
+                            buf=new StringBuilder(path.length()*2);
+                            break loop;
+                        }
+                       
+                }
+            }
+            if (buf==null)
+                return null;
+        }
+        
+        synchronized(buf)
+        {
+            if (bytes!=null)
+            {
+                for (int i=0;i<bytes.length;i++)
+                {
+                    byte c=bytes[i];       
+                    switch(c)
+                    {
+                      case '%':
+                          buf.append("%25");
+                          continue;
+                      case '?':
+                          buf.append("%3F");
+                          continue;
+                      case ';':
+                          buf.append("%3B");
+                          continue;
+                      case '#':
+                          buf.append("%23");
+                          continue;
+                      case '"':
+                          buf.append("%22");
+                          continue;
+                      case '\'':
+                          buf.append("%27");
+                          continue;
+                      case '<':
+                          buf.append("%3C");
+                          continue;
+                      case '>':
+                          buf.append("%3E");
+                          continue;
+                      case ' ':
+                          buf.append("%20");
+                          continue;
+                      default:
+                          if (c<0)
+                          {
+                              buf.append('%');
+                              TypeUtil.toHex(c,buf);
+                          }
+                          else
+                              buf.append((char)c);
+                          continue;
+                    }
+                }
+                
+            }
+            else
+            {
+                for (int i=0;i<path.length();i++)
+                {
+                    char c=path.charAt(i);       
+                    switch(c)
+                    {
+                        case '%':
+                            buf.append("%25");
+                            continue;
+                        case '?':
+                            buf.append("%3F");
+                            continue;
+                        case ';':
+                            buf.append("%3B");
+                            continue;
+                        case '#':
+                            buf.append("%23");
+                            continue;
+                        case '"':
+                            buf.append("%22");
+                            continue;
+                        case '\'':
+                            buf.append("%27");
+                            continue;
+                        case '<':
+                            buf.append("%3C");
+                            continue;
+                        case '>':
+                            buf.append("%3E");
+                            continue;
+                        case ' ':
+                            buf.append("%20");
+                            continue;
+                        default:
+                            buf.append(c);
+                            continue;
+                    }
+                }
+            }
+        }
+
+        return buf;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** Encode a URI path.
+     * @param path The path the encode
+     * @param buf StringBuilder to encode path into (or null)
+     * @param encode String of characters to encode. % is always encoded.
+     * @return The StringBuilder or null if no substitutions required.
+     */
+    public static StringBuilder encodeString(StringBuilder buf,
+                                             String path,
+                                             String encode)
+    {
+        if (buf==null)
+        {
+        loop:
+            for (int i=0;i<path.length();i++)
+            {
+                char c=path.charAt(i);
+                if (c=='%' || encode.indexOf(c)>=0)
+                {    
+                    buf=new StringBuilder(path.length()<<1);
+                    break loop;
+                }
+            }
+            if (buf==null)
+                return null;
+        }
+        
+        synchronized(buf)
+        {
+            for (int i=0;i<path.length();i++)
+            {
+                char c=path.charAt(i);
+                if (c=='%' || encode.indexOf(c)>=0)
+                {
+                    buf.append('%');
+                    StringUtil.append(buf,(byte)(0xff&c),16);
+                }
+                else
+                    buf.append(c);
+            }
+        }
+
+        return buf;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /* Decode a URI path and strip parameters
+     * @param path The path the encode
+     * @param buf StringBuilder to encode path into
+     */
+    public static String decodePath(String path)
+    {
+        if (path==null)
+            return null;
+        // Array to hold all converted characters
+        char[] chars=null;
+        int n=0;
+        // Array to hold a sequence of %encodings
+        byte[] bytes=null;
+        int b=0;
+        
+        int len=path.length();
+        
+        for (int i=0;i<len;i++)
+        {
+            char c = path.charAt(i);
+
+            if (c=='%' && (i+2)<len)
+            {
+                if (chars==null)
+                {
+                    chars=new char[len];
+                    bytes=new byte[len];
+                    path.getChars(0,i,chars,0);
+                }
+                bytes[b++]=(byte)(0xff&TypeUtil.parseInt(path,i+1,2,16));
+                i+=2;
+                continue;
+            }
+            else if (c==';')
+            {
+                if (chars==null)
+                {
+                    chars=new char[len];
+                    path.getChars(0,i,chars,0);
+                    n=i;
+                }
+                break;
+            }
+            else if (bytes==null)
+            {
+                n++;
+                continue;
+            }
+            
+            // Do we have some bytes to convert?
+            if (b>0)
+            {
+                String s=new String(bytes,0,b,__CHARSET);
+                s.getChars(0,s.length(),chars,n);
+                n+=s.length();
+                b=0;
+            }
+            
+            chars[n++]=c;
+        }
+
+        if (chars==null)
+            return path;
+
+        // if we have a remaining sequence of bytes
+        if (b>0)
+        {
+            String s=new String(bytes,0,b,__CHARSET);
+            s.getChars(0,s.length(),chars,n);
+            n+=s.length();
+        }
+        
+        return new String(chars,0,n);
+    }
+    
+    /* ------------------------------------------------------------ */
+    /* Decode a URI path and strip parameters.
+     * @param path The path the encode
+     * @param buf StringBuilder to encode path into
+     */
+    public static String decodePath(byte[] buf, int offset, int length)
+    {
+        byte[] bytes=null;
+        int n=0;
+        
+        for (int i=0;i<length;i++)
+        {
+            byte b = buf[i + offset];
+            
+            if (b=='%' && (i+2)<length)
+            {
+                b=(byte)(0xff&TypeUtil.parseInt(buf,i+offset+1,2,16));
+                i+=2;
+            }
+            else if (b==';')
+            {
+                length=i;
+                break;
+            }
+            else if (bytes==null)
+            {
+                n++;
+                continue;
+            }
+            
+            if (bytes==null)
+            {
+                bytes=new byte[length];
+                for (int j=0;j<n;j++)
+                    bytes[j]=buf[j + offset];
+            }
+            
+            bytes[n++]=b;
+        }
+
+        if (bytes==null)
+            return new String(buf,offset,length,__CHARSET);
+        return new String(bytes,0,n,__CHARSET);
+    }
+
+    
+    /* ------------------------------------------------------------ */
+    /** Add two URI path segments.
+     * Handles null and empty paths, path and query params (eg ?a=b or
+     * ;JSESSIONID=xxx) and avoids duplicate '/'
+     * @param p1 URI path segment (should be encoded)
+     * @param p2 URI path segment (should be encoded)
+     * @return Legally combined path segments.
+     */
+    public static String addPaths(String p1, String p2)
+    {
+        if (p1==null || p1.length()==0)
+        {
+            if (p1!=null && p2==null)
+                return p1;
+            return p2;
+        }
+        if (p2==null || p2.length()==0)
+            return p1;
+        
+        int split=p1.indexOf(';');
+        if (split<0)
+            split=p1.indexOf('?');
+        if (split==0)
+            return p2+p1;
+        if (split<0)
+            split=p1.length();
+
+        StringBuilder buf = new StringBuilder(p1.length()+p2.length()+2);
+        buf.append(p1);
+        
+        if (buf.charAt(split-1)=='/')
+        {
+            if (p2.startsWith(URIUtil.SLASH))
+            {
+                buf.deleteCharAt(split-1);
+                buf.insert(split-1,p2);
+            }
+            else
+                buf.insert(split,p2);
+        }
+        else
+        {
+            if (p2.startsWith(URIUtil.SLASH))
+                buf.insert(split,p2);
+            else
+            {
+                buf.insert(split,'/');
+                buf.insert(split+1,p2);
+            }
+        }
+
+        return buf.toString();
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** Return the parent Path.
+     * Treat a URI like a directory path and return the parent directory.
+     */
+    public static String parentPath(String p)
+    {
+        if (p==null || URIUtil.SLASH.equals(p))
+            return null;
+        int slash=p.lastIndexOf('/',p.length()-2);
+        if (slash>=0)
+            return p.substring(0,slash+1);
+        return null;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** Convert a path to a cananonical form.
+     * All instances of "." and ".." are factored out.  Null is returned
+     * if the path tries to .. above its root.
+     * @param path 
+     * @return path or null.
+     */
+    public static String canonicalPath(String path)
+    {
+        if (path==null || path.length()==0)
+            return path;
+
+        int end=path.length();
+        int start = path.lastIndexOf('/', end);
+
+    search:
+        while (end>0)
+        {
+            switch(end-start)
+            {
+              case 2: // possible single dot
+                  if (path.charAt(start+1)!='.')
+                      break;
+                  break search;
+              case 3: // possible double dot
+                  if (path.charAt(start+1)!='.' || path.charAt(start+2)!='.')
+                      break;
+                  break search;
+            }
+            
+            end=start;
+            start=path.lastIndexOf('/',end-1);
+        }
+
+        // If we have checked the entire string
+        if (start>=end)
+            return path;
+        
+        StringBuilder buf = new StringBuilder(path);
+        int delStart=-1;
+        int delEnd=-1;
+        int skip=0;
+        
+        while (end>0)
+        {
+            switch(end-start)
+            {       
+              case 2: // possible single dot
+                  if (buf.charAt(start+1)!='.')
+                  {
+                      if (skip>0 && --skip==0)
+                      {   
+                          delStart=start>=0?start:0;
+                          if(delStart>0 && delEnd==buf.length() && buf.charAt(delEnd-1)=='.')
+                              delStart++;
+                      }
+                      break;
+                  }
+                  
+                  if(start<0 && buf.length()>2 && buf.charAt(1)=='/' && buf.charAt(2)=='/')
+                      break;
+                  
+                  if(delEnd<0)
+                      delEnd=end;
+                  delStart=start;
+                  if (delStart<0 || delStart==0&&buf.charAt(delStart)=='/')
+                  {
+                      delStart++;
+                      if (delEnd<buf.length() && buf.charAt(delEnd)=='/')
+                          delEnd++;
+                      break;
+                  }
+                  if (end==buf.length())
+                      delStart++;
+                  
+                  end=start--;
+                  while (start>=0 && buf.charAt(start)!='/')
+                      start--;
+                  continue;
+                  
+              case 3: // possible double dot
+                  if (buf.charAt(start+1)!='.' || buf.charAt(start+2)!='.')
+                  {
+                      if (skip>0 && --skip==0)
+                      {   delStart=start>=0?start:0;
+                          if(delStart>0 && delEnd==buf.length() && buf.charAt(delEnd-1)=='.')
+                              delStart++;
+                      }
+                      break;
+                  }
+                  
+                  delStart=start;
+                  if (delEnd<0)
+                      delEnd=end;
+
+                  skip++;
+                  end=start--;
+                  while (start>=0 && buf.charAt(start)!='/')
+                      start--;
+                  continue;
+
+              default:
+                  if (skip>0 && --skip==0)
+                  {
+                      delStart=start>=0?start:0;
+                      if(delEnd==buf.length() && buf.charAt(delEnd-1)=='.')
+                          delStart++;
+                  }
+            }     
+            
+            // Do the delete
+            if (skip<=0 && delStart>=0 && delEnd>=delStart)
+            {  
+                buf.delete(delStart,delEnd);
+                delStart=delEnd=-1;
+                if (skip>0)
+                    delEnd=end;
+            }
+            
+            end=start--;
+            while (start>=0 && buf.charAt(start)!='/')
+                start--;
+        }      
+
+        // Too many ..
+        if (skip>0)
+            return null;
+        
+        // Do the delete
+        if (delEnd>=0)
+            buf.delete(delStart,delEnd);
+
+        return buf.toString();
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Convert a path to a compact form.
+     * All instances of "//" and "///" etc. are factored out to single "/" 
+     * @param path 
+     * @return path
+     */
+    public static String compactPath(String path)
+    {
+        if (path==null || path.length()==0)
+            return path;
+
+        int state=0;
+        int end=path.length();
+        int i=0;
+        
+        loop:
+        while (i<end)
+        {
+            char c=path.charAt(i);
+            switch(c)
+            {
+                case '?':
+                    return path;
+                case '/':
+                    state++;
+                    if (state==2)
+                        break loop;
+                    break;
+                default:
+                    state=0;
+            }
+            i++;
+        }
+        
+        if (state<2)
+            return path;
+        
+        StringBuffer buf = new StringBuffer(path.length());
+        buf.append(path,0,i);
+        
+        loop2:
+        while (i<end)
+        {
+            char c=path.charAt(i);
+            switch(c)
+            {
+                case '?':
+                    buf.append(path,i,end);
+                    break loop2;
+                case '/':
+                    if (state++==0)
+                        buf.append(c);
+                    break;
+                default:
+                    state=0;
+                    buf.append(c);
+            }
+            i++;
+        }
+        
+        return buf.toString();
+    }
+
+    /* ------------------------------------------------------------ */
+    /** 
+     * @param uri URI
+     * @return True if the uri has a scheme
+     */
+    public static boolean hasScheme(String uri)
+    {
+        for (int i=0;i<uri.length();i++)
+        {
+            char c=uri.charAt(i);
+            if (c==':')
+                return true;
+            if (!(c>='a'&&c<='z' ||
+                  c>='A'&&c<='Z' ||
+                  (i>0 &&(c>='0'&&c<='9' ||
+                          c=='.' ||
+                          c=='+' ||
+                          c=='-'))
+                  ))
+                break;
+        }
+        return false;
+    }
+    
+    public static void appendSchemeHostPort(StringBuilder url,String scheme,String server, int port)
+    {
+        if (server.indexOf(':')>=0&&server.charAt(0)!='[')
+            url.append(scheme).append("://").append('[').append(server).append(']');
+        else
+            url.append(scheme).append("://").append(server);
+
+        if (port > 0 && (("http".equalsIgnoreCase(scheme) && port != 80) || ("https".equalsIgnoreCase(scheme) && port != 443)))
+            url.append(':').append(port);
+    }
+    
+    public static void appendSchemeHostPort(StringBuffer url,String scheme,String server, int port)
+    {
+        synchronized (url)
+        {
+            if (server.indexOf(':')>=0&&server.charAt(0)!='[')
+                url.append(scheme).append("://").append('[').append(server).append(']');
+            else
+                url.append(scheme).append("://").append(server);
+
+            if (port > 0 && (("http".equalsIgnoreCase(scheme) && port != 80) || ("https".equalsIgnoreCase(scheme) && port != 443)))
+                url.append(':').append(port);
+        }
+    }
+}
+
+
+
diff --git a/lib/jetty/org/eclipse/jetty/util/UrlEncoded.java b/lib/jetty/org/eclipse/jetty/util/UrlEncoded.java
new file mode 100644 (file)
index 0000000..ef7f2f5
--- /dev/null
@@ -0,0 +1,1064 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util;
+
+import static org.eclipse.jetty.util.TypeUtil.convertHexDigit;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.StringWriter;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+import java.util.List;
+import java.util.Map;
+
+import org.eclipse.jetty.util.Utf8Appendable.NotUtf8Exception;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/* ------------------------------------------------------------ */
+/** Handles coding of MIME  "x-www-form-urlencoded".
+ * <p>
+ * This class handles the encoding and decoding for either
+ * the query string of a URL or the _content of a POST HTTP request.
+ *
+ * <h4>Notes</h4>
+ * The UTF-8 charset is assumed, unless otherwise defined by either
+ * passing a parameter or setting the "org.eclipse.jetty.util.UrlEncoding.charset"
+ * System property.
+ * <p>
+ * The hashtable either contains String single values, vectors
+ * of String or arrays of Strings.
+ * <p>
+ * This class is only partially synchronised.  In particular, simple
+ * get operations are not protected from concurrent updates.
+ *
+ * @see java.net.URLEncoder
+ */
+@SuppressWarnings("serial")
+public class UrlEncoded extends MultiMap<String> implements Cloneable
+{
+    static final Logger LOG = Log.getLogger(UrlEncoded.class);
+
+    public static final Charset ENCODING;
+    static
+    {
+        Charset encoding;
+        try
+        {
+            String charset = System.getProperty("org.eclipse.jetty.util.UrlEncoding.charset");
+            encoding = charset == null ? StandardCharsets.UTF_8 : Charset.forName(charset);
+        }
+        catch(Exception e)
+        {
+            LOG.warn(e);
+            encoding=StandardCharsets.UTF_8;
+        }
+        ENCODING=encoding;
+    }
+    
+    /* ----------------------------------------------------------------- */
+    public UrlEncoded(UrlEncoded url)
+    {
+        super(url);
+    }
+    
+    /* ----------------------------------------------------------------- */
+    public UrlEncoded()
+    {
+    }
+    
+    public UrlEncoded(String query)
+    {
+        decodeTo(query,this,ENCODING,-1);
+    }
+
+    /* ----------------------------------------------------------------- */
+    public void decode(String query)
+    {
+        decodeTo(query,this,ENCODING,-1);
+    }
+    
+    /* ----------------------------------------------------------------- */
+    public void decode(String query,Charset charset)
+    {
+        decodeTo(query,this,charset,-1);
+    }
+    
+    /* -------------------------------------------------------------- */
+    /** Encode Hashtable with % encoding.
+     */
+    public String encode()
+    {
+        return encode(ENCODING,false);
+    }
+    
+    /* -------------------------------------------------------------- */
+    /** Encode Hashtable with % encoding.
+     */
+    public String encode(Charset charset)
+    {
+        return encode(charset,false);
+    }
+    
+    /* -------------------------------------------------------------- */
+    /** Encode Hashtable with % encoding.
+     * @param equalsForNullValue if True, then an '=' is always used, even
+     * for parameters without a value. e.g. "blah?a=&b=&c=".
+     */
+    public synchronized String encode(Charset charset, boolean equalsForNullValue)
+    {
+        return encode(this,charset,equalsForNullValue);
+    }
+    
+    /* -------------------------------------------------------------- */
+    /** Encode Hashtable with % encoding.
+     * @param equalsForNullValue if True, then an '=' is always used, even
+     * for parameters without a value. e.g. "blah?a=&b=&c=".
+     */
+    public static String encode(MultiMap<String> map, Charset charset, boolean equalsForNullValue)
+    {
+        if (charset==null)
+            charset=ENCODING;
+
+        StringBuilder result = new StringBuilder(128);
+
+        boolean delim = false;
+        for(Map.Entry<String, List<String>> entry: map.entrySet())
+        {
+            String key = entry.getKey().toString();
+            List<String> list = entry.getValue();
+            int s=list.size();
+            
+            if (delim)
+            {
+                result.append('&');
+            }
+
+            if (s==0)
+            {
+                result.append(encodeString(key,charset));
+                if(equalsForNullValue)
+                    result.append('=');
+            }
+            else
+            {
+                for (int i=0;i<s;i++)
+                {
+                    if (i>0)
+                        result.append('&');
+                    String val=list.get(i);
+                    result.append(encodeString(key,charset));
+
+                    if (val!=null)
+                    {
+                        String str=val.toString();
+                        if (str.length()>0)
+                        {
+                            result.append('=');
+                            result.append(encodeString(str,charset));
+                        }
+                        else if (equalsForNullValue)
+                            result.append('=');
+                    }
+                    else if (equalsForNullValue)
+                        result.append('=');
+                }
+            }
+            delim = true;
+        }
+        return result.toString();
+    }
+
+    /* -------------------------------------------------------------- */
+    /** Decoded parameters to Map.
+     * @param content the string containing the encoded parameters
+     */
+    public static void decodeTo(String content, MultiMap<String> map, String charset, int maxKeys)
+    {
+        decodeTo(content,map,charset==null?null:Charset.forName(charset),maxKeys);
+    }
+    
+    /* -------------------------------------------------------------- */
+    /** Decoded parameters to Map.
+     * @param content the string containing the encoded parameters
+     */
+    public static void decodeTo(String content, MultiMap<String> map, Charset charset, int maxKeys)
+    {
+        if (charset==null)
+            charset=ENCODING;
+
+        synchronized(map)
+        {
+            String key = null;
+            String value = null;
+            int mark=-1;
+            boolean encoded=false;
+            for (int i=0;i<content.length();i++)
+            {
+                char c = content.charAt(i);
+                switch (c)
+                {
+                  case '&':
+                      int l=i-mark-1;
+                      value = l==0?"":
+                          (encoded?decodeString(content,mark+1,l,charset):content.substring(mark+1,i));
+                      mark=i;
+                      encoded=false;
+                      if (key != null)
+                      {
+                          map.add(key,value);
+                      }
+                      else if (value!=null&&value.length()>0)
+                      {
+                          map.add(value,"");
+                      }
+                      key = null;
+                      value=null;
+                      if (maxKeys>0 && map.size()>maxKeys)
+                          throw new IllegalStateException("Form too many keys");
+                      break;
+                  case '=':
+                      if (key!=null)
+                          break;
+                      key = encoded?decodeString(content,mark+1,i-mark-1,charset):content.substring(mark+1,i);
+                      mark=i;
+                      encoded=false;
+                      break;
+                  case '+':
+                      encoded=true;
+                      break;
+                  case '%':
+                      encoded=true;
+                      break;
+                }                
+            }
+            
+            if (key != null)
+            {
+                int l=content.length()-mark-1;
+                value = l==0?"":(encoded?decodeString(content,mark+1,l,charset):content.substring(mark+1));
+                map.add(key,value);
+            }
+            else if (mark<content.length())
+            {
+                key = encoded
+                    ?decodeString(content,mark+1,content.length()-mark-1,charset)
+                    :content.substring(mark+1);
+                if (key != null && key.length() > 0)
+                {
+                    map.add(key,"");
+                }
+            }
+        }
+    }
+
+    /* -------------------------------------------------------------- */
+    /** Decoded parameters to Map.
+     * @param raw the byte[] containing the encoded parameters
+     * @param offset the offset within raw to decode from
+     * @param length the length of the section to decode
+     * @param map the {@link MultiMap} to populate
+     */
+    public static void decodeUtf8To(byte[] raw,int offset, int length, MultiMap<String> map)
+    {
+        Utf8StringBuilder buffer = new Utf8StringBuilder();
+        synchronized(map)
+        {
+            String key = null;
+            String value = null;
+
+            int end=offset+length;
+            for (int i=offset;i<end;i++)
+            {
+                byte b=raw[i];
+                try
+                {
+                    switch ((char)(0xff&b))
+                    {
+                        case '&':
+                            value = buffer.toReplacedString();
+                            buffer.reset();
+                            if (key != null)
+                            {
+                                map.add(key,value);
+                            }
+                            else if (value!=null&&value.length()>0)
+                            {
+                                map.add(value,"");
+                            }
+                            key = null;
+                            value=null;
+                            break;
+
+                        case '=':
+                            if (key!=null)
+                            {
+                                buffer.append(b);
+                                break;
+                            }
+                            key = buffer.toReplacedString();
+                            buffer.reset();
+                            break;
+
+                        case '+':
+                            buffer.append((byte)' ');
+                            break;
+
+                        case '%':
+                            if (i+2<end)
+                            {
+                                if ('u'==raw[i+1])
+                                {
+                                    i++;
+                                    if (i+4<end)
+                                    {
+                                        byte top=raw[++i];
+                                        byte hi=raw[++i];
+                                        byte lo=raw[++i];
+                                        byte bot=raw[++i];
+                                        buffer.getStringBuilder().append(Character.toChars((convertHexDigit(top)<<12) +(convertHexDigit(hi)<<8) + (convertHexDigit(lo)<<4) +convertHexDigit(bot)));
+                                    }
+                                    else
+                                    {
+                                        buffer.getStringBuilder().append(Utf8Appendable.REPLACEMENT);
+                                        i=end;
+                                    }
+                                }
+                                else
+                                {
+                                    byte hi=raw[++i];
+                                    byte lo=raw[++i];
+                                    buffer.append((byte)((convertHexDigit(hi)<<4) + convertHexDigit(lo)));
+                                }
+                            }
+                            else
+                            {
+                                buffer.getStringBuilder().append(Utf8Appendable.REPLACEMENT);
+                                i=end;
+                            }
+                            break;
+                            
+                        default:
+                            buffer.append(b);
+                            break;
+                    }
+                }
+                catch(NotUtf8Exception e)
+                {
+                    LOG.warn(e.toString());
+                    LOG.debug(e);
+                }
+                catch(NumberFormatException e)
+                {
+                    buffer.append(Utf8Appendable.REPLACEMENT_UTF8,0,3);
+                    LOG.warn(e.toString());
+                    LOG.debug(e);
+                }
+            }
+            
+            if (key != null)
+            {
+                value = buffer.toReplacedString();
+                buffer.reset();
+                map.add(key,value);
+            }
+            else if (buffer.length()>0)
+            {
+                map.add(buffer.toReplacedString(),"");
+            }
+        }
+    }
+
+    /* -------------------------------------------------------------- */
+    /** Decoded parameters to Map.
+     * @param in InputSteam to read
+     * @param map MultiMap to add parameters to
+     * @param maxLength maximum number of keys to read or -1 for no limit
+     */
+    public static void decode88591To(InputStream in, MultiMap<String> map, int maxLength, int maxKeys)
+    throws IOException
+    {
+        synchronized(map)
+        {
+            StringBuffer buffer = new StringBuffer();
+            String key = null;
+            String value = null;
+            
+            int b;
+
+            int totalLength=0;
+            while ((b=in.read())>=0)
+            {
+                switch ((char) b)
+                {
+                    case '&':
+                        value = buffer.length()==0?"":buffer.toString();
+                        buffer.setLength(0);
+                        if (key != null)
+                        {
+                            map.add(key,value);
+                        }
+                        else if (value!=null&&value.length()>0)
+                        {
+                            map.add(value,"");
+                        }
+                        key = null;
+                        value=null;
+                        if (maxKeys>0 && map.size()>maxKeys)
+                            throw new IllegalStateException("Form too many keys");
+                        break;
+                        
+                    case '=':
+                        if (key!=null)
+                        {
+                            buffer.append((char)b);
+                            break;
+                        }
+                        key = buffer.toString();
+                        buffer.setLength(0);
+                        break;
+                        
+                    case '+':
+                        buffer.append(' ');
+                        break;
+                        
+                    case '%':
+                        int code0=in.read();
+                        if ('u'==code0)
+                        {
+                            int code1=in.read();
+                            if (code1>=0)
+                            {
+                                int code2=in.read();
+                                if (code2>=0)
+                                {
+                                    int code3=in.read();
+                                    if (code3>=0)
+                                        buffer.append(Character.toChars((convertHexDigit(code0)<<12)+(convertHexDigit(code1)<<8)+(convertHexDigit(code2)<<4)+convertHexDigit(code3)));
+                                }
+                            }
+                        }
+                        else if (code0>=0)
+                        {
+                            int code1=in.read();
+                            if (code1>=0)
+                                buffer.append((char)((convertHexDigit(code0)<<4)+convertHexDigit(code1)));
+                        }
+                        break;
+                     
+                    default:
+                        buffer.append((char)b);
+                    break;
+                }
+                if (maxLength>=0 && (++totalLength > maxLength))
+                    throw new IllegalStateException("Form too large");
+            }
+            
+            if (key != null)
+            {
+                value = buffer.length()==0?"":buffer.toString();
+                buffer.setLength(0);
+                map.add(key,value);
+            }
+            else if (buffer.length()>0)
+            {
+                map.add(buffer.toString(), "");
+            }
+        }
+    }
+    
+    /* -------------------------------------------------------------- */
+    /** Decoded parameters to Map.
+     * @param in InputSteam to read
+     * @param map MultiMap to add parameters to
+     * @param maxLength maximum number of keys to read or -1 for no limit
+     */
+    public static void decodeUtf8To(InputStream in, MultiMap<String> map, int maxLength, int maxKeys)
+    throws IOException
+    {
+        synchronized(map)
+        {
+            Utf8StringBuilder buffer = new Utf8StringBuilder();
+            String key = null;
+            String value = null;
+            
+            int b;
+            
+            int totalLength=0;
+            while ((b=in.read())>=0)
+            {
+                try
+                {
+                    switch ((char) b)
+                    {
+                        case '&':
+                            value = buffer.toReplacedString();
+                            buffer.reset();
+                            if (key != null)
+                            {
+                                map.add(key,value);
+                            }
+                            else if (value!=null&&value.length()>0)
+                            {
+                                map.add(value,"");
+                            }
+                            key = null;
+                            value=null;
+                            if (maxKeys>0 && map.size()>maxKeys)
+                                throw new IllegalStateException("Form too many keys");
+                            break;
+
+                        case '=':
+                            if (key!=null)
+                            {
+                                buffer.append((byte)b);
+                                break;
+                            }
+                            key = buffer.toReplacedString(); 
+                            buffer.reset();
+                            break;
+
+                        case '+':
+                            buffer.append((byte)' ');
+                            break;
+
+                        case '%':
+                            int code0=in.read();
+                            boolean decoded=false;
+                            if ('u'==code0)
+                            {
+                                code0=in.read(); // XXX: we have to read the next byte, otherwise code0 is always 'u'
+                                if (code0>=0)
+                                {
+                                    int code1=in.read();
+                                    if (code1>=0)
+                                    {
+                                        int code2=in.read();
+                                        if (code2>=0)
+                                        {
+                                            int code3=in.read();
+                                            if (code3>=0)
+                                            {
+                                                buffer.getStringBuilder().append(Character.toChars
+                                                    ((convertHexDigit(code0)<<12)+(convertHexDigit(code1)<<8)+(convertHexDigit(code2)<<4)+convertHexDigit(code3)));
+                                                decoded=true;
+                                            }
+                                        }
+                                    }
+                                }
+                            }
+                            else if (code0>=0)
+                            {
+                                int code1=in.read();
+                                if (code1>=0)
+                                {
+                                    buffer.append((byte)((convertHexDigit(code0)<<4)+convertHexDigit(code1)));
+                                    decoded=true;
+                                }
+                            }
+                            
+                            if (!decoded)
+                                buffer.getStringBuilder().append(Utf8Appendable.REPLACEMENT);
+
+                            break;
+                          
+                        default:
+                            buffer.append((byte)b);
+                            break;
+                    }
+                }
+                catch(NotUtf8Exception e)
+                {
+                    LOG.warn(e.toString());
+                    LOG.debug(e);
+                }
+                catch(NumberFormatException e)
+                {
+                    buffer.append(Utf8Appendable.REPLACEMENT_UTF8,0,3);
+                    LOG.warn(e.toString());
+                    LOG.debug(e);
+                }
+                if (maxLength>=0 && (++totalLength > maxLength))
+                    throw new IllegalStateException("Form too large");
+            }
+            
+            if (key != null)
+            {
+                value = buffer.toReplacedString();
+                buffer.reset();
+                map.add(key,value);
+            }
+            else if (buffer.length()>0)
+            {
+                map.add(buffer.toReplacedString(), "");
+            }
+        }
+    }
+    
+    /* -------------------------------------------------------------- */
+    public static void decodeUtf16To(InputStream in, MultiMap<String> map, int maxLength, int maxKeys) throws IOException
+    {
+        InputStreamReader input = new InputStreamReader(in,StandardCharsets.UTF_16);
+        StringWriter buf = new StringWriter(8192);
+        IO.copy(input,buf,maxLength);
+        
+        decodeTo(buf.getBuffer().toString(),map,StandardCharsets.UTF_16,maxKeys);
+    }
+
+    /* -------------------------------------------------------------- */
+    /** Decoded parameters to Map.
+     * @param in the stream containing the encoded parameters
+     */
+    public static void decodeTo(InputStream in, MultiMap<String> map, String charset, int maxLength, int maxKeys)
+    throws IOException
+    {
+        if (charset==null)
+        {
+            if (ENCODING.equals(StandardCharsets.UTF_8))
+                decodeUtf8To(in,map,maxLength,maxKeys);
+            else
+                decodeTo(in,map,ENCODING,maxLength,maxKeys);
+        }
+        else if (StringUtil.__UTF8.equalsIgnoreCase(charset))
+            decodeUtf8To(in,map,maxLength,maxKeys);
+        else if (StringUtil.__ISO_8859_1.equalsIgnoreCase(charset))
+            decode88591To(in,map,maxLength,maxKeys);
+        else if (StringUtil.__UTF16.equalsIgnoreCase(charset))
+            decodeUtf16To(in,map,maxLength,maxKeys);
+        else
+            decodeTo(in,map,Charset.forName(charset),maxLength,maxKeys);
+    }
+    
+    /* -------------------------------------------------------------- */
+    /** Decoded parameters to Map.
+     * @param in the stream containing the encoded parameters
+     */
+    public static void decodeTo(InputStream in, MultiMap<String> map, Charset charset, int maxLength, int maxKeys)
+    throws IOException
+    {
+        //no charset present, use the configured default
+        if (charset==null) 
+           charset=ENCODING;
+            
+        if (StandardCharsets.UTF_8.equals(charset))
+        {
+            decodeUtf8To(in,map,maxLength,maxKeys);
+            return;
+        }
+        
+        if (StandardCharsets.ISO_8859_1.equals(charset))
+        {
+            decode88591To(in,map,maxLength,maxKeys);
+            return;
+        }
+
+        if (StandardCharsets.UTF_16.equals(charset)) // Should be all 2 byte encodings
+        {
+            decodeUtf16To(in,map,maxLength,maxKeys);
+            return;
+        }
+
+        synchronized(map)
+        {
+            String key = null;
+            String value = null;
+            
+            int c;
+            
+            int totalLength = 0;
+            ByteArrayOutputStream2 output = new ByteArrayOutputStream2();
+            
+            int size=0;
+            
+            while ((c=in.read())>0)
+            {
+                switch ((char) c)
+                {
+                    case '&':
+                        size=output.size();
+                        value = size==0?"":output.toString(charset);
+                        output.setCount(0);
+                        if (key != null)
+                        {
+                            map.add(key,value);
+                        }
+                        else if (value!=null&&value.length()>0)
+                        {
+                            map.add(value,"");
+                        }
+                        key = null;
+                        value=null;
+                        if (maxKeys>0 && map.size()>maxKeys)
+                            throw new IllegalStateException("Form too many keys");
+                        break;
+                    case '=':
+                        if (key!=null)
+                        {
+                            output.write(c);
+                            break;
+                        }
+                        size=output.size();
+                        key = size==0?"":output.toString(charset);
+                        output.setCount(0);
+                        break;
+                    case '+':
+                        output.write(' ');
+                        break;
+                    case '%':
+                        int code0=in.read();
+                        if ('u'==code0)
+                        {
+                            int code1=in.read();
+                            if (code1>=0)
+                            {
+                                int code2=in.read();
+                                if (code2>=0)
+                                {
+                                    int code3=in.read();
+                                    if (code3>=0)
+                                        output.write(new String(Character.toChars((convertHexDigit(code0)<<12)+(convertHexDigit(code1)<<8)+(convertHexDigit(code2)<<4)+convertHexDigit(code3))).getBytes(charset));
+                                }
+                            }
+                            
+                        }
+                        else if (code0>=0)
+                        {
+                            int code1=in.read();
+                            if (code1>=0)
+                                output.write((convertHexDigit(code0)<<4)+convertHexDigit(code1));
+                        }
+                        break;
+                    default:
+                        output.write(c);
+                    break;
+                }
+                
+                totalLength++;
+                if (maxLength>=0 && totalLength > maxLength)
+                    throw new IllegalStateException("Form too large");
+            }
+
+            size=output.size();
+            if (key != null)
+            {
+                value = size==0?"":output.toString(charset);
+                output.setCount(0);
+                map.add(key,value);
+            }
+            else if (size>0)
+                map.add(output.toString(charset),"");
+        }
+    }
+    
+    /* -------------------------------------------------------------- */
+    /** Decode String with % encoding.
+     * This method makes the assumption that the majority of calls
+     * will need no decoding.
+     */
+    public static String decodeString(String encoded,int offset,int length,Charset charset)
+    {
+        if (charset==null || StandardCharsets.UTF_8.equals(charset))
+        {
+            Utf8StringBuffer buffer=null;
+
+            for (int i=0;i<length;i++)
+            {
+                char c = encoded.charAt(offset+i);
+                if (c<0||c>0xff)
+                {
+                    if (buffer==null)
+                    {
+                        buffer=new Utf8StringBuffer(length);
+                        buffer.getStringBuffer().append(encoded,offset,offset+i+1);
+                    }
+                    else
+                        buffer.getStringBuffer().append(c);
+                }
+                else if (c=='+')
+                {
+                    if (buffer==null)
+                    {
+                        buffer=new Utf8StringBuffer(length);
+                        buffer.getStringBuffer().append(encoded,offset,offset+i);
+                    }
+                    
+                    buffer.getStringBuffer().append(' ');
+                }
+                else if (c=='%')
+                {
+                    if (buffer==null)
+                    {
+                        buffer=new Utf8StringBuffer(length);
+                        buffer.getStringBuffer().append(encoded,offset,offset+i);
+                    }
+                    
+                    if ((i+2)<length)
+                    {
+                        try
+                        {
+                            if ('u'==encoded.charAt(offset+i+1))
+                            {
+                                if((i+5)<length)
+                                {
+                                    int o=offset+i+2;
+                                    i+=5;
+                                    String unicode = new String(Character.toChars(TypeUtil.parseInt(encoded,o,4,16)));
+                                    buffer.getStringBuffer().append(unicode); 
+                                }
+                                else
+                                {
+                                    i=length;
+                                    buffer.getStringBuffer().append(Utf8Appendable.REPLACEMENT); 
+                                }
+                            }
+                            else
+                            {
+                                int o=offset+i+1;
+                                i+=2;
+                                byte b=(byte)TypeUtil.parseInt(encoded,o,2,16);
+                                buffer.append(b);
+                            }
+                        }
+                        catch(NotUtf8Exception e)
+                        {
+                            LOG.warn(e.toString());
+                            LOG.debug(e);
+                        }
+                        catch(NumberFormatException e)
+                        {
+                            LOG.warn(e.toString());
+                            LOG.debug(e);
+                            buffer.getStringBuffer().append(Utf8Appendable.REPLACEMENT);  
+                        }
+                    }
+                    else
+                    {
+                        buffer.getStringBuffer().append(Utf8Appendable.REPLACEMENT); 
+                        i=length;
+                    }
+                }
+                else if (buffer!=null)
+                    buffer.getStringBuffer().append(c);
+            }
+
+            if (buffer==null)
+            {
+                if (offset==0 && encoded.length()==length)
+                    return encoded;
+                return encoded.substring(offset,offset+length);
+            }
+
+            return buffer.toReplacedString();
+        }
+        else
+        {
+            StringBuffer buffer=null;
+
+            for (int i=0;i<length;i++)
+            {
+                char c = encoded.charAt(offset+i);
+                if (c<0||c>0xff)
+                {
+                    if (buffer==null)
+                    {
+                        buffer=new StringBuffer(length);
+                        buffer.append(encoded,offset,offset+i+1);
+                    }
+                    else
+                        buffer.append(c);
+                }
+                else if (c=='+')
+                {
+                    if (buffer==null)
+                    {
+                        buffer=new StringBuffer(length);
+                        buffer.append(encoded,offset,offset+i);
+                    }
+
+                    buffer.append(' ');
+                }
+                else if (c=='%')
+                {
+                    if (buffer==null)
+                    {
+                        buffer=new StringBuffer(length);
+                        buffer.append(encoded,offset,offset+i);
+                    }
+
+                    byte[] ba=new byte[length];
+                    int n=0;
+                    while(c>=0 && c<=0xff)
+                    {
+                        if (c=='%')
+                        {   
+                            if(i+2<length)
+                            {
+                                try
+                                {
+                                    if ('u'==encoded.charAt(offset+i+1))
+                                    {
+                                        if (i+6<length)
+                                        {
+                                            int o=offset+i+2;
+                                            i+=6;
+                                            String unicode = new String(Character.toChars(TypeUtil.parseInt(encoded,o,4,16)));
+                                            byte[] reencoded = unicode.getBytes(charset);
+                                            System.arraycopy(reencoded,0,ba,n,reencoded.length);
+                                            n+=reencoded.length;
+                                        }
+                                        else
+                                        {
+                                            ba[n++] = (byte)'?';
+                                            i=length;
+                                        }
+                                    }
+                                    else
+                                    {
+                                        int o=offset+i+1;
+                                        i+=3;
+                                        ba[n]=(byte)TypeUtil.parseInt(encoded,o,2,16);
+                                        n++;
+                                    }
+                                }
+                                catch(Exception e)
+                                {   
+                                    LOG.warn(e.toString());
+                                    LOG.debug(e);
+                                    ba[n++] = (byte)'?';
+                                }
+                            }
+                            else
+                            {
+                                    ba[n++] = (byte)'?';
+                                    i=length;
+                            }
+                        }
+                        else if (c=='+')
+                        {
+                            ba[n++]=(byte)' ';
+                            i++;
+                        }
+                        else
+                        {
+                            ba[n++]=(byte)c;
+                            i++;
+                        }
+
+                        if (i>=length)
+                            break;
+                        c = encoded.charAt(offset+i);
+                    }
+
+                    i--;
+                    buffer.append(new String(ba,0,n,charset));
+
+                }
+                else if (buffer!=null)
+                    buffer.append(c);
+            }
+
+            if (buffer==null)
+            {
+                if (offset==0 && encoded.length()==length)
+                    return encoded;
+                return encoded.substring(offset,offset+length);
+            }
+
+            return buffer.toString();
+        }
+
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** Perform URL encoding.
+     * @param string 
+     * @return encoded string.
+     */
+    public static String encodeString(String string)
+    {
+        return encodeString(string,ENCODING);
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** Perform URL encoding.
+     * @param string 
+     * @return encoded string.
+     */
+    public static String encodeString(String string,Charset charset)
+    {
+        if (charset==null)
+            charset=ENCODING;
+        byte[] bytes=null;
+        bytes=string.getBytes(charset);
+        
+        int len=bytes.length;
+        byte[] encoded= new byte[bytes.length*3];
+        int n=0;
+        boolean noEncode=true;
+        
+        for (int i=0;i<len;i++)
+        {
+            byte b = bytes[i];
+            
+            if (b==' ')
+            {
+                noEncode=false;
+                encoded[n++]=(byte)'+';
+            }
+            else if (b>='a' && b<='z' ||
+                     b>='A' && b<='Z' ||
+                     b>='0' && b<='9')
+            {
+                encoded[n++]=b;
+            }
+            else
+            {
+                noEncode=false;
+                encoded[n++]=(byte)'%';
+                byte nibble= (byte) ((b&0xf0)>>4);
+                if (nibble>=10)
+                    encoded[n++]=(byte)('A'+nibble-10);
+                else
+                    encoded[n++]=(byte)('0'+nibble);
+                nibble= (byte) (b&0xf);
+                if (nibble>=10)
+                    encoded[n++]=(byte)('A'+nibble-10);
+                else
+                    encoded[n++]=(byte)('0'+nibble);
+            }
+        }
+
+        if (noEncode)
+            return string;
+        
+        return new String(encoded,0,n,charset);
+    }
+
+
+    /* ------------------------------------------------------------ */
+    /** 
+     */
+    @Override
+    public Object clone()
+    {
+        return new UrlEncoded(this);
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/util/Utf8Appendable.java b/lib/jetty/org/eclipse/jetty/util/Utf8Appendable.java
new file mode 100644 (file)
index 0000000..ff58764
--- /dev/null
@@ -0,0 +1,256 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/* ------------------------------------------------------------ */
+/**
+ * Utf8 Appendable abstract base class
+ *
+ * This abstract class wraps a standard {@link java.lang.Appendable} and provides methods to append UTF-8 encoded bytes, that are converted into characters.
+ *
+ * This class is stateful and up to 4 calls to {@link #append(byte)} may be needed before state a character is appended to the string buffer.
+ *
+ * The UTF-8 decoding is done by this class and no additional buffers or Readers are used. The UTF-8 code was inspired by
+ * http://bjoern.hoehrmann.de/utf-8/decoder/dfa/
+ *
+ * License information for Bjoern Hoehrmann's code:
+ *
+ * Copyright (c) 2008-2009 Bjoern Hoehrmann <bjoern@hoehrmann.de>
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+ * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+ * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ **/
+public abstract class Utf8Appendable
+{
+    protected static final Logger LOG = Log.getLogger(Utf8Appendable.class);
+    public static final char REPLACEMENT = '\ufffd';
+    public static final byte[] REPLACEMENT_UTF8 = new byte[] {(byte)0xEF,(byte)0xBF,(byte)0xBD };
+    private static final int UTF8_ACCEPT = 0;
+    private static final int UTF8_REJECT = 12;
+
+    protected final Appendable _appendable;
+    protected int _state = UTF8_ACCEPT;
+
+    private static final byte[] BYTE_TABLE =
+    {
+        // The first part of the table maps bytes to character classes that
+        // to reduce the size of the transition table and create bitmasks.
+         0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,  0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+         0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,  0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+         0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,  0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+         0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,  0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+         1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,  9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,
+         7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,  7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
+         8,8,2,2,2,2,2,2,2,2,2,2,2,2,2,2,  2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,
+        10,3,3,3,3,3,3,3,3,3,3,3,3,4,3,3, 11,6,6,6,5,8,8,8,8,8,8,8,8,8,8,8
+    };
+
+    private static final byte[] TRANS_TABLE =
+    {
+        // The second part is a transition table that maps a combination
+        // of a state of the automaton and a character class to a state.
+         0,12,24,36,60,96,84,12,12,12,48,72, 12,12,12,12,12,12,12,12,12,12,12,12,
+        12, 0,12,12,12,12,12, 0,12, 0,12,12, 12,24,12,12,12,12,12,24,12,24,12,12,
+        12,12,12,12,12,12,12,24,12,12,12,12, 12,24,12,12,12,12,12,12,12,24,12,12,
+        12,12,12,12,12,12,12,36,12,36,12,12, 12,36,12,12,12,12,12,36,12,36,12,12,
+        12,36,12,12,12,12,12,12,12,12,12,12
+    };
+
+    private int _codep;
+
+    public Utf8Appendable(Appendable appendable)
+    {
+        _appendable = appendable;
+    }
+
+    public abstract int length();
+
+    protected void reset()
+    {
+        _state = UTF8_ACCEPT;
+    }
+
+    public void append(byte b)
+    {
+        try
+        {
+            appendByte(b);
+        }
+        catch (IOException e)
+        {
+            throw new RuntimeException(e);
+        }
+    }
+    
+    public void append(ByteBuffer buf)
+    {
+        try
+        {
+            while (buf.remaining() > 0)
+            {
+                appendByte(buf.get());
+            }
+        }
+        catch (IOException e)
+        {
+            throw new RuntimeException(e);
+        }
+    }
+
+    public void append(byte[] b, int offset, int length)
+    {
+        try
+        {
+            int end = offset + length;
+            for (int i = offset; i < end; i++)
+                appendByte(b[i]);
+        }
+        catch (IOException e)
+        {
+            throw new RuntimeException(e);
+        }
+    }
+
+    public boolean append(byte[] b, int offset, int length, int maxChars)
+    {
+        try
+        {
+            int end = offset + length;
+            for (int i = offset; i < end; i++)
+            {
+                if (length() > maxChars)
+                    return false;
+                appendByte(b[i]);
+            }
+            return true;
+        }
+        catch (IOException e)
+        {
+            throw new RuntimeException(e);
+        }
+    }
+
+    protected void appendByte(byte b) throws IOException
+    {
+
+        if (b > 0 && _state == UTF8_ACCEPT)
+        {
+            _appendable.append((char)(b & 0xFF));
+        }
+        else
+        {
+            int i = b & 0xFF;
+            int type = BYTE_TABLE[i];
+            _codep = _state == UTF8_ACCEPT ? (0xFF >> type) & i : (i & 0x3F) | (_codep << 6);
+            int next = TRANS_TABLE[_state + type];
+
+            switch(next)
+            {
+                case UTF8_ACCEPT:
+                    _state=next;
+                    if (_codep < Character.MIN_HIGH_SURROGATE)
+                    {
+                        _appendable.append((char)_codep);
+                    }
+                    else
+                    {
+                        for (char c : Character.toChars(_codep))
+                            _appendable.append(c);
+                    }
+                    break;
+                    
+                case UTF8_REJECT:
+                    String reason = "byte "+TypeUtil.toHexString(b)+" in state "+(_state/12);
+                    _codep=0;
+                    _state = UTF8_ACCEPT;
+                    _appendable.append(REPLACEMENT);
+                    throw new NotUtf8Exception(reason);
+                    
+                default:
+                    _state=next;
+                    
+            }
+        }
+    }
+
+    public boolean isUtf8SequenceComplete()
+    {
+        return _state == UTF8_ACCEPT;
+    }
+
+    @SuppressWarnings("serial")
+    public static class NotUtf8Exception extends IllegalArgumentException
+    {
+        public NotUtf8Exception(String reason)
+        {
+            super("Not valid UTF8! "+reason);
+        }
+    }
+
+    protected void checkState()
+    {
+        if (!isUtf8SequenceComplete())
+        {
+            _codep=0;
+            _state = UTF8_ACCEPT;
+            try
+            {
+                _appendable.append(REPLACEMENT);
+            }
+            catch(IOException e)
+            {
+                throw new RuntimeException(e);
+            }
+            throw new NotUtf8Exception("incomplete UTF8 sequence");
+        }
+    }
+    
+    public String toReplacedString()
+    {
+        if (!isUtf8SequenceComplete())
+        {
+            _codep=0;
+            _state = UTF8_ACCEPT;
+            try
+            {
+                _appendable.append(REPLACEMENT);
+            }
+            catch(IOException e)
+            {
+                throw new RuntimeException(e);
+            }
+            Throwable th= new NotUtf8Exception("incomplete UTF8 sequence");
+            LOG.warn(th.toString());
+            LOG.debug(th);
+        }
+        return _appendable.toString();
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/util/Utf8LineParser.java b/lib/jetty/org/eclipse/jetty/util/Utf8LineParser.java
new file mode 100644 (file)
index 0000000..b54cf41
--- /dev/null
@@ -0,0 +1,101 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util;
+
+import java.nio.ByteBuffer;
+
+import org.eclipse.jetty.util.Utf8Appendable.NotUtf8Exception;
+
+/**
+ * Stateful parser for lines of UTF8 formatted text, looking for <code>"\n"</code> as a line termination character.
+ * <p>
+ * For use with new IO framework that is based on ByteBuffer parsing.
+ */
+public class Utf8LineParser
+{
+    private enum State
+    {
+        START,
+        PARSE,
+        END;
+    }
+
+    private State state;
+    private Utf8StringBuilder utf;
+
+    public Utf8LineParser()
+    {
+        this.state = State.START;
+    }
+
+    /**
+     * Parse a ByteBuffer (could be a partial buffer), and return once a complete line of UTF8 parsed text has been reached.
+     *
+     * @param buf
+     *            the buffer to parse (could be an incomplete buffer)
+     * @return the line of UTF8 parsed text, or null if no line end termination has been reached within the {@link ByteBuffer#remaining() remaining} bytes of
+     *         the provided ByteBuffer. (In the case of a null, a subsequent ByteBuffer with a line end termination should be provided)
+     * @throws NotUtf8Exception
+     *             if the input buffer has bytes that do not conform to UTF8 validation (validation performed by {@link Utf8StringBuilder}
+     */
+    public String parse(ByteBuffer buf)
+    {
+        byte b;
+        while (buf.remaining() > 0)
+        {
+            b = buf.get();
+            if (parseByte(b))
+            {
+                state = State.START;
+                return utf.toString();
+            }
+        }
+        // have not reached end of line (yet)
+        return null;
+    }
+
+    private boolean parseByte(byte b)
+    {
+        switch (state)
+        {
+            case START:
+                utf = new Utf8StringBuilder();
+                state = State.PARSE;
+                return parseByte(b);
+            case PARSE:
+                // not waiting on more UTF sequence parts.
+                if (utf.isUtf8SequenceComplete() && ((b == '\r') || (b == '\n')))
+                {
+                    state = State.END;
+                    return parseByte(b);
+                }
+                utf.append(b);
+                break;
+            case END:
+                if (b == '\n')
+                {
+                    // we've reached the end
+                    state = State.START;
+                    return true;
+                }
+                break;
+        }
+        return false;
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/util/Utf8StringBuffer.java b/lib/jetty/org/eclipse/jetty/util/Utf8StringBuffer.java
new file mode 100644 (file)
index 0000000..63fb1ac
--- /dev/null
@@ -0,0 +1,75 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util;
+
+/* ------------------------------------------------------------ */
+/**
+ * UTF-8 StringBuffer.
+ *
+ * This class wraps a standard {@link java.lang.StringBuffer} and provides methods to append
+ * UTF-8 encoded bytes, that are converted into characters.
+ *
+ * This class is stateful and up to 4 calls to {@link #append(byte)} may be needed before
+ * state a character is appended to the string buffer.
+ *
+ * The UTF-8 decoding is done by this class and no additional buffers or Readers are used.
+ * The UTF-8 code was inspired by http://bjoern.hoehrmann.de/utf-8/decoder/dfa/
+ */
+public class Utf8StringBuffer extends Utf8Appendable
+{
+    final StringBuffer _buffer;
+
+    public Utf8StringBuffer()
+    {
+        super(new StringBuffer());
+        _buffer = (StringBuffer)_appendable;
+    }
+
+    public Utf8StringBuffer(int capacity)
+    {
+        super(new StringBuffer(capacity));
+        _buffer = (StringBuffer)_appendable;
+    }
+
+    @Override
+    public int length()
+    {
+        return _buffer.length();
+    }
+
+    @Override
+    public void reset()
+    {
+        super.reset();
+        _buffer.setLength(0);
+    }
+
+    public StringBuffer getStringBuffer()
+    {
+        checkState();
+        return _buffer;
+    }
+
+    @Override
+    public String toString()
+    {
+        checkState();
+        return _buffer.toString();
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/util/Utf8StringBuilder.java b/lib/jetty/org/eclipse/jetty/util/Utf8StringBuilder.java
new file mode 100644 (file)
index 0000000..28fa20b
--- /dev/null
@@ -0,0 +1,78 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util;
+
+
+/* ------------------------------------------------------------ */
+/** UTF-8 StringBuilder.
+ *
+ * This class wraps a standard {@link java.lang.StringBuilder} and provides methods to append
+ * UTF-8 encoded bytes, that are converted into characters.
+ *
+ * This class is stateful and up to 4 calls to {@link #append(byte)} may be needed before
+ * state a character is appended to the string buffer.
+ *
+ * The UTF-8 decoding is done by this class and no additional buffers or Readers are used.
+ * The UTF-8 code was inspired by http://bjoern.hoehrmann.de/utf-8/decoder/dfa/
+ *
+ */
+public class Utf8StringBuilder extends Utf8Appendable
+{
+    final StringBuilder _buffer;
+
+    public Utf8StringBuilder()
+    {
+        super(new StringBuilder());
+        _buffer=(StringBuilder)_appendable;
+    }
+
+    public Utf8StringBuilder(int capacity)
+    {
+        super(new StringBuilder(capacity));
+        _buffer=(StringBuilder)_appendable;
+    }
+
+    @Override
+    public int length()
+    {
+        return _buffer.length();
+    }
+
+    @Override
+    public void reset()
+    {
+        super.reset();
+        _buffer.setLength(0);
+    }
+
+    public StringBuilder getStringBuilder()
+    {
+        checkState();
+        return _buffer;
+    }
+
+    @Override
+    public String toString()
+    {
+        checkState();
+        return _buffer.toString();
+    }
+
+
+}
diff --git a/lib/jetty/org/eclipse/jetty/util/annotation/ManagedAttribute.java b/lib/jetty/org/eclipse/jetty/util/annotation/ManagedAttribute.java
new file mode 100644 (file)
index 0000000..29a805f
--- /dev/null
@@ -0,0 +1,82 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util.annotation;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * The @ManagedAttribute annotation is used to indicate that a given method 
+ * exposes a JMX attribute. This annotation is placed always on the reader 
+ * method of a given attribute. Unless it is marked as read-only in the 
+ * configuration of the annotation a corresponding setter is looked for 
+ * following normal naming conventions. For example if this annotation is 
+ * on a method called getFoo() then a method called setFoo() would be looked 
+ * for and if found wired automatically into the jmx attribute.
+ *
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+@Target( { ElementType.METHOD } )
+public @interface ManagedAttribute
+{
+    /**
+     * Description of the Managed Attribute
+     * 
+     * @returngit checkout
+     */
+    String value() default "Not Specified";
+    
+    /**
+     * name to use for the attribute
+     * 
+     * @return the name of the attribute
+     */
+    String name() default "";
+    
+    /**
+     * Is the managed field read-only?
+     * 
+     * Required only when a setter exists but should not be exposed via JMX
+     * 
+     * @return true if readonly
+     */
+    boolean readonly() default false;
+  
+    /**
+     * Does the managed field exist on a proxy object?
+     * 
+     * 
+     * @return true if a proxy object is involved
+     */
+    boolean proxied() default false;
+    
+    
+    /**
+     * If is a field references a setter that doesn't conform to standards for discovery
+     * it can be set here.
+     * 
+     * @return the full name of the setter in question
+     */
+    String setter() default "";
+    
+}
diff --git a/lib/jetty/org/eclipse/jetty/util/annotation/ManagedObject.java b/lib/jetty/org/eclipse/jetty/util/annotation/ManagedObject.java
new file mode 100644 (file)
index 0000000..15f4b55
--- /dev/null
@@ -0,0 +1,45 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util.annotation;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * The @ManagedObject annotation is used on a class at the top level to 
+ * indicate that it should be exposed as an mbean. It has only one attribute 
+ * to it which is used as the description of the MBean. Should multiple 
+ * @ManagedObject annotations be found in the chain of influence then the 
+ * first description is used.
+ *
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+@Target( { ElementType.TYPE } )
+public @interface ManagedObject
+{
+    /**
+     * Description of the Managed Object
+     */
+    String value() default "Not Specified";
+  
+}
diff --git a/lib/jetty/org/eclipse/jetty/util/annotation/ManagedOperation.java b/lib/jetty/org/eclipse/jetty/util/annotation/ManagedOperation.java
new file mode 100644 (file)
index 0000000..3a29368
--- /dev/null
@@ -0,0 +1,60 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util.annotation;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * The @ManagedOperation annotation is used to indicate that a given method 
+ * should be considered a JMX operation.
+ *
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+@Target( { ElementType.METHOD } )
+public @interface ManagedOperation
+{
+    /**
+     * Description of the Managed Object
+     */
+    String value() default "Not Specified";
+    
+    /**
+     * The impact of an operation. 
+     * 
+     * NOTE: Valid values are UNKNOWN, ACTION, INFO, ACTION_INFO
+     * 
+     * NOTE: applies to METHOD
+     * 
+     * @return String representing the impact of the operation
+     */
+    String impact() default "UNKNOWN";
+    
+    /**
+     * Does the managed field exist on a proxy object?
+     * 
+     * 
+     * @return true if a proxy object is involved
+     */
+    boolean proxied() default false;
+}
diff --git a/lib/jetty/org/eclipse/jetty/util/annotation/Name.java b/lib/jetty/org/eclipse/jetty/util/annotation/Name.java
new file mode 100644 (file)
index 0000000..b79e76e
--- /dev/null
@@ -0,0 +1,49 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util.annotation;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * This annotation is used to describe variables in method 
+ * signatures so that when rendered into tools like JConsole 
+ * it is clear what the parameters are. For example:
+ *
+ * public void doodle(@Name(value="doodle", description="A description of the argument") String doodle)
+ * 
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+@Target( { ElementType.PARAMETER } )
+public @interface Name
+{
+    /**
+     * the name of the parameter
+     */
+    String value();
+    
+    /**
+     * the description of the parameter
+     */
+    String description() default "";
+}
diff --git a/lib/jetty/org/eclipse/jetty/util/annotation/package-info.java b/lib/jetty/org/eclipse/jetty/util/annotation/package-info.java
new file mode 100644 (file)
index 0000000..5f0038b
--- /dev/null
@@ -0,0 +1,23 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+/**
+ * Jetty Util : Common Utility Annotations
+ */
+package org.eclipse.jetty.util.annotation;
+
diff --git a/lib/jetty/org/eclipse/jetty/util/component/AbstractLifeCycle.java b/lib/jetty/org/eclipse/jetty/util/component/AbstractLifeCycle.java
new file mode 100644 (file)
index 0000000..8f2d9dc
--- /dev/null
@@ -0,0 +1,234 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util.component;
+
+import java.lang.management.ManagementFactory;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+import org.eclipse.jetty.util.annotation.ManagedAttribute;
+import org.eclipse.jetty.util.annotation.ManagedObject;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/**
+ * Basic implementation of the life cycle interface for components.
+ */
+@ManagedObject("Abstract Implementation of LifeCycle")
+public abstract class AbstractLifeCycle implements LifeCycle
+{
+    private static final Logger LOG = Log.getLogger(AbstractLifeCycle.class);
+    
+    public static final String STOPPED="STOPPED";
+    public static final String FAILED="FAILED";
+    public static final String STARTING="STARTING";
+    public static final String STARTED="STARTED";
+    public static final String STOPPING="STOPPING";
+    public static final String RUNNING="RUNNING";
+
+    private final CopyOnWriteArrayList<LifeCycle.Listener> _listeners=new CopyOnWriteArrayList<LifeCycle.Listener>();
+    private final Object _lock = new Object();
+    private final int __FAILED = -1, __STOPPED = 0, __STARTING = 1, __STARTED = 2, __STOPPING = 3;
+    private volatile int _state = __STOPPED;
+    private long _stopTimeout = 30000;
+
+    protected void doStart() throws Exception
+    {
+    }
+
+    protected void doStop() throws Exception
+    {
+    }
+    
+    @Override
+    public final void start() throws Exception
+    {
+        synchronized (_lock)
+        {
+            try
+            {
+                if (_state == __STARTED || _state == __STARTING)
+                    return;
+                setStarting();
+                doStart();
+                setStarted();
+            }
+            catch (Throwable e)
+            {
+                setFailed(e);
+                throw e;
+            }
+        }
+    }
+
+    @Override
+    public final void stop() throws Exception
+    {
+        synchronized (_lock)
+        {
+            try
+            {
+                if (_state == __STOPPING || _state == __STOPPED)
+                    return;
+                setStopping();
+                doStop();
+                setStopped();
+            }
+            catch (Throwable e)
+            {
+                setFailed(e);
+                throw e;
+            }
+        }
+    }
+
+    @Override
+    public boolean isRunning()
+    {
+        final int state = _state;
+
+        return state == __STARTED || state == __STARTING;
+    }
+
+    @Override
+    public boolean isStarted()
+    {
+        return _state == __STARTED;
+    }
+
+    @Override
+    public boolean isStarting()
+    {
+        return _state == __STARTING;
+    }
+
+    @Override
+    public boolean isStopping()
+    {
+        return _state == __STOPPING;
+    }
+
+    @Override
+    public boolean isStopped()
+    {
+        return _state == __STOPPED;
+    }
+
+    @Override
+    public boolean isFailed()
+    {
+        return _state == __FAILED;
+    }
+
+    @Override
+    public void addLifeCycleListener(LifeCycle.Listener listener)
+    {
+        _listeners.add(listener);
+    }
+
+    @Override
+    public void removeLifeCycleListener(LifeCycle.Listener listener)
+    {
+        _listeners.remove(listener);
+    }
+
+    @ManagedAttribute(value="Lifecycle State for this instance", readonly=true)
+    public String getState()
+    {
+        switch(_state)
+        {
+            case __FAILED: return FAILED;
+            case __STARTING: return STARTING;
+            case __STARTED: return STARTED;
+            case __STOPPING: return STOPPING;
+            case __STOPPED: return STOPPED;
+        }
+        return null;
+    }
+
+    public static String getState(LifeCycle lc)
+    {
+        if (lc.isStarting()) return STARTING;
+        if (lc.isStarted()) return STARTED;
+        if (lc.isStopping()) return STOPPING;
+        if (lc.isStopped()) return STOPPED;
+        return FAILED;
+    }
+
+    private void setStarted()
+    {
+        _state = __STARTED;
+        if (LOG.isDebugEnabled())
+            
+        LOG.debug(STARTED+" @{}ms {}",ManagementFactory.getRuntimeMXBean().getUptime(),this);
+        for (Listener listener : _listeners)
+            listener.lifeCycleStarted(this);
+    }
+
+    private void setStarting()
+    {
+        LOG.debug("starting {}",this);
+        _state = __STARTING;
+        for (Listener listener : _listeners)
+            listener.lifeCycleStarting(this);
+    }
+
+    private void setStopping()
+    {
+        LOG.debug("stopping {}",this);
+        _state = __STOPPING;
+        for (Listener listener : _listeners)
+            listener.lifeCycleStopping(this);
+    }
+
+    private void setStopped()
+    {
+        _state = __STOPPED;
+        LOG.debug("{} {}",STOPPED,this);
+        for (Listener listener : _listeners)
+            listener.lifeCycleStopped(this);
+    }
+
+    private void setFailed(Throwable th)
+    {
+        _state = __FAILED;
+        LOG.warn(FAILED+" " + this+": "+th,th);
+        for (Listener listener : _listeners)
+            listener.lifeCycleFailure(this,th);
+    }
+
+    @ManagedAttribute(value="The stop timeout in milliseconds")
+    public long getStopTimeout()
+    {
+        return _stopTimeout;
+    }
+
+    public void setStopTimeout(long stopTimeout)
+    {
+        this._stopTimeout = stopTimeout;
+    }
+
+    public static abstract class AbstractLifeCycleListener implements LifeCycle.Listener
+    {
+        @Override public void lifeCycleFailure(LifeCycle event, Throwable cause) {}
+        @Override public void lifeCycleStarted(LifeCycle event) {}
+        @Override public void lifeCycleStarting(LifeCycle event) {}
+        @Override public void lifeCycleStopped(LifeCycle event) {}
+        @Override public void lifeCycleStopping(LifeCycle event) {}
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/util/component/Container.java b/lib/jetty/org/eclipse/jetty/util/component/Container.java
new file mode 100644 (file)
index 0000000..a5a4c75
--- /dev/null
@@ -0,0 +1,92 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util.component;
+
+import java.util.Collection;
+
+public interface Container
+{
+    /* ------------------------------------------------------------ */
+    /**
+     * Add a bean.  If the bean is-a {@link Listener}, then also do an implicit {@link #addEventListener(Listener)}.
+     * @param o the bean object to add
+     * @return true if the bean was added, false if it was already present
+     */
+    public boolean addBean(Object o);
+
+    /**
+     * @return the list of beans known to this aggregate
+     * @see #getBean(Class)
+     */
+    public Collection<Object> getBeans();
+
+    /**
+     * @param clazz the class of the beans
+     * @return the list of beans of the given class (or subclass)
+     * @see #getBeans()
+     */
+    public <T> Collection<T> getBeans(Class<T> clazz);
+
+    /**
+     * @param clazz the class of the bean
+     * @return the first bean of a specific class (or subclass), or null if no such bean exist
+     */
+    public <T> T getBean(Class<T> clazz);
+
+    /**
+     * Removes the given bean.
+     * If the bean is-a {@link Listener}, then also do an implicit {@link #removeEventListener(Listener)}.
+     * @return whether the bean was removed
+     */
+    public boolean removeBean(Object o);
+    
+    /**
+     * Add an event listener. 
+     * @see Container#addBean(Object)
+     * @param listener
+     */
+    public void addEventListener(Listener listener);
+    
+    /**
+     * Remove an event listener. 
+     * @see Container#removeBean(Object)
+     * @param listener
+     */
+    public void removeEventListener(Listener listener);
+
+    /**
+     * A listener for Container events.
+     * If an added bean implements this interface it will receive the events
+     * for this container.
+     */
+    public interface Listener
+    {
+        void beanAdded(Container parent,Object child);
+        void beanRemoved(Container parent,Object child);
+    }
+    
+    /**
+     * Inherited Listener.
+     * If an added bean implements this interface, then it will 
+     * be added to all contained beans that are themselves Containers
+     */
+    public interface InheritedListener extends Listener
+    {
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/util/component/ContainerLifeCycle.java b/lib/jetty/org/eclipse/jetty/util/component/ContainerLifeCycle.java
new file mode 100644 (file)
index 0000000..464c0f7
--- /dev/null
@@ -0,0 +1,811 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util.component;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+import org.eclipse.jetty.util.annotation.ManagedObject;
+import org.eclipse.jetty.util.annotation.ManagedOperation;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/**
+ * An ContainerLifeCycle is an {@link LifeCycle} implementation for a collection of contained beans.
+ * <p>
+ * Beans can be added the ContainerLifeCycle either as managed beans or as unmanaged beans.  A managed bean is started, stopped and destroyed with the aggregate.
+ * An unmanaged bean is associated with the aggregate for the purposes of {@link #dump()}, but it's lifecycle must be managed externally.
+ * <p>
+ * When a {@link LifeCycle} bean is added without a managed state being specified the state is determined heuristically:
+ * <ul>
+ *   <li>If the added bean is running, it will be added as an unmanaged bean.
+ *   <li>If the added bean is !running and the container is !running, it will be added as an AUTO bean (see below).
+ *   <li>If the added bean is !running and the container is starting, it will be added as an managed bean and will be started (this handles the frequent case of 
+ *   new beans added during calls to doStart).
+ *   <li>If the added bean is !running and the container is started, it will be added as an unmanaged bean.
+ * </ul>
+ * When the container is started, then all contained managed beans will also be started.  Any contained Auto beans 
+ * will be check for their status and if already started will be switched unmanaged beans, else they will be 
+ * started and switched to managed beans.  Beans added after a container is started are not started and their state needs to
+ * be explicitly managed.
+ * <p>
+ * When stopping the container, a contained bean will be stopped by this aggregate only if it
+ * is started by this aggregate.
+ * <p>
+ * The methods {@link #addBean(Object, boolean)}, {@link #manage(Object)} and {@link #unmanage(Object)} can be used to
+ * explicitly control the life cycle relationship.
+ * <p>
+ * If adding a bean that is shared between multiple {@link ContainerLifeCycle} instances, then it should be started before being added, so it is unmanaged, or
+ * the API must be used to explicitly set it as unmanaged.
+ * <p>
+ * This class also provides utility methods to dump deep structures of objects.  It the dump, the following symbols are used to indicate the type of contained object:
+ * <pre>
+ * SomeContainerLifeCycleInstance
+ *   +- contained POJO instance
+ *   += contained MANAGED object, started and stopped with this instance
+ *   +~ referenced UNMANAGED object, with separate lifecycle
+ *   +? referenced AUTO object that could become MANAGED or UNMANAGED.
+ * </pre>
+ */
+
+/* ------------------------------------------------------------ */
+/**
+ */
+@ManagedObject("Implementation of Container and LifeCycle")
+public class ContainerLifeCycle extends AbstractLifeCycle implements Container, Destroyable, Dumpable
+{
+    private static final Logger LOG = Log.getLogger(ContainerLifeCycle.class);
+    private final List<Bean> _beans = new CopyOnWriteArrayList<>();
+    private final List<Container.Listener> _listeners = new CopyOnWriteArrayList<>();
+    private boolean _doStarted = false;
+
+
+    public ContainerLifeCycle()
+    {
+    }
+
+    /**
+     * Starts the managed lifecycle beans in the order they were added.
+     */
+    @Override
+    protected void doStart() throws Exception
+    {
+        // indicate that we are started, so that addBean will start other beans added.
+        _doStarted = true;
+
+        // start our managed and auto beans
+        for (Bean b : _beans)
+        {
+            if (b._bean instanceof LifeCycle)
+            {
+                LifeCycle l = (LifeCycle)b._bean;
+                switch(b._managed)
+                {
+                    case MANAGED:
+                        if (!l.isRunning())
+                            start(l);
+                        break;
+                    case AUTO:
+                        if (l.isRunning())
+                            unmanage(b);
+                        else
+                        {
+                            manage(b);
+                            start(l);
+                        }
+                        break;
+                }
+            }
+        }
+
+        super.doStart();
+    }
+
+    /**
+     * Starts the given lifecycle.
+     *
+     * @param l
+     * @throws Exception
+     */
+    protected void start(LifeCycle l) throws Exception
+    {
+        l.start();
+    }
+    
+    /**
+     * Stops the given lifecycle.
+     *
+     * @param l
+     * @throws Exception
+     */
+    protected void stop(LifeCycle l) throws Exception
+    {
+        l.stop();
+    }
+
+    /**
+     * Stops the managed lifecycle beans in the reverse order they were added.
+     */
+    @Override
+    protected void doStop() throws Exception
+    {
+        _doStarted = false;
+        super.doStop();
+        List<Bean> reverse = new ArrayList<>(_beans);
+        Collections.reverse(reverse);
+        for (Bean b : reverse)
+        {
+            if (b._managed==Managed.MANAGED && b._bean instanceof LifeCycle)
+            {
+                LifeCycle l = (LifeCycle)b._bean;
+                if (l.isRunning())
+                    stop(l);
+            }
+        }
+    }
+
+    /**
+     * Destroys the managed Destroyable beans in the reverse order they were added.
+     */
+    @Override
+    public void destroy()
+    {
+        List<Bean> reverse = new ArrayList<>(_beans);
+        Collections.reverse(reverse);
+        for (Bean b : reverse)
+        {
+            if (b._bean instanceof Destroyable && (b._managed==Managed.MANAGED || b._managed==Managed.POJO))
+            {
+                Destroyable d = (Destroyable)b._bean;
+                d.destroy();
+            }
+        }
+        _beans.clear();
+    }
+
+
+    /**
+     * @param bean the bean to test
+     * @return whether this aggregate contains the bean
+     */
+    public boolean contains(Object bean)
+    {
+        for (Bean b : _beans)
+            if (b._bean == bean)
+                return true;
+        return false;
+    }
+
+    /**
+     * @param bean the bean to test
+     * @return whether this aggregate contains and manages the bean
+     */
+    public boolean isManaged(Object bean)
+    {
+        for (Bean b : _beans)
+            if (b._bean == bean)
+                return b.isManaged();
+        return false;
+    }
+
+    /**
+     * Adds the given bean, detecting whether to manage it or not.
+     * If the bean is a {@link LifeCycle}, then it will be managed if it is not
+     * already started and not managed if it is already started.
+     * The {@link #addBean(Object, boolean)}
+     * method should be used if this is not correct, or the {@link #manage(Object)} and {@link #unmanage(Object)}
+     * methods may be used after an add to change the status.
+     *
+     * @param o the bean object to add
+     * @return true if the bean was added, false if it was already present
+     */
+    @Override
+    public boolean addBean(Object o)
+    {
+        if (o instanceof LifeCycle)
+        {
+            LifeCycle l = (LifeCycle)o;
+            return addBean(o,l.isRunning()?Managed.UNMANAGED:Managed.AUTO);
+        }
+
+        return addBean(o,Managed.POJO);
+    }
+
+    /**
+     * Adds the given bean, explicitly managing it or not.
+     *
+     * @param o       The bean object to add
+     * @param managed whether to managed the lifecycle of the bean
+     * @return true if the bean was added, false if it was already present
+     */
+    public boolean addBean(Object o, boolean managed)
+    {
+        if (o instanceof LifeCycle)
+            return addBean(o,managed?Managed.MANAGED:Managed.UNMANAGED);
+        return addBean(o,managed?Managed.POJO:Managed.UNMANAGED);
+    }
+
+    public boolean addBean(Object o, Managed managed)
+    {
+        if (contains(o))
+            return false;
+
+        Bean new_bean = new Bean(o);
+
+        // if the bean is a Listener
+        if (o instanceof Container.Listener)
+            addEventListener((Container.Listener)o);
+
+        // Add the bean
+        _beans.add(new_bean);
+
+        // Tell existing listeners about the new bean
+        for (Container.Listener l:_listeners)
+            l.beanAdded(this,o);
+
+        try
+        {
+            switch (managed)
+            {
+                case UNMANAGED:
+                    unmanage(new_bean);
+                    break;
+
+                case MANAGED:
+                    manage(new_bean);
+
+                    if (isStarting() && _doStarted)
+                    {
+                        LifeCycle l = (LifeCycle)o;
+                        if (!l.isRunning())
+                            start(l);
+                    }
+                    break;
+
+                case AUTO:
+                    if (o instanceof LifeCycle)
+                    {
+                        LifeCycle l = (LifeCycle)o;
+                        if (isStarting())
+                        {
+                            if (l.isRunning())
+                                unmanage(new_bean);
+                            else if (_doStarted)
+                            {
+                                manage(new_bean);
+                                start(l);
+                            }
+                            else
+                                new_bean._managed=Managed.AUTO;      
+                        }
+                        else if (isStarted())
+                            unmanage(new_bean);
+                        else
+                            new_bean._managed=Managed.AUTO;
+                    }
+                    else
+                        new_bean._managed=Managed.POJO;
+                    break;
+
+                case POJO:
+                    new_bean._managed=Managed.POJO;
+            }
+        }
+        catch (RuntimeException | Error e)
+        {
+            throw e;
+        }
+        catch (Exception e)
+        {
+            throw new RuntimeException(e);
+        }
+
+        LOG.debug("{} added {}",this,new_bean);
+
+        return true;
+    }
+
+    
+    /* ------------------------------------------------------------ */
+    /** Add a managed lifecycle.
+     * <p>This is a conveniance method that uses addBean(lifecycle,true)
+     * and then ensures that the added bean is started iff this container
+     * is running.  Exception from nested calls to start are caught and 
+     * wrapped as RuntimeExceptions
+     * @param lifecycle
+     */
+    public void addManaged(LifeCycle lifecycle)
+    {
+        addBean(lifecycle,true);
+        try
+        {
+            if (isRunning() && !lifecycle.isRunning())
+                start(lifecycle);
+        }
+        catch (RuntimeException | Error e)
+        {
+            throw e;
+        }
+        catch (Exception e)
+        {
+            throw new RuntimeException(e);
+        }
+    }
+
+    @Override
+    public void addEventListener(Container.Listener listener)
+    {
+        if (_listeners.contains(listener))
+            return;
+        
+        _listeners.add(listener);
+
+        // tell it about existing beans
+        for (Bean b:_beans)
+        {
+            listener.beanAdded(this,b._bean);
+
+            // handle inheritance
+            if (listener instanceof InheritedListener && b.isManaged() && b._bean instanceof Container)
+            {
+                if (b._bean instanceof ContainerLifeCycle)
+                     ((ContainerLifeCycle)b._bean).addBean(listener, false);
+                 else
+                     ((Container)b._bean).addBean(listener);
+            }
+        }
+    }
+
+    /**
+     * Manages a bean already contained by this aggregate, so that it is started/stopped/destroyed with this
+     * aggregate.
+     *
+     * @param bean The bean to manage (must already have been added).
+     */
+    public void manage(Object bean)
+    {
+        for (Bean b : _beans)
+        {
+            if (b._bean == bean)
+            {
+                manage(b);
+                return;
+            }
+        }
+        throw new IllegalArgumentException("Unknown bean " + bean);
+    }
+
+    private void manage(Bean bean)
+    {
+        if (bean._managed!=Managed.MANAGED)
+        {
+            bean._managed=Managed.MANAGED;
+
+            if (bean._bean instanceof Container)
+            {
+                for (Container.Listener l:_listeners)
+                {
+                    if (l instanceof InheritedListener)
+                    {
+                        if (bean._bean instanceof ContainerLifeCycle)
+                            ((ContainerLifeCycle)bean._bean).addBean(l,false);
+                        else
+                            ((Container)bean._bean).addBean(l);
+                    }
+                }
+            }
+
+            if (bean._bean instanceof AbstractLifeCycle)
+            {
+                ((AbstractLifeCycle)bean._bean).setStopTimeout(getStopTimeout());
+            }
+        }
+    }
+
+    /**
+     * Unmanages a bean already contained by this aggregate, so that it is not started/stopped/destroyed with this
+     * aggregate.
+     *
+     * @param bean The bean to unmanage (must already have been added).
+     */
+    public void unmanage(Object bean)
+    {
+        for (Bean b : _beans)
+        {
+            if (b._bean == bean)
+            {
+                unmanage(b);
+                return;
+            }
+        }
+        throw new IllegalArgumentException("Unknown bean " + bean);
+    }
+
+    private void unmanage(Bean bean)
+    {
+        if (bean._managed!=Managed.UNMANAGED)
+        {
+            if (bean._managed==Managed.MANAGED && bean._bean instanceof Container)
+            {
+                for (Container.Listener l:_listeners)
+                {
+                    if (l instanceof InheritedListener)
+                        ((Container)bean._bean).removeBean(l);
+                }
+            }
+            bean._managed=Managed.UNMANAGED;
+        }
+    }
+
+    @Override
+    public Collection<Object> getBeans()
+    {
+        return getBeans(Object.class);
+    }
+
+    public void setBeans(Collection<Object> beans)
+    {
+        for (Object bean : beans)
+            addBean(bean);
+    }
+
+    @Override
+    public <T> Collection<T> getBeans(Class<T> clazz)
+    {
+        ArrayList<T> beans = new ArrayList<>();
+        for (Bean b : _beans)
+        {
+            if (clazz.isInstance(b._bean))
+                beans.add(clazz.cast(b._bean));
+        }
+        return beans;
+    }
+
+    @Override
+    public <T> T getBean(Class<T> clazz)
+    {
+        for (Bean b : _beans)
+        {
+            if (clazz.isInstance(b._bean))
+                return clazz.cast(b._bean);
+        }
+        return null;
+    }
+
+    /**
+     * Removes all bean
+     */
+    public void removeBeans()
+    {
+        ArrayList<Bean> beans= new ArrayList<>(_beans);
+        for (Bean b : beans)
+            remove(b);
+    }
+
+    private Bean getBean(Object o)
+    {
+        for (Bean b : _beans)
+        {
+            if (b._bean == o)
+                return b;
+        }
+        return null;
+    }
+
+    @Override
+    public boolean removeBean(Object o)
+    {
+        Bean b=getBean(o);
+        return b!=null && remove(b);
+    }
+
+    private boolean remove(Bean bean)
+    {
+        if (_beans.remove(bean))
+        {
+            
+            unmanage(bean);
+
+            for (Container.Listener l:_listeners)
+                l.beanRemoved(this,bean._bean);
+
+            if (bean._bean instanceof Container.Listener)
+                removeEventListener((Container.Listener)bean._bean);
+
+            // stop managed beans
+            if (bean._managed==Managed.MANAGED && bean._bean instanceof LifeCycle)
+            {
+                try
+                {
+                    stop((LifeCycle)bean._bean);
+                }
+                catch(RuntimeException | Error e)
+                {
+                    throw e;
+                }
+                catch (Exception e)
+                {
+                    throw new RuntimeException(e);
+                }
+            }
+            return true;
+        }
+        return false;
+    }
+
+    @Override
+    public void removeEventListener(Container.Listener listener)
+    {
+        if (_listeners.remove(listener))
+        {
+            // remove existing beans
+            for (Bean b:_beans)
+            {
+                listener.beanRemoved(this,b._bean);
+
+                if (listener instanceof InheritedListener && b.isManaged() && b._bean instanceof Container)
+                    ((Container)b._bean).removeBean(listener);
+            }
+        }
+    }
+
+    @Override
+    public void setStopTimeout(long stopTimeout)
+    {
+        super.setStopTimeout(stopTimeout);
+        for (Bean bean : _beans)
+        {
+            if (bean.isManaged() && bean._bean instanceof AbstractLifeCycle)
+                ((AbstractLifeCycle)bean._bean).setStopTimeout(stopTimeout);
+        }
+    }
+
+    /**
+     * Dumps to {@link System#err}.
+     * @see #dump()
+     */
+    @ManagedOperation("Dump the object to stderr")
+    public void dumpStdErr()
+    {
+        try
+        {
+            dump(System.err, "");
+        }
+        catch (IOException e)
+        {
+            LOG.warn(e);
+        }
+    }
+
+    @Override
+    @ManagedOperation("Dump the object to a string")
+    public String dump()
+    {
+        return dump(this);
+    }
+
+    public static String dump(Dumpable dumpable)
+    {
+        StringBuilder b = new StringBuilder();
+        try
+        {
+            dumpable.dump(b, "");
+        }
+        catch (IOException e)
+        {
+            LOG.warn(e);
+        }
+        return b.toString();
+    }
+
+    public void dump(Appendable out) throws IOException
+    {
+        dump(out, "");
+    }
+
+    protected void dumpThis(Appendable out) throws IOException
+    {
+        out.append(String.valueOf(this)).append(" - ").append(getState()).append("\n");
+    }
+
+    public static void dumpObject(Appendable out, Object o) throws IOException
+    {
+        try
+        {
+            if (o instanceof LifeCycle)
+                out.append(String.valueOf(o)).append(" - ").append((AbstractLifeCycle.getState((LifeCycle)o))).append("\n");
+            else
+                out.append(String.valueOf(o)).append("\n");
+        }
+        catch (Throwable th)
+        {
+            out.append(" => ").append(th.toString()).append('\n');
+        }
+    }
+
+    @Override
+    public void dump(Appendable out, String indent) throws IOException
+    {
+        dumpBeans(out,indent);
+    }
+
+    protected void dumpBeans(Appendable out, String indent, Collection<?>... collections) throws IOException
+    {
+        dumpThis(out);
+        int size = _beans.size();
+        for (Collection<?> c : collections)
+            size += c.size();
+        if (size == 0)
+            return;
+        int i = 0;
+        for (Bean b : _beans)
+        {
+            i++;
+
+            switch(b._managed)
+            {
+                case POJO:
+                    out.append(indent).append(" +- ");
+                    if (b._bean instanceof Dumpable)
+                        ((Dumpable)b._bean).dump(out, indent + (i == size ? "    " : " |  "));
+                    else
+                        dumpObject(out, b._bean);
+                    break;
+
+                case MANAGED:
+                    out.append(indent).append(" += ");
+                    if (b._bean instanceof Dumpable)
+                        ((Dumpable)b._bean).dump(out, indent + (i == size ? "    " : " |  "));
+                    else
+                        dumpObject(out, b._bean);
+                    break;
+
+                case UNMANAGED:
+                    out.append(indent).append(" +~ ");
+                    dumpObject(out, b._bean);
+                    break;
+
+                case AUTO:
+                    out.append(indent).append(" +? ");
+                    if (b._bean instanceof Dumpable)
+                        ((Dumpable)b._bean).dump(out, indent + (i == size ? "    " : " |  "));
+                    else
+                        dumpObject(out, b._bean);
+                    break;
+
+            }
+        }
+
+        if (i<size)
+            out.append(indent).append(" |\n");
+
+        for (Collection<?> c : collections)
+        {
+            for (Object o : c)
+            {
+                i++;
+                out.append(indent).append(" +> ");
+
+                if (o instanceof Dumpable)
+                    ((Dumpable)o).dump(out, indent + (i == size ? "    " : " |  "));
+                else
+                    dumpObject(out, o);
+            }
+        }
+    }
+
+    public static void dump(Appendable out, String indent, Collection<?>... collections) throws IOException
+    {
+        if (collections.length == 0)
+            return;
+        int size = 0;
+        for (Collection<?> c : collections)
+            size += c.size();
+        if (size == 0)
+            return;
+
+        int i = 0;
+        for (Collection<?> c : collections)
+        {
+            for (Object o : c)
+            {
+                i++;
+                out.append(indent).append(" +- ");
+
+                if (o instanceof Dumpable)
+                    ((Dumpable)o).dump(out, indent + (i == size ? "    " : " |  "));
+                else
+                    dumpObject(out, o);
+            }
+        }
+    }
+
+
+    enum Managed { POJO, MANAGED, UNMANAGED, AUTO };
+
+    private static class Bean
+    {
+        private final Object _bean;
+        private volatile Managed _managed = Managed.POJO;
+
+        private Bean(Object b)
+        {
+            _bean = b;
+        }
+
+        public boolean isManaged()
+        {
+            return _managed==Managed.MANAGED;
+        }
+
+        @Override
+        public String toString()
+        {
+            return String.format("{%s,%s}", _bean, _managed);
+        }
+    }
+
+    public void updateBean(Object oldBean, final Object newBean)
+    {
+        if (newBean!=oldBean)
+        {
+            if (oldBean!=null)
+                removeBean(oldBean);
+            if (newBean!=null)
+                addBean(newBean);
+        }
+    }
+
+    public void updateBeans(Object[] oldBeans, final Object[] newBeans)
+    {
+        // remove oldChildren not in newChildren
+        if (oldBeans!=null)
+        {
+            loop: for (Object o:oldBeans)
+            {
+                if (newBeans!=null)
+                {
+                    for (Object n:newBeans)
+                        if (o==n)
+                            continue loop;
+                }
+                removeBean(o);
+            }
+        }
+
+        // add new beans not in old
+        if (newBeans!=null)
+        {
+            loop: for (Object n:newBeans)
+            {
+                if (oldBeans!=null)
+                {
+                    for (Object o:oldBeans)
+                        if (o==n)
+                            continue loop;
+                }
+                addBean(n);
+            }
+        }
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/util/component/Destroyable.java b/lib/jetty/org/eclipse/jetty/util/component/Destroyable.java
new file mode 100644 (file)
index 0000000..2e7e441
--- /dev/null
@@ -0,0 +1,31 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util.component;
+
+
+/**
+ * <p>A Destroyable is an object which can be destroyed.</p>
+ * <p>Typically a Destroyable is a {@link LifeCycle} component that can hold onto
+ * resources over multiple start/stop cycles.   A call to destroy will release all
+ * resources and will prevent any further start/stop cycles from being successful.</p>
+ */
+public interface Destroyable
+{
+    void destroy();
+}
diff --git a/lib/jetty/org/eclipse/jetty/util/component/Dumpable.java b/lib/jetty/org/eclipse/jetty/util/component/Dumpable.java
new file mode 100644 (file)
index 0000000..2a1882b
--- /dev/null
@@ -0,0 +1,33 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util.component;
+
+import java.io.IOException;
+
+import org.eclipse.jetty.util.annotation.ManagedObject;
+import org.eclipse.jetty.util.annotation.ManagedOperation;
+
+@ManagedObject("Dumpable Object")
+public interface Dumpable
+{
+    @ManagedOperation(value="Dump the nested Object state as a String", impact="INFO")
+    String dump();
+    
+    void dump(Appendable out,String indent) throws IOException;
+}
diff --git a/lib/jetty/org/eclipse/jetty/util/component/FileDestroyable.java b/lib/jetty/org/eclipse/jetty/util/component/FileDestroyable.java
new file mode 100644 (file)
index 0000000..9493645
--- /dev/null
@@ -0,0 +1,95 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util.component;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+import org.eclipse.jetty.util.IO;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.resource.Resource;
+
+public class FileDestroyable implements Destroyable
+{
+    private static final Logger LOG = Log.getLogger(FileDestroyable.class);
+    final List<File> _files = new ArrayList<File>();
+
+    public FileDestroyable()
+    {
+    }
+    
+    public FileDestroyable(String file) throws IOException
+    {
+        _files.add(Resource.newResource(file).getFile());
+    }
+    
+    public FileDestroyable(File file)
+    {
+        _files.add(file);
+    }
+    
+    public void addFile(String file) throws IOException
+    {
+        try(Resource r = Resource.newResource(file);)
+        {
+            _files.add(r.getFile());
+        }
+    }
+    
+    public void addFile(File file)
+    {
+        _files.add(file);
+    }
+    
+    public void addFiles(Collection<File> files)
+    {
+        _files.addAll(files);
+    }
+    
+    public void removeFile(String file) throws IOException
+    {
+        try(Resource r = Resource.newResource(file);)
+        {
+            _files.remove(r.getFile());
+        }
+    }
+    
+    public void removeFile(File file)
+    {
+        _files.remove(file);
+    }
+    
+    @Override
+    public void destroy()
+    {
+        for (File file : _files)
+        {
+            if (file.exists())
+            {
+                LOG.debug("Destroy {}",file);
+                IO.delete(file);
+            }
+        }
+    }
+
+}
diff --git a/lib/jetty/org/eclipse/jetty/util/component/FileNoticeLifeCycleListener.java b/lib/jetty/org/eclipse/jetty/util/component/FileNoticeLifeCycleListener.java
new file mode 100644 (file)
index 0000000..1c4dcec
--- /dev/null
@@ -0,0 +1,79 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util.component;
+
+import java.io.FileWriter;
+import java.io.Writer;
+
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+
+/* ------------------------------------------------------------ */
+/** A LifeCycle Listener that writes state changes to a file.
+ * <p>This can be used with the jetty.sh script to wait for successful startup.
+ */
+public class FileNoticeLifeCycleListener implements LifeCycle.Listener
+{
+    private static final Logger LOG = Log.getLogger(FileNoticeLifeCycleListener.class);
+    
+    private final String _filename;
+    
+    public FileNoticeLifeCycleListener(String filename)
+    {
+        _filename=filename;
+    }
+
+    private void writeState(String action, LifeCycle lifecycle)
+    {
+        try (Writer out = new FileWriter(_filename,true))
+        {
+            out.append(action).append(" ").append(lifecycle.toString()).append("\n");
+        }
+        catch(Exception e)
+        {
+            LOG.warn(e);
+        }
+    }
+    
+    public void lifeCycleStarting(LifeCycle event)
+    {  
+        writeState("STARTING",event);      
+    }
+
+    public void lifeCycleStarted(LifeCycle event)
+    {        
+        writeState("STARTED",event); 
+    }
+
+    public void lifeCycleFailure(LifeCycle event, Throwable cause)
+    {        
+        writeState("FAILED",event);
+    }
+
+    public void lifeCycleStopping(LifeCycle event)
+    {        
+        writeState("STOPPING",event);
+    }
+
+    public void lifeCycleStopped(LifeCycle event)
+    {        
+        writeState("STOPPED",event);
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/util/component/Graceful.java b/lib/jetty/org/eclipse/jetty/util/component/Graceful.java
new file mode 100644 (file)
index 0000000..96bec33
--- /dev/null
@@ -0,0 +1,29 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util.component;
+
+import java.util.concurrent.Future;
+
+/* ------------------------------------------------------------ */
+/* A Lifecycle that can be gracefully shutdown.
+ */
+public interface Graceful
+{
+    public Future<Void> shutdown();
+}
diff --git a/lib/jetty/org/eclipse/jetty/util/component/LifeCycle.java b/lib/jetty/org/eclipse/jetty/util/component/LifeCycle.java
new file mode 100644 (file)
index 0000000..f58fd1a
--- /dev/null
@@ -0,0 +1,125 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util.component;
+
+import java.util.EventListener;
+
+import org.eclipse.jetty.util.annotation.ManagedObject;
+import org.eclipse.jetty.util.annotation.ManagedOperation;
+
+/* ------------------------------------------------------------ */
+/**
+ * The lifecycle interface for generic components.
+ * <br />
+ * Classes implementing this interface have a defined life cycle
+ * defined by the methods of this interface.
+ *
+ * 
+ */
+@ManagedObject("Lifecycle Interface for startable components")
+public interface LifeCycle
+{
+    /* ------------------------------------------------------------ */
+    /**
+     * Starts the component.
+     * @throws Exception If the component fails to start
+     * @see #isStarted()
+     * @see #stop()
+     * @see #isFailed()
+     */
+    @ManagedOperation(value="Starts the instance", impact="ACTION")
+    public void start()
+        throws Exception;
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Stops the component.
+     * The component may wait for current activities to complete
+     * normally, but it can be interrupted.
+     * @exception Exception If the component fails to stop
+     * @see #isStopped()
+     * @see #start()
+     * @see #isFailed()
+     */
+    @ManagedOperation(value="Stops the instance", impact="ACTION")
+    public void stop()
+        throws Exception;
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return true if the component is starting or has been started.
+     */
+    public boolean isRunning();
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return true if the component has been started.
+     * @see #start()
+     * @see #isStarting()
+     */
+    public boolean isStarted();
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return true if the component is starting.
+     * @see #isStarted()
+     */
+    public boolean isStarting();
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return true if the component is stopping.
+     * @see #isStopped()
+     */
+    public boolean isStopping();
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return true if the component has been stopped.
+     * @see #stop()
+     * @see #isStopping()
+     */
+    public boolean isStopped();
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return true if the component has failed to start or has failed to stop.
+     */
+    public boolean isFailed();
+    
+    /* ------------------------------------------------------------ */
+    public void addLifeCycleListener(LifeCycle.Listener listener);
+
+    /* ------------------------------------------------------------ */
+    public void removeLifeCycleListener(LifeCycle.Listener listener);
+    
+
+    /* ------------------------------------------------------------ */
+    /** Listener.
+     * A listener for Lifecycle events.
+     */
+    public interface Listener extends EventListener
+    {
+        public void lifeCycleStarting(LifeCycle event);
+        public void lifeCycleStarted(LifeCycle event);
+        public void lifeCycleFailure(LifeCycle event,Throwable cause);
+        public void lifeCycleStopping(LifeCycle event);
+        public void lifeCycleStopped(LifeCycle event);
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/util/component/package-info.java b/lib/jetty/org/eclipse/jetty/util/component/package-info.java
new file mode 100644 (file)
index 0000000..2ae3d19
--- /dev/null
@@ -0,0 +1,23 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+/**
+ * Jetty Util : Jetty Lifecycle Management
+ */
+package org.eclipse.jetty.util.component;
+
diff --git a/lib/jetty/org/eclipse/jetty/util/log/AbstractLogger.java b/lib/jetty/org/eclipse/jetty/util/log/AbstractLogger.java
new file mode 100644 (file)
index 0000000..6645376
--- /dev/null
@@ -0,0 +1,84 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util.log;
+
+
+/* ------------------------------------------------------------ */
+/** Abstract Logger.
+ * Manages the atomic registration of the logger by name.
+ */
+public abstract class AbstractLogger implements Logger
+{
+    @Override
+    public final Logger getLogger(String name)
+    {
+        if (isBlank(name))
+            return this;
+
+        final String basename = getName();
+        final String fullname = (isBlank(basename) || Log.getRootLogger()==this)?name:(basename + "." + name);
+        
+        Logger logger = Log.getLoggers().get(fullname);
+        if (logger == null)
+        {
+            Logger newlog = newLogger(fullname);
+            
+            logger = Log.getMutableLoggers().putIfAbsent(fullname,newlog);
+            if (logger == null)
+                logger=newlog;
+        }
+
+        return logger;
+    }
+    
+
+    protected abstract Logger newLogger(String fullname);
+
+    /**
+     * A more robust form of name blank test. Will return true for null names, and names that have only whitespace
+     *
+     * @param name
+     *            the name to test
+     * @return true for null or blank name, false if any non-whitespace character is found.
+     */
+    private static boolean isBlank(String name)
+    {
+        if (name == null)
+        {
+            return true;
+        }
+        int size = name.length();
+        char c;
+        for (int i = 0; i < size; i++)
+        {
+            c = name.charAt(i);
+            if (!Character.isWhitespace(c))
+            {
+                return false;
+            }
+        }
+        return true;
+    }
+    
+    public void debug(String msg, long arg)
+    {
+        if (isDebugEnabled())
+            debug(msg,new Long(arg));
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/util/log/JavaUtilLog.java b/lib/jetty/org/eclipse/jetty/util/log/JavaUtilLog.java
new file mode 100644 (file)
index 0000000..094d978
--- /dev/null
@@ -0,0 +1,172 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util.log;
+
+import java.util.logging.Level;
+
+/**
+ * <p>
+ * Implementation of Jetty {@link Logger} based on {@link java.util.logging.Logger}.
+ * </p>
+ *
+ * <p>
+ * You can also set the logger level using <a href="http://java.sun.com/j2se/1.5.0/docs/guide/logging/overview.html">
+ * standard java.util.logging configuration</a>.
+ * </p>
+ */
+public class JavaUtilLog extends AbstractLogger
+{
+    private Level configuredLevel;
+    private java.util.logging.Logger _logger;
+
+    public JavaUtilLog()
+    {
+        this("org.eclipse.jetty.util.log");
+    }
+
+    public JavaUtilLog(String name)
+    {
+        _logger = java.util.logging.Logger.getLogger(name);
+        if (Boolean.parseBoolean(Log.__props.getProperty("org.eclipse.jetty.util.log.DEBUG", "false")))
+        {
+            _logger.setLevel(Level.FINE);
+        }
+        configuredLevel = _logger.getLevel();
+    }
+
+    public String getName()
+    {
+        return _logger.getName();
+    }
+
+    public void warn(String msg, Object... args)
+    {
+        if (_logger.isLoggable(Level.WARNING))
+            _logger.log(Level.WARNING,format(msg,args));
+    }
+
+    public void warn(Throwable thrown)
+    {
+        warn("", thrown);
+    }
+
+    public void warn(String msg, Throwable thrown)
+    {
+        _logger.log(Level.WARNING, msg, thrown);
+    }
+
+    public void info(String msg, Object... args)
+    {
+        if (_logger.isLoggable(Level.INFO))
+            _logger.log(Level.INFO, format(msg, args));
+    }
+
+    public void info(Throwable thrown)
+    {
+        info("", thrown);
+    }
+
+    public void info(String msg, Throwable thrown)
+    {
+        _logger.log(Level.INFO, msg, thrown);
+    }
+
+    public boolean isDebugEnabled()
+    {
+        return _logger.isLoggable(Level.FINE);
+    }
+
+    public void setDebugEnabled(boolean enabled)
+    {
+        if (enabled)
+        {
+            configuredLevel = _logger.getLevel();
+            _logger.setLevel(Level.FINE);
+        }
+        else
+        {
+            _logger.setLevel(configuredLevel);
+        }
+    }
+
+    public void debug(String msg, Object... args)
+    {
+        if (_logger.isLoggable(Level.FINE))
+            _logger.log(Level.FINE,format(msg, args));
+    }
+
+    public void debug(String msg, long arg)
+    {
+        if (_logger.isLoggable(Level.FINE))
+            _logger.log(Level.FINE,format(msg, arg));
+    }
+
+    public void debug(Throwable thrown)
+    {
+        debug("", thrown);
+    }
+
+    public void debug(String msg, Throwable thrown)
+    {
+        _logger.log(Level.FINE, msg, thrown);
+    }
+
+    /**
+     * Create a Child Logger of this Logger.
+     */
+    protected Logger newLogger(String fullname)
+    {
+        return new JavaUtilLog(fullname);
+    }
+
+    public void ignore(Throwable ignored)
+    {
+        if (Log.isIgnored())
+        {
+            warn(Log.IGNORED, ignored);
+        }
+    }
+
+    private String format(String msg, Object... args)
+    {
+        msg = String.valueOf(msg); // Avoids NPE
+        String braces = "{}";
+        StringBuilder builder = new StringBuilder();
+        int start = 0;
+        for (Object arg : args)
+        {
+            int bracesIndex = msg.indexOf(braces, start);
+            if (bracesIndex < 0)
+            {
+                builder.append(msg.substring(start));
+                builder.append(" ");
+                builder.append(arg);
+                start = msg.length();
+            }
+            else
+            {
+                builder.append(msg.substring(start, bracesIndex));
+                builder.append(String.valueOf(arg));
+                start = bracesIndex + braces.length();
+            }
+        }
+        builder.append(msg.substring(start));
+        return builder.toString();
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/util/log/Log.java b/lib/jetty/org/eclipse/jetty/util/log/Log.java
new file mode 100644 (file)
index 0000000..91b61f9
--- /dev/null
@@ -0,0 +1,317 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util.log;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.management.ManagementFactory;
+import java.lang.reflect.Method;
+import java.net.URL;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Properties;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+import org.eclipse.jetty.util.Loader;
+import org.eclipse.jetty.util.annotation.ManagedAttribute;
+
+/**
+ * Logging.
+ * This class provides a static logging interface.  If an instance of the
+ * org.slf4j.Logger class is found on the classpath, the static log methods
+ * are directed to a slf4j logger for "org.eclipse.log".   Otherwise the logs
+ * are directed to stderr.
+ * <p>
+ * The "org.eclipse.jetty.util.log.class" system property can be used
+ * to select a specific logging implementation.
+ * <p>
+ * If the system property org.eclipse.jetty.util.log.IGNORED is set,
+ * then ignored exceptions are logged in detail.
+ *
+ * @see StdErrLog
+ * @see Slf4jLog
+ */
+public class Log
+{
+    public final static String EXCEPTION= "EXCEPTION ";
+    public final static String IGNORED= "IGNORED ";
+
+    /**
+     * Logging Configuration Properties
+     */
+    protected static final Properties __props;
+    /**
+     * The {@link Logger} implementation class name
+     */
+    public static String __logClass;
+    /**
+     * Legacy flag indicating if {@link Logger#ignore(Throwable)} methods produce any output in the {@link Logger}s
+     */
+    public static boolean __ignored;
+
+    /**
+     * Hold loggers only.
+     */
+    private final static ConcurrentMap<String, Logger> __loggers = new ConcurrentHashMap<>();
+
+
+    static
+    {
+        /* Instantiate a default configuration properties (empty)
+         */
+        __props = new Properties();
+
+        AccessController.doPrivileged(new PrivilegedAction<Object>()
+        {
+            public Object run()
+            {
+                /* First see if the jetty-logging.properties object exists in the classpath.
+                 * This is an optional feature used by embedded mode use, and test cases to allow for early
+                 * configuration of the Log class in situations where access to the System.properties are
+                 * either too late or just impossible.
+                 */
+                loadProperties("jetty-logging.properties",__props);
+
+                /*
+                 * Next see if an OS specific jetty-logging.properties object exists in the classpath. 
+                 * This really for setting up test specific logging behavior based on OS.
+                 */
+                String osName = System.getProperty("os.name");
+                // NOTE: cannot use jetty-util's StringUtil as that initializes logging itself.
+                if (osName != null && osName.length() > 0)
+                {
+                    osName = osName.toLowerCase(Locale.ENGLISH).replace(' ','-');
+                    loadProperties("jetty-logging-" + osName + ".properties",__props);
+                }
+
+                /* Now load the System.properties as-is into the __props, these values will override
+                 * any key conflicts in __props.
+                 */
+                @SuppressWarnings("unchecked")
+                Enumeration<String> systemKeyEnum = (Enumeration<String>)System.getProperties().propertyNames();
+                while (systemKeyEnum.hasMoreElements())
+                {
+                    String key = systemKeyEnum.nextElement();
+                    String val = System.getProperty(key);
+                    // protect against application code insertion of non-String values (returned as null)
+                    if (val != null)
+                    {
+                        __props.setProperty(key,val);
+                    }
+                }
+
+                /* Now use the configuration properties to configure the Log statics
+                 */
+                __logClass = __props.getProperty("org.eclipse.jetty.util.log.class","org.eclipse.jetty.util.log.Slf4jLog");
+                __ignored = Boolean.parseBoolean(__props.getProperty("org.eclipse.jetty.util.log.IGNORED","false"));
+                return null;
+            }
+        });
+    }
+    
+    private static void loadProperties(String resourceName, Properties props)
+    {
+        URL testProps = Loader.getResource(Log.class,resourceName);
+        if (testProps != null)
+        {
+            try (InputStream in = testProps.openStream())
+            {
+                Properties p = new Properties();
+                p.load(in);
+                for (Object key : p.keySet())
+                {
+                    Object value = p.get(key);
+                    if (value != null)
+                    {
+                        props.put(key,value);
+                    }
+                }
+            }
+            catch (IOException e)
+            {
+                System.err.println("[WARN] Error loading logging config: " + testProps);
+                e.printStackTrace(System.err);
+            }
+        }
+    }
+
+    private static Logger LOG;
+    private static boolean __initialized=false;
+
+    public static void initialized()
+    {   
+        synchronized (Log.class)
+        {
+            if (__initialized)
+                return;
+            __initialized = true;
+
+            final long uptime=ManagementFactory.getRuntimeMXBean().getUptime();
+
+            try
+            {
+                Class<?> log_class = Loader.loadClass(Log.class, __logClass);
+                if (LOG == null || !LOG.getClass().equals(log_class))
+                {
+                    LOG = (Logger)log_class.newInstance();
+                    LOG.debug("Logging to {} via {}", LOG, log_class.getName());
+                }
+            }
+            catch(Throwable e)
+            {
+                // Unable to load specified Logger implementation, default to standard logging.
+                initStandardLogging(e);
+            }
+
+            if (LOG!=null)
+                LOG.info(String.format("Logging initialized @%dms",uptime));
+        }
+    }
+
+    private static void initStandardLogging(Throwable e)
+    {
+        Class<?> log_class;
+        if(e != null && __ignored)
+        {
+            e.printStackTrace(System.err);
+        }
+
+        if (LOG == null)
+        {
+            log_class = StdErrLog.class;
+            LOG = new StdErrLog();
+            LOG.debug("Logging to {} via {}", LOG, log_class.getName());
+        }
+    }
+    
+    public static Logger getLog()
+    {
+        initialized();
+        return LOG;
+    }
+
+    public static void setLog(Logger log)
+    {
+        Log.LOG = log;
+    }
+
+    /**
+     * Get the root logger.
+     * @return the root logger
+     */
+    public static Logger getRootLogger() {
+        initialized();
+        return LOG;
+    }
+
+    static boolean isIgnored()
+    {
+        return __ignored;
+    }
+
+    /**
+     * Set Log to parent Logger.
+     * <p>
+     * If there is a different Log class available from a parent classloader,
+     * call {@link #getLogger(String)} on it and construct a {@link LoggerLog} instance
+     * as this Log's Logger, so that logging is delegated to the parent Log.
+     * <p>
+     * This should be used if a webapp is using Log, but wishes the logging to be
+     * directed to the containers log.
+     * <p>
+     * If there is not parent Log, then this call is equivalent to<pre>
+     *   Log.setLog(Log.getLogger(name));
+     * </pre>
+     * @param name Logger name
+     */
+    public static void setLogToParent(String name)
+    {
+        ClassLoader loader = Log.class.getClassLoader();
+        if (loader!=null && loader.getParent()!=null)
+        {
+            try
+            {
+                Class<?> uberlog = loader.getParent().loadClass("org.eclipse.jetty.util.log.Log");
+                Method getLogger = uberlog.getMethod("getLogger", new Class[]{String.class});
+                Object logger = getLogger.invoke(null,name);
+                setLog(new LoggerLog(logger));
+            }
+            catch (Exception e)
+            {
+                e.printStackTrace();
+            }
+        }
+        else
+        {
+            setLog(getLogger(name));
+        }
+    }
+
+    /**
+     * Obtain a named Logger based on the fully qualified class name.
+     *
+     * @param clazz
+     *            the class to base the Logger name off of
+     * @return the Logger with the given name
+     */
+    public static Logger getLogger(Class<?> clazz)
+    {
+        return getLogger(clazz.getName());
+    }
+
+    /**
+     * Obtain a named Logger or the default Logger if null is passed.
+     * @param name the Logger name
+     * @return the Logger with the given name
+     */
+    public static Logger getLogger(String name)
+    {
+        initialized();
+
+        if(name==null)
+            return LOG;
+
+        Logger logger = __loggers.get(name);
+        if(logger==null)
+            logger = LOG.getLogger(name);
+
+        return logger;
+    }
+
+    static ConcurrentMap<String, Logger> getMutableLoggers()
+    {
+        return __loggers;
+    }
+    
+    /**
+     * Get a map of all configured {@link Logger} instances.
+     *
+     * @return a map of all configured {@link Logger} instances
+     */
+    @ManagedAttribute("list of all instantiated loggers")
+    public static Map<String, Logger> getLoggers()
+    {
+        return Collections.unmodifiableMap(__loggers);
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/util/log/Logger.java b/lib/jetty/org/eclipse/jetty/util/log/Logger.java
new file mode 100644 (file)
index 0000000..ac6e086
--- /dev/null
@@ -0,0 +1,122 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util.log;
+
+/**
+ * A simple logging facade that is intended simply to capture the style of logging as used by Jetty.
+ */
+public interface Logger
+{
+    /**
+     * @return the name of this logger
+     */
+    public String getName();
+
+    /**
+     * Formats and logs at warn level.
+     * @param msg the formatting string
+     * @param args the optional arguments
+     */
+    public void warn(String msg, Object... args);
+
+    /**
+     * Logs the given Throwable information at warn level
+     * @param thrown the Throwable to log
+     */
+    public void warn(Throwable thrown);
+
+    /**
+     * Logs the given message at warn level, with Throwable information.
+     * @param msg the message to log
+     * @param thrown the Throwable to log
+     */
+    public void warn(String msg, Throwable thrown);
+
+    /**
+     * Formats and logs at info level.
+     * @param msg the formatting string
+     * @param args the optional arguments
+     */
+    public void info(String msg, Object... args);
+
+    /**
+     * Logs the given Throwable information at info level
+     * @param thrown the Throwable to log
+     */
+    public void info(Throwable thrown);
+
+    /**
+     * Logs the given message at info level, with Throwable information.
+     * @param msg the message to log
+     * @param thrown the Throwable to log
+     */
+    public void info(String msg, Throwable thrown);
+
+    /**
+     * @return whether the debug level is enabled
+     */
+    public boolean isDebugEnabled();
+
+    /**
+     * Mutator used to turn debug on programmatically.
+     * @param enabled whether to enable the debug level
+     */
+    public void setDebugEnabled(boolean enabled);
+
+    /**
+     * Formats and logs at debug level.
+     * @param msg the formatting string
+     * @param args the optional arguments
+     */
+    public void debug(String msg, Object... args);
+    
+
+    /**
+     * Formats and logs at debug level.
+     * avoids autoboxing of integers
+     * @param msg the formatting string
+     * @param value long value
+     */
+    public void debug(String msg, long value);
+
+    /**
+     * Logs the given Throwable information at debug level
+     * @param thrown the Throwable to log
+     */
+    public void debug(Throwable thrown);
+
+    /**
+     * Logs the given message at debug level, with Throwable information.
+     * @param msg the message to log
+     * @param thrown the Throwable to log
+     */
+    public void debug(String msg, Throwable thrown);
+
+    /**
+     * @param name the name of the logger
+     * @return a logger with the given name
+     */
+    public Logger getLogger(String name);
+    
+    /**
+     * Ignore an exception.
+     * <p>This should be used rather than an empty catch block.
+     */
+    public void ignore(Throwable ignored); 
+}
diff --git a/lib/jetty/org/eclipse/jetty/util/log/LoggerLog.java b/lib/jetty/org/eclipse/jetty/util/log/LoggerLog.java
new file mode 100644 (file)
index 0000000..1026cb0
--- /dev/null
@@ -0,0 +1,229 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util.log;
+
+import java.lang.reflect.Method;
+
+/**
+ *
+ */
+public class LoggerLog extends AbstractLogger
+{
+    private final Object _logger;
+    private final Method _debugMT;
+    private final Method _debugMAA;
+    private final Method _infoMT;
+    private final Method _infoMAA;
+    private final Method _warnMT;
+    private final Method _warnMAA;
+    private final Method _setDebugEnabledE;
+    private final Method _getLoggerN;
+    private final Method _getName;
+    private volatile boolean _debug;
+
+    public LoggerLog(Object logger)
+    {
+        try
+        {
+            _logger = logger;
+            Class<?> lc = logger.getClass();
+            _debugMT = lc.getMethod("debug", new Class[]{String.class, Throwable.class});
+            _debugMAA = lc.getMethod("debug", new Class[]{String.class, Object[].class});
+            _infoMT = lc.getMethod("info", new Class[]{String.class, Throwable.class});
+            _infoMAA = lc.getMethod("info", new Class[]{String.class, Object[].class});
+            _warnMT = lc.getMethod("warn", new Class[]{String.class, Throwable.class});
+            _warnMAA = lc.getMethod("warn", new Class[]{String.class, Object[].class});
+            Method _isDebugEnabled = lc.getMethod("isDebugEnabled");
+            _setDebugEnabledE = lc.getMethod("setDebugEnabled", new Class[]{Boolean.TYPE});
+            _getLoggerN = lc.getMethod("getLogger", new Class[]{String.class});
+            _getName = lc.getMethod("getName");
+
+            _debug = (Boolean)_isDebugEnabled.invoke(_logger);
+        }
+        catch(Exception x)
+        {
+            throw new IllegalStateException(x);
+        }
+    }
+
+    public String getName()
+    {
+        try
+        {
+            return (String)_getName.invoke(_logger);
+        }
+        catch (Exception e)
+        {
+            e.printStackTrace();
+            return null;
+        }
+    }
+
+    public void warn(String msg, Object... args)
+    {
+        try
+        {
+            _warnMAA.invoke(_logger, args);
+        }
+        catch (Exception e)
+        {
+            e.printStackTrace();
+        }
+    }
+
+    public void warn(Throwable thrown)
+    {
+        warn("", thrown);
+    }
+
+    public void warn(String msg, Throwable thrown)
+    {
+        try
+        {
+            _warnMT.invoke(_logger, msg, thrown);
+        }
+        catch (Exception e)
+        {
+            e.printStackTrace();
+        }
+    }
+
+    public void info(String msg, Object... args)
+    {
+        try
+        {
+            _infoMAA.invoke(_logger, args);
+        }
+        catch (Exception e)
+        {
+            e.printStackTrace();
+        }
+    }
+
+    public void info(Throwable thrown)
+    {
+        info("", thrown);
+    }
+
+    public void info(String msg, Throwable thrown)
+    {
+        try
+        {
+            _infoMT.invoke(_logger, msg, thrown);
+        }
+        catch (Exception e)
+        {
+            e.printStackTrace();
+        }
+    }
+
+    public boolean isDebugEnabled()
+    {
+        return _debug;
+    }
+
+    public void setDebugEnabled(boolean enabled)
+    {
+        try
+        {
+            _setDebugEnabledE.invoke(_logger, enabled);
+            _debug = enabled;
+        }
+        catch (Exception e)
+        {
+            e.printStackTrace();
+        }
+    }
+
+    
+    public void debug(String msg, Object... args)
+    {
+        if (!_debug)
+            return;
+
+        try
+        {
+            _debugMAA.invoke(_logger, args);
+        }
+        catch (Exception e)
+        {
+            e.printStackTrace();
+        }
+    }
+
+    public void debug(Throwable thrown)
+    {
+        debug("", thrown);
+    }
+
+    public void debug(String msg, Throwable th)
+    {
+        if (!_debug)
+            return;
+
+        try
+        {
+            _debugMT.invoke(_logger, msg, th);
+        }
+        catch (Exception e)
+        {
+            e.printStackTrace();
+        }
+    }
+
+    public void debug(String msg, long value)
+    {
+        if (!_debug)
+            return;
+
+        try
+        {
+            _debugMAA.invoke(_logger, new Object[]{new Long(value)});
+        }
+        catch (Exception e)
+        {
+            e.printStackTrace();
+        }
+    }
+    
+    public void ignore(Throwable ignored)
+    {
+        if (Log.isIgnored())
+        {
+            warn(Log.IGNORED, ignored);
+        }
+    }
+
+    /**
+     * Create a Child Logger of this Logger.
+     */
+    protected Logger newLogger(String fullname)
+    {
+        try
+        {
+            Object logger=_getLoggerN.invoke(_logger, fullname);
+            return new LoggerLog(logger);
+        }
+        catch (Exception e)
+        {
+            e.printStackTrace();
+            return this;
+        }
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/util/log/StacklessLogging.java b/lib/jetty/org/eclipse/jetty/util/log/StacklessLogging.java
new file mode 100644 (file)
index 0000000..fd6fb32
--- /dev/null
@@ -0,0 +1,69 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util.log;
+
+/**
+ * A try-with-resources compatible layer for {@link StdErrLog#setHideStacks(boolean) hiding stacktraces} within the scope of the <code>try</code> block when
+ * logging with {@link StdErrLog} implementation.
+ * <p>
+ * Use of other logging implementation cause no effect when using this class
+ * <p>
+ * Example:
+ * 
+ * <pre>
+ * try (StacklessLogging scope = new StacklessLogging(EventDriver.class,Noisy.class))
+ * {
+ *     doActionThatCausesStackTraces();
+ * }
+ * </pre>
+ */
+public class StacklessLogging implements AutoCloseable
+{
+    private final Class<?> clazzes[];
+
+    public StacklessLogging(Class<?>... classesToSquelch)
+    {
+        this.clazzes = classesToSquelch;
+        hideStacks(true);
+    }
+
+    @Override
+    public void close() throws Exception
+    {
+        hideStacks(false);
+    }
+
+    private void hideStacks(boolean hide)
+    {
+        for (Class<?> clazz : clazzes)
+        {
+            Logger log = Log.getLogger(clazz);
+            if (log == null)
+            {
+                // not interested in classes without loggers
+                continue;
+            }
+            if (log instanceof StdErrLog)
+            {
+                // only operate on loggers that are of type StdErrLog
+                ((StdErrLog)log).setHideStacks(hide);
+            }
+        }
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/util/log/StdErrLog.java b/lib/jetty/org/eclipse/jetty/util/log/StdErrLog.java
new file mode 100644 (file)
index 0000000..fda1c72
--- /dev/null
@@ -0,0 +1,779 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util.log;
+
+import java.io.PrintStream;
+import java.security.AccessControlException;
+import java.util.Properties;
+
+import org.eclipse.jetty.util.DateCache;
+import org.eclipse.jetty.util.annotation.ManagedAttribute;
+import org.eclipse.jetty.util.annotation.ManagedObject;
+
+/**
+ * StdErr Logging implementation. 
+ * <p>
+ * A Jetty {@link Logger} that sends all logs to STDERR ({@link System#err}) with basic formatting.
+ * <p>
+ * Supports named loggers, and properties based configuration. 
+ * <p>
+ * Configuration Properties:
+ * <dl>
+ *   <dt>${name|hierarchy}.LEVEL=(ALL|DEBUG|INFO|WARN|OFF)</dt>
+ *   <dd>
+ *   Sets the level that the Logger should log at.<br/>
+ *   Names can be a package name, or a fully qualified class name.<br/>
+ *   Default: INFO<br/>
+ *   <br/>
+ *   Examples:
+ *   <dl>
+ *   <dt>org.eclipse.jetty.LEVEL=WARN</dt>
+ *   <dd>indicates that all of the jetty specific classes, in any package that 
+ *   starts with <code>org.eclipse.jetty</code> should log at level WARN.</dd>
+ *   <dt>org.eclipse.jetty.io.ChannelEndPoint.LEVEL=ALL</dt>
+ *   <dd>indicates that the specific class, ChannelEndPoint, should log all
+ *   logging events that it can generate, including DEBUG, INFO, WARN (and even special
+ *   internally ignored exception cases).</dd>
+ *   </dl>  
+ *   </dd>
+ *   
+ *   <dt>${name}.SOURCE=(true|false)</dt>
+ *   <dd>
+ *   Logger specific, attempt to print the java source file name and line number
+ *   where the logging event originated from.<br/>
+ *   Name must be a fully qualified class name (package name hierarchy is not supported
+ *   by this configurable)<br/>
+ *   Warning: this is a slow operation and will have an impact on performance!<br/>
+ *   Default: false
+ *   </dd>
+ *   
+ *   <dt>${name}.STACKS=(true|false)</dt>
+ *   <dd>
+ *   Logger specific, control the display of stacktraces.<br/>
+ *   Name must be a fully qualified class name (package name hierarchy is not supported
+ *   by this configurable)<br/>
+ *   Default: true
+ *   </dd>
+ *   
+ *   <dt>org.eclipse.jetty.util.log.stderr.SOURCE=(true|false)</dt>
+ *   <dd>Special Global Configuration, attempt to print the java source file name and line number
+ *   where the logging event originated from.<br/>
+ *   Default: false
+ *   </dd>
+ *   
+ *   <dt>org.eclipse.jetty.util.log.stderr.LONG=(true|false)</dt>
+ *   <dd>Special Global Configuration, when true, output logging events to STDERR using
+ *   long form, fully qualified class names.  when false, use abbreviated package names<br/>
+ *   Default: false
+ *   </dd>
+ *   <dt>org.eclipse.jetty.util.log.stderr.ESCAPE=(true|false)</dt>
+ *   <dd>Global Configuration, when true output logging events to STDERR are always
+ *   escaped so that control characters are replaced with '?";  '\r' with '<' and '\n' replaced '|'<br/>
+ *   Default: true
+ *   </dd>
+ * </dl>
+ */
+@ManagedObject("Jetty StdErr Logging Implementation")
+public class StdErrLog extends AbstractLogger
+{
+    private static final String EOL = System.getProperty("line.separator");
+    private static DateCache _dateCache;
+    private static final Properties __props = new Properties();
+
+    private final static boolean __source = Boolean.parseBoolean(Log.__props.getProperty("org.eclipse.jetty.util.log.SOURCE",
+            Log.__props.getProperty("org.eclipse.jetty.util.log.stderr.SOURCE","false")));
+    private final static boolean __long = Boolean.parseBoolean(Log.__props.getProperty("org.eclipse.jetty.util.log.stderr.LONG","false"));
+    private final static boolean __escape = Boolean.parseBoolean(Log.__props.getProperty("org.eclipse.jetty.util.log.stderr.ESCAPE","true"));
+
+    static
+    {
+        __props.putAll(Log.__props);
+
+        String deprecatedProperties[] =
+        { "DEBUG", "org.eclipse.jetty.util.log.DEBUG", "org.eclipse.jetty.util.log.stderr.DEBUG" };
+
+        // Toss a message to users about deprecated system properties
+        for (String deprecatedProp : deprecatedProperties)
+        {
+            if (System.getProperty(deprecatedProp) != null)
+            {
+                System.err.printf("System Property [%s] has been deprecated! (Use org.eclipse.jetty.LEVEL=DEBUG instead)%n",deprecatedProp);
+            }
+        }
+
+        try
+        {
+            _dateCache = new DateCache("yyyy-MM-dd HH:mm:ss");
+        }
+        catch (Exception x)
+        {
+            x.printStackTrace(System.err);
+        }
+    }
+
+    public static final int LEVEL_ALL = 0;
+    public static final int LEVEL_DEBUG = 1;
+    public static final int LEVEL_INFO = 2;
+    public static final int LEVEL_WARN = 3;
+    public static final int LEVEL_OFF = 10;
+
+    private int _level = LEVEL_INFO;
+    // Level that this Logger was configured as (remembered in special case of .setDebugEnabled())
+    private int _configuredLevel;
+    private PrintStream _stderr = null;
+    private boolean _source = __source;
+    // Print the long form names, otherwise use abbreviated
+    private boolean _printLongNames = __long;
+    // The full log name, as provided by the system.
+    private final String _name;
+    // The abbreviated log name (used by default, unless _long is specified)
+    private final String _abbrevname;
+    private boolean _hideStacks = false;
+
+    /**
+     * Obtain a StdErrLog reference for the specified class, a convenience method used most often during testing to allow for control over a specific logger.
+     * <p>
+     * Must be actively using StdErrLog as the Logger implementation.
+     * 
+     * @param clazz
+     *            the Class reference for the logger to use.
+     * @return the StdErrLog logger
+     * @throws RuntimeException
+     *             if StdErrLog is not the active Logger implementation.
+     */
+    public static StdErrLog getLogger(Class<?> clazz)
+    {
+        Logger log = Log.getLogger(clazz);
+        if (log instanceof StdErrLog)
+        {
+            return (StdErrLog)log;
+        }
+        throw new RuntimeException("Logger for " + clazz + " is not of type StdErrLog");
+    }
+
+    /**
+     * Construct an anonymous StdErrLog (no name).
+     * <p>
+     * NOTE: Discouraged usage!
+     */
+    public StdErrLog()
+    {
+        this(null);
+    }
+
+    /**
+     * Construct a named StdErrLog using the {@link Log} defined properties
+     * 
+     * @param name
+     *            the name of the logger
+     */
+    public StdErrLog(String name)
+    {
+        this(name,__props);
+    }
+
+    /**
+     * Construct a named Logger using the provided properties to configure logger.
+     * 
+     * @param name
+     *            the name of the logger
+     * @param props
+     *            the configuration properties
+     */
+    public StdErrLog(String name, Properties props)
+    {
+        if (props!=null && props!=__props)
+            __props.putAll(props);
+        this._name = name == null?"":name;
+        this._abbrevname = condensePackageString(this._name);
+        this._level = getLoggingLevel(props,this._name);
+        this._configuredLevel = this._level;
+
+        try
+        {
+            String source = getLoggingProperty(props,_name,"SOURCE");
+            _source = source==null?__source:Boolean.parseBoolean(source);
+        }
+        catch (AccessControlException ace)
+        {
+            _source = __source;
+        }
+
+        try
+        {
+            // allow stacktrace display to be controlled by properties as well
+            String stacks = getLoggingProperty(props,_name,"STACKS");
+            _hideStacks = stacks==null?false:!Boolean.parseBoolean(stacks);
+        }
+        catch (AccessControlException ignore)
+        {
+            /* ignore */
+        }        
+    }
+
+    /**
+     * Get the Logging Level for the provided log name. Using the FQCN first, then each package segment from longest to
+     * shortest.
+     *
+     * @param props
+     *            the properties to check
+     * @param name
+     *            the name to get log for
+     * @return the logging level
+     */
+    public static int getLoggingLevel(Properties props, final String name)
+    {
+        // Calculate the level this named logger should operate under.
+        // Checking with FQCN first, then each package segment from longest to shortest.
+        String nameSegment = name;
+
+        while ((nameSegment != null) && (nameSegment.length() > 0))
+        {
+            String levelStr = props.getProperty(nameSegment + ".LEVEL");
+            // System.err.printf("[StdErrLog.CONFIG] Checking for property [%s.LEVEL] = %s%n",nameSegment,levelStr);
+            int level = getLevelId(nameSegment + ".LEVEL",levelStr);
+            if (level != (-1))
+            {
+                return level;
+            }
+
+            // Trim and try again.
+            int idx = nameSegment.lastIndexOf('.');
+            if (idx >= 0)
+            {
+                nameSegment = nameSegment.substring(0,idx);
+            }
+            else
+            {
+                nameSegment = null;
+            }
+        }
+
+        // Default Logging Level
+        return getLevelId("log.LEVEL",props.getProperty("log.LEVEL","INFO"));
+    }
+    
+    public static String getLoggingProperty(Properties props, String name, String property)
+    {
+        // Calculate the level this named logger should operate under.
+        // Checking with FQCN first, then each package segment from longest to shortest.
+        String nameSegment = name;
+
+        while ((nameSegment != null) && (nameSegment.length() > 0))
+        {
+            String s = props.getProperty(nameSegment+"."+property);
+            if (s!=null)
+                return s;
+
+            // Trim and try again.
+            int idx = nameSegment.lastIndexOf('.');
+            nameSegment = (idx >= 0)?nameSegment.substring(0,idx):null;
+        }
+
+        return null;
+    }
+
+    protected static int getLevelId(String levelSegment, String levelName)
+    {
+        if (levelName == null)
+        {
+            return -1;
+        }
+        String levelStr = levelName.trim();
+        if ("ALL".equalsIgnoreCase(levelStr))
+        {
+            return LEVEL_ALL;
+        }
+        else if ("DEBUG".equalsIgnoreCase(levelStr))
+        {
+            return LEVEL_DEBUG;
+        }
+        else if ("INFO".equalsIgnoreCase(levelStr))
+        {
+            return LEVEL_INFO;
+        }
+        else if ("WARN".equalsIgnoreCase(levelStr))
+        {
+            return LEVEL_WARN;
+        }
+        else if ("OFF".equalsIgnoreCase(levelStr))
+        {
+            return LEVEL_OFF;
+        }
+
+        System.err.println("Unknown StdErrLog level [" + levelSegment + "]=[" + levelStr + "], expecting only [ALL, DEBUG, INFO, WARN, OFF] as values.");
+        return -1;
+    }
+
+    /**
+     * Condenses a classname by stripping down the package name to just the first character of each package name
+     * segment.Configured
+     * <p>
+     *
+     * <pre>
+     * Examples:
+     * "org.eclipse.jetty.test.FooTest"           = "oejt.FooTest"
+     * "org.eclipse.jetty.server.logging.LogTest" = "orjsl.LogTest"
+     * </pre>
+     *
+     * @param classname
+     *            the fully qualified class name
+     * @return the condensed name
+     */
+    protected static String condensePackageString(String classname)
+    {
+        String parts[] = classname.split("\\.");
+        StringBuilder dense = new StringBuilder();
+        for (int i = 0; i < (parts.length - 1); i++)
+        {
+            dense.append(parts[i].charAt(0));
+        }
+        if (dense.length() > 0)
+        {
+            dense.append('.');
+        }
+        dense.append(parts[parts.length - 1]);
+        return dense.toString();
+    }
+
+    public String getName()
+    {
+        return _name;
+    }
+
+    public void setPrintLongNames(boolean printLongNames)
+    {
+        this._printLongNames = printLongNames;
+    }
+
+    public boolean isPrintLongNames()
+    {
+        return this._printLongNames;
+    }
+
+    public boolean isHideStacks()
+    {
+        return _hideStacks;
+    }
+
+    public void setHideStacks(boolean hideStacks)
+    {
+        _hideStacks = hideStacks;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Is the source of a log, logged
+     *
+     * @return true if the class, method, file and line number of a log is logged.
+     */
+    public boolean isSource()
+    {
+        return _source;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Set if a log source is logged.
+     *
+     * @param source
+     *            true if the class, method, file and line number of a log is logged.
+     */
+    public void setSource(boolean source)
+    {
+        _source = source;
+    }
+
+    public void warn(String msg, Object... args)
+    {
+        if (_level <= LEVEL_WARN)
+        {
+            StringBuilder buffer = new StringBuilder(64);
+            format(buffer,":WARN:",msg,args);
+            (_stderr==null?System.err:_stderr).println(buffer);
+        }
+    }
+
+    public void warn(Throwable thrown)
+    {
+        warn("",thrown);
+    }
+
+    public void warn(String msg, Throwable thrown)
+    {
+        if (_level <= LEVEL_WARN)
+        {
+            StringBuilder buffer = new StringBuilder(64);
+            format(buffer,":WARN:",msg,thrown);
+            (_stderr==null?System.err:_stderr).println(buffer);
+        }
+    }
+
+    public void info(String msg, Object... args)
+    {
+        if (_level <= LEVEL_INFO)
+        {
+            StringBuilder buffer = new StringBuilder(64);
+            format(buffer,":INFO:",msg,args);
+            (_stderr==null?System.err:_stderr).println(buffer);
+        }
+    }
+
+    public void info(Throwable thrown)
+    {
+        info("",thrown);
+    }
+
+    public void info(String msg, Throwable thrown)
+    {
+        if (_level <= LEVEL_INFO)
+        {
+            StringBuilder buffer = new StringBuilder(64);
+            format(buffer,":INFO:",msg,thrown);
+            (_stderr==null?System.err:_stderr).println(buffer);
+        }
+    }
+
+    @ManagedAttribute("is debug enabled for root logger Log.LOG")
+    public boolean isDebugEnabled()
+    {
+        return (_level <= LEVEL_DEBUG);
+    }
+
+    /**
+     * Legacy interface where a programmatic configuration of the logger level
+     * is done as a wholesale approach.
+     */
+    @Override
+    public void setDebugEnabled(boolean enabled)
+    {
+        if (enabled)
+        {
+            this._level = LEVEL_DEBUG;
+
+            for (Logger log : Log.getLoggers().values())
+            {                
+                if (log.getName().startsWith(getName()) && log instanceof StdErrLog)
+                    ((StdErrLog)log).setLevel(LEVEL_DEBUG);
+            }
+        }
+        else
+        {
+            this._level = this._configuredLevel;
+
+            for (Logger log : Log.getLoggers().values())
+            {
+                if (log.getName().startsWith(getName()) && log instanceof StdErrLog)
+                    ((StdErrLog)log).setLevel(((StdErrLog)log)._configuredLevel);
+            }
+        }
+    }
+
+    public int getLevel()
+    {
+        return _level;
+    }
+
+    /**
+     * Set the level for this logger.
+     * <p>
+     * Available values ({@link StdErrLog#LEVEL_ALL}, {@link StdErrLog#LEVEL_DEBUG}, {@link StdErrLog#LEVEL_INFO},
+     * {@link StdErrLog#LEVEL_WARN})
+     *
+     * @param level
+     *            the level to set the logger to
+     */
+    public void setLevel(int level)
+    {
+        this._level = level;
+    }
+
+    public void setStdErrStream(PrintStream stream)
+    {
+        this._stderr = stream==System.err?null:stream;
+    }
+
+    public void debug(String msg, Object... args)
+    {
+        if (_level <= LEVEL_DEBUG)
+        {
+            StringBuilder buffer = new StringBuilder(64);
+            format(buffer,":DBUG:",msg,args);
+            (_stderr==null?System.err:_stderr).println(buffer);
+        }
+    }
+
+    public void debug(String msg, long arg)
+    {
+        if (isDebugEnabled())
+        {
+            StringBuilder buffer = new StringBuilder(64);
+            format(buffer,":DBUG:",msg,arg);
+            (_stderr==null?System.err:_stderr).println(buffer);
+        }
+    }
+    
+    public void debug(Throwable thrown)
+    {
+        debug("",thrown);
+    }
+
+    public void debug(String msg, Throwable thrown)
+    {
+        if (_level <= LEVEL_DEBUG)
+        {
+            StringBuilder buffer = new StringBuilder(64);
+            format(buffer,":DBUG:",msg,thrown);
+            (_stderr==null?System.err:_stderr).println(buffer);
+        }
+    }
+
+    private void format(StringBuilder buffer, String level, String msg, Object... args)
+    {
+        long now = System.currentTimeMillis();
+        int ms=(int)(now%1000);
+        String d = _dateCache.formatNow(now);
+        tag(buffer,d,ms,level);
+        format(buffer,msg,args);
+    }
+
+    private void format(StringBuilder buffer, String level, String msg, Throwable thrown)
+    {
+        format(buffer,level,msg);
+        if (isHideStacks())
+        {
+            format(buffer,": "+String.valueOf(thrown));
+        }
+        else
+        {
+            format(buffer,thrown);
+        }
+    }
+
+    private void tag(StringBuilder buffer, String d, int ms, String tag)
+    {
+        buffer.setLength(0);
+        buffer.append(d);
+        if (ms > 99)
+        {
+            buffer.append('.');
+        }
+        else if (ms > 9)
+        {
+            buffer.append(".0");
+        }
+        else
+        {
+            buffer.append(".00");
+        }
+        buffer.append(ms).append(tag);
+        if (_printLongNames)
+        {
+            buffer.append(_name);
+        }
+        else
+        {
+            buffer.append(_abbrevname);
+        }
+        buffer.append(':');
+        buffer.append(Thread.currentThread().getName()).append(": ");
+        if (_source)
+        {
+            Throwable source = new Throwable();
+            StackTraceElement[] frames = source.getStackTrace();
+            for (int i = 0; i < frames.length; i++)
+            {
+                final StackTraceElement frame = frames[i];
+                String clazz = frame.getClassName();
+                if (clazz.equals(StdErrLog.class.getName()) || clazz.equals(Log.class.getName()))
+                {
+                    continue;
+                }
+                if (!_printLongNames && clazz.startsWith("org.eclipse.jetty."))
+                {
+                    buffer.append(condensePackageString(clazz));
+                }
+                else
+                {
+                    buffer.append(clazz);
+                }
+                buffer.append('#').append(frame.getMethodName());
+                if (frame.getFileName() != null)
+                {
+                    buffer.append('(').append(frame.getFileName()).append(':').append(frame.getLineNumber()).append(')');
+                }
+                buffer.append(':');
+                break;
+            }
+        }
+    }
+
+    private void format(StringBuilder builder, String msg, Object... args)
+    {
+        if (msg == null)
+        {
+            msg = "";
+            for (int i = 0; i < args.length; i++)
+            {
+                msg += "{} ";
+            }
+        }
+        String braces = "{}";
+        int start = 0;
+        for (Object arg : args)
+        {
+            int bracesIndex = msg.indexOf(braces,start);
+            if (bracesIndex < 0)
+            {
+                escape(builder,msg.substring(start));
+                builder.append(" ");
+                builder.append(arg);
+                start = msg.length();
+            }
+            else
+            {
+                escape(builder,msg.substring(start,bracesIndex));
+                builder.append(String.valueOf(arg));
+                start = bracesIndex + braces.length();
+            }
+        }
+        escape(builder,msg.substring(start));
+    }
+
+    private void escape(StringBuilder builder, String string)
+    {
+        if (__escape)
+        {
+            for (int i = 0; i < string.length(); ++i)
+            {
+                char c = string.charAt(i);
+                if (Character.isISOControl(c))
+                {
+                    if (c == '\n')
+                    {
+                        builder.append('|');
+                    }
+                    else if (c == '\r')
+                    {
+                        builder.append('<');
+                    }
+                    else
+                    {
+                        builder.append('?');
+                    }
+                }
+                else
+                {
+                    builder.append(c);
+                }
+            }
+        }
+        else
+            builder.append(string);
+    }
+
+    private void format(StringBuilder buffer, Throwable thrown)
+    {
+        if (thrown == null)
+        {
+            buffer.append("null");
+        }
+        else
+        {
+            buffer.append(EOL);
+            format(buffer,thrown.toString());
+            StackTraceElement[] elements = thrown.getStackTrace();
+            for (int i = 0; elements != null && i < elements.length; i++)
+            {
+                buffer.append(EOL).append("\tat ");
+                format(buffer,elements[i].toString());
+            }
+
+            Throwable cause = thrown.getCause();
+            if (cause != null && cause != thrown)
+            {
+                buffer.append(EOL).append("Caused by: ");
+                format(buffer,cause);
+            }
+        }
+    }
+
+
+    /**
+     * Create a Child Logger of this Logger.
+     */
+    @Override
+    protected Logger newLogger(String fullname)
+    {
+        StdErrLog logger = new StdErrLog(fullname);
+        // Preserve configuration for new loggers configuration
+        logger.setPrintLongNames(_printLongNames);
+        logger._stderr = this._stderr;
+
+        // Force the child to have any programmatic configuration
+        if (_level!=_configuredLevel)
+            logger._level=_level;
+
+        return logger;
+    }
+
+    @Override
+    public String toString()
+    {
+        StringBuilder s = new StringBuilder();
+        s.append("StdErrLog:");
+        s.append(_name);
+        s.append(":LEVEL=");
+        switch (_level)
+        {
+            case LEVEL_ALL:
+                s.append("ALL");
+                break;
+            case LEVEL_DEBUG:
+                s.append("DEBUG");
+                break;
+            case LEVEL_INFO:
+                s.append("INFO");
+                break;
+            case LEVEL_WARN:
+                s.append("WARN");
+                break;
+            default:
+                s.append("?");
+                break;
+        }
+        return s.toString();
+    }
+
+    public static void setProperties(Properties props)
+    {
+        __props.clear();
+        __props.putAll(props);
+    }
+
+    public void ignore(Throwable ignored)
+    {
+        if (_level <= LEVEL_ALL)
+        {
+            StringBuilder buffer = new StringBuilder(64);
+            format(buffer,":IGNORED:","",ignored);
+            (_stderr==null?System.err:_stderr).println(buffer);
+        }
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/util/log/package-info.java b/lib/jetty/org/eclipse/jetty/util/log/package-info.java
new file mode 100644 (file)
index 0000000..27166b6
--- /dev/null
@@ -0,0 +1,23 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+/**
+ * Jetty Util : Common Logging Integrations
+ */
+package org.eclipse.jetty.util.log;
+
diff --git a/lib/jetty/org/eclipse/jetty/util/package-info.java b/lib/jetty/org/eclipse/jetty/util/package-info.java
new file mode 100644 (file)
index 0000000..6545cbe
--- /dev/null
@@ -0,0 +1,23 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+/**
+ * Jetty Util : Common Utility Classes
+ */
+package org.eclipse.jetty.util;
+
diff --git a/lib/jetty/org/eclipse/jetty/util/preventers/AWTLeakPreventer.java b/lib/jetty/org/eclipse/jetty/util/preventers/AWTLeakPreventer.java
new file mode 100644 (file)
index 0000000..0f6caf9
--- /dev/null
@@ -0,0 +1,47 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util.preventers;
+
+import java.awt.Toolkit;
+
+/**
+ * AWTLeakPreventer
+ *
+ * See https://issues.jboss.org/browse/AS7-3733
+ * 
+ * The java.awt.Toolkit class has a static field that is the default toolkit. 
+ * Creating the default toolkit causes the creation of an EventQueue, which has a 
+ * classloader field initialized by the thread context class loader. 
+ *
+ */
+public class AWTLeakPreventer extends AbstractLeakPreventer
+{
+   
+    /* ------------------------------------------------------------ */
+    /** 
+     * @see org.eclipse.jetty.util.preventers.AbstractLeakPreventer#prevent(java.lang.ClassLoader)
+     */
+    @Override
+    public void prevent(ClassLoader loader)
+    {
+        LOG.debug("Pinning classloader for java.awt.EventQueue using "+loader);
+        Toolkit.getDefaultToolkit();
+    }
+
+}
diff --git a/lib/jetty/org/eclipse/jetty/util/preventers/AbstractLeakPreventer.java b/lib/jetty/org/eclipse/jetty/util/preventers/AbstractLeakPreventer.java
new file mode 100644 (file)
index 0000000..2322a76
--- /dev/null
@@ -0,0 +1,62 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+
+package org.eclipse.jetty.util.preventers;
+
+import org.eclipse.jetty.util.component.AbstractLifeCycle;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/**
+ * AbstractLeakPreventer
+ *
+ * Abstract base class for code that seeks to avoid pinning of webapp classloaders by using the jetty classloader to
+ * proactively call the code that pins them (generally pinned as static data members, or as static
+ * data members that are daemon threads (which use the context classloader)).
+ * 
+ * Instances of subclasses of this class should be set with Server.addBean(), which will
+ * ensure that they are called when the Server instance starts up, which will have the jetty
+ * classloader in scope.
+ *
+ */
+public abstract class AbstractLeakPreventer extends AbstractLifeCycle
+{
+    protected static final Logger LOG = Log.getLogger(AbstractLeakPreventer.class);
+    
+    /* ------------------------------------------------------------ */
+    abstract public void prevent(ClassLoader loader);
+    
+    
+    /* ------------------------------------------------------------ */
+    @Override
+    protected void doStart() throws Exception
+    {
+        ClassLoader loader = Thread.currentThread().getContextClassLoader();
+        try
+        {
+            Thread.currentThread().setContextClassLoader(getClass().getClassLoader());
+            prevent(getClass().getClassLoader());
+            super.doStart();
+        }
+        finally
+        {
+            Thread.currentThread().setContextClassLoader(loader);
+        }
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/util/preventers/AppContextLeakPreventer.java b/lib/jetty/org/eclipse/jetty/util/preventers/AppContextLeakPreventer.java
new file mode 100644 (file)
index 0000000..0cfd3c9
--- /dev/null
@@ -0,0 +1,41 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util.preventers;
+
+import javax.imageio.ImageIO;
+
+/**
+ * AppContextLeakPreventer
+ *
+ * Cause the classloader that is pinned by AppContext.getAppContext() to be 
+ * a container or system classloader, not a webapp classloader.
+ * 
+ * Inspired by Tomcat JreMemoryLeakPrevention.
+ */
+public class AppContextLeakPreventer extends AbstractLeakPreventer
+{
+    /* ------------------------------------------------------------ */
+    @Override
+    public void prevent(ClassLoader loader)
+    {
+        LOG.debug("Pinning classloader for AppContext.getContext() with "+loader);
+        ImageIO.getUseCache();
+    }
+
+}
diff --git a/lib/jetty/org/eclipse/jetty/util/preventers/DOMLeakPreventer.java b/lib/jetty/org/eclipse/jetty/util/preventers/DOMLeakPreventer.java
new file mode 100644 (file)
index 0000000..5fee365
--- /dev/null
@@ -0,0 +1,56 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util.preventers;
+
+import javax.xml.parsers.DocumentBuilderFactory;
+
+/**
+ * DOMLeakPreventer
+ *
+ * See http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6916498
+ * 
+ * Prevent the RuntimeException that is a static member of AbstractDOMParser
+ * from pinning a webapp classloader by causing it to be set here by a non-webapp classloader.
+ * 
+ * Note that according to the bug report, a heap dump may not identify the GCRoot, making 
+ * it difficult to identify the cause of the leak.
+ *
+ */
+public class DOMLeakPreventer extends AbstractLeakPreventer
+{
+    /* ------------------------------------------------------------ */
+    /** 
+     * @see org.eclipse.jetty.util.preventers.AbstractLeakPreventer#prevent(java.lang.ClassLoader)
+     */
+    @Override
+    public void prevent(ClassLoader loader)
+    {
+        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+        try 
+        {
+            factory.newDocumentBuilder();
+        } 
+        catch (Exception e) 
+        {
+            LOG.warn(e);
+        }
+
+    }
+
+}
diff --git a/lib/jetty/org/eclipse/jetty/util/preventers/DriverManagerLeakPreventer.java b/lib/jetty/org/eclipse/jetty/util/preventers/DriverManagerLeakPreventer.java
new file mode 100644 (file)
index 0000000..d229ba7
--- /dev/null
@@ -0,0 +1,42 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+
+package org.eclipse.jetty.util.preventers;
+
+import java.sql.DriverManager;
+
+
+/**
+ * DriverManagerLeakPreventer
+ *
+ * Cause DriverManager.getCallerClassLoader() to be called, which will pin the classloader.
+ * 
+ * Inspired by Tomcat JreMemoryLeakPrevention.
+ */
+public class DriverManagerLeakPreventer extends AbstractLeakPreventer
+{
+    /* ------------------------------------------------------------ */
+    @Override
+    public void prevent(ClassLoader loader)
+    {
+        LOG.debug("Pinning DriverManager classloader with "+loader);
+        DriverManager.getDrivers();
+    }
+
+}
diff --git a/lib/jetty/org/eclipse/jetty/util/preventers/GCThreadLeakPreventer.java b/lib/jetty/org/eclipse/jetty/util/preventers/GCThreadLeakPreventer.java
new file mode 100644 (file)
index 0000000..6ea4de2
--- /dev/null
@@ -0,0 +1,64 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util.preventers;
+
+import java.lang.reflect.Method;
+
+/**
+ * GCThreadLeakPreventer
+ *
+ * Prevents a call to sun.misc.GC.requestLatency pinning a webapp classloader
+ * by calling it with a non-webapp classloader. The problem appears to be that
+ * when this method is called, a daemon thread is created which takes the 
+ * context classloader. A known caller of this method is the RMI impl. See
+ * http://stackoverflow.com/questions/6626680/does-java-garbage-collection-log-entry-full-gc-system-mean-some-class-called
+ * 
+ * This preventer will start the thread with the longest possible interval, although
+ * subsequent calls can vary that. Recommend to only use this class if you're doing
+ * RMI.
+ * 
+ * Inspired by Tomcat JreMemoryLeakPrevention.
+ *
+ */
+public class GCThreadLeakPreventer extends AbstractLeakPreventer
+{
+    /* ------------------------------------------------------------ */
+    /** 
+     * @see org.eclipse.jetty.util.preventers.AbstractLeakPreventer#prevent(java.lang.ClassLoader)
+     */
+    @Override
+    public void prevent(ClassLoader loader)
+    {
+        try
+        {
+            Class clazz = Class.forName("sun.misc.GC");
+            Method requestLatency = clazz.getMethod("requestLatency", new Class[] {long.class});
+            requestLatency.invoke(null, Long.valueOf(Long.MAX_VALUE-1));
+        }
+        catch (ClassNotFoundException e)
+        {
+            LOG.ignore(e);
+        }
+        catch (Exception e)
+        {
+            LOG.warn(e);
+        }
+    }
+
+}
diff --git a/lib/jetty/org/eclipse/jetty/util/preventers/Java2DLeakPreventer.java b/lib/jetty/org/eclipse/jetty/util/preventers/Java2DLeakPreventer.java
new file mode 100644 (file)
index 0000000..3a2ad82
--- /dev/null
@@ -0,0 +1,49 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util.preventers;
+
+/**
+ * Java2DLeakPreventer
+ *
+ * Prevent pinning of webapp classloader by pre-loading sun.java2d.Disposer class
+ * before webapp classloaders are created.
+ * 
+ * See https://issues.apache.org/bugzilla/show_bug.cgi?id=51687
+ *
+ */
+public class Java2DLeakPreventer extends AbstractLeakPreventer
+{
+    /* ------------------------------------------------------------ */
+    /** 
+     * @see org.eclipse.jetty.util.preventers.AbstractLeakPreventer#prevent(java.lang.ClassLoader)
+     */
+    @Override
+    public void prevent(ClassLoader loader)
+    {
+        try
+        {
+            Class.forName("sun.java2d.Disposer", true, loader);
+        }
+        catch (ClassNotFoundException e)
+        {
+            LOG.ignore(e);
+        }
+    }
+
+}
diff --git a/lib/jetty/org/eclipse/jetty/util/preventers/LDAPLeakPreventer.java b/lib/jetty/org/eclipse/jetty/util/preventers/LDAPLeakPreventer.java
new file mode 100644 (file)
index 0000000..5c497d1
--- /dev/null
@@ -0,0 +1,51 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util.preventers;
+
+/**
+ * LDAPLeakPreventer
+ *
+ * If com.sun.jndi.LdapPoolManager class is loaded and the system property
+ * com.sun.jndi.ldap.connect.pool.timeout is set to a nonzero value, a daemon
+ * thread is started which can pin a webapp classloader if it is the first to
+ * load the LdapPoolManager.
+ * 
+ * Inspired by Tomcat JreMemoryLeakPrevention
+ *
+ */
+public class LDAPLeakPreventer extends AbstractLeakPreventer
+{
+    /* ------------------------------------------------------------ */
+    /** 
+     * @see org.eclipse.jetty.util.preventers.AbstractLeakPreventer#prevent(java.lang.ClassLoader)
+     */
+    @Override
+    public void prevent(ClassLoader loader)
+    {
+        try
+        {
+            Class.forName("com.sun.jndi.LdapPoolManager", true, loader);
+        }
+        catch (ClassNotFoundException e)
+        {
+            LOG.ignore(e);
+        }
+    }
+
+}
diff --git a/lib/jetty/org/eclipse/jetty/util/preventers/LoginConfigurationLeakPreventer.java b/lib/jetty/org/eclipse/jetty/util/preventers/LoginConfigurationLeakPreventer.java
new file mode 100644 (file)
index 0000000..c1d9fe2
--- /dev/null
@@ -0,0 +1,49 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util.preventers;
+
+/**
+ * LoginConfigurationLeakPreventer
+ *
+ * The javax.security.auth.login.Configuration class keeps a static reference to the 
+ * thread context classloader. We prevent a webapp context classloader being used for
+ * that by invoking the classloading here.
+ * 
+ * Inspired by Tomcat JreMemoryLeakPrevention
+ */
+public class LoginConfigurationLeakPreventer extends AbstractLeakPreventer
+{
+    /* ------------------------------------------------------------ */
+    /** 
+     * @see org.eclipse.jetty.util.preventers.AbstractLeakPreventer#prevent(java.lang.ClassLoader)
+     */
+    @Override
+    public void prevent(ClassLoader loader)
+    {
+        try
+        {
+            Class.forName("javax.security.auth.login.Configuration", true, loader);
+        }
+        catch (ClassNotFoundException e)
+        {
+            LOG.warn(e);
+        }
+    }
+
+}
diff --git a/lib/jetty/org/eclipse/jetty/util/preventers/SecurityProviderLeakPreventer.java b/lib/jetty/org/eclipse/jetty/util/preventers/SecurityProviderLeakPreventer.java
new file mode 100644 (file)
index 0000000..9976c56
--- /dev/null
@@ -0,0 +1,44 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util.preventers;
+
+import java.security.Security;
+
+/**
+ * SecurityProviderLeakPreventer
+ *
+ * Some security providers, such as sun.security.pkcs11.SunPKCS11 start a deamon thread,
+ * which will use the thread context classloader. Load them here to ensure the classloader
+ * is not a webapp classloader.
+ *
+ * Inspired by Tomcat JreMemoryLeakPrevention
+ */
+public class SecurityProviderLeakPreventer extends AbstractLeakPreventer
+{
+    /* ------------------------------------------------------------ */
+    /** 
+     * @see org.eclipse.jetty.util.preventers.AbstractLeakPreventer#prevent(java.lang.ClassLoader)
+     */
+    @Override
+    public void prevent(ClassLoader loader)
+    {
+        Security.getProviders();
+    }
+
+}
diff --git a/lib/jetty/org/eclipse/jetty/util/preventers/package-info.java b/lib/jetty/org/eclipse/jetty/util/preventers/package-info.java
new file mode 100644 (file)
index 0000000..a6800e8
--- /dev/null
@@ -0,0 +1,23 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+/**
+ * Jetty Util : Common Memory Leak Prevention Tooling
+ */
+package org.eclipse.jetty.util.preventers;
+
diff --git a/lib/jetty/org/eclipse/jetty/util/resource/BadResource.java b/lib/jetty/org/eclipse/jetty/util/resource/BadResource.java
new file mode 100644 (file)
index 0000000..72a9ed4
--- /dev/null
@@ -0,0 +1,130 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util.resource;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+
+
+/* ------------------------------------------------------------ */
+/** Bad Resource.
+ *
+ * A Resource that is returned for a bade URL.  Acts as a resource
+ * that does not exist and throws appropriate exceptions.
+ *
+ * 
+ */
+class BadResource extends URLResource
+{
+    /* ------------------------------------------------------------ */
+    private String _message=null;
+        
+    /* -------------------------------------------------------- */
+    BadResource(URL url,  String message)
+    {
+        super(url,null);
+        _message=message;
+    }
+    
+
+    /* -------------------------------------------------------- */
+    @Override
+    public boolean exists()
+    {
+        return false;
+    }
+        
+    /* -------------------------------------------------------- */
+    @Override
+    public long lastModified()
+    {
+        return -1;
+    }
+
+    /* -------------------------------------------------------- */
+    @Override
+    public boolean isDirectory()
+    {
+        return false;
+    }
+
+    /* --------------------------------------------------------- */
+    @Override
+    public long length()
+    {
+        return -1;
+    }
+        
+        
+    /* ------------------------------------------------------------ */
+    @Override
+    public File getFile()
+    {
+        return null;
+    }
+        
+    /* --------------------------------------------------------- */
+    @Override
+    public InputStream getInputStream() throws IOException
+    {
+        throw new FileNotFoundException(_message);
+    }
+        
+    /* --------------------------------------------------------- */
+    @Override
+    public boolean delete()
+        throws SecurityException
+    {
+        throw new SecurityException(_message);
+    }
+
+    /* --------------------------------------------------------- */
+    @Override
+    public boolean renameTo( Resource dest)
+        throws SecurityException
+    {
+        throw new SecurityException(_message);
+    }
+
+    /* --------------------------------------------------------- */
+    @Override
+    public String[] list()
+    {
+        return null;
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public void copyTo(File destination)
+        throws IOException
+    {
+        throw new SecurityException(_message);
+    }
+    
+    /* ------------------------------------------------------------ */
+    @Override
+    public String toString()
+    {
+        return super.toString()+"; BadResource="+_message;
+    }
+    
+}
diff --git a/lib/jetty/org/eclipse/jetty/util/resource/EmptyResource.java b/lib/jetty/org/eclipse/jetty/util/resource/EmptyResource.java
new file mode 100644 (file)
index 0000000..3dad17b
--- /dev/null
@@ -0,0 +1,130 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util.resource;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.nio.channels.ReadableByteChannel;
+
+/**
+ * EmptyResource
+ *
+ * Represents a resource that does does not refer to any file, url, jar etc. 
+ */
+public class EmptyResource extends Resource
+{
+    public static final Resource INSTANCE = new EmptyResource();
+    
+    private EmptyResource()
+    {
+    }
+
+    @Override
+    public boolean isContainedIn(Resource r) throws MalformedURLException
+    {
+        return false;
+    }
+
+    @Override
+    public void close()
+    {
+    }
+
+    @Override
+    public boolean exists()
+    {
+        return false;
+    }
+
+    @Override
+    public boolean isDirectory()
+    {
+        return false;
+    }
+
+    @Override
+    public long lastModified()
+    {
+        return 0;
+    }
+
+    @Override
+    public long length()
+    {
+        return 0;
+    }
+
+    @Override
+    public URL getURL()
+    {
+        return null;
+    }
+
+    @Override
+    public File getFile() throws IOException
+    {
+        return null;
+    }
+
+    @Override
+    public String getName()
+    {
+        return null;
+    }
+
+    @Override
+    public InputStream getInputStream() throws IOException
+    {
+        return null;
+    }
+
+    @Override
+    public ReadableByteChannel getReadableByteChannel() throws IOException
+    {
+        return null;
+    }
+
+    @Override
+    public boolean delete() throws SecurityException
+    {
+        return false;
+    }
+
+    @Override
+    public boolean renameTo(Resource dest) throws SecurityException
+    {
+        return false;
+    }
+
+    @Override
+    public String[] list()
+    {
+        return null;
+    }
+
+    @Override
+    public Resource addPath(String path) throws IOException, MalformedURLException
+    {
+        return null;
+    }
+
+}
diff --git a/lib/jetty/org/eclipse/jetty/util/resource/FileResource.java b/lib/jetty/org/eclipse/jetty/util/resource/FileResource.java
new file mode 100644 (file)
index 0000000..4498842
--- /dev/null
@@ -0,0 +1,429 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util.resource;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.MalformedURLException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.net.URLConnection;
+import java.nio.channels.FileChannel;
+import java.nio.channels.ReadableByteChannel;
+import java.nio.file.StandardOpenOption;
+import java.security.Permission;
+
+import org.eclipse.jetty.util.IO;
+import org.eclipse.jetty.util.URIUtil;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+
+/* ------------------------------------------------------------ */
+/** File Resource.
+ *
+ * Handle resources of implied or explicit file type.
+ * This class can check for aliasing in the filesystem (eg case
+ * insensitivity).  By default this is turned on, or it can be controlled 
+ * by calling the static method @see FileResource#setCheckAliases(boolean)
+ * 
+ */
+public class FileResource extends Resource
+{
+    private static final Logger LOG = Log.getLogger(FileResource.class);
+
+    /* ------------------------------------------------------------ */
+    private final File _file;
+    private final String _uri;
+    private final URI _alias;
+    
+    /* -------------------------------------------------------- */
+    public FileResource(URL url)
+        throws IOException, URISyntaxException
+    {
+        File file;
+        try
+        {
+            // Try standard API to convert URL to file.
+            file =new File(url.toURI());
+        }
+        catch (URISyntaxException e) 
+        {
+            throw e;
+        }
+        catch (Exception e)
+        {
+            if (!url.toString().startsWith("file:"))
+                throw new IllegalArgumentException("!file:");
+            
+            LOG.ignore(e);
+            try
+            {
+                // Assume that File.toURL produced unencoded chars. So try encoding them.
+                String file_url="file:"+URIUtil.encodePath(url.toString().substring(5));           
+                URI uri = new URI(file_url);
+                if (uri.getAuthority()==null) 
+                    file = new File(uri);
+                else
+                    file = new File("//"+uri.getAuthority()+URIUtil.decodePath(url.getFile()));
+            }
+            catch (Exception e2)
+            {
+                LOG.ignore(e2);
+                // Still can't get the file.  Doh! try good old hack!
+                URLConnection connection=url.openConnection();
+                Permission perm = connection.getPermission();
+                file = new File(perm==null?url.getFile():perm.getName());
+            }
+        }
+        
+        _file=file;
+        _uri=normalizeURI(_file,url.toURI());
+        _alias=checkAlias(_file);
+    }
+
+    /* -------------------------------------------------------- */
+    public FileResource(URI uri)
+    {
+        File file=new File(uri);
+        _file=file;
+        URI file_uri=_file.toURI();
+        _uri=normalizeURI(_file,uri);
+        
+        if (!_uri.equals(file_uri.toString()))
+        {
+            // URI and File URI are different.  Is it just an encoding difference?
+            if (!file_uri.toString().equals(URIUtil.decodePath(uri.toString())))
+                 _alias=_file.toURI();
+            else
+                _alias=checkAlias(_file);
+        }
+        else
+            _alias=checkAlias(_file);
+    }
+
+    /* -------------------------------------------------------- */
+    FileResource(File file)
+    {
+        _file=file;
+        _uri=normalizeURI(_file,_file.toURI());
+        _alias=checkAlias(_file);
+    }
+
+    /* -------------------------------------------------------- */
+    private static String normalizeURI(File file, URI uri)
+    {
+        String u =uri.toASCIIString();
+        if (file.isDirectory())
+        {
+            if(!u.endsWith("/"))
+                u+="/";
+        } 
+        else if (file.exists() && u.endsWith("/"))
+            u=u.substring(0,u.length()-1);
+        return u;
+    }
+
+    /* -------------------------------------------------------- */
+    private static URI checkAlias(File file)
+    {
+        try
+        {
+            String abs=file.getAbsolutePath();
+            String can=file.getCanonicalPath();
+
+            if (!abs.equals(can))
+            {
+                LOG.debug("ALIAS abs={} can={}",abs,can);
+
+                URI alias=new File(can).toURI();
+                // Have to encode the path as File.toURI does not!
+                return new URI("file://"+URIUtil.encodePath(alias.getPath()));  
+            }
+        }
+        catch(Exception e)
+        {
+            LOG.warn("bad alias for {}: {}",file,e.toString());
+            LOG.debug(e);
+            try
+            {
+                return new URI("http://eclipse.org/bad/canonical/alias");
+            }
+            catch(Exception e2)
+            {
+                LOG.ignore(e2);
+                throw new RuntimeException(e);
+            }
+        }
+
+        return null;
+    }
+    
+    /* -------------------------------------------------------- */
+    @Override
+    public Resource addPath(String path)
+        throws IOException,MalformedURLException
+    {
+        path = org.eclipse.jetty.util.URIUtil.canonicalPath(path);
+
+        if (path==null)
+            throw new MalformedURLException();   
+        
+        if ("/".equals(path))
+            return this;
+        
+        path=URIUtil.encodePath(path);
+        // The encoded path should be a suffix of the resource (give or take a directory / )
+        URI uri;
+        try
+        {
+            if (_file.isDirectory())
+            {
+                // treat all paths being added as relative
+                uri=new URI(URIUtil.addPaths(_uri,path));
+            }
+            else
+            {
+                uri=new URI(_uri+path);
+            }
+        }
+        catch(final URISyntaxException e)
+        {
+            throw new MalformedURLException(){{initCause(e);}};
+        }
+
+        return new FileResource(uri);
+    }
+   
+    
+    /* ------------------------------------------------------------ */
+    @Override
+    public URI getAlias()
+    {
+        return _alias;
+    }
+    
+    /* -------------------------------------------------------- */
+    /**
+     * Returns true if the resource exists.
+     */
+    @Override
+    public boolean exists()
+    {
+        return _file.exists();
+    }
+        
+    /* -------------------------------------------------------- */
+    /**
+     * Returns the last modified time
+     */
+    @Override
+    public long lastModified()
+    {
+        return _file.lastModified();
+    }
+
+    /* -------------------------------------------------------- */
+    /**
+     * Returns true if the resource is a container/directory.
+     */
+    @Override
+    public boolean isDirectory()
+    {
+        return _file.exists() && _file.isDirectory() || _uri.endsWith("/");
+    }
+
+    /* --------------------------------------------------------- */
+    /**
+     * Return the length of the resource
+     */
+    @Override
+    public long length()
+    {
+        return _file.length();
+    }
+        
+
+    /* --------------------------------------------------------- */
+    /**
+     * Returns the name of the resource
+     */
+    @Override
+    public String getName()
+    {
+        return _file.getAbsolutePath();
+    }
+        
+    /* ------------------------------------------------------------ */
+    /**
+     * Returns an File representing the given resource or NULL if this
+     * is not possible.
+     */
+    @Override
+    public File getFile()
+    {
+        return _file;
+    }
+        
+    /* --------------------------------------------------------- */
+    /**
+     * Returns an input stream to the resource
+     */
+    @Override
+    public InputStream getInputStream() throws IOException
+    {
+        return new FileInputStream(_file);
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public ReadableByteChannel getReadableByteChannel() throws IOException
+    {
+        return FileChannel.open(_file.toPath(),StandardOpenOption.READ);
+    }
+        
+    /* --------------------------------------------------------- */
+    /**
+     * Deletes the given resource
+     */
+    @Override
+    public boolean delete()
+        throws SecurityException
+    {
+        return _file.delete();
+    }
+
+    /* --------------------------------------------------------- */
+    /**
+     * Rename the given resource
+     */
+    @Override
+    public boolean renameTo( Resource dest)
+        throws SecurityException
+    {
+        if( dest instanceof FileResource)
+            return _file.renameTo( ((FileResource)dest)._file);
+        else
+            return false;
+    }
+
+    /* --------------------------------------------------------- */
+    /**
+     * Returns a list of resources contained in the given resource
+     */
+    @Override
+    public String[] list()
+    {
+        String[] list =_file.list();
+        if (list==null)
+            return null;
+        for (int i=list.length;i-->0;)
+        {
+            if (new File(_file,list[i]).isDirectory() &&
+                !list[i].endsWith("/"))
+                list[i]+="/";
+        }
+        return list;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** 
+     * @param o
+     * @return <code>true</code> of the object <code>o</code> is a {@link FileResource} pointing to the same file as this resource. 
+     */
+    @Override
+    public boolean equals( Object o)
+    {
+        if (this == o)
+            return true;
+
+        if (null == o || ! (o instanceof FileResource))
+            return false;
+
+        FileResource f=(FileResource)o;
+        return f._file == _file || (null != _file && _file.equals(f._file));
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return the hashcode.
+     */
+    @Override
+    public int hashCode()
+    {
+       return null == _file ? super.hashCode() : _file.hashCode();
+    }
+    
+    /* ------------------------------------------------------------ */
+    @Override
+    public void copyTo(File destination)
+        throws IOException
+    {
+        if (isDirectory())
+        {
+            IO.copyDir(getFile(),destination);
+        }
+        else
+        {
+            if (destination.exists())
+                throw new IllegalArgumentException(destination+" exists");
+            IO.copy(getFile(),destination);
+        }
+    }
+
+    @Override
+    public boolean isContainedIn(Resource r) throws MalformedURLException
+    {
+        return false;
+    }
+
+    @Override
+    public void close()
+    {
+    }
+
+    @Override
+    public URL getURL()
+    {
+        try
+        {
+            return new URL(_uri);
+        }
+        catch (MalformedURLException e)
+        {
+            throw new IllegalStateException(e);
+        }
+    }
+    
+    @Override
+    public URI getURI()
+    {
+        return _file.toURI();
+    }
+
+    @Override
+    public String toString()
+    {
+        return _uri;
+    }
+
+}
diff --git a/lib/jetty/org/eclipse/jetty/util/resource/JarFileResource.java b/lib/jetty/org/eclipse/jetty/util/resource/JarFileResource.java
new file mode 100644 (file)
index 0000000..434aa88
--- /dev/null
@@ -0,0 +1,412 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util.resource;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.JarURLConnection;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.jar.JarEntry;
+import java.util.jar.JarFile;
+
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/* ------------------------------------------------------------ */
+class JarFileResource extends JarResource
+{
+    private static final Logger LOG = Log.getLogger(JarFileResource.class);
+    private JarFile _jarFile;
+    private File _file;
+    private String[] _list;
+    private JarEntry _entry;
+    private boolean _directory;
+    private String _jarUrl;
+    private String _path;
+    private boolean _exists;
+    
+    /* -------------------------------------------------------- */
+    protected JarFileResource(URL url)
+    {
+        super(url);
+    }
+    
+    /* ------------------------------------------------------------ */
+    protected JarFileResource(URL url, boolean useCaches)
+    {
+        super(url, useCaches);
+    }
+   
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public synchronized void close()
+    {
+        _list=null;
+        _entry=null;
+        _file=null;
+        //if the jvm is not doing url caching, then the JarFiles will not be cached either,
+        //and so they are safe to close
+        if (!getUseCaches())
+        {
+            if ( _jarFile != null )
+            {
+                try
+                {
+                    LOG.debug("Closing JarFile "+_jarFile.getName());
+                    _jarFile.close();
+                }
+                catch ( IOException ioe )
+                {
+                    LOG.ignore(ioe);
+                }
+            }
+        }
+        _jarFile=null;
+        super.close();
+    }
+    
+    /* ------------------------------------------------------------ */
+    @Override
+    protected synchronized boolean checkConnection()
+    {
+        try
+        {
+            super.checkConnection();
+        }
+        finally
+        {
+            if (_jarConnection==null)
+            {
+                _entry=null;
+                _file=null;
+                _jarFile=null;
+                _list=null;
+            }
+        }
+        return _jarFile!=null;
+    }
+
+
+    /* ------------------------------------------------------------ */
+    @Override
+    protected synchronized void newConnection()
+        throws IOException
+    {
+        super.newConnection();
+        
+        _entry=null;
+        _file=null;
+        _jarFile=null;
+        _list=null;
+        
+        int sep = _urlString.indexOf("!/");
+        _jarUrl=_urlString.substring(0,sep+2);
+        _path=_urlString.substring(sep+2);
+        if (_path.length()==0)
+            _path=null;   
+        _jarFile=_jarConnection.getJarFile();
+        _file=new File(_jarFile.getName());
+    }
+    
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * Returns true if the represented resource exists.
+     */
+    @Override
+    public boolean exists()
+    {
+        if (_exists)
+            return true;
+
+        if (_urlString.endsWith("!/"))
+        {
+            
+            String file_url=_urlString.substring(4,_urlString.length()-2);
+            try{return newResource(file_url).exists();}
+            catch(Exception e) {LOG.ignore(e); return false;}
+        }
+        
+        boolean check=checkConnection();
+        
+        // Is this a root URL?
+        if (_jarUrl!=null && _path==null)
+        {
+            // Then if it exists it is a directory
+            _directory=check;
+            return true;
+        }
+        else 
+        {
+            // Can we find a file for it?
+            JarFile jarFile=null;
+            if (check)
+                // Yes
+                jarFile=_jarFile;
+            else
+            {
+                // No - so lets look if the root entry exists.
+                try
+                {
+                    JarURLConnection c=(JarURLConnection)((new URL(_jarUrl)).openConnection());
+                    c.setUseCaches(getUseCaches());
+                    jarFile=c.getJarFile();
+                }
+                catch(Exception e)
+                {
+                       LOG.ignore(e);
+                }
+            }
+
+            // Do we need to look more closely?
+            if (jarFile!=null && _entry==null && !_directory)
+            {
+                // OK - we have a JarFile, lets look at the entries for our path
+                Enumeration<JarEntry> e=jarFile.entries();
+                while(e.hasMoreElements())
+                {
+                    JarEntry entry = e.nextElement();
+                    String name=entry.getName().replace('\\','/');
+                    
+                    // Do we have a match
+                    if (name.equals(_path))
+                    {
+                        _entry=entry;
+                        // Is the match a directory
+                        _directory=_path.endsWith("/");
+                        break;
+                    }
+                    else if (_path.endsWith("/"))
+                    {
+                        if (name.startsWith(_path))
+                        {
+                            _directory=true;
+                            break;
+                        }
+                    }
+                    else if (name.startsWith(_path) && name.length()>_path.length() && name.charAt(_path.length())=='/')
+                    {
+                        _directory=true;
+                        break;
+                    }
+                }
+            }
+        }    
+        
+        _exists= ( _directory || _entry!=null);
+        return _exists;
+    }
+
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * Returns true if the represented resource is a container/directory.
+     * If the resource is not a file, resources ending with "/" are
+     * considered directories.
+     */
+    @Override
+    public boolean isDirectory()
+    {
+        return _urlString.endsWith("/") || exists() && _directory;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * Returns the last modified time
+     */
+    @Override
+    public long lastModified()
+    {
+        if (checkConnection() && _file!=null)
+        {
+            if (exists() && _entry!=null)
+                return _entry.getTime();
+            return _file.lastModified();
+        }
+        return -1;
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public synchronized String[] list()
+    {
+        if (isDirectory() && _list==null)
+        {
+            List<String> list = null;
+            try
+            {
+                list = listEntries();
+            }
+            catch (Exception e)
+            {
+                //Sun's JarURLConnection impl for jar: protocol will close a JarFile in its connect() method if
+                //useCaches == false (eg someone called URLConnection with defaultUseCaches==true).
+                //As their sun.net.www.protocol.jar package caches JarFiles and/or connections, we can wind up in 
+                //the situation where the JarFile we have remembered in our _jarFile member has actually been closed
+                //by other code.
+                //So, do one retry to drop a connection and get a fresh JarFile
+                LOG.warn("Retrying list:"+e);
+                LOG.debug(e);
+                release();
+                list = listEntries();
+            }
+
+            if (list != null)
+            {
+                _list=new String[list.size()];
+                list.toArray(_list);
+            }  
+        }
+        return _list;
+    }
+    
+    
+    /* ------------------------------------------------------------ */
+    private List<String> listEntries ()
+    {
+        checkConnection();
+        
+        ArrayList<String> list = new ArrayList<String>(32);
+        JarFile jarFile=_jarFile;
+        if(jarFile==null)
+        {
+            try
+            {
+                JarURLConnection jc=(JarURLConnection)((new URL(_jarUrl)).openConnection());
+                jc.setUseCaches(getUseCaches());
+                jarFile=jc.getJarFile();
+            }
+            catch(Exception e)
+            {
+
+                e.printStackTrace();
+                 LOG.ignore(e);
+            }
+                if(jarFile==null)
+                    throw new IllegalStateException();
+        }
+        
+        Enumeration<JarEntry> e=jarFile.entries();
+        String dir=_urlString.substring(_urlString.indexOf("!/")+2);
+        while(e.hasMoreElements())
+        {
+            JarEntry entry = e.nextElement();               
+            String name=entry.getName().replace('\\','/');               
+            if(!name.startsWith(dir) || name.length()==dir.length())
+            {
+                continue;
+            }
+            String listName=name.substring(dir.length());               
+            int dash=listName.indexOf('/');
+            if (dash>=0)
+            {
+                //when listing jar:file urls, you get back one
+                //entry for the dir itself, which we ignore
+                if (dash==0 && listName.length()==1)
+                    continue;
+                //when listing jar:file urls, all files and
+                //subdirs have a leading /, which we remove
+                if (dash==0)
+                    listName=listName.substring(dash+1, listName.length());
+                else
+                    listName=listName.substring(0,dash+1);
+                
+                if (list.contains(listName))
+                    continue;
+            }
+            
+            list.add(listName);
+        }
+        
+        return list;
+    }
+    
+    
+    
+    
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * Return the length of the resource
+     */
+    @Override
+    public long length()
+    {
+        if (isDirectory())
+            return -1;
+
+        if (_entry!=null)
+            return _entry.getSize();
+        
+        return -1;
+    }
+
+    
+    /**
+     * Take a Resource that possibly might use URLConnection caching
+     * and turn it into one that doesn't.
+     * @param resource
+     * @return the non-caching resource
+     */
+    public static Resource getNonCachingResource (Resource resource)
+    {
+        if (!(resource instanceof JarFileResource))
+            return resource;
+        
+        JarFileResource oldResource = (JarFileResource)resource;
+        
+        JarFileResource newResource = new JarFileResource(oldResource.getURL(), false);
+        return newResource;
+        
+    }
+    
+    /**
+     * Check if this jar:file: resource is contained in the
+     * named resource. Eg <code>jar:file:///a/b/c/foo.jar!/x.html</code> isContainedIn <code>file:///a/b/c/foo.jar</code>
+     * @param resource
+     * @return true if resource is contained in the named resource
+     * @throws MalformedURLException
+     */
+    @Override
+    public boolean isContainedIn (Resource resource) 
+    throws MalformedURLException
+    {
+        String string = _urlString;
+        int index = string.indexOf("!/");
+        if (index > 0)
+            string = string.substring(0,index);
+        if (string.startsWith("jar:"))
+            string = string.substring(4);
+        URL url = new URL(string);
+        return url.sameFile(resource.getURL());     
+    }
+}
+
+
+
+
+
+
+
+
diff --git a/lib/jetty/org/eclipse/jetty/util/resource/JarResource.java b/lib/jetty/org/eclipse/jetty/util/resource/JarResource.java
new file mode 100644 (file)
index 0000000..3dbb70a
--- /dev/null
@@ -0,0 +1,269 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util.resource;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.FilterInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.JarURLConnection;
+import java.net.URL;
+import java.util.jar.JarEntry;
+import java.util.jar.JarInputStream;
+import java.util.jar.Manifest;
+
+import org.eclipse.jetty.util.IO;
+import org.eclipse.jetty.util.URIUtil;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+
+/* ------------------------------------------------------------ */
+public class JarResource extends URLResource
+{
+    private static final Logger LOG = Log.getLogger(JarResource.class);
+    protected JarURLConnection _jarConnection;
+    
+    /* -------------------------------------------------------- */
+    protected JarResource(URL url)
+    {
+        super(url,null);
+    }
+
+    /* ------------------------------------------------------------ */
+    protected JarResource(URL url, boolean useCaches)
+    {
+        super(url, null, useCaches);
+    }
+    
+    /* ------------------------------------------------------------ */
+    @Override
+    public synchronized void close()
+    {
+        _jarConnection=null;
+        super.close();
+    }
+    
+    /* ------------------------------------------------------------ */
+    @Override
+    protected synchronized boolean checkConnection()
+    {
+        super.checkConnection();
+        try
+        {
+            if (_jarConnection!=_connection)
+                newConnection();
+        }
+        catch(IOException e)
+        {
+            LOG.ignore(e);
+            _jarConnection=null;
+        }
+        
+        return _jarConnection!=null;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @throws IOException Sub-classes of <code>JarResource</code> may throw an IOException (or subclass) 
+     */
+    protected void newConnection() throws IOException
+    {
+        _jarConnection=(JarURLConnection)_connection;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * Returns true if the respresenetd resource exists.
+     */
+    @Override
+    public boolean exists()
+    {
+        if (_urlString.endsWith("!/"))
+            return checkConnection();
+        else
+            return super.exists();
+    }    
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public File getFile()
+        throws IOException
+    {
+        return null;
+    }
+    
+    /* ------------------------------------------------------------ */
+    @Override
+    public InputStream getInputStream()
+        throws java.io.IOException
+    {     
+        checkConnection();
+        if (!_urlString.endsWith("!/"))
+            return new FilterInputStream(super.getInputStream()) 
+            {
+                @Override
+                public void close() throws IOException {this.in=IO.getClosedStream();}
+            };
+
+        URL url = new URL(_urlString.substring(4,_urlString.length()-2));      
+        InputStream is = url.openStream();
+        return is;
+    }
+    
+    /* ------------------------------------------------------------ */
+    @Override
+    public void copyTo(File directory)
+        throws IOException
+    {
+        if (!exists())
+            return;
+        
+        if(LOG.isDebugEnabled())
+            LOG.debug("Extract "+this+" to "+directory);
+        
+        String urlString = this.getURL().toExternalForm().trim();
+        int endOfJarUrl = urlString.indexOf("!/");
+        int startOfJarUrl = (endOfJarUrl >= 0?4:0);
+        
+        if (endOfJarUrl < 0)
+            throw new IOException("Not a valid jar url: "+urlString);
+        
+        URL jarFileURL = new URL(urlString.substring(startOfJarUrl, endOfJarUrl));
+        String subEntryName = (endOfJarUrl+2 < urlString.length() ? urlString.substring(endOfJarUrl + 2) : null);
+        boolean subEntryIsDir = (subEntryName != null && subEntryName.endsWith("/")?true:false);
+      
+        if (LOG.isDebugEnabled()) 
+            LOG.debug("Extracting entry = "+subEntryName+" from jar "+jarFileURL);
+        
+        try (InputStream is = jarFileURL.openConnection().getInputStream();
+                JarInputStream jin = new JarInputStream(is))
+        {
+            JarEntry entry;
+            boolean shouldExtract;
+            while((entry=jin.getNextJarEntry())!=null)
+            {
+                String entryName = entry.getName();
+                if ((subEntryName != null) && (entryName.startsWith(subEntryName)))
+                {
+                    // is the subentry really a dir?
+                    if (!subEntryIsDir && subEntryName.length()+1==entryName.length() && entryName.endsWith("/"))
+                            subEntryIsDir=true;
+
+                    //if there is a particular subEntry that we are looking for, only
+                    //extract it.
+                    if (subEntryIsDir)
+                    {
+                        //if it is a subdirectory we are looking for, then we
+                        //are looking to extract its contents into the target
+                        //directory. Remove the name of the subdirectory so
+                        //that we don't wind up creating it too.
+                        entryName = entryName.substring(subEntryName.length());
+                        if (!entryName.equals(""))
+                        {
+                            //the entry is
+                            shouldExtract = true;
+                        }
+                        else
+                            shouldExtract = false;
+                    }
+                    else
+                        shouldExtract = true;
+                }
+                else if ((subEntryName != null) && (!entryName.startsWith(subEntryName)))
+                {
+                    //there is a particular entry we are looking for, and this one
+                    //isn't it
+                    shouldExtract = false;
+                }
+                else
+                {
+                    //we are extracting everything
+                    shouldExtract =  true;
+                }
+
+                if (!shouldExtract)
+                {
+                    if (LOG.isDebugEnabled())
+                        LOG.debug("Skipping entry: "+entryName);
+                    continue;
+                }
+
+                String dotCheck = entryName.replace('\\', '/');
+                dotCheck = URIUtil.canonicalPath(dotCheck);
+                if (dotCheck == null)
+                {
+                    if (LOG.isDebugEnabled())
+                        LOG.debug("Invalid entry: "+entryName);
+                    continue;
+                }
+
+                File file=new File(directory,entryName);
+
+                if (entry.isDirectory())
+                {
+                    // Make directory
+                    if (!file.exists())
+                        file.mkdirs();
+                }
+                else
+                {
+                    // make directory (some jars don't list dirs)
+                    File dir = new File(file.getParent());
+                    if (!dir.exists())
+                        dir.mkdirs();
+
+                    // Make file
+                    try (OutputStream fout = new FileOutputStream(file))
+                    {
+                        IO.copy(jin,fout);
+                    }
+
+                    // touch the file.
+                    if (entry.getTime()>=0)
+                        file.setLastModified(entry.getTime());
+                }
+            }
+
+            if ((subEntryName == null) || (subEntryName != null && subEntryName.equalsIgnoreCase("META-INF/MANIFEST.MF")))
+            {
+                Manifest manifest = jin.getManifest();
+                if (manifest != null)
+                {
+                    File metaInf = new File (directory, "META-INF");
+                    metaInf.mkdir();
+                    File f = new File(metaInf, "MANIFEST.MF");
+                    try (OutputStream fout = new FileOutputStream(f))
+                    {
+                        manifest.write(fout);
+                    }
+                }
+            }
+        }
+    }   
+    
+    public static Resource newJarResource(Resource resource) throws IOException
+    {
+        if (resource instanceof JarResource)
+            return resource;
+        return Resource.newResource("jar:" + resource + "!/");
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/util/resource/Resource.java b/lib/jetty/org/eclipse/jetty/util/resource/Resource.java
new file mode 100644 (file)
index 0000000..b36a935
--- /dev/null
@@ -0,0 +1,709 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util.resource;
+
+import java.io.Closeable;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.MalformedURLException;
+import java.net.URI;
+import java.net.URL;
+import java.nio.channels.ReadableByteChannel;
+import java.text.DateFormat;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Date;
+
+import org.eclipse.jetty.util.B64Code;
+import org.eclipse.jetty.util.IO;
+import org.eclipse.jetty.util.Loader;
+import org.eclipse.jetty.util.StringUtil;
+import org.eclipse.jetty.util.URIUtil;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+
+/* ------------------------------------------------------------ */
+/** 
+ * Abstract resource class.
+ * <p>
+ * This class provides a resource abstraction, where a resource may be
+ * a file, a URL or an entry in a jar file.
+ * </p>
+ */
+public abstract class Resource implements ResourceFactory, Closeable
+{
+    private static final Logger LOG = Log.getLogger(Resource.class);
+    public static boolean __defaultUseCaches = true;
+    volatile Object _associate;
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Change the default setting for url connection caches.
+     * Subsequent URLConnections will use this default.
+     * @param useCaches
+     */
+    public static void setDefaultUseCaches (boolean useCaches)
+    {
+        __defaultUseCaches=useCaches;
+    }
+
+    /* ------------------------------------------------------------ */
+    public static boolean getDefaultUseCaches ()
+    {
+        return __defaultUseCaches;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** Construct a resource from a uri.
+     * @param uri A URI.
+     * @return A Resource object.
+     * @throws MalformedURLException Problem accessing URI
+     */
+    public static Resource newResource(URI uri)
+        throws MalformedURLException
+    {
+        return newResource(uri.toURL());
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** Construct a resource from a url.
+     * @param url A URL.
+     * @return A Resource object.
+     */
+    public static Resource newResource(URL url)
+    {
+        return newResource(url, __defaultUseCaches);
+    }
+    
+    /* ------------------------------------------------------------ */   
+    /**
+     * Construct a resource from a url.
+     * @param url the url for which to make the resource
+     * @param useCaches true enables URLConnection caching if applicable to the type of resource
+     * @return
+     */
+    static Resource newResource(URL url, boolean useCaches)
+    {
+        if (url==null)
+            return null;
+
+        String url_string=url.toExternalForm();
+        if( url_string.startsWith( "file:"))
+        {
+            try
+            {
+                FileResource fileResource= new FileResource(url);
+                return fileResource;
+            }
+            catch(Exception e)
+            {
+                LOG.warn(e.toString());
+                LOG.debug(Log.EXCEPTION,e);
+                return new BadResource(url,e.toString());
+            }
+        }
+        else if( url_string.startsWith( "jar:file:"))
+        {
+            return new JarFileResource(url, useCaches);
+        }
+        else if( url_string.startsWith( "jar:"))
+        {
+            return new JarResource(url, useCaches);
+        }
+
+        return new URLResource(url,null,useCaches);
+    }
+
+    
+    
+    /* ------------------------------------------------------------ */
+    /** Construct a resource from a string.
+     * @param resource A URL or filename.
+     * @throws MalformedURLException Problem accessing URI
+     * @return A Resource object.
+     */
+    public static Resource newResource(String resource)
+        throws MalformedURLException
+    {
+        return newResource(resource, __defaultUseCaches);
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** Construct a resource from a string.
+     * @param resource A URL or filename.
+     * @param useCaches controls URLConnection caching
+     * @return A Resource object.
+     * @throws MalformedURLException Problem accessing URI
+     */
+    public static Resource newResource(String resource, boolean useCaches)       
+        throws MalformedURLException
+    {
+        URL url=null;
+        try
+        {
+            // Try to format as a URL?
+            url = new URL(resource);
+        }
+        catch(MalformedURLException e)
+        {
+            if(!resource.startsWith("ftp:") &&
+               !resource.startsWith("file:") &&
+               !resource.startsWith("jar:"))
+            {
+                try
+                {
+                    // It's a file.
+                    if (resource.startsWith("./"))
+                        resource=resource.substring(2);
+                    
+                    File file=new File(resource).getCanonicalFile();
+                    return new FileResource(file);
+                }
+                catch(Exception e2)
+                {
+                    LOG.debug(Log.EXCEPTION,e2);
+                    throw e;
+                }
+            }
+            else
+            {
+                LOG.warn("Bad Resource: "+resource);
+                throw e;
+            }
+        }
+
+        return newResource(url);
+    }
+
+    /* ------------------------------------------------------------ */
+    public static Resource newResource(File file)
+    {
+        return new FileResource(file);
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Construct a system resource from a string.
+     * The resource is tried as classloader resource before being
+     * treated as a normal resource.
+     * @param resource Resource as string representation 
+     * @return The new Resource
+     * @throws IOException Problem accessing resource.
+     */
+    public static Resource newSystemResource(String resource)
+        throws IOException
+    {
+        URL url=null;
+        // Try to format as a URL?
+        ClassLoader loader=Thread.currentThread().getContextClassLoader();
+        if (loader!=null)
+        {
+            try
+            {
+                url = loader.getResource(resource);
+                if (url == null && resource.startsWith("/"))
+                    url = loader.getResource(resource.substring(1));
+            }
+            catch (IllegalArgumentException e)
+            {
+                // Catches scenario where a bad Windows path like "C:\dev" is
+                // improperly escaped, which various downstream classloaders
+                // tend to have a problem with
+                url = null;
+            }
+        }
+        if (url==null)
+        {
+            loader=Resource.class.getClassLoader();
+            if (loader!=null)
+            {
+                url=loader.getResource(resource);
+                if (url==null && resource.startsWith("/"))
+                    url=loader.getResource(resource.substring(1));
+            }
+        }
+        
+        if (url==null)
+        {
+            url=ClassLoader.getSystemResource(resource);
+            if (url==null && resource.startsWith("/"))
+                url=ClassLoader.getSystemResource(resource.substring(1));
+        }
+        
+        if (url==null)
+            return null;
+        
+        return newResource(url);
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Find a classpath resource.
+     */
+    public static Resource newClassPathResource(String resource)
+    {
+        return newClassPathResource(resource,true,false);
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Find a classpath resource.
+     * The {@link java.lang.Class#getResource(String)} method is used to lookup the resource. If it is not
+     * found, then the {@link Loader#getResource(Class, String)} method is used.
+     * If it is still not found, then {@link ClassLoader#getSystemResource(String)} is used.
+     * Unlike {@link ClassLoader#getSystemResource(String)} this method does not check for normal resources.
+     * @param name The relative name of the resource
+     * @param useCaches True if URL caches are to be used.
+     * @param checkParents True if forced searching of parent Classloaders is performed to work around 
+     * loaders with inverted priorities
+     * @return Resource or null
+     */
+    public static Resource newClassPathResource(String name,boolean useCaches,boolean checkParents)
+    {
+        URL url=Resource.class.getResource(name);
+        
+        if (url==null)
+            url=Loader.getResource(Resource.class,name);
+        if (url==null)
+            return null;
+        return newResource(url,useCaches);
+    }
+    
+    /* ------------------------------------------------------------ */
+    public static boolean isContainedIn (Resource r, Resource containingResource) throws MalformedURLException
+    {
+        return r.isContainedIn(containingResource);
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    protected void finalize()
+    {
+        close();
+    }
+    
+    /* ------------------------------------------------------------ */
+    public abstract boolean isContainedIn (Resource r) throws MalformedURLException;
+    
+    
+    /* ------------------------------------------------------------ */
+    /** Release any temporary resources held by the resource.
+     * @deprecated use {@link #close()}
+     */
+    public final void release()
+    {
+        close();
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Release any temporary resources held by the resource.
+     */
+    @Override
+    public abstract void close();
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Returns true if the respresened resource exists.
+     */
+    public abstract boolean exists();
+    
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Returns true if the respresenetd resource is a container/directory.
+     * If the resource is not a file, resources ending with "/" are
+     * considered directories.
+     */
+    public abstract boolean isDirectory();
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Returns the last modified time
+     */
+    public abstract long lastModified();
+
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Return the length of the resource
+     */
+    public abstract long length();
+    
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Returns an URL representing the given resource
+     */
+    public abstract URL getURL();
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Returns an URI representing the given resource
+     */
+    public URI getURI()
+    {
+        try
+        {
+            return getURL().toURI();
+        }
+        catch(Exception e)
+        {
+            throw new RuntimeException(e);
+        }
+    }
+    
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Returns an File representing the given resource or NULL if this
+     * is not possible.
+     */
+    public abstract File getFile()
+        throws IOException;
+    
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Returns the name of the resource
+     */
+    public abstract String getName();
+    
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Returns an input stream to the resource
+     */
+    public abstract InputStream getInputStream()
+        throws java.io.IOException;
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * Returns an readable bytechannel to the resource or null if one is not available.
+     */
+    public abstract ReadableByteChannel getReadableByteChannel()
+        throws java.io.IOException;
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Deletes the given resource
+     */
+    public abstract boolean delete()
+        throws SecurityException;
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * Rename the given resource
+     */
+    public abstract boolean renameTo( Resource dest)
+        throws SecurityException;
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * Returns a list of resource names contained in the given resource
+     * The resource names are not URL encoded.
+     */
+    public abstract String[] list();
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Returns the resource contained inside the current resource with the
+     * given name.
+     * @param path The path segment to add, which is not encoded
+     */
+    public abstract Resource addPath(String path)
+        throws IOException,MalformedURLException;
+
+    /* ------------------------------------------------------------ */
+    /** Get a resource from within this resource.
+     * <p>
+     * This method is essentially an alias for {@link #addPath(String)}, but without checked exceptions.
+     * This method satisfied the {@link ResourceFactory} interface.
+     * @see org.eclipse.jetty.util.resource.ResourceFactory#getResource(java.lang.String)
+     */
+    @Override
+    public Resource getResource(String path)
+    {
+        try
+        {
+            return addPath(path);
+        }
+        catch(Exception e)
+        {
+            LOG.debug(e);
+            return null;
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /** 
+     * @deprecated
+     */
+    public String encode(String uri)
+    {
+        return null;
+    }
+        
+    /* ------------------------------------------------------------ */
+    public Object getAssociate()
+    {
+        return _associate;
+    }
+
+    /* ------------------------------------------------------------ */
+    public void setAssociate(Object o)
+    {
+        _associate=o;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * @return The canonical Alias of this resource or null if none.
+     */
+    public URI getAlias()
+    {
+        return null;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** Get the resource list as a HTML directory listing.
+     * @param base The base URL
+     * @param parent True if the parent directory should be included
+     * @return String of HTML
+     */
+    public String getListHTML(String base,boolean parent)
+        throws IOException
+    {
+        base=URIUtil.canonicalPath(base);
+        if (base==null || !isDirectory())
+            return null;
+        
+        String[] ls = list();
+        if (ls==null)
+            return null;
+        Arrays.sort(ls);
+        
+        String decodedBase = URIUtil.decodePath(base);
+        String title = "Directory: "+deTag(decodedBase);
+
+        StringBuilder buf=new StringBuilder(4096);
+        buf.append("<HTML><HEAD>");
+        buf.append("<LINK HREF=\"").append("jetty-dir.css").append("\" REL=\"stylesheet\" TYPE=\"text/css\"/><TITLE>");
+        buf.append(title);
+        buf.append("</TITLE></HEAD><BODY>\n<H1>");
+        buf.append(title);
+        buf.append("</H1>\n<TABLE BORDER=0>\n");
+        
+        if (parent)
+        {
+            buf.append("<TR><TD><A HREF=\"");
+            buf.append(URIUtil.addPaths(base,"../"));
+            buf.append("\">Parent Directory</A></TD><TD></TD><TD></TD></TR>\n");
+        }
+        
+        String encodedBase = hrefEncodeURI(base);
+        
+        DateFormat dfmt=DateFormat.getDateTimeInstance(DateFormat.MEDIUM,
+                                                       DateFormat.MEDIUM);
+        for (int i=0 ; i< ls.length ; i++)
+        {
+            Resource item = addPath(ls[i]);
+            
+            buf.append("\n<TR><TD><A HREF=\"");
+            String path=URIUtil.addPaths(encodedBase,URIUtil.encodePath(ls[i]));
+            
+            buf.append(path);
+            
+            if (item.isDirectory() && !path.endsWith("/"))
+                buf.append(URIUtil.SLASH);
+            
+            // URIUtil.encodePath(buf,path);
+            buf.append("\">");
+            buf.append(deTag(ls[i]));
+            buf.append("&nbsp;");
+            buf.append("</A></TD><TD ALIGN=right>");
+            buf.append(item.length());
+            buf.append(" bytes&nbsp;</TD><TD>");
+            buf.append(dfmt.format(new Date(item.lastModified())));
+            buf.append("</TD></TR>");
+        }
+        buf.append("</TABLE>\n");
+       buf.append("</BODY></HTML>\n");
+        
+        return buf.toString();
+    }
+    
+    /**
+     * Encode any characters that could break the URI string in an HREF.
+     * Such as <a href="/path/to;<script>Window.alert("XSS"+'%20'+"here");</script>">Link</a>
+     * 
+     * The above example would parse incorrectly on various browsers as the "<" or '"' characters
+     * would end the href attribute value string prematurely.
+     * 
+     * @param raw the raw text to encode.
+     * @return the defanged text.
+     */
+    private static String hrefEncodeURI(String raw) 
+    {
+        StringBuffer buf = null;
+
+        loop:
+        for (int i=0;i<raw.length();i++)
+        {
+            char c=raw.charAt(i);
+            switch(c)
+            {
+                case '\'':
+                case '"':
+                case '<':
+                case '>':
+                    buf=new StringBuffer(raw.length()<<1);
+                    break loop;
+            }
+        }
+        if (buf==null)
+            return raw;
+
+        for (int i=0;i<raw.length();i++)
+        {
+            char c=raw.charAt(i);       
+            switch(c)
+            {
+              case '"':
+                  buf.append("%22");
+                  continue;
+              case '\'':
+                  buf.append("%27");
+                  continue;
+              case '<':
+                  buf.append("%3C");
+                  continue;
+              case '>':
+                  buf.append("%3E");
+                  continue;
+              default:
+                  buf.append(c);
+                  continue;
+            }
+        }
+
+        return buf.toString();
+    }
+    
+    private static String deTag(String raw) 
+    {
+        return StringUtil.replace( StringUtil.replace(raw,"<","&lt;"), ">", "&gt;");
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** 
+     * @param out 
+     * @param start First byte to write
+     * @param count Bytes to write or -1 for all of them.
+     */
+    public void writeTo(OutputStream out,long start,long count)
+        throws IOException
+    {
+        try (InputStream in = getInputStream())
+        {
+            in.skip(start);
+            if (count<0)
+                IO.copy(in,out);
+            else
+                IO.copy(in,out,count);
+        }
+    }    
+    
+    /* ------------------------------------------------------------ */
+    public void copyTo(File destination)
+        throws IOException
+    {
+        if (destination.exists())
+            throw new IllegalArgumentException(destination+" exists");
+        try (OutputStream out = new FileOutputStream(destination))
+        {
+            writeTo(out,0,-1);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    public String getWeakETag()
+    {
+        try
+        {
+            StringBuilder b = new StringBuilder(32);
+            b.append("W/\"");
+            
+            String name=getName();
+            int length=name.length();
+            long lhash=0;
+            for (int i=0; i<length;i++)
+                lhash=31*lhash+name.charAt(i);
+            
+            B64Code.encode(lastModified()^lhash,b);
+            B64Code.encode(length()^lhash,b);
+            b.append('"');
+            return b.toString();
+        } 
+        catch (IOException e)
+        {
+            throw new RuntimeException(e);
+        }
+    }
+    
+    /* ------------------------------------------------------------ */
+    public Collection<Resource> getAllResources()
+    {
+        try
+        {
+            ArrayList<Resource> deep=new ArrayList<>();
+            {
+                String[] list=list();
+                if (list!=null)
+                {
+                    for (String i:list)
+                    {
+                        Resource r=addPath(i);
+                        if (r.isDirectory())
+                            deep.addAll(r.getAllResources());
+                        else
+                            deep.add(r);
+                    }
+                }
+            }
+            return deep;
+        }
+        catch(Exception e)
+        {
+            throw new IllegalStateException(e);
+        }
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** Generate a properly encoded URL from a {@link File} instance.
+     * @param file Target file. 
+     * @return URL of the target file.
+     * @throws MalformedURLException 
+     */
+    public static URL toURL(File file) throws MalformedURLException
+    {
+        return file.toURI().toURL();
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/util/resource/ResourceCollection.java b/lib/jetty/org/eclipse/jetty/util/resource/ResourceCollection.java
new file mode 100644 (file)
index 0000000..8135e0b
--- /dev/null
@@ -0,0 +1,493 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util.resource;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.nio.channels.ReadableByteChannel;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.StringTokenizer;
+
+import org.eclipse.jetty.util.URIUtil;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/**
+ * A collection of resources (dirs).
+ * Allows webapps to have multiple (static) sources.
+ * The first resource in the collection is the main resource.
+ * If a resource is not found in the main resource, it looks it up in 
+ * the order the resources were constructed.
+ * 
+ * 
+ *
+ */
+public class ResourceCollection extends Resource
+{
+    private static final Logger LOG = Log.getLogger(ResourceCollection.class);
+    private Resource[] _resources;
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Instantiates an empty resource collection.
+     * 
+     * This constructor is used when configuring jetty-maven-plugin.
+     */
+    public ResourceCollection()
+    {
+        _resources = new Resource[0];
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * Instantiates a new resource collection.
+     *
+     * @param resources the resources to be added to collection
+     */
+    public ResourceCollection(Resource... resources)
+    {
+        List<Resource> list = new ArrayList<Resource>();
+        for (Resource r : resources)
+        {
+            if (r==null)
+                continue;
+            if (r instanceof ResourceCollection)
+            {
+                for (Resource r2 : ((ResourceCollection)r).getResources())
+                    list.add(r2);
+            }
+            else
+                list.add(r);
+        }
+        _resources = list.toArray(new Resource[list.size()]);
+        for(Resource r : _resources)
+        {
+            if(!r.exists() || !r.isDirectory())
+                throw new IllegalArgumentException(r + " is not an existing directory.");
+        }
+    }
+    
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * Instantiates a new resource collection.
+     *
+     * @param resources the resource strings to be added to collection
+     */
+    public ResourceCollection(String[] resources)
+    {
+        _resources = new Resource[resources.length];
+        try
+        {
+            for(int i=0; i<resources.length; i++)
+            {
+                _resources[i] = Resource.newResource(resources[i]);
+                if(!_resources[i].exists() || !_resources[i].isDirectory())
+                    throw new IllegalArgumentException(_resources[i] + " is not an existing directory.");
+            }
+        }
+        catch(IllegalArgumentException e)
+        {
+            throw e;
+        }
+        catch(Exception e)
+        {
+            throw new RuntimeException(e);
+        }
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * Instantiates a new resource collection.
+     *
+     * @param csvResources the string containing comma-separated resource strings
+     */
+    public ResourceCollection(String csvResources)
+    {
+        setResourcesAsCSV(csvResources);
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * Retrieves the resource collection's resources.
+     * 
+     * @return the resource array
+     */
+    public Resource[] getResources()
+    {
+        return _resources;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * Sets the resource collection's resources.
+     *
+     * @param resources the new resource array
+     */
+    public void setResources(Resource[] resources)
+    {
+        _resources = resources != null ? resources : new Resource[0];
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Sets the resources as string of comma-separated values.
+     * This method should be used when configuring jetty-maven-plugin.
+     *
+     * @param csvResources the comma-separated string containing
+     *                     one or more resource strings.
+     */
+    public void setResourcesAsCSV(String csvResources)
+    {
+        StringTokenizer tokenizer = new StringTokenizer(csvResources, ",;");
+        int len = tokenizer.countTokens();
+        if(len==0)
+        {
+            throw new IllegalArgumentException("ResourceCollection@setResourcesAsCSV(String) " +
+                    " argument must be a string containing one or more comma-separated resource strings.");
+        }
+        
+        List<Resource> resources = new ArrayList<>();
+        
+        try
+        {            
+            while(tokenizer.hasMoreTokens())
+            {
+                Resource resource = Resource.newResource(tokenizer.nextToken().trim());
+                if(!resource.exists() || !resource.isDirectory())
+                    LOG.warn(" !exist "+resource);
+                else
+                    resources.add(resource);
+            }
+        }
+        catch(Exception e)
+        {
+            throw new RuntimeException(e);
+        }
+
+        _resources = resources.toArray(new Resource[resources.size()]);
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * @param path The path segment to add
+     * @return The contained resource (found first) in the collection of resources
+     */
+    @Override
+    public Resource addPath(String path) throws IOException, MalformedURLException
+    {
+        if(_resources==null)
+            throw new IllegalStateException("*resources* not set.");
+        
+        if(path==null)
+            throw new MalformedURLException();
+        
+        if(path.length()==0 || URIUtil.SLASH.equals(path))
+            return this;
+        
+        Resource resource=null;
+        ArrayList<Resource> resources = null;
+        int i=0;
+        for(; i<_resources.length; i++)
+        {
+            resource = _resources[i].addPath(path);  
+            if (resource.exists())
+            {
+                if (resource.isDirectory())
+                    break;       
+                return resource;
+            }
+        }  
+
+        for(i++; i<_resources.length; i++)
+        {
+            Resource r = _resources[i].addPath(path); 
+            if (r.exists() && r.isDirectory())
+            {
+                if (resources==null)
+                    resources = new ArrayList<Resource>();
+                    
+                if (resource!=null)
+                {
+                    resources.add(resource);
+                    resource=null;
+                }
+                
+                resources.add(r);
+            }
+        }
+
+        if (resource!=null)
+            return resource;
+        if (resources!=null)
+            return new ResourceCollection(resources.toArray(new Resource[resources.size()]));
+        return null;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * @param path
+     * @return the resource(file) if found, returns a list of resource dirs if its a dir, else null.
+     * @throws IOException
+     * @throws MalformedURLException
+     */
+    protected Object findResource(String path) throws IOException, MalformedURLException
+    {        
+        Resource resource=null;
+        ArrayList<Resource> resources = null;
+        int i=0;
+        for(; i<_resources.length; i++)
+        {
+            resource = _resources[i].addPath(path);  
+            if (resource.exists())
+            {
+                if (resource.isDirectory())
+                    break;
+               
+                return resource;
+            }
+        }  
+
+        for(i++; i<_resources.length; i++)
+        {
+            Resource r = _resources[i].addPath(path); 
+            if (r.exists() && r.isDirectory())
+            {
+                if (resource!=null)
+                {
+                    resources = new ArrayList<Resource>();
+                    resources.add(resource);
+                }
+                resources.add(r);
+            }
+        }
+        
+        if (resource!=null)
+            return resource;
+        if (resources!=null)
+            return resources;
+        return null;
+    }
+    
+    /* ------------------------------------------------------------ */
+    @Override
+    public boolean delete() throws SecurityException
+    {
+        throw new UnsupportedOperationException();
+    }
+    
+    /* ------------------------------------------------------------ */
+    @Override
+    public boolean exists()
+    {
+        if(_resources==null)
+            throw new IllegalStateException("*resources* not set.");
+        
+        return true;
+    }
+    
+    /* ------------------------------------------------------------ */
+    @Override
+    public File getFile() throws IOException
+    {
+        if(_resources==null)
+            throw new IllegalStateException("*resources* not set.");
+        
+        for(Resource r : _resources)
+        {
+            File f = r.getFile();
+            if(f!=null)
+                return f;
+        }
+        return null;
+    }
+    
+    /* ------------------------------------------------------------ */
+    @Override
+    public InputStream getInputStream() throws IOException
+    {
+        if(_resources==null)
+            throw new IllegalStateException("*resources* not set.");
+        
+        for(Resource r : _resources)
+        {
+            InputStream is = r.getInputStream();
+            if(is!=null)
+                return is;
+        }
+        return null;
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override 
+    public ReadableByteChannel getReadableByteChannel() throws IOException
+    {
+        if(_resources==null)
+            throw new IllegalStateException("*resources* not set.");
+        
+        for(Resource r : _resources)
+        {
+            ReadableByteChannel channel = r.getReadableByteChannel();
+            if(channel!=null)
+                return channel;
+        }
+        return null;
+    }
+    
+    /* ------------------------------------------------------------ */
+    @Override
+    public String getName()
+    {
+        if(_resources==null)
+            throw new IllegalStateException("*resources* not set.");
+        
+        for(Resource r : _resources)
+        {
+            String name = r.getName();
+            if(name!=null)
+                return name;
+        }
+        return null;
+    }
+    
+    /* ------------------------------------------------------------ */
+    @Override
+    public URL getURL()
+    {
+        if(_resources==null)
+            throw new IllegalStateException("*resources* not set.");
+        
+        for(Resource r : _resources)
+        {
+            URL url = r.getURL();
+            if(url!=null)
+                return url;
+        }
+        return null;
+    }
+    
+    /* ------------------------------------------------------------ */
+    @Override
+    public boolean isDirectory()
+    {
+        if(_resources==null)
+            throw new IllegalStateException("*resources* not set.");
+        
+        return true;
+    }
+    
+    /* ------------------------------------------------------------ */
+    @Override
+    public long lastModified()
+    {
+        if(_resources==null)
+            throw new IllegalStateException("*resources* not set.");
+        
+        for(Resource r : _resources)
+        {
+            long lm = r.lastModified();
+            if (lm!=-1)
+                return lm;
+        }
+        return -1;
+    }
+    
+    /* ------------------------------------------------------------ */
+    @Override
+    public long length()
+    {
+        return -1;
+    }    
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * @return The list of resource names(merged) contained in the collection of resources.
+     */    
+    @Override
+    public String[] list()
+    {
+        if(_resources==null)
+            throw new IllegalStateException("*resources* not set.");
+        
+        HashSet<String> set = new HashSet<String>();
+        for(Resource r : _resources)
+        {
+            for(String s : r.list())
+                set.add(s);
+        }
+        String[] result=set.toArray(new String[set.size()]);
+        Arrays.sort(result);
+        return result;
+    }
+    
+    /* ------------------------------------------------------------ */
+    @Override
+    public void close()
+    {
+        if(_resources==null)
+            throw new IllegalStateException("*resources* not set.");
+        
+        for(Resource r : _resources)
+            r.close();
+    }
+    
+    /* ------------------------------------------------------------ */
+    @Override
+    public boolean renameTo(Resource dest) throws SecurityException
+    {
+        throw new UnsupportedOperationException();
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public void copyTo(File destination)
+        throws IOException
+    {
+        for (int r=_resources.length;r-->0;)
+            _resources[r].copyTo(destination);
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * @return the list of resources separated by a path separator
+     */
+    @Override
+    public String toString()
+    {
+        if(_resources==null)
+            return "[]";
+        
+        return String.valueOf(Arrays.asList(_resources));
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public boolean isContainedIn(Resource r) throws MalformedURLException
+    {
+        // TODO could look at implementing the semantic of is this collection a subset of the Resource r?
+        return false;
+    }
+
+}
diff --git a/lib/jetty/org/eclipse/jetty/util/resource/ResourceFactory.java b/lib/jetty/org/eclipse/jetty/util/resource/ResourceFactory.java
new file mode 100644 (file)
index 0000000..707a672
--- /dev/null
@@ -0,0 +1,34 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util.resource;
+
+
+/* ------------------------------------------------------------ */
+/** ResourceFactory.
+ */
+public interface ResourceFactory
+{
+    
+    /* ------------------------------------------------------------ */
+    /** Get a resource for a path.
+     * @param path The path to the resource
+     * @return The resource or null 
+     */
+    Resource getResource(String path);
+}
diff --git a/lib/jetty/org/eclipse/jetty/util/resource/URLResource.java b/lib/jetty/org/eclipse/jetty/util/resource/URLResource.java
new file mode 100644 (file)
index 0000000..b696817
--- /dev/null
@@ -0,0 +1,316 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util.resource;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.URLConnection;
+import java.nio.channels.ReadableByteChannel;
+import java.security.Permission;
+
+import org.eclipse.jetty.util.URIUtil;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/* ------------------------------------------------------------ */
+/** Abstract resource class.
+ */
+public class URLResource extends Resource
+{
+    private static final Logger LOG = Log.getLogger(URLResource.class);
+    protected final URL _url;
+    protected final String _urlString;
+    
+    protected URLConnection _connection;
+    protected InputStream _in=null;
+    transient boolean _useCaches = Resource.__defaultUseCaches;
+    
+    /* ------------------------------------------------------------ */
+    protected URLResource(URL url, URLConnection connection)
+    {
+        _url = url;
+        _urlString=_url.toExternalForm();
+        _connection=connection;
+    }
+    
+    /* ------------------------------------------------------------ */
+    protected URLResource (URL url, URLConnection connection, boolean useCaches)
+    {
+        this (url, connection);
+        _useCaches = useCaches;
+    }
+
+    /* ------------------------------------------------------------ */
+    protected synchronized boolean checkConnection()
+    {
+        if (_connection==null)
+        {
+            try{
+                _connection=_url.openConnection();
+                _connection.setUseCaches(_useCaches);
+            }
+            catch(IOException e)
+            {
+                LOG.ignore(e);
+            }
+        }
+        return _connection!=null;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Release any resources held by the resource.
+     */
+    @Override
+    public synchronized void close()
+    {
+        if (_in!=null)
+        {
+            try{_in.close();}catch(IOException e){LOG.ignore(e);}
+            _in=null;
+        }
+
+        if (_connection!=null)
+            _connection=null;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Returns true if the represented resource exists.
+     */
+    @Override
+    public boolean exists()
+    {
+        try
+        {
+            synchronized(this)
+            {
+                if (checkConnection() && _in==null )
+                    _in = _connection.getInputStream();
+            }
+        }
+        catch (IOException e)
+        {
+            LOG.ignore(e);
+        }
+        return _in!=null;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Returns true if the represented resource is a container/directory.
+     * If the resource is not a file, resources ending with "/" are
+     * considered directories.
+     */
+    @Override
+    public boolean isDirectory()
+    {
+        return exists() && _urlString.endsWith("/");
+    }
+
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Returns the last modified time
+     */
+    @Override
+    public long lastModified()
+    {
+        if (checkConnection())
+            return _connection.getLastModified();
+        return -1;
+    }
+
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Return the length of the resource
+     */
+    @Override
+    public long length()
+    {
+        if (checkConnection())
+            return _connection.getContentLength();
+        return -1;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Returns an URL representing the given resource
+     */
+    @Override
+    public URL getURL()
+    {
+        return _url;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Returns an File representing the given resource or NULL if this
+     * is not possible.
+     */
+    @Override
+    public File getFile()
+        throws IOException
+    {
+        // Try the permission hack
+        if (checkConnection())
+        {
+            Permission perm = _connection.getPermission();
+            if (perm instanceof java.io.FilePermission)
+                return new File(perm.getName());
+        }
+
+        // Try the URL file arg
+        try {return new File(_url.getFile());}
+        catch(Exception e) {LOG.ignore(e);}
+
+        // Don't know the file
+        return null;    
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Returns the name of the resource
+     */
+    @Override
+    public String getName()
+    {
+        return _url.toExternalForm();
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Returns an input stream to the resource
+     */
+    @Override
+    public synchronized InputStream getInputStream()
+        throws java.io.IOException
+    {
+        if (!checkConnection())
+            throw new IOException( "Invalid resource");
+
+        try
+        {    
+            if( _in != null)
+            {
+                InputStream in = _in;
+                _in=null;
+                return in;
+            }
+            return _connection.getInputStream();
+        }
+        finally
+        {
+            _connection=null;
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public ReadableByteChannel getReadableByteChannel() throws IOException
+    {
+        return null;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Deletes the given resource
+     */
+    @Override
+    public boolean delete()
+        throws SecurityException
+    {
+        throw new SecurityException( "Delete not supported");
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Rename the given resource
+     */
+    @Override
+    public boolean renameTo( Resource dest)
+        throws SecurityException
+    {
+        throw new SecurityException( "RenameTo not supported");
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Returns a list of resource names contained in the given resource
+     */
+    @Override
+    public String[] list()
+    {
+        return null;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Returns the resource contained inside the current resource with the
+     * given name
+     */
+    @Override
+    public Resource addPath(String path)
+        throws IOException,MalformedURLException
+    {
+        if (path==null)
+            return null;
+
+        path = URIUtil.canonicalPath(path);
+
+        return newResource(URIUtil.addPaths(_url.toExternalForm(),path));
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public String toString()
+    {
+        return _urlString;
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public int hashCode()
+    {
+        return _urlString.hashCode();
+    }
+    
+    /* ------------------------------------------------------------ */
+    @Override
+    public boolean equals( Object o)
+    {
+        return o instanceof URLResource && _urlString.equals(((URLResource)o)._urlString);
+    }
+
+    /* ------------------------------------------------------------ */
+    public boolean getUseCaches ()
+    {
+        return _useCaches;
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public boolean isContainedIn (Resource containingResource) throws MalformedURLException
+    {
+        return false;
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/util/resource/package-info.java b/lib/jetty/org/eclipse/jetty/util/resource/package-info.java
new file mode 100644 (file)
index 0000000..f8d2428
--- /dev/null
@@ -0,0 +1,23 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+/**
+ * Jetty Util : Common Resource Utilities
+ */
+package org.eclipse.jetty.util.resource;
+
diff --git a/lib/jetty/org/eclipse/jetty/util/security/CertificateUtils.java b/lib/jetty/org/eclipse/jetty/util/security/CertificateUtils.java
new file mode 100644 (file)
index 0000000..4ed9ae2
--- /dev/null
@@ -0,0 +1,94 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util.security;
+
+import java.io.InputStream;
+import java.security.KeyStore;
+import java.security.cert.CRL;
+import java.security.cert.CertificateFactory;
+import java.util.Collection;
+
+import org.eclipse.jetty.util.resource.Resource;
+
+public class CertificateUtils
+{
+    /* ------------------------------------------------------------ */
+    public static KeyStore getKeyStore(InputStream storeStream, String storePath, String storeType, String storeProvider, String storePassword) throws Exception
+    {
+        KeyStore keystore = null;
+
+        if (storeStream != null || storePath != null)
+        {
+            InputStream inStream = storeStream;
+            try
+            {
+                if (inStream == null)
+                {
+                    inStream = Resource.newResource(storePath).getInputStream();
+                }
+                
+                if (storeProvider != null)
+                {
+                    keystore = KeyStore.getInstance(storeType, storeProvider);
+                }
+                else
+                {
+                    keystore = KeyStore.getInstance(storeType);
+                }
+    
+                keystore.load(inStream, storePassword == null ? null : storePassword.toCharArray());
+            }
+            finally
+            {
+                if (inStream != null)
+                {
+                    inStream.close();
+                }
+            }
+        }
+        
+        return keystore;
+    }
+
+    /* ------------------------------------------------------------ */
+    public static Collection<? extends CRL> loadCRL(String crlPath) throws Exception
+    {
+        Collection<? extends CRL> crlList = null;
+
+        if (crlPath != null)
+        {
+            InputStream in = null;
+            try
+            {
+                in = Resource.newResource(crlPath).getInputStream();
+                crlList = CertificateFactory.getInstance("X.509").generateCRLs(in);
+            }
+            finally
+            {
+                if (in != null)
+                {
+                    in.close();
+                }
+            }
+        }
+
+        return crlList;
+    }
+    
+}
diff --git a/lib/jetty/org/eclipse/jetty/util/security/CertificateValidator.java b/lib/jetty/org/eclipse/jetty/util/security/CertificateValidator.java
new file mode 100644 (file)
index 0000000..2ead387
--- /dev/null
@@ -0,0 +1,343 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util.security;
+
+import java.security.GeneralSecurityException;
+import java.security.InvalidParameterException;
+import java.security.KeyStore;
+import java.security.KeyStoreException;
+import java.security.Security;
+import java.security.cert.CRL;
+import java.security.cert.CertPathBuilder;
+import java.security.cert.CertPathBuilderResult;
+import java.security.cert.CertPathValidator;
+import java.security.cert.CertStore;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateException;
+import java.security.cert.CollectionCertStoreParameters;
+import java.security.cert.PKIXBuilderParameters;
+import java.security.cert.X509CertSelector;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Enumeration;
+import java.util.concurrent.atomic.AtomicLong;
+
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/**
+ * Convenience class to handle validation of certificates, aliases and keystores
+ *
+ * Allows specifying Certificate Revocation List (CRL), as well as enabling
+ * CRL Distribution Points Protocol (CRLDP) certificate extension support,
+ * and also enabling On-Line Certificate Status Protocol (OCSP) support.
+ * 
+ * IMPORTANT: at least one of the above mechanisms *MUST* be configured and
+ * operational, otherwise certificate validation *WILL FAIL* unconditionally.
+ */
+public class CertificateValidator
+{
+    private static final Logger LOG = Log.getLogger(CertificateValidator.class);
+    private static AtomicLong __aliasCount = new AtomicLong();
+    
+    private KeyStore _trustStore;
+    private Collection<? extends CRL> _crls;
+
+    /** Maximum certification path length (n - number of intermediate certs, -1 for unlimited) */
+    private int _maxCertPathLength = -1;
+    /** CRL Distribution Points (CRLDP) support */
+    private boolean _enableCRLDP = false;
+    /** On-Line Certificate Status Protocol (OCSP) support */
+    private boolean _enableOCSP = false;
+    /** Location of OCSP Responder */
+    private String _ocspResponderURL;
+    
+    /**
+     * creates an instance of the certificate validator 
+     *
+     * @param trustStore 
+     * @param crls
+     */
+    public CertificateValidator(KeyStore trustStore, Collection<? extends CRL> crls)
+    {
+        if (trustStore == null)
+        {
+            throw new InvalidParameterException("TrustStore must be specified for CertificateValidator.");
+        }
+        
+        _trustStore = trustStore;
+        _crls = crls;
+    }
+    
+    /**
+     * validates all aliases inside of a given keystore
+     * 
+     * @param keyStore
+     * @throws CertificateException
+     */
+    public void validate( KeyStore keyStore ) throws CertificateException
+    {
+        try
+        {
+            Enumeration<String> aliases = keyStore.aliases();
+            
+            for ( ; aliases.hasMoreElements(); )
+            {
+                String alias = aliases.nextElement();
+                
+                validate(keyStore,alias);
+            }
+            
+        }
+        catch ( KeyStoreException kse )
+        {
+            throw new CertificateException("Unable to retrieve aliases from keystore", kse);
+        }
+    }
+    
+
+    /**
+     * validates a specific alias inside of the keystore being passed in
+     * 
+     * @param keyStore
+     * @param keyAlias
+     * @return the keyAlias if valid
+     * @throws CertificateException
+     */
+    public String validate(KeyStore keyStore, String keyAlias) throws CertificateException
+    {
+        String result = null;
+
+        if (keyAlias != null)
+        {
+            try
+            {
+                validate(keyStore, keyStore.getCertificate(keyAlias));
+            }
+            catch (KeyStoreException kse)
+            {
+                LOG.debug(kse);
+                throw new CertificateException("Unable to validate certificate" +
+                        " for alias [" + keyAlias + "]: " + kse.getMessage(), kse);
+            }
+            result = keyAlias;            
+        }
+        
+        return result;
+    }
+    
+    /**
+     * validates a specific certificate inside of the keystore being passed in
+     * 
+     * @param keyStore
+     * @param cert
+     * @throws CertificateException
+     */
+    public void validate(KeyStore keyStore, Certificate cert) throws CertificateException
+    {
+        Certificate[] certChain = null;
+        
+        if (cert != null && cert instanceof X509Certificate)
+        {
+            ((X509Certificate)cert).checkValidity();
+            
+            String certAlias = null;
+            try
+            {
+                if (keyStore == null)
+                {
+                    throw new InvalidParameterException("Keystore cannot be null");
+                }
+
+                certAlias = keyStore.getCertificateAlias((X509Certificate)cert);
+                if (certAlias == null)
+                {
+                    certAlias = "JETTY" + String.format("%016X",__aliasCount.incrementAndGet());
+                    keyStore.setCertificateEntry(certAlias, cert);
+                }
+                
+                certChain = keyStore.getCertificateChain(certAlias);
+                if (certChain == null || certChain.length == 0)
+                {
+                    throw new IllegalStateException("Unable to retrieve certificate chain");
+                }
+            }
+            catch (KeyStoreException kse)
+            {
+                LOG.debug(kse);
+                throw new CertificateException("Unable to validate certificate" +
+                        (certAlias == null ? "":" for alias [" +certAlias + "]") + ": " + kse.getMessage(), kse);
+            }
+            
+            validate(certChain);
+        } 
+    }
+    
+    public void validate(Certificate[] certChain) throws CertificateException
+    {
+        try
+        {
+            ArrayList<X509Certificate> certList = new ArrayList<X509Certificate>();
+            for (Certificate item : certChain)
+            {
+                if (item == null)
+                    continue;
+                
+                if (!(item instanceof X509Certificate))
+                {
+                    throw new IllegalStateException("Invalid certificate type in chain");
+                }
+                
+                certList.add((X509Certificate)item);
+            }
+    
+            if (certList.isEmpty())
+            {
+                throw new IllegalStateException("Invalid certificate chain");
+                
+            }
+    
+            X509CertSelector certSelect = new X509CertSelector();
+            certSelect.setCertificate(certList.get(0));
+            
+            // Configure certification path builder parameters
+            PKIXBuilderParameters pbParams = new PKIXBuilderParameters(_trustStore, certSelect);
+            pbParams.addCertStore(CertStore.getInstance("Collection", new CollectionCertStoreParameters(certList)));
+    
+            // Set maximum certification path length
+            pbParams.setMaxPathLength(_maxCertPathLength);
+    
+            // Enable revocation checking
+            pbParams.setRevocationEnabled(true);
+    
+            // Set static Certificate Revocation List
+            if (_crls != null && !_crls.isEmpty())
+            {
+                pbParams.addCertStore(CertStore.getInstance("Collection", new CollectionCertStoreParameters(_crls)));
+            }
+    
+            // Enable On-Line Certificate Status Protocol (OCSP) support
+            if (_enableOCSP)
+            {
+                Security.setProperty("ocsp.enable","true");
+            }
+            // Enable Certificate Revocation List Distribution Points (CRLDP) support
+            if (_enableCRLDP)
+            {
+                System.setProperty("com.sun.security.enableCRLDP","true");
+            }
+    
+            // Build certification path
+            CertPathBuilderResult buildResult = CertPathBuilder.getInstance("PKIX").build(pbParams);               
+            
+            // Validate certification path
+            CertPathValidator.getInstance("PKIX").validate(buildResult.getCertPath(),pbParams);
+        }
+        catch (GeneralSecurityException gse)
+        {
+            LOG.debug(gse);
+            throw new CertificateException("Unable to validate certificate: " + gse.getMessage(), gse);
+        }
+    }
+
+    public KeyStore getTrustStore()
+    {
+        return _trustStore;
+    }
+
+    public Collection<? extends CRL> getCrls()
+    {
+        return _crls;
+    }
+
+    /**
+     * @return Maximum number of intermediate certificates in
+     * the certification path (-1 for unlimited)
+     */
+    public int getMaxCertPathLength()
+    {
+        return _maxCertPathLength;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param maxCertPathLength
+     *            maximum number of intermediate certificates in
+     *            the certification path (-1 for unlimited)
+     */
+    public void setMaxCertPathLength(int maxCertPathLength)
+    {
+        _maxCertPathLength = maxCertPathLength;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** 
+     * @return true if CRL Distribution Points support is enabled
+     */
+    public boolean isEnableCRLDP()
+    {
+        return _enableCRLDP;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Enables CRL Distribution Points Support
+     * @param enableCRLDP true - turn on, false - turns off
+     */
+    public void setEnableCRLDP(boolean enableCRLDP)
+    {
+        _enableCRLDP = enableCRLDP;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** 
+     * @return true if On-Line Certificate Status Protocol support is enabled
+     */
+    public boolean isEnableOCSP()
+    {
+        return _enableOCSP;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Enables On-Line Certificate Status Protocol support
+     * @param enableOCSP true - turn on, false - turn off
+     */
+    public void setEnableOCSP(boolean enableOCSP)
+    {
+        _enableOCSP = enableOCSP;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** 
+     * @return Location of the OCSP Responder
+     */
+    public String getOcspResponderURL()
+    {
+        return _ocspResponderURL;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Set the location of the OCSP Responder.
+     * @param ocspResponderURL location of the OCSP Responder
+     */
+    public void setOcspResponderURL(String ocspResponderURL)
+    {
+        _ocspResponderURL = ocspResponderURL;
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/util/security/Constraint.java b/lib/jetty/org/eclipse/jetty/util/security/Constraint.java
new file mode 100644 (file)
index 0000000..28c003b
--- /dev/null
@@ -0,0 +1,254 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util.security;
+
+import java.io.Serializable;
+import java.util.Arrays;
+
+/* ------------------------------------------------------------ */
+/**
+ * Constraint
+ * 
+ * Describe an auth and/or data constraint.
+ * 
+ * 
+ */
+public class Constraint implements Cloneable, Serializable
+{
+    /* ------------------------------------------------------------ */
+    public final static String __BASIC_AUTH = "BASIC";
+
+    public final static String __FORM_AUTH = "FORM";
+
+    public final static String __DIGEST_AUTH = "DIGEST";
+
+    public final static String __CERT_AUTH = "CLIENT_CERT";
+
+    public final static String __CERT_AUTH2 = "CLIENT-CERT";
+    
+    public final static String __SPNEGO_AUTH = "SPNEGO";
+    
+    public final static String __NEGOTIATE_AUTH = "NEGOTIATE";
+    
+    public static boolean validateMethod (String method)
+    {
+        if (method == null)
+            return false;
+        method = method.trim();
+        return (method.equals(__FORM_AUTH) 
+                || method.equals(__BASIC_AUTH) 
+                || method.equals (__DIGEST_AUTH) 
+                || method.equals (__CERT_AUTH) 
+                || method.equals(__CERT_AUTH2)
+                || method.equals(__SPNEGO_AUTH)
+                || method.equals(__NEGOTIATE_AUTH));
+    }
+
+    /* ------------------------------------------------------------ */
+    public final static int DC_UNSET = -1, DC_NONE = 0, DC_INTEGRAL = 1, DC_CONFIDENTIAL = 2, DC_FORBIDDEN = 3;
+
+    /* ------------------------------------------------------------ */
+    public final static String NONE = "NONE";
+
+    public final static String ANY_ROLE = "*";
+    
+    public final static String ANY_AUTH = "**"; //Servlet Spec 3.1 pg 140
+
+    /* ------------------------------------------------------------ */
+    private String _name;
+
+    private String[] _roles;
+
+    private int _dataConstraint = DC_UNSET;
+
+    private boolean _anyRole = false;
+    
+    private boolean _anyAuth = false;
+
+    private boolean _authenticate = false;
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Constructor.
+     */
+    public Constraint()
+    {
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Conveniance Constructor.
+     * 
+     * @param name
+     * @param role
+     */
+    public Constraint(String name, String role)
+    {
+        setName(name);
+        setRoles(new String[] { role });
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public Object clone() throws CloneNotSupportedException
+    {
+        return super.clone();
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param name
+     */
+    public void setName(String name)
+    {
+        _name = name;
+    }
+
+    /* ------------------------------------------------------------ */
+    public String getName()
+    {
+        return _name;
+    }
+
+    /* ------------------------------------------------------------ */
+    public void setRoles(String[] roles)
+    {
+        _roles = roles;
+        _anyRole = false;
+        _anyAuth = false;
+        if (roles != null) 
+        {
+            for (int i = roles.length; i-- > 0;)
+            {
+                _anyRole |= ANY_ROLE.equals(roles[i]);
+                _anyAuth |= ANY_AUTH.equals(roles[i]);
+            }
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return True if any user role is permitted.
+     */
+    public boolean isAnyRole()
+    {
+        return _anyRole;
+    }
+    
+    
+    /* ------------------------------------------------------------ */
+    /** Servlet Spec 3.1, pg 140
+     * @return True if any authenticated user is permitted (ie a role "**" was specified in the constraint).
+     */
+    public boolean isAnyAuth()
+    {
+        return _anyAuth;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return List of roles for this constraint.
+     */
+    public String[] getRoles()
+    {
+        return _roles;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param role
+     * @return True if the constraint contains the role.
+     */
+    public boolean hasRole(String role)
+    {
+        if (_anyRole) return true;
+        if (_roles != null) for (int i = _roles.length; i-- > 0;)
+            if (role.equals(_roles[i])) return true;
+        return false;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param authenticate True if users must be authenticated
+     */
+    public void setAuthenticate(boolean authenticate)
+    {
+        _authenticate = authenticate;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return True if the constraint requires request authentication
+     */
+    public boolean getAuthenticate()
+    {
+        return _authenticate;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return True if authentication required but no roles set
+     */
+    public boolean isForbidden()
+    {
+        return _authenticate && !_anyRole && (_roles == null || _roles.length == 0);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param c Data constrain indicator: 0=DC+NONE, 1=DC_INTEGRAL &
+     *                2=DC_CONFIDENTIAL
+     */
+    public void setDataConstraint(int c)
+    {
+        if (c < 0 || c > DC_CONFIDENTIAL) throw new IllegalArgumentException("Constraint out of range");
+        _dataConstraint = c;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Data constrain indicator: 0=DC+NONE, 1=DC_INTEGRAL &
+     *         2=DC_CONFIDENTIAL
+     */
+    public int getDataConstraint()
+    {
+        return _dataConstraint;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return True if a data constraint has been set.
+     */
+    public boolean hasDataConstraint()
+    {
+        return _dataConstraint >= DC_NONE;
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public String toString()
+    {
+        return "SC{" + _name
+               + ","
+               + (_anyRole ? "*" : (_roles == null ? "-" : Arrays.asList(_roles).toString()))
+               + ","
+               + (_dataConstraint == DC_UNSET ? "DC_UNSET}" : (_dataConstraint == DC_NONE ? "NONE}" : (_dataConstraint == DC_INTEGRAL ? "INTEGRAL}" : "CONFIDENTIAL}")));
+    }
+
+}
diff --git a/lib/jetty/org/eclipse/jetty/util/security/Credential.java b/lib/jetty/org/eclipse/jetty/util/security/Credential.java
new file mode 100644 (file)
index 0000000..1feb604
--- /dev/null
@@ -0,0 +1,229 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util.security;
+
+import java.io.Serializable;
+import java.nio.charset.StandardCharsets;
+import java.security.MessageDigest;
+
+import org.eclipse.jetty.util.TypeUtil;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/* ------------------------------------------------------------ */
+/**
+ * Credentials. The Credential class represents an abstract mechanism for
+ * checking authentication credentials. A credential instance either represents
+ * a secret, or some data that could only be derived from knowing the secret.
+ * <p>
+ * Often a Credential is related to a Password via a one way algorithm, so while
+ * a Password itself is a Credential, a UnixCrypt or MD5 digest of a a password
+ * is only a credential that can be checked against the password.
+ * <p>
+ * This class includes an implementation for unix Crypt an MD5 digest.
+ * 
+ * @see Password
+ * 
+ */
+public abstract class Credential implements Serializable
+{
+    private static final Logger LOG = Log.getLogger(Credential.class);
+
+    private static final long serialVersionUID = -7760551052768181572L;
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Check a credential
+     * 
+     * @param credentials The credential to check against. This may either be
+     *                another Credential object, a Password object or a String
+     *                which is interpreted by this credential.
+     * @return True if the credentials indicated that the shared secret is known
+     *         to both this Credential and the passed credential.
+     */
+    public abstract boolean check(Object credentials);
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Get a credential from a String. If the credential String starts with a
+     * known Credential type (eg "CRYPT:" or "MD5:" ) then a Credential of that
+     * type is returned. Else the credential is assumed to be a Password.
+     * 
+     * @param credential String representation of the credential
+     * @return A Credential or Password instance.
+     */
+    public static Credential getCredential(String credential)
+    {
+        if (credential.startsWith(Crypt.__TYPE)) return new Crypt(credential);
+        if (credential.startsWith(MD5.__TYPE)) return new MD5(credential);
+
+        return new Password(credential);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Unix Crypt Credentials
+     */
+    public static class Crypt extends Credential
+    {
+        private static final long serialVersionUID = -2027792997664744210L;
+
+        public static final String __TYPE = "CRYPT:";
+
+        private final String _cooked;
+
+        Crypt(String cooked)
+        {
+            _cooked = cooked.startsWith(Crypt.__TYPE) ? cooked.substring(__TYPE.length()) : cooked;
+        }
+
+        @Override
+        public boolean check(Object credentials)
+        {
+            if (credentials instanceof char[])
+                credentials=new String((char[])credentials);
+            if (!(credentials instanceof String) && !(credentials instanceof Password)) 
+                LOG.warn("Can't check " + credentials.getClass() + " against CRYPT");
+
+            String passwd = credentials.toString();
+            return _cooked.equals(UnixCrypt.crypt(passwd, _cooked));
+        }
+
+        public static String crypt(String user, String pw)
+        {
+            return "CRYPT:" + UnixCrypt.crypt(pw, user);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * MD5 Credentials
+     */
+    public static class MD5 extends Credential
+    {
+        private static final long serialVersionUID = 5533846540822684240L;
+
+        public static final String __TYPE = "MD5:";
+
+        public static final Object __md5Lock = new Object();
+
+        private static MessageDigest __md;
+
+        private final byte[] _digest;
+
+        /* ------------------------------------------------------------ */
+        MD5(String digest)
+        {
+            digest = digest.startsWith(__TYPE) ? digest.substring(__TYPE.length()) : digest;
+            _digest = TypeUtil.parseBytes(digest, 16);
+        }
+
+        /* ------------------------------------------------------------ */
+        public byte[] getDigest()
+        {
+            return _digest;
+        }
+
+        /* ------------------------------------------------------------ */
+        @Override
+        public boolean check(Object credentials)
+        {
+            try
+            {
+                byte[] digest = null;
+
+                if (credentials instanceof char[])
+                    credentials=new String((char[])credentials);
+                if (credentials instanceof Password || credentials instanceof String)
+                {
+                    synchronized (__md5Lock)
+                    {
+                        if (__md == null) __md = MessageDigest.getInstance("MD5");
+                        __md.reset();
+                        __md.update(credentials.toString().getBytes(StandardCharsets.ISO_8859_1));
+                        digest = __md.digest();
+                    }
+                    if (digest == null || digest.length != _digest.length) return false;
+                    for (int i = 0; i < digest.length; i++)
+                        if (digest[i] != _digest[i]) return false;
+                    return true;
+                }
+                else if (credentials instanceof MD5)
+                {
+                    MD5 md5 = (MD5) credentials;
+                    if (_digest.length != md5._digest.length) return false;
+                    for (int i = 0; i < _digest.length; i++)
+                        if (_digest[i] != md5._digest[i]) return false;
+                    return true;
+                }
+                else if (credentials instanceof Credential)
+                {
+                    // Allow credential to attempt check - i.e. this'll work
+                    // for DigestAuthModule$Digest credentials
+                    return ((Credential) credentials).check(this);
+                }
+                else
+                {
+                    LOG.warn("Can't check " + credentials.getClass() + " against MD5");
+                    return false;
+                }
+            }
+            catch (Exception e)
+            {
+                LOG.warn(e);
+                return false;
+            }
+        }
+
+        /* ------------------------------------------------------------ */
+        public static String digest(String password)
+        {
+            try
+            {
+                byte[] digest;
+                synchronized (__md5Lock)
+                {
+                    if (__md == null)
+                    {
+                        try
+                        {
+                            __md = MessageDigest.getInstance("MD5");
+                        }
+                        catch (Exception e)
+                        {
+                            LOG.warn(e);
+                            return null;
+                        }
+                    }
+
+                    __md.reset();
+                    __md.update(password.getBytes(StandardCharsets.ISO_8859_1));
+                    digest = __md.digest();
+                }
+
+                return __TYPE + TypeUtil.toString(digest, 16);
+            }
+            catch (Exception e)
+            {
+                LOG.warn(e);
+                return null;
+            }
+        }
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/util/security/Password.java b/lib/jetty/org/eclipse/jetty/util/security/Password.java
new file mode 100644 (file)
index 0000000..13160c1
--- /dev/null
@@ -0,0 +1,267 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util.security;
+
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/* ------------------------------------------------------------ */
+/**
+ * Password utility class.
+ * 
+ * This utility class gets a password or pass phrase either by:
+ * 
+ * <PRE>
+ *  + Password is set as a system property.
+ *  + The password is prompted for and read from standard input
+ *  + A program is run to get the password.
+ * </pre>
+ * 
+ * Passwords that begin with OBF: are de obfuscated. Passwords can be obfuscated
+ * by run org.eclipse.util.Password as a main class. Obfuscated password are
+ * required if a system needs to recover the full password (eg. so that it may
+ * be passed to another system). They are not secure, but prevent casual
+ * observation.
+ * <p>
+ * Passwords that begin with CRYPT: are oneway encrypted with UnixCrypt. The
+ * real password cannot be retrieved, but comparisons can be made to other
+ * passwords. A Crypt can be generated by running org.eclipse.util.UnixCrypt as
+ * a main class, passing password and then the username. Checksum passwords are
+ * a secure(ish) way to store passwords that only need to be checked rather than
+ * recovered. Note that it is not strong security - specially if simple
+ * passwords are used.
+ * 
+ * 
+ */
+public class Password extends Credential
+{
+    private static final Logger LOG = Log.getLogger(Password.class);
+
+    private static final long serialVersionUID = 5062906681431569445L;
+
+    public static final String __OBFUSCATE = "OBF:";
+
+    private String _pw;
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Constructor.
+     * 
+     * @param password The String password.
+     */
+    public Password(String password)
+    {
+        _pw = password;
+
+        // expand password
+        while (_pw != null && _pw.startsWith(__OBFUSCATE))
+            _pw = deobfuscate(_pw);
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public String toString()
+    {
+        return _pw;
+    }
+
+    /* ------------------------------------------------------------ */
+    public String toStarString()
+    {
+        return "*****************************************************".substring(0, _pw.length());
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public boolean check(Object credentials)
+    {
+        if (this == credentials) return true;
+
+        if (credentials instanceof Password) return credentials.equals(_pw);
+
+        if (credentials instanceof String) return credentials.equals(_pw);
+
+        if (credentials instanceof char[]) return Arrays.equals(_pw.toCharArray(), (char[]) credentials);
+
+        if (credentials instanceof Credential) return ((Credential) credentials).check(_pw);
+
+        return false;
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public boolean equals(Object o)
+    {
+        if (this == o) 
+            return true;
+
+        if (null == o) 
+            return false;
+
+        if (o instanceof Password)
+        {
+            Password p = (Password) o;
+            //noinspection StringEquality
+            return p._pw == _pw || (null != _pw && _pw.equals(p._pw));
+        }
+
+        if (o instanceof String) 
+            return o.equals(_pw);
+
+        return false;
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public int hashCode()
+    {
+        return null == _pw ? super.hashCode() : _pw.hashCode();
+    }
+
+    /* ------------------------------------------------------------ */
+    public static String obfuscate(String s)
+    {
+        StringBuilder buf = new StringBuilder();
+        byte[] b = s.getBytes(StandardCharsets.UTF_8);
+
+        buf.append(__OBFUSCATE);
+        for (int i = 0; i < b.length; i++)
+        {
+            byte b1 = b[i];
+            byte b2 = b[b.length - (i + 1)];
+            if (b1<0 || b2<0)
+            {
+                int i0 = (0xff&b1)*256 + (0xff&b2); 
+                String x = Integer.toString(i0, 36).toLowerCase();
+                buf.append("U0000",0,5-x.length());
+                buf.append(x);
+            }
+            else
+            {
+                int i1 = 127 + b1 + b2;
+                int i2 = 127 + b1 - b2;
+                int i0 = i1 * 256 + i2;
+                String x = Integer.toString(i0, 36).toLowerCase();
+
+                int j0 = Integer.parseInt(x, 36);
+                int j1 = (i0 / 256);
+                int j2 = (i0 % 256);
+                byte bx = (byte) ((j1 + j2 - 254) / 2);
+                
+                buf.append("000",0,4-x.length());
+                buf.append(x);
+            }
+
+        }
+        return buf.toString();
+
+    }
+
+    /* ------------------------------------------------------------ */
+    public static String deobfuscate(String s)
+    {
+        if (s.startsWith(__OBFUSCATE)) s = s.substring(4);
+
+        byte[] b = new byte[s.length() / 2];
+        int l = 0;
+        for (int i = 0; i < s.length(); i += 4)
+        {
+            if (s.charAt(i)=='U')
+            {
+                i++;
+                String x = s.substring(i, i + 4);
+                int i0 = Integer.parseInt(x, 36);
+                byte bx = (byte)(i0>>8);
+                b[l++] = bx;
+            }
+            else
+            {
+                String x = s.substring(i, i + 4);
+                int i0 = Integer.parseInt(x, 36);
+                int i1 = (i0 / 256);
+                int i2 = (i0 % 256);
+                byte bx = (byte) ((i1 + i2 - 254) / 2);
+                b[l++] = bx;
+            }
+        }
+
+        return new String(b, 0, l,StandardCharsets.UTF_8);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Get a password. A password is obtained by trying
+     * <UL>
+     * <LI>Calling <Code>System.getProperty(realm,dft)</Code>
+     * <LI>Prompting for a password
+     * <LI>Using promptDft if nothing was entered.
+     * </UL>
+     * 
+     * @param realm The realm name for the password, used as a SystemProperty
+     *                name.
+     * @param dft The default password.
+     * @param promptDft The default to use if prompting for the password.
+     * @return Password
+     */
+    public static Password getPassword(String realm, String dft, String promptDft)
+    {
+        String passwd = System.getProperty(realm, dft);
+        if (passwd == null || passwd.length() == 0)
+        {
+            try
+            {
+                System.out.print(realm + ((promptDft != null && promptDft.length() > 0) ? " [dft]" : "") + " : ");
+                System.out.flush();
+                byte[] buf = new byte[512];
+                int len = System.in.read(buf);
+                if (len > 0) passwd = new String(buf, 0, len).trim();
+            }
+            catch (IOException e)
+            {
+                LOG.warn(Log.EXCEPTION, e);
+            }
+            if (passwd == null || passwd.length() == 0) passwd = promptDft;
+        }
+        return new Password(passwd);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param arg
+     */
+    public static void main(String[] arg)
+    {
+        if (arg.length != 1 && arg.length != 2)
+        {
+            System.err.println("Usage - java org.eclipse.jetty.security.Password [<user>] <password>");
+            System.err.println("If the password is ?, the user will be prompted for the password");
+            System.exit(1);
+        }
+        String p = arg[arg.length == 1 ? 0 : 1];
+        Password pw = new Password(p);
+        System.err.println(pw.toString());
+        System.err.println(obfuscate(pw.toString()));
+        System.err.println(Credential.MD5.digest(p));
+        if (arg.length == 2) System.err.println(Credential.Crypt.crypt(arg[0], pw.toString()));
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/util/security/UnixCrypt.java b/lib/jetty/org/eclipse/jetty/util/security/UnixCrypt.java
new file mode 100644 (file)
index 0000000..e3f98e8
--- /dev/null
@@ -0,0 +1,461 @@
+/*
+ * @(#)UnixCrypt.java  0.9 96/11/25
+ *
+ * Copyright (c) 1996 Aki Yoshida. All rights reserved.
+ *
+ * Permission to use, copy, modify and distribute this software
+ * for non-commercial or commercial purposes and without fee is
+ * hereby granted provided that this copyright notice appears in
+ * all copies.
+ */
+
+/**
+ * Unix crypt(3C) utility
+ *
+ * @version    0.9, 11/25/96
+ * @author     Aki Yoshida
+ */
+
+/**
+ * modified April 2001
+ * by Iris Van den Broeke, Daniel Deville
+ */
+
+package org.eclipse.jetty.util.security;
+
+
+/* ------------------------------------------------------------ */
+/**
+ * Unix Crypt. Implements the one way cryptography used by Unix systems for
+ * simple password protection.
+ * 
+ * @version $Id: UnixCrypt.java,v 1.1 2005/10/05 14:09:14 janb Exp $
+ * @author Greg Wilkins (gregw)
+ */
+public class UnixCrypt
+{
+
+    /* (mostly) Standard DES Tables from Tom Truscott */
+    private static final byte[] IP = { /* initial permutation */
+    58, 50, 42, 34, 26, 18, 10, 2, 60, 52, 44, 36, 28, 20, 12, 4, 62, 54, 46, 38, 30, 22, 14, 6, 64, 56, 48, 40, 32, 24, 16, 8, 57, 49, 41, 33, 25, 17, 9, 1,
+            59, 51, 43, 35, 27, 19, 11, 3, 61, 53, 45, 37, 29, 21, 13, 5, 63, 55, 47, 39, 31, 23, 15, 7 };
+
+    /* The final permutation is the inverse of IP - no table is necessary */
+    private static final byte[] ExpandTr = { /* expansion operation */
+    32, 1, 2, 3, 4, 5, 4, 5, 6, 7, 8, 9, 8, 9, 10, 11, 12, 13, 12, 13, 14, 15, 16, 17, 16, 17, 18, 19, 20, 21, 20, 21, 22, 23, 24, 25, 24, 25, 26, 27, 28, 29,
+            28, 29, 30, 31, 32, 1 };
+
+    private static final byte[] PC1 = { /* permuted choice table 1 */
+    57, 49, 41, 33, 25, 17, 9, 1, 58, 50, 42, 34, 26, 18, 10, 2, 59, 51, 43, 35, 27, 19, 11, 3, 60, 52, 44, 36,
+
+    63, 55, 47, 39, 31, 23, 15, 7, 62, 54, 46, 38, 30, 22, 14, 6, 61, 53, 45, 37, 29, 21, 13, 5, 28, 20, 12, 4 };
+
+    private static final byte[] Rotates = { /* PC1 rotation schedule */
+    1, 1, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 1 };
+
+    private static final byte[] PC2 = { /* permuted choice table 2 */
+    9, 18, 14, 17, 11, 24, 1, 5, 22, 25, 3, 28, 15, 6, 21, 10, 35, 38, 23, 19, 12, 4, 26, 8, 43, 54, 16, 7, 27, 20, 13, 2,
+
+    0, 0, 41, 52, 31, 37, 47, 55, 0, 0, 30, 40, 51, 45, 33, 48, 0, 0, 44, 49, 39, 56, 34, 53, 0, 0, 46, 42, 50, 36, 29, 32 };
+
+    private static final byte[][] S = { /* 48->32 bit substitution tables */
+            /* S[1] */
+            { 14, 4, 13, 1, 2, 15, 11, 8, 3, 10, 6, 12, 5, 9, 0, 7, 0, 15, 7, 4, 14, 2, 13, 1, 10, 6, 12, 11, 9, 5, 3, 8, 4, 1, 14, 8, 13, 6, 2, 11, 15, 12, 9,
+             7, 3, 10, 5, 0, 15, 12, 8, 2, 4, 9, 1, 7, 5, 11, 3, 14, 10, 0, 6, 13 },
+            /* S[2] */
+            { 15, 1, 8, 14, 6, 11, 3, 4, 9, 7, 2, 13, 12, 0, 5, 10, 3, 13, 4, 7, 15, 2, 8, 14, 12, 0, 1, 10, 6, 9, 11, 5, 0, 14, 7, 11, 10, 4, 13, 1, 5, 8, 12,
+             6, 9, 3, 2, 15, 13, 8, 10, 1, 3, 15, 4, 2, 11, 6, 7, 12, 0, 5, 14, 9 },
+            /* S[3] */
+            { 10, 0, 9, 14, 6, 3, 15, 5, 1, 13, 12, 7, 11, 4, 2, 8, 13, 7, 0, 9, 3, 4, 6, 10, 2, 8, 5, 14, 12, 11, 15, 1, 13, 6, 4, 9, 8, 15, 3, 0, 11, 1, 2,
+             12, 5, 10, 14, 7, 1, 10, 13, 0, 6, 9, 8, 7, 4, 15, 14, 3, 11, 5, 2, 12 },
+            /* S[4] */
+            { 7, 13, 14, 3, 0, 6, 9, 10, 1, 2, 8, 5, 11, 12, 4, 15, 13, 8, 11, 5, 6, 15, 0, 3, 4, 7, 2, 12, 1, 10, 14, 9, 10, 6, 9, 0, 12, 11, 7, 13, 15, 1, 3,
+             14, 5, 2, 8, 4, 3, 15, 0, 6, 10, 1, 13, 8, 9, 4, 5, 11, 12, 7, 2, 14 },
+            /* S[5] */
+            { 2, 12, 4, 1, 7, 10, 11, 6, 8, 5, 3, 15, 13, 0, 14, 9, 14, 11, 2, 12, 4, 7, 13, 1, 5, 0, 15, 10, 3, 9, 8, 6, 4, 2, 1, 11, 10, 13, 7, 8, 15, 9, 12,
+             5, 6, 3, 0, 14, 11, 8, 12, 7, 1, 14, 2, 13, 6, 15, 0, 9, 10, 4, 5, 3 },
+            /* S[6] */
+            { 12, 1, 10, 15, 9, 2, 6, 8, 0, 13, 3, 4, 14, 7, 5, 11, 10, 15, 4, 2, 7, 12, 9, 5, 6, 1, 13, 14, 0, 11, 3, 8, 9, 14, 15, 5, 2, 8, 12, 3, 7, 0, 4,
+             10, 1, 13, 11, 6, 4, 3, 2, 12, 9, 5, 15, 10, 11, 14, 1, 7, 6, 0, 8, 13 },
+            /* S[7] */
+            { 4, 11, 2, 14, 15, 0, 8, 13, 3, 12, 9, 7, 5, 10, 6, 1, 13, 0, 11, 7, 4, 9, 1, 10, 14, 3, 5, 12, 2, 15, 8, 6, 1, 4, 11, 13, 12, 3, 7, 14, 10, 15,
+             6, 8, 0, 5, 9, 2, 6, 11, 13, 8, 1, 4, 10, 7, 9, 5, 0, 15, 14, 2, 3, 12 },
+            /* S[8] */
+            { 13, 2, 8, 4, 6, 15, 11, 1, 10, 9, 3, 14, 5, 0, 12, 7, 1, 15, 13, 8, 10, 3, 7, 4, 12, 5, 6, 11, 0, 14, 9, 2, 7, 11, 4, 1, 9, 12, 14, 2, 0, 6, 10,
+             13, 15, 3, 5, 8, 2, 1, 14, 7, 4, 10, 8, 13, 15, 12, 9, 0, 3, 5, 6, 11 } };
+
+    private static final byte[] P32Tr = { /* 32-bit permutation function */
+    16, 7, 20, 21, 29, 12, 28, 17, 1, 15, 23, 26, 5, 18, 31, 10, 2, 8, 24, 14, 32, 27, 3, 9, 19, 13, 30, 6, 22, 11, 4, 25 };
+
+    private static final byte[] CIFP = { /*
+                                             * compressed/interleaved
+                                             * permutation
+                                             */
+    1, 2, 3, 4, 17, 18, 19, 20, 5, 6, 7, 8, 21, 22, 23, 24, 9, 10, 11, 12, 25, 26, 27, 28, 13, 14, 15, 16, 29, 30, 31, 32,
+
+    33, 34, 35, 36, 49, 50, 51, 52, 37, 38, 39, 40, 53, 54, 55, 56, 41, 42, 43, 44, 57, 58, 59, 60, 45, 46, 47, 48, 61, 62, 63, 64 };
+
+    private static final byte[] ITOA64 = { /* 0..63 => ascii-64 */
+    (byte) '.', (byte) '/', (byte) '0', (byte) '1', (byte) '2', (byte) '3', (byte) '4', (byte) '5', (byte) '6', (byte) '7', (byte) '8', (byte) '9', (byte) 'A',
+            (byte) 'B', (byte) 'C', (byte) 'D', (byte) 'E', (byte) 'F', (byte) 'G', (byte) 'H', (byte) 'I', (byte) 'J', (byte) 'K', (byte) 'L', (byte) 'M',
+            (byte) 'N', (byte) 'O', (byte) 'P', (byte) 'Q', (byte) 'R', (byte) 'S', (byte) 'T', (byte) 'U', (byte) 'V', (byte) 'W', (byte) 'X', (byte) 'Y',
+            (byte) 'Z', (byte) 'a', (byte) 'b', (byte) 'c', (byte) 'd', (byte) 'e', (byte) 'f', (byte) 'g', (byte) 'h', (byte) 'i', (byte) 'j', (byte) 'k',
+            (byte) 'l', (byte) 'm', (byte) 'n', (byte) 'o', (byte) 'p', (byte) 'q', (byte) 'r', (byte) 's', (byte) 't', (byte) 'u', (byte) 'v', (byte) 'w',
+            (byte) 'x', (byte) 'y', (byte) 'z' };
+
+    /* ===== Tables that are initialized at run time ==================== */
+
+    private static final byte[] A64TOI = new byte[128]; /* ascii-64 => 0..63 */
+
+    /* Initial key schedule permutation */
+    private static final long[][] PC1ROT = new long[16][16];
+
+    /* Subsequent key schedule rotation permutations */
+    private static final long[][][] PC2ROT = new long[2][16][16];
+
+    /* Initial permutation/expansion table */
+    private static final long[][] IE3264 = new long[8][16];
+
+    /* Table that combines the S, P, and E operations. */
+    private static final long[][] SPE = new long[8][64];
+
+    /* compressed/interleaved => final permutation table */
+    private static final long[][] CF6464 = new long[16][16];
+
+    /* ==================================== */
+
+    static
+    {
+        byte[] perm = new byte[64];
+        byte[] temp = new byte[64];
+
+        // inverse table.
+        for (int i = 0; i < 64; i++)
+            A64TOI[ITOA64[i]] = (byte) i;
+
+        // PC1ROT - bit reverse, then PC1, then Rotate, then PC2
+        for (int i = 0; i < 64; i++)
+            perm[i] = (byte) 0;
+        
+        for (int i = 0; i < 64; i++)
+        {
+            int k;
+            if ((k = PC2[i]) == 0) continue;
+            k += Rotates[0] - 1;
+            if ((k % 28) < Rotates[0]) k -= 28;
+            k = PC1[k];
+            if (k > 0)
+            {
+                k--;
+                k = (k | 0x07) - (k & 0x07);
+                k++;
+            }
+            perm[i] = (byte) k;
+        }
+        init_perm(PC1ROT, perm, 8);
+
+        // PC2ROT - PC2 inverse, then Rotate, then PC2
+        for (int j = 0; j < 2; j++)
+        {
+            int k;
+            for (int i = 0; i < 64; i++)
+                perm[i] = temp[i] = 0;
+            for (int i = 0; i < 64; i++)
+            {
+                if ((k = PC2[i]) == 0) continue;
+                temp[k - 1] = (byte) (i + 1);
+            }
+            for (int i = 0; i < 64; i++)
+            {
+                if ((k = PC2[i]) == 0) continue;
+                k += j;
+                if ((k % 28) <= j) k -= 28;
+                perm[i] = temp[k];
+            }
+
+            init_perm(PC2ROT[j], perm, 8);
+        }
+
+        // Bit reverse, intial permupation, expantion
+        for (int i = 0; i < 8; i++)
+        {
+            for (int j = 0; j < 8; j++)
+            {
+                int k = (j < 2) ? 0 : IP[ExpandTr[i * 6 + j - 2] - 1];
+                if (k > 32)
+                    k -= 32;
+                else if (k > 0) k--;
+                if (k > 0)
+                {
+                    k--;
+                    k = (k | 0x07) - (k & 0x07);
+                    k++;
+                }
+                perm[i * 8 + j] = (byte) k;
+            }
+        }
+
+        init_perm(IE3264, perm, 8);
+
+        // Compression, final permutation, bit reverse
+        for (int i = 0; i < 64; i++)
+        {
+            int k = IP[CIFP[i] - 1];
+            if (k > 0)
+            {
+                k--;
+                k = (k | 0x07) - (k & 0x07);
+                k++;
+            }
+            perm[k - 1] = (byte) (i + 1);
+        }
+
+        init_perm(CF6464, perm, 8);
+
+        // SPE table
+        for (int i = 0; i < 48; i++)
+            perm[i] = P32Tr[ExpandTr[i] - 1];
+        for (int t = 0; t < 8; t++)
+        {
+            for (int j = 0; j < 64; j++)
+            {
+                int k = (((j >> 0) & 0x01) << 5) | (((j >> 1) & 0x01) << 3)
+                        | (((j >> 2) & 0x01) << 2)
+                        | (((j >> 3) & 0x01) << 1)
+                        | (((j >> 4) & 0x01) << 0)
+                        | (((j >> 5) & 0x01) << 4);
+                k = S[t][k];
+                k = (((k >> 3) & 0x01) << 0) | (((k >> 2) & 0x01) << 1) | (((k >> 1) & 0x01) << 2) | (((k >> 0) & 0x01) << 3);
+                for (int i = 0; i < 32; i++)
+                    temp[i] = 0;
+                for (int i = 0; i < 4; i++)
+                    temp[4 * t + i] = (byte) ((k >> i) & 0x01);
+                long kk = 0;
+                for (int i = 24; --i >= 0;)
+                    kk = ((kk << 1) | ((long) temp[perm[i] - 1]) << 32 | (temp[perm[i + 24] - 1]));
+
+                SPE[t][j] = to_six_bit(kk);
+            }
+        }
+    }
+
+    /**
+     * You can't call the constructer.
+     */
+    private UnixCrypt()
+    {
+    }
+
+    /**
+     * Returns the transposed and split code of a 24-bit code into a 4-byte
+     * code, each having 6 bits.
+     */
+    private static int to_six_bit(int num)
+    {
+        return (((num << 26) & 0xfc000000) | ((num << 12) & 0xfc0000) | ((num >> 2) & 0xfc00) | ((num >> 16) & 0xfc));
+    }
+
+    /**
+     * Returns the transposed and split code of two 24-bit code into two 4-byte
+     * code, each having 6 bits.
+     */
+    private static long to_six_bit(long num)
+    {
+        return (((num << 26) & 0xfc000000fc000000L) | ((num << 12) & 0xfc000000fc0000L) | ((num >> 2) & 0xfc000000fc00L) | ((num >> 16) & 0xfc000000fcL));
+    }
+
+    /**
+     * Returns the permutation of the given 64-bit code with the specified
+     * permutataion table.
+     */
+    private static long perm6464(long c, long[][] p)
+    {
+        long out = 0L;
+        for (int i = 8; --i >= 0;)
+        {
+            int t = (int) (0x00ff & c);
+            c >>= 8;
+            long tp = p[i << 1][t & 0x0f];
+            out |= tp;
+            tp = p[(i << 1) + 1][t >> 4];
+            out |= tp;
+        }
+        return out;
+    }
+
+    /**
+     * Returns the permutation of the given 32-bit code with the specified
+     * permutataion table.
+     */
+    private static long perm3264(int c, long[][] p)
+    {
+        long out = 0L;
+        for (int i = 4; --i >= 0;)
+        {
+            int t = (0x00ff & c);
+            c >>= 8;
+            long tp = p[i << 1][t & 0x0f];
+            out |= tp;
+            tp = p[(i << 1) + 1][t >> 4];
+            out |= tp;
+        }
+        return out;
+    }
+
+    /**
+     * Returns the key schedule for the given key.
+     */
+    private static long[] des_setkey(long keyword)
+    {
+        long K = perm6464(keyword, PC1ROT);
+        long[] KS = new long[16];
+        KS[0] = K & ~0x0303030300000000L;
+
+        for (int i = 1; i < 16; i++)
+        {
+            KS[i] = K;
+            K = perm6464(K, PC2ROT[Rotates[i] - 1]);
+
+            KS[i] = K & ~0x0303030300000000L;
+        }
+        return KS;
+    }
+
+    /**
+     * Returns the DES encrypted code of the given word with the specified
+     * environment.
+     */
+    private static long des_cipher(long in, int salt, int num_iter, long[] KS)
+    {
+        salt = to_six_bit(salt);
+        long L = in;
+        long R = L;
+        L &= 0x5555555555555555L;
+        R = (R & 0xaaaaaaaa00000000L) | ((R >> 1) & 0x0000000055555555L);
+        L = ((((L << 1) | (L << 32)) & 0xffffffff00000000L) | ((R | (R >> 32)) & 0x00000000ffffffffL));
+
+        L = perm3264((int) (L >> 32), IE3264);
+        R = perm3264((int) (L & 0xffffffff), IE3264);
+
+        while (--num_iter >= 0)
+        {
+            for (int loop_count = 0; loop_count < 8; loop_count++)
+            {
+                long kp;
+                long B;
+                long k;
+
+                kp = KS[(loop_count << 1)];
+                k = ((R >> 32) ^ R) & salt & 0xffffffffL;
+                k |= (k << 32);
+                B = (k ^ R ^ kp);
+
+                L ^= (SPE[0][(int) ((B >> 58) & 0x3f)] ^ SPE[1][(int) ((B >> 50) & 0x3f)]
+                      ^ SPE[2][(int) ((B >> 42) & 0x3f)]
+                      ^ SPE[3][(int) ((B >> 34) & 0x3f)]
+                      ^ SPE[4][(int) ((B >> 26) & 0x3f)]
+                      ^ SPE[5][(int) ((B >> 18) & 0x3f)]
+                      ^ SPE[6][(int) ((B >> 10) & 0x3f)] ^ SPE[7][(int) ((B >> 2) & 0x3f)]);
+
+                kp = KS[(loop_count << 1) + 1];
+                k = ((L >> 32) ^ L) & salt & 0xffffffffL;
+                k |= (k << 32);
+                B = (k ^ L ^ kp);
+
+                R ^= (SPE[0][(int) ((B >> 58) & 0x3f)] ^ SPE[1][(int) ((B >> 50) & 0x3f)]
+                      ^ SPE[2][(int) ((B >> 42) & 0x3f)]
+                      ^ SPE[3][(int) ((B >> 34) & 0x3f)]
+                      ^ SPE[4][(int) ((B >> 26) & 0x3f)]
+                      ^ SPE[5][(int) ((B >> 18) & 0x3f)]
+                      ^ SPE[6][(int) ((B >> 10) & 0x3f)] ^ SPE[7][(int) ((B >> 2) & 0x3f)]);
+            }
+            // swap L and R
+            L ^= R;
+            R ^= L;
+            L ^= R;
+        }
+        L = ((((L >> 35) & 0x0f0f0f0fL) | (((L & 0xffffffff) << 1) & 0xf0f0f0f0L)) << 32 | (((R >> 35) & 0x0f0f0f0fL) | (((R & 0xffffffff) << 1) & 0xf0f0f0f0L)));
+
+        L = perm6464(L, CF6464);
+
+        return L;
+    }
+
+    /**
+     * Initializes the given permutation table with the mapping table.
+     */
+    private static void init_perm(long[][] perm, byte[] p, int chars_out)
+    {
+        for (int k = 0; k < chars_out * 8; k++)
+        {
+
+            int l = p[k] - 1;
+            if (l < 0) continue;
+            int i = l >> 2;
+            l = 1 << (l & 0x03);
+            for (int j = 0; j < 16; j++)
+            {
+                int s = ((k & 0x07) + ((7 - (k >> 3)) << 3));
+                if ((j & l) != 0x00) perm[i][j] |= (1L << s);
+            }
+        }
+    }
+
+    /**
+     * Encrypts String into crypt (Unix) code.
+     * 
+     * @param key the key to be encrypted
+     * @param setting the salt to be used
+     * @return the encrypted String
+     */
+    public static String crypt(String key, String setting)
+    {
+        long constdatablock = 0L; /* encryption constant */
+        byte[] cryptresult = new byte[13]; /* encrypted result */
+        long keyword = 0L;
+        /* invalid parameters! */
+        if (key == null || setting == null) return "*"; // will NOT match under
+        // ANY circumstances!
+
+        int keylen = key.length();
+
+        for (int i = 0; i < 8; i++)
+        {
+            keyword = (keyword << 8) | ((i < keylen) ? 2 * key.charAt(i) : 0);
+        }
+
+        long[] KS = des_setkey(keyword);
+
+        int salt = 0;
+        for (int i = 2; --i >= 0;)
+        {
+            char c = (i < setting.length()) ? setting.charAt(i) : '.';
+            cryptresult[i] = (byte) c;
+            salt = (salt << 6) | (0x00ff & A64TOI[c]);
+        }
+
+        long rsltblock = des_cipher(constdatablock, salt, 25, KS);
+
+        cryptresult[12] = ITOA64[(((int) rsltblock) << 2) & 0x3f];
+        rsltblock >>= 4;
+        for (int i = 12; --i >= 2;)
+        {
+            cryptresult[i] = ITOA64[((int) rsltblock) & 0x3f];
+            rsltblock >>= 6;
+        }
+
+        return new String(cryptresult, 0, 13);
+    }
+
+    public static void main(String[] arg)
+    {
+        if (arg.length != 2)
+        {
+            System.err.println("Usage - java org.eclipse.util.UnixCrypt <key> <salt>");
+            System.exit(1);
+        }
+
+        System.err.println("Crypt=" + crypt(arg[0], arg[1]));
+    }
+
+}
diff --git a/lib/jetty/org/eclipse/jetty/util/security/package-info.java b/lib/jetty/org/eclipse/jetty/util/security/package-info.java
new file mode 100644 (file)
index 0000000..a9271a5
--- /dev/null
@@ -0,0 +1,23 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+/**
+ * Jetty Util : Common Security Utilities
+ */
+package org.eclipse.jetty.util.security;
+
diff --git a/lib/jetty/org/eclipse/jetty/util/ssl/AliasedX509ExtendedKeyManager.java b/lib/jetty/org/eclipse/jetty/util/ssl/AliasedX509ExtendedKeyManager.java
new file mode 100644 (file)
index 0000000..b97aa34
--- /dev/null
@@ -0,0 +1,130 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util.ssl;
+
+import java.net.Socket;
+import java.security.Principal;
+import java.security.PrivateKey;
+import java.security.cert.X509Certificate;
+
+import javax.net.ssl.SSLEngine;
+import javax.net.ssl.X509ExtendedKeyManager;
+import javax.net.ssl.X509KeyManager;
+
+
+/* ------------------------------------------------------------ */
+/**
+ * KeyManager to select a key with desired alias
+ * while delegating processing to specified KeyManager
+ * Can be used both with server and client sockets
+ */
+public class AliasedX509ExtendedKeyManager extends X509ExtendedKeyManager
+{
+    private String _keyAlias;
+    private X509KeyManager _keyManager;
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Construct KeyManager instance
+     * @param keyAlias Alias of the key to be selected
+     * @param keyManager Instance of KeyManager to be wrapped
+     * @throws Exception
+     */
+    public AliasedX509ExtendedKeyManager(String keyAlias, X509KeyManager keyManager) throws Exception
+    {
+        _keyAlias = keyAlias;
+        _keyManager = keyManager;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see javax.net.ssl.X509KeyManager#chooseClientAlias(java.lang.String[], java.security.Principal[], java.net.Socket)
+     */
+    public String chooseClientAlias(String[] keyType, Principal[] issuers, Socket socket)
+    {
+        return _keyAlias == null ? _keyManager.chooseClientAlias(keyType, issuers, socket) : _keyAlias;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see javax.net.ssl.X509KeyManager#chooseServerAlias(java.lang.String, java.security.Principal[], java.net.Socket)
+     */
+    public String chooseServerAlias(String keyType, Principal[] issuers, Socket socket)
+    {
+        return _keyAlias == null ? _keyManager.chooseServerAlias(keyType, issuers, socket) : _keyAlias;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see javax.net.ssl.X509KeyManager#getClientAliases(java.lang.String, java.security.Principal[])
+     */
+    public String[] getClientAliases(String keyType, Principal[] issuers)
+    {
+        return _keyManager.getClientAliases(keyType, issuers);
+    }
+
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see javax.net.ssl.X509KeyManager#getServerAliases(java.lang.String, java.security.Principal[])
+     */
+    public String[] getServerAliases(String keyType, Principal[] issuers)
+    {
+        return _keyManager.getServerAliases(keyType, issuers);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see javax.net.ssl.X509KeyManager#getCertificateChain(java.lang.String)
+     */
+    public X509Certificate[] getCertificateChain(String alias)
+    {
+        return _keyManager.getCertificateChain(alias);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see javax.net.ssl.X509KeyManager#getPrivateKey(java.lang.String)
+     */
+    public PrivateKey getPrivateKey(String alias)
+    {
+        return _keyManager.getPrivateKey(alias);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see javax.net.ssl.X509ExtendedKeyManager#chooseEngineServerAlias(java.lang.String, java.security.Principal[], javax.net.ssl.SSLEngine)
+     */
+    @Override
+    public String chooseEngineServerAlias(String keyType, Principal[] issuers, SSLEngine engine)
+    {
+        return _keyAlias == null ? super.chooseEngineServerAlias(keyType,issuers,engine) : _keyAlias;
+    }
+
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see javax.net.ssl.X509ExtendedKeyManager#chooseEngineClientAlias(String[], Principal[], SSLEngine)
+     */
+    @Override
+    public String chooseEngineClientAlias(String keyType[], Principal[] issuers, SSLEngine engine)
+    {
+        return _keyAlias == null ? super.chooseEngineClientAlias(keyType,issuers,engine) : _keyAlias;
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/util/ssl/AliasedX509KeyManager.java b/lib/jetty/org/eclipse/jetty/util/ssl/AliasedX509KeyManager.java
new file mode 100644 (file)
index 0000000..29d33bb
--- /dev/null
@@ -0,0 +1,107 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util.ssl;
+
+import java.net.Socket;
+import java.security.Principal;
+import java.security.PrivateKey;
+import java.security.cert.X509Certificate;
+
+import javax.net.ssl.X509KeyManager;
+
+
+/* ------------------------------------------------------------ */
+/**
+ * KeyManager to select a key with desired alias
+ * while delegating processing to specified KeyManager
+ * Can be used both with server and client sockets
+ */
+public class AliasedX509KeyManager implements X509KeyManager
+{
+    private String _keyAlias;
+    private X509KeyManager _keyManager;
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Construct KeyManager instance
+     * @param keyAlias Alias of the key to be selected
+     * @param keyManager Instance of KeyManager to be wrapped
+     * @throws Exception
+     */
+    public AliasedX509KeyManager(String keyAlias, X509KeyManager keyManager) throws Exception
+    {
+        _keyAlias = keyAlias;
+        _keyManager = keyManager;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see javax.net.ssl.X509KeyManager#chooseClientAlias(java.lang.String[], java.security.Principal[], java.net.Socket)
+     */
+    public String chooseClientAlias(String[] keyType, Principal[] issuers, Socket socket)
+    {
+        return _keyAlias == null ? _keyManager.chooseClientAlias(keyType, issuers, socket) : _keyAlias;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see javax.net.ssl.X509KeyManager#chooseServerAlias(java.lang.String, java.security.Principal[], java.net.Socket)
+     */
+    public String chooseServerAlias(String keyType, Principal[] issuers, Socket socket)
+    {
+        return _keyAlias == null ?_keyManager.chooseServerAlias(keyType, issuers, socket) : _keyAlias;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see javax.net.ssl.X509KeyManager#getClientAliases(java.lang.String, java.security.Principal[])
+     */
+    public String[] getClientAliases(String keyType, Principal[] issuers)
+    {
+        return _keyManager.getClientAliases(keyType, issuers);
+    }
+
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see javax.net.ssl.X509KeyManager#getServerAliases(java.lang.String, java.security.Principal[])
+     */
+    public String[] getServerAliases(String keyType, Principal[] issuers)
+    {
+        return _keyManager.getServerAliases(keyType, issuers);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see javax.net.ssl.X509KeyManager#getCertificateChain(java.lang.String)
+     */
+    public X509Certificate[] getCertificateChain(String alias)
+    {
+        return _keyManager.getCertificateChain(alias);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see javax.net.ssl.X509KeyManager#getPrivateKey(java.lang.String)
+     */
+    public PrivateKey getPrivateKey(String alias)
+    {
+        return _keyManager.getPrivateKey(alias);
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/util/ssl/SslContextFactory.java b/lib/jetty/org/eclipse/jetty/util/ssl/SslContextFactory.java
new file mode 100644 (file)
index 0000000..016c812
--- /dev/null
@@ -0,0 +1,1484 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util.ssl;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.security.InvalidParameterException;
+import java.security.KeyStore;
+import java.security.SecureRandom;
+import java.security.Security;
+import java.security.cert.CRL;
+import java.security.cert.CertStore;
+import java.security.cert.Certificate;
+import java.security.cert.CollectionCertStoreParameters;
+import java.security.cert.PKIXBuilderParameters;
+import java.security.cert.X509CertSelector;
+import java.security.cert.X509Certificate;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.CopyOnWriteArraySet;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import javax.net.ssl.CertPathTrustManagerParameters;
+import javax.net.ssl.KeyManager;
+import javax.net.ssl.KeyManagerFactory;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLEngine;
+import javax.net.ssl.SSLParameters;
+import javax.net.ssl.SSLPeerUnverifiedException;
+import javax.net.ssl.SSLServerSocket;
+import javax.net.ssl.SSLServerSocketFactory;
+import javax.net.ssl.SSLSession;
+import javax.net.ssl.SSLSocket;
+import javax.net.ssl.SSLSocketFactory;
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.TrustManagerFactory;
+import javax.net.ssl.X509KeyManager;
+import javax.net.ssl.X509TrustManager;
+
+import org.eclipse.jetty.util.IO;
+import org.eclipse.jetty.util.component.AbstractLifeCycle;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.resource.Resource;
+import org.eclipse.jetty.util.security.CertificateUtils;
+import org.eclipse.jetty.util.security.CertificateValidator;
+import org.eclipse.jetty.util.security.Password;
+
+
+/**
+ * SslContextFactory is used to configure SSL connectors
+ * as well as HttpClient. It holds all SSL parameters and
+ * creates SSL context based on these parameters to be
+ * used by the SSL connectors.
+ */
+public class SslContextFactory extends AbstractLifeCycle
+{
+    public final static TrustManager[] TRUST_ALL_CERTS = new X509TrustManager[]{new X509TrustManager()
+    {
+        public java.security.cert.X509Certificate[] getAcceptedIssuers()
+        {
+            return new java.security.cert.X509Certificate[]{};
+        }
+
+        public void checkClientTrusted(java.security.cert.X509Certificate[] certs, String authType)
+        {
+        }
+
+        public void checkServerTrusted(java.security.cert.X509Certificate[] certs, String authType)
+        {
+        }
+    }};
+
+    static final Logger LOG = Log.getLogger(SslContextFactory.class);
+
+    public static final String DEFAULT_KEYMANAGERFACTORY_ALGORITHM =
+        (Security.getProperty("ssl.KeyManagerFactory.algorithm") == null ?
+                KeyManagerFactory.getDefaultAlgorithm() : Security.getProperty("ssl.KeyManagerFactory.algorithm"));
+
+    public static final String DEFAULT_TRUSTMANAGERFACTORY_ALGORITHM =
+        (Security.getProperty("ssl.TrustManagerFactory.algorithm") == null ?
+                TrustManagerFactory.getDefaultAlgorithm() : Security.getProperty("ssl.TrustManagerFactory.algorithm"));
+
+    /** String name of key password property. */
+    public static final String KEYPASSWORD_PROPERTY = "org.eclipse.jetty.ssl.keypassword";
+
+    /** String name of keystore password property. */
+    public static final String PASSWORD_PROPERTY = "org.eclipse.jetty.ssl.password";
+
+    /** Excluded protocols. */
+    private final Set<String> _excludeProtocols = new LinkedHashSet<>();
+
+    /** Included protocols. */
+    private Set<String> _includeProtocols = null;
+
+    /** Excluded cipher suites. */
+    private final Set<String> _excludeCipherSuites = new LinkedHashSet<>();
+    /** Included cipher suites. */
+    private Set<String> _includeCipherSuites = null;
+
+    /** Keystore path. */
+    private String _keyStorePath;
+    /** Keystore provider name */
+    private String _keyStoreProvider;
+    /** Keystore type */
+    private String _keyStoreType = "JKS";
+    /** Keystore input stream */
+    private InputStream _keyStoreInputStream;
+
+    /** SSL certificate alias */
+    private String _certAlias;
+
+    /** Truststore path */
+    private String _trustStorePath;
+    /** Truststore provider name */
+    private String _trustStoreProvider;
+    /** Truststore type */
+    private String _trustStoreType = "JKS";
+    /** Truststore input stream */
+    private InputStream _trustStoreInputStream;
+
+    /** Set to true if client certificate authentication is required */
+    private boolean _needClientAuth = false;
+    /** Set to true if client certificate authentication is desired */
+    private boolean _wantClientAuth = false;
+
+    /** Keystore password */
+    private transient Password _keyStorePassword;
+    /** Key manager password */
+    private transient Password _keyManagerPassword;
+    /** Truststore password */
+    private transient Password _trustStorePassword;
+
+    /** SSL provider name */
+    private String _sslProvider;
+    /** SSL protocol name */
+    private String _sslProtocol = "TLS";
+
+    /** SecureRandom algorithm */
+    private String _secureRandomAlgorithm;
+    /** KeyManager factory algorithm */
+    private String _keyManagerFactoryAlgorithm = DEFAULT_KEYMANAGERFACTORY_ALGORITHM;
+    /** TrustManager factory algorithm */
+    private String _trustManagerFactoryAlgorithm = DEFAULT_TRUSTMANAGERFACTORY_ALGORITHM;
+
+    /** Set to true if SSL certificate validation is required */
+    private boolean _validateCerts;
+    /** Set to true if SSL certificate of the peer validation is required */
+    private boolean _validatePeerCerts;
+    /** Maximum certification path length (n - number of intermediate certs, -1 for unlimited) */
+    private int _maxCertPathLength = -1;
+    /** Path to file that contains Certificate Revocation List */
+    private String _crlPath;
+    /** Set to true to enable CRL Distribution Points (CRLDP) support */
+    private boolean _enableCRLDP = false;
+    /** Set to true to enable On-Line Certificate Status Protocol (OCSP) support */
+    private boolean _enableOCSP = false;
+    /** Location of OCSP Responder */
+    private String _ocspResponderURL;
+
+    /** SSL keystore */
+    private KeyStore _keyStore;
+    /** SSL truststore */
+    private KeyStore _trustStore;
+    /** Set to true to enable SSL Session caching */
+    private boolean _sessionCachingEnabled = true;
+    /** SSL session cache size */
+    private int _sslSessionCacheSize;
+    /** SSL session timeout */
+    private int _sslSessionTimeout;
+
+    /** SSL context */
+    private SSLContext _context;
+
+    /** EndpointIdentificationAlgorithm - when set to "HTTPS" hostname verification will be enabled */
+    private String _endpointIdentificationAlgorithm = null;
+
+    /** Whether to blindly trust certificates */
+    private boolean _trustAll;
+
+    /** Whether TLS renegotiation is allowed */
+    private boolean _renegotiationAllowed = true;
+
+    /**
+     * Construct an instance of SslContextFactory
+     * Default constructor for use in XmlConfiguration files
+     */
+    public SslContextFactory()
+    {
+        this(false);
+    }
+
+    /**
+     * Construct an instance of SslContextFactory
+     * Default constructor for use in XmlConfiguration files
+     * @param trustAll whether to blindly trust all certificates
+     * @see #setTrustAll(boolean)
+     */
+    public SslContextFactory(boolean trustAll)
+    {
+        setTrustAll(trustAll);
+    }
+
+    /**
+     * Construct an instance of SslContextFactory
+     * @param keyStorePath default keystore location
+     */
+    public SslContextFactory(String keyStorePath)
+    {
+        _keyStorePath = keyStorePath;
+    }
+
+    /**
+     * Create the SSLContext object and start the lifecycle
+     * @see org.eclipse.jetty.util.component.AbstractLifeCycle#doStart()
+     */
+    @Override
+    protected void doStart() throws Exception
+    {
+        if (_context == null)
+        {
+            if (_keyStore==null && _keyStoreInputStream == null && _keyStorePath == null &&
+                _trustStore==null && _trustStoreInputStream == null && _trustStorePath == null )
+            {
+                TrustManager[] trust_managers=null;
+
+                if (_trustAll)
+                {
+                    LOG.debug("No keystore or trust store configured.  ACCEPTING UNTRUSTED CERTIFICATES!!!!!");
+                    // Create a trust manager that does not validate certificate chains
+                    trust_managers = TRUST_ALL_CERTS;
+                }
+
+                SecureRandom secureRandom = (_secureRandomAlgorithm == null)?null:SecureRandom.getInstance(_secureRandomAlgorithm);
+                SSLContext context = SSLContext.getInstance(_sslProtocol);
+                context.init(null, trust_managers, secureRandom);
+                _context = context;
+            }
+            else
+            {
+                // verify that keystore and truststore
+                // parameters are set up correctly
+                checkKeyStore();
+
+                KeyStore keyStore = loadKeyStore();
+                KeyStore trustStore = loadTrustStore();
+
+                Collection<? extends CRL> crls = loadCRL(_crlPath);
+
+                if (_validateCerts && keyStore != null)
+                {
+                    if (_certAlias == null)
+                    {
+                        List<String> aliases = Collections.list(keyStore.aliases());
+                        _certAlias = aliases.size() == 1 ? aliases.get(0) : null;
+                    }
+
+                    Certificate cert = _certAlias == null?null:keyStore.getCertificate(_certAlias);
+                    if (cert == null)
+                    {
+                        throw new Exception("No certificate found in the keystore" + (_certAlias==null ? "":" for alias " + _certAlias));
+                    }
+
+                    CertificateValidator validator = new CertificateValidator(trustStore, crls);
+                    validator.setMaxCertPathLength(_maxCertPathLength);
+                    validator.setEnableCRLDP(_enableCRLDP);
+                    validator.setEnableOCSP(_enableOCSP);
+                    validator.setOcspResponderURL(_ocspResponderURL);
+                    validator.validate(keyStore, cert);
+                }
+
+                KeyManager[] keyManagers = getKeyManagers(keyStore);
+                TrustManager[] trustManagers = getTrustManagers(trustStore,crls);
+
+                SecureRandom secureRandom = (_secureRandomAlgorithm == null)?null:SecureRandom.getInstance(_secureRandomAlgorithm);
+                SSLContext context = _sslProvider == null ? SSLContext.getInstance(_sslProtocol) : SSLContext.getInstance(_sslProtocol,_sslProvider);
+                context.init(keyManagers,trustManagers,secureRandom);
+                _context = context;
+            }
+
+            SSLEngine engine = newSSLEngine();
+            LOG.debug("Enabled Protocols {} of {}",Arrays.asList(engine.getEnabledProtocols()),Arrays.asList(engine.getSupportedProtocols()));
+            if (LOG.isDebugEnabled())
+                LOG.debug("Enabled Ciphers   {} of {}",Arrays.asList(engine.getEnabledCipherSuites()),Arrays.asList(engine.getSupportedCipherSuites()));
+        }
+    }
+
+    @Override
+    protected void doStop() throws Exception
+    {
+        _context = null;
+        super.doStop();
+    }
+
+    /**
+     * @return The array of protocol names to exclude from
+     * {@link SSLEngine#setEnabledProtocols(String[])}
+     */
+    public String[] getExcludeProtocols()
+    {
+        return _excludeProtocols.toArray(new String[_excludeProtocols.size()]);
+    }
+
+    /**
+     * @param protocols
+     *            The array of protocol names to exclude from
+     *            {@link SSLEngine#setEnabledProtocols(String[])}
+     */
+    public void setExcludeProtocols(String... protocols)
+    {
+        checkNotStarted();
+        _excludeProtocols.clear();
+        _excludeProtocols.addAll(Arrays.asList(protocols));
+    }
+
+    /**
+     * @param protocol Protocol names to add to {@link SSLEngine#setEnabledProtocols(String[])}
+     */
+    public void addExcludeProtocols(String... protocol)
+    {
+        checkNotStarted();
+        _excludeProtocols.addAll(Arrays.asList(protocol));
+    }
+
+    /**
+     * @return The array of protocol names to include in
+     * {@link SSLEngine#setEnabledProtocols(String[])}
+     */
+    public String[] getIncludeProtocols()
+    {
+        return _includeProtocols.toArray(new String[_includeProtocols.size()]);
+    }
+
+    /**
+     * @param protocols
+     *            The array of protocol names to include in
+     *            {@link SSLEngine#setEnabledProtocols(String[])}
+     */
+    public void setIncludeProtocols(String... protocols)
+    {
+        checkNotStarted();
+        _includeProtocols = new LinkedHashSet<>(Arrays.asList(protocols));
+    }
+
+    /**
+     * @return The array of cipher suite names to exclude from
+     * {@link SSLEngine#setEnabledCipherSuites(String[])}
+     */
+    public String[] getExcludeCipherSuites()
+    {
+        return _excludeCipherSuites.toArray(new String[_excludeCipherSuites.size()]);
+    }
+
+    /**
+     * You can either use the exact cipher suite name or a a regular expression.
+     * @param cipherSuites
+     *            The array of cipher suite names to exclude from
+     *            {@link SSLEngine#setEnabledCipherSuites(String[])}
+     */
+    public void setExcludeCipherSuites(String... cipherSuites)
+    {
+        checkNotStarted();
+        _excludeCipherSuites.clear();
+        _excludeCipherSuites.addAll(Arrays.asList(cipherSuites));
+    }
+
+    /**
+     * @param cipher Cipher names to add to {@link SSLEngine#setEnabledCipherSuites(String[])}
+     */
+    public void addExcludeCipherSuites(String... cipher)
+    {
+        checkNotStarted();
+        _excludeCipherSuites.addAll(Arrays.asList(cipher));
+    }
+
+    /**
+     * @return The array of cipher suite names to include in
+     * {@link SSLEngine#setEnabledCipherSuites(String[])}
+     */
+    public String[] getIncludeCipherSuites()
+    {
+        return _includeCipherSuites.toArray(new String[_includeCipherSuites.size()]);
+    }
+
+    /**
+     * You can either use the exact cipher suite name or a a regular expression.
+     * @param cipherSuites
+     *            The array of cipher suite names to include in
+     *            {@link SSLEngine#setEnabledCipherSuites(String[])}
+     */
+    public void setIncludeCipherSuites(String... cipherSuites)
+    {
+        checkNotStarted();
+        _includeCipherSuites = new LinkedHashSet<>(Arrays.asList(cipherSuites));
+    }
+
+    /**
+     * @return The file or URL of the SSL Key store.
+     */
+    public String getKeyStorePath()
+    {
+        return _keyStorePath;
+    }
+
+    /**
+     * @param keyStorePath
+     *            The file or URL of the SSL Key store.
+     */
+    public void setKeyStorePath(String keyStorePath)
+    {
+        checkNotStarted();
+        _keyStorePath = keyStorePath;
+    }
+
+    /**
+     * @return The provider of the key store
+     */
+    public String getKeyStoreProvider()
+    {
+        return _keyStoreProvider;
+    }
+
+    /**
+     * @param keyStoreProvider
+     *            The provider of the key store
+     */
+    public void setKeyStoreProvider(String keyStoreProvider)
+    {
+        checkNotStarted();
+        _keyStoreProvider = keyStoreProvider;
+    }
+
+    /**
+     * @return The type of the key store (default "JKS")
+     */
+    public String getKeyStoreType()
+    {
+        return (_keyStoreType);
+    }
+
+    /**
+     * @param keyStoreType
+     *            The type of the key store (default "JKS")
+     */
+    public void setKeyStoreType(String keyStoreType)
+    {
+        checkNotStarted();
+        _keyStoreType = keyStoreType;
+    }
+
+    /**
+     * @return Alias of SSL certificate for the connector
+     */
+    public String getCertAlias()
+    {
+        return _certAlias;
+    }
+
+    /**
+     * @param certAlias
+     *            Alias of SSL certificate for the connector
+     */
+    public void setCertAlias(String certAlias)
+    {
+        checkNotStarted();
+        _certAlias = certAlias;
+    }
+
+    /**
+     * @return The file name or URL of the trust store location
+     */
+    public String getTrustStore()
+    {
+        return _trustStorePath;
+    }
+
+    /**
+     * @param trustStorePath
+     *            The file name or URL of the trust store location
+     */
+    public void setTrustStorePath(String trustStorePath)
+    {
+        checkNotStarted();
+        _trustStorePath = trustStorePath;
+    }
+
+    /**
+     * @return The provider of the trust store
+     */
+    public String getTrustStoreProvider()
+    {
+        return _trustStoreProvider;
+    }
+
+    /**
+     * @param trustStoreProvider
+     *            The provider of the trust store
+     */
+    public void setTrustStoreProvider(String trustStoreProvider)
+    {
+        checkNotStarted();
+        _trustStoreProvider = trustStoreProvider;
+    }
+
+    /**
+     * @return The type of the trust store (default "JKS")
+     */
+    public String getTrustStoreType()
+    {
+        return _trustStoreType;
+    }
+
+    /**
+     * @param trustStoreType
+     *            The type of the trust store (default "JKS")
+     */
+    public void setTrustStoreType(String trustStoreType)
+    {
+        checkNotStarted();
+        _trustStoreType = trustStoreType;
+    }
+
+    /**
+     * @return True if SSL needs client authentication.
+     * @see SSLEngine#getNeedClientAuth()
+     */
+    public boolean getNeedClientAuth()
+    {
+        return _needClientAuth;
+    }
+
+    /**
+     * @param needClientAuth
+     *            True if SSL needs client authentication.
+     * @see SSLEngine#getNeedClientAuth()
+     */
+    public void setNeedClientAuth(boolean needClientAuth)
+    {
+        checkNotStarted();
+        _needClientAuth = needClientAuth;
+    }
+
+    /**
+     * @return True if SSL wants client authentication.
+     * @see SSLEngine#getWantClientAuth()
+     */
+    public boolean getWantClientAuth()
+    {
+        return _wantClientAuth;
+    }
+
+    /**
+     * @param wantClientAuth
+     *            True if SSL wants client authentication.
+     * @see SSLEngine#getWantClientAuth()
+     */
+    public void setWantClientAuth(boolean wantClientAuth)
+    {
+        checkNotStarted();
+        _wantClientAuth = wantClientAuth;
+    }
+
+    /**
+     * @return true if SSL certificate has to be validated
+     */
+    public boolean isValidateCerts()
+    {
+        return _validateCerts;
+    }
+
+    /**
+     * @param validateCerts
+     *            true if SSL certificates have to be validated
+     */
+    public void setValidateCerts(boolean validateCerts)
+    {
+        checkNotStarted();
+        _validateCerts = validateCerts;
+    }
+
+    /**
+     * @return true if SSL certificates of the peer have to be validated
+     */
+    public boolean isValidatePeerCerts()
+    {
+        return _validatePeerCerts;
+    }
+
+    /**
+     * @param validatePeerCerts
+     *            true if SSL certificates of the peer have to be validated
+     */
+    public void setValidatePeerCerts(boolean validatePeerCerts)
+    {
+        checkNotStarted();
+        _validatePeerCerts = validatePeerCerts;
+    }
+
+
+    /**
+     * @param password
+     *            The password for the key store
+     */
+    public void setKeyStorePassword(String password)
+    {
+        checkNotStarted();
+        _keyStorePassword = Password.getPassword(PASSWORD_PROPERTY,password,null);
+    }
+
+    /**
+     * @param password
+     *            The password (if any) for the specific key within the key store
+     */
+    public void setKeyManagerPassword(String password)
+    {
+        checkNotStarted();
+        _keyManagerPassword = Password.getPassword(KEYPASSWORD_PROPERTY,password,null);
+    }
+
+    /**
+     * @param password
+     *            The password for the trust store
+     */
+    public void setTrustStorePassword(String password)
+    {
+        checkNotStarted();
+        _trustStorePassword = Password.getPassword(PASSWORD_PROPERTY,password,null);
+    }
+
+    /**
+     * @return The SSL provider name, which if set is passed to
+     * {@link SSLContext#getInstance(String, String)}
+     */
+    public String getProvider()
+    {
+        return _sslProvider;
+    }
+
+    /**
+     * @param provider
+     *            The SSL provider name, which if set is passed to
+     *            {@link SSLContext#getInstance(String, String)}
+     */
+    public void setProvider(String provider)
+    {
+        checkNotStarted();
+        _sslProvider = provider;
+    }
+
+    /**
+     * @return The SSL protocol (default "TLS") passed to
+     * {@link SSLContext#getInstance(String, String)}
+     */
+    public String getProtocol()
+    {
+        return _sslProtocol;
+    }
+
+    /**
+     * @param protocol
+     *            The SSL protocol (default "TLS") passed to
+     *            {@link SSLContext#getInstance(String, String)}
+     */
+    public void setProtocol(String protocol)
+    {
+        checkNotStarted();
+        _sslProtocol = protocol;
+    }
+
+    /**
+     * @return The algorithm name, which if set is passed to
+     * {@link SecureRandom#getInstance(String)} to obtain the {@link SecureRandom} instance passed to
+     * {@link SSLContext#init(javax.net.ssl.KeyManager[], javax.net.ssl.TrustManager[], SecureRandom)}
+     */
+    public String getSecureRandomAlgorithm()
+    {
+        return _secureRandomAlgorithm;
+    }
+
+    /**
+     * @param algorithm
+     *            The algorithm name, which if set is passed to
+     *            {@link SecureRandom#getInstance(String)} to obtain the {@link SecureRandom} instance passed to
+     *            {@link SSLContext#init(javax.net.ssl.KeyManager[], javax.net.ssl.TrustManager[], SecureRandom)}
+     */
+    public void setSecureRandomAlgorithm(String algorithm)
+    {
+        checkNotStarted();
+        _secureRandomAlgorithm = algorithm;
+    }
+
+    /**
+     * @return The algorithm name (default "SunX509") used by the {@link KeyManagerFactory}
+     */
+    public String getSslKeyManagerFactoryAlgorithm()
+    {
+        return (_keyManagerFactoryAlgorithm);
+    }
+
+    /**
+     * @param algorithm
+     *            The algorithm name (default "SunX509") used by the {@link KeyManagerFactory}
+     */
+    public void setSslKeyManagerFactoryAlgorithm(String algorithm)
+    {
+        checkNotStarted();
+        _keyManagerFactoryAlgorithm = algorithm;
+    }
+
+    /**
+     * @return The algorithm name (default "SunX509") used by the {@link TrustManagerFactory}
+     */
+    public String getTrustManagerFactoryAlgorithm()
+    {
+        return (_trustManagerFactoryAlgorithm);
+    }
+
+    /**
+     * @return True if all certificates should be trusted if there is no KeyStore or TrustStore
+     */
+    public boolean isTrustAll()
+    {
+        return _trustAll;
+    }
+
+    /**
+     * @param trustAll True if all certificates should be trusted if there is no KeyStore or TrustStore
+     */
+    public void setTrustAll(boolean trustAll)
+    {
+        _trustAll = trustAll;
+        if(trustAll)
+            setEndpointIdentificationAlgorithm(null);
+    }
+
+    /**
+     * @param algorithm
+     *            The algorithm name (default "SunX509") used by the {@link TrustManagerFactory}
+     *            Use the string "TrustAll" to install a trust manager that trusts all.
+     */
+    public void setTrustManagerFactoryAlgorithm(String algorithm)
+    {
+        checkNotStarted();
+        _trustManagerFactoryAlgorithm = algorithm;
+    }
+
+    /**
+     * @return whether TLS renegotiation is allowed (true by default)
+     */
+    public boolean isRenegotiationAllowed()
+    {
+        return _renegotiationAllowed;
+    }
+
+    /**
+     * @param renegotiationAllowed whether TLS renegotiation is allowed
+     */
+    public void setRenegotiationAllowed(boolean renegotiationAllowed)
+    {
+        _renegotiationAllowed = renegotiationAllowed;
+    }
+
+    /**
+     * @return Path to file that contains Certificate Revocation List
+     */
+    public String getCrlPath()
+    {
+        return _crlPath;
+    }
+
+    /**
+     * @param crlPath
+     *            Path to file that contains Certificate Revocation List
+     */
+    public void setCrlPath(String crlPath)
+    {
+        checkNotStarted();
+        _crlPath = crlPath;
+    }
+
+    /**
+     * @return Maximum number of intermediate certificates in
+     * the certification path (-1 for unlimited)
+     */
+    public int getMaxCertPathLength()
+    {
+        return _maxCertPathLength;
+    }
+
+    /**
+     * @param maxCertPathLength
+     *            maximum number of intermediate certificates in
+     *            the certification path (-1 for unlimited)
+     */
+    public void setMaxCertPathLength(int maxCertPathLength)
+    {
+        checkNotStarted();
+        _maxCertPathLength = maxCertPathLength;
+    }
+
+    /**
+     * @return The SSLContext
+     */
+    public SSLContext getSslContext()
+    {
+        if (!isStarted())
+            throw new IllegalStateException(getState());
+        return _context;
+    }
+
+    /**
+     * @param sslContext
+     *            Set a preconfigured SSLContext
+     */
+    public void setSslContext(SSLContext sslContext)
+    {
+        checkNotStarted();
+        _context = sslContext;
+    }
+
+    /**
+     * When set to "HTTPS" hostname verification will be enabled
+     *
+     * @param endpointIdentificationAlgorithm Set the endpointIdentificationAlgorithm
+     */
+    public void setEndpointIdentificationAlgorithm(String endpointIdentificationAlgorithm)
+    {
+        this._endpointIdentificationAlgorithm = endpointIdentificationAlgorithm;
+    }
+
+    /**
+     * Override this method to provide alternate way to load a keystore.
+     *
+     * @return the key store instance
+     * @throws Exception if the keystore cannot be loaded
+     */
+    protected KeyStore loadKeyStore() throws Exception
+    {
+        return _keyStore != null ? _keyStore : CertificateUtils.getKeyStore(_keyStoreInputStream,
+                _keyStorePath, _keyStoreType, _keyStoreProvider,
+                _keyStorePassword==null? null: _keyStorePassword.toString());
+    }
+
+    /**
+     * Override this method to provide alternate way to load a truststore.
+     *
+     * @return the key store instance
+     * @throws Exception if the truststore cannot be loaded
+     */
+    protected KeyStore loadTrustStore() throws Exception
+    {
+        return _trustStore != null ? _trustStore : CertificateUtils.getKeyStore(_trustStoreInputStream,
+                _trustStorePath, _trustStoreType,  _trustStoreProvider,
+                _trustStorePassword==null? null: _trustStorePassword.toString());
+    }
+
+    /**
+     * Loads certificate revocation list (CRL) from a file.
+     *
+     * Required for integrations to be able to override the mechanism used to
+     * load CRL in order to provide their own implementation.
+     *
+     * @param crlPath path of certificate revocation list file
+     * @return Collection of CRL's
+     * @throws Exception if the certificate revocation list cannot be loaded
+     */
+    protected Collection<? extends CRL> loadCRL(String crlPath) throws Exception
+    {
+        return CertificateUtils.loadCRL(crlPath);
+    }
+
+    protected KeyManager[] getKeyManagers(KeyStore keyStore) throws Exception
+    {
+        KeyManager[] managers = null;
+
+        if (keyStore != null)
+        {
+            KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(_keyManagerFactoryAlgorithm);
+            keyManagerFactory.init(keyStore,_keyManagerPassword == null?(_keyStorePassword == null?null:_keyStorePassword.toString().toCharArray()):_keyManagerPassword.toString().toCharArray());
+            managers = keyManagerFactory.getKeyManagers();
+
+            if (_certAlias != null)
+            {
+                for (int idx = 0; idx < managers.length; idx++)
+                {
+                    if (managers[idx] instanceof X509KeyManager)
+                    {
+                        managers[idx] = new AliasedX509ExtendedKeyManager(_certAlias,(X509KeyManager)managers[idx]);
+                    }
+                }
+            }
+        }
+
+        return managers;
+    }
+
+    protected TrustManager[] getTrustManagers(KeyStore trustStore, Collection<? extends CRL> crls) throws Exception
+    {
+        TrustManager[] managers = null;
+        if (trustStore != null)
+        {
+            // Revocation checking is only supported for PKIX algorithm
+            if (_validatePeerCerts && _trustManagerFactoryAlgorithm.equalsIgnoreCase("PKIX"))
+            {
+                PKIXBuilderParameters pbParams = new PKIXBuilderParameters(trustStore,new X509CertSelector());
+
+                // Set maximum certification path length
+                pbParams.setMaxPathLength(_maxCertPathLength);
+
+                // Make sure revocation checking is enabled
+                pbParams.setRevocationEnabled(true);
+
+                if (crls != null && !crls.isEmpty())
+                {
+                    pbParams.addCertStore(CertStore.getInstance("Collection",new CollectionCertStoreParameters(crls)));
+                }
+
+                if (_enableCRLDP)
+                {
+                    // Enable Certificate Revocation List Distribution Points (CRLDP) support
+                    System.setProperty("com.sun.security.enableCRLDP","true");
+                }
+
+                if (_enableOCSP)
+                {
+                    // Enable On-Line Certificate Status Protocol (OCSP) support
+                    Security.setProperty("ocsp.enable","true");
+
+                    if (_ocspResponderURL != null)
+                    {
+                        // Override location of OCSP Responder
+                        Security.setProperty("ocsp.responderURL", _ocspResponderURL);
+                    }
+                }
+
+                TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(_trustManagerFactoryAlgorithm);
+                trustManagerFactory.init(new CertPathTrustManagerParameters(pbParams));
+
+                managers = trustManagerFactory.getTrustManagers();
+            }
+            else
+            {
+                TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(_trustManagerFactoryAlgorithm);
+                trustManagerFactory.init(trustStore);
+
+                managers = trustManagerFactory.getTrustManagers();
+            }
+        }
+
+        return managers;
+    }
+
+    /**
+     * Check KeyStore Configuration. Ensures that if keystore has been
+     * configured but there's no truststore, that keystore is
+     * used as truststore.
+     * @throws IllegalStateException if SslContextFactory configuration can't be used.
+     */
+    public void checkKeyStore()
+    {
+        if (_context != null)
+            return;
+
+        if (_keyStore == null && _keyStoreInputStream == null && _keyStorePath == null)
+            throw new IllegalStateException("SSL doesn't have a valid keystore");
+
+        // if the keystore has been configured but there is no
+        // truststore configured, use the keystore as the truststore
+        if (_trustStore == null && _trustStoreInputStream == null && _trustStorePath == null)
+        {
+            _trustStore = _keyStore;
+            _trustStorePath = _keyStorePath;
+            _trustStoreInputStream = _keyStoreInputStream;
+            _trustStoreType = _keyStoreType;
+            _trustStoreProvider = _keyStoreProvider;
+            _trustStorePassword = _keyStorePassword;
+            _trustManagerFactoryAlgorithm = _keyManagerFactoryAlgorithm;
+        }
+
+        // It's the same stream we cannot read it twice, so read it once in memory
+        if (_keyStoreInputStream != null && _keyStoreInputStream == _trustStoreInputStream)
+        {
+            try
+            {
+                ByteArrayOutputStream baos = new ByteArrayOutputStream();
+                IO.copy(_keyStoreInputStream, baos);
+                _keyStoreInputStream.close();
+
+                _keyStoreInputStream = new ByteArrayInputStream(baos.toByteArray());
+                _trustStoreInputStream = new ByteArrayInputStream(baos.toByteArray());
+            }
+            catch (Exception ex)
+            {
+                throw new IllegalStateException(ex);
+            }
+        }
+    }
+
+    /**
+     * Select protocols to be used by the connector
+     * based on configured inclusion and exclusion lists
+     * as well as enabled and supported protocols.
+     * @param enabledProtocols Array of enabled protocols
+     * @param supportedProtocols Array of supported protocols
+     * @return Array of protocols to enable
+     */
+    public String[] selectProtocols(String[] enabledProtocols, String[] supportedProtocols)
+    {
+        Set<String> selected_protocols = new LinkedHashSet<>();
+
+        // Set the starting protocols - either from the included or enabled list
+        if (_includeProtocols!=null)
+        {
+            // Use only the supported included protocols
+            for (String protocol : _includeProtocols)
+                if(Arrays.asList(supportedProtocols).contains(protocol))
+                    selected_protocols.add(protocol);
+        }
+        else
+            selected_protocols.addAll(Arrays.asList(enabledProtocols));
+
+
+        // Remove any excluded protocols
+        selected_protocols.removeAll(_excludeProtocols);
+
+        return selected_protocols.toArray(new String[selected_protocols.size()]);
+    }
+
+    /**
+     * Select cipher suites to be used by the connector
+     * based on configured inclusion and exclusion lists
+     * as well as enabled and supported cipher suite lists.
+     * @param enabledCipherSuites Array of enabled cipher suites
+     * @param supportedCipherSuites Array of supported cipher suites
+     * @return Array of cipher suites to enable
+     */
+    public String[] selectCipherSuites(String[] enabledCipherSuites, String[] supportedCipherSuites)
+    {
+        Set<String> selected_ciphers = new CopyOnWriteArraySet<>();
+
+        // Set the starting ciphers - either from the included or enabled list
+        if (_includeCipherSuites!=null)
+            processIncludeCipherSuites(supportedCipherSuites, selected_ciphers);
+        else
+            selected_ciphers.addAll(Arrays.asList(enabledCipherSuites));
+
+        removeExcludedCipherSuites(selected_ciphers);
+
+        return selected_ciphers.toArray(new String[selected_ciphers.size()]);
+    }
+
+    private void processIncludeCipherSuites(String[] supportedCipherSuites, Set<String> selected_ciphers)
+    {
+        for (String cipherSuite : _includeCipherSuites)
+        {
+            Pattern p = Pattern.compile(cipherSuite);
+            for (String supportedCipherSuite : supportedCipherSuites)
+            {
+                Matcher m = p.matcher(supportedCipherSuite);
+                if (m.matches())
+                    selected_ciphers.add(supportedCipherSuite);
+            }
+        }
+    }
+
+    private void removeExcludedCipherSuites(Set<String> selected_ciphers)
+    {
+        for (String excludeCipherSuite : _excludeCipherSuites)
+        {
+            Pattern excludeCipherPattern = Pattern.compile(excludeCipherSuite);
+            for (String selectedCipherSuite : selected_ciphers)
+            {
+                Matcher m = excludeCipherPattern.matcher(selectedCipherSuite);
+                if (m.matches())
+                    selected_ciphers.remove(selectedCipherSuite);
+            }
+        }
+    }
+
+    /**
+     * Check if the lifecycle has been started and throw runtime exception
+     */
+    protected void checkNotStarted()
+    {
+        if (isStarted())
+            throw new IllegalStateException("Cannot modify configuration when "+getState());
+    }
+
+    /**
+     * @return true if CRL Distribution Points support is enabled
+     */
+    public boolean isEnableCRLDP()
+    {
+        return _enableCRLDP;
+    }
+
+    /** Enables CRL Distribution Points Support
+     * @param enableCRLDP true - turn on, false - turns off
+     */
+    public void setEnableCRLDP(boolean enableCRLDP)
+    {
+        checkNotStarted();
+        _enableCRLDP = enableCRLDP;
+    }
+
+    /**
+     * @return true if On-Line Certificate Status Protocol support is enabled
+     */
+    public boolean isEnableOCSP()
+    {
+        return _enableOCSP;
+    }
+
+    /** Enables On-Line Certificate Status Protocol support
+     * @param enableOCSP true - turn on, false - turn off
+     */
+    public void setEnableOCSP(boolean enableOCSP)
+    {
+        checkNotStarted();
+        _enableOCSP = enableOCSP;
+    }
+
+    /**
+     * @return Location of the OCSP Responder
+     */
+    public String getOcspResponderURL()
+    {
+        return _ocspResponderURL;
+    }
+
+    /** Set the location of the OCSP Responder.
+     * @param ocspResponderURL location of the OCSP Responder
+     */
+    public void setOcspResponderURL(String ocspResponderURL)
+    {
+        checkNotStarted();
+        _ocspResponderURL = ocspResponderURL;
+    }
+
+    /** Set the key store.
+     * @param keyStore the key store to set
+     */
+    public void setKeyStore(KeyStore keyStore)
+    {
+        checkNotStarted();
+        _keyStore = keyStore;
+    }
+
+    /** Set the trust store.
+     * @param trustStore the trust store to set
+     */
+    public void setTrustStore(KeyStore trustStore)
+    {
+        checkNotStarted();
+        _trustStore = trustStore;
+    }
+
+    /** Set the key store resource.
+     * @param resource the key store resource to set
+     */
+    public void setKeyStoreResource(Resource resource)
+    {
+        checkNotStarted();
+        try
+        {
+            _keyStoreInputStream = resource.getInputStream();
+        }
+        catch (IOException e)
+        {
+             throw new InvalidParameterException("Unable to get resource "+
+                     "input stream for resource "+resource.toString());
+        }
+    }
+
+    /** Set the trust store resource.
+     * @param resource the trust store resource to set
+     */
+    public void setTrustStoreResource(Resource resource)
+    {
+        checkNotStarted();
+        try
+        {
+            _trustStoreInputStream = resource.getInputStream();
+        }
+        catch (IOException e)
+        {
+             throw new InvalidParameterException("Unable to get resource "+
+                     "input stream for resource "+resource.toString());
+        }
+    }
+
+    /**
+    * @return true if SSL Session caching is enabled
+    */
+    public boolean isSessionCachingEnabled()
+    {
+        return _sessionCachingEnabled;
+    }
+
+    /** Set the flag to enable SSL Session caching.
+    * @param enableSessionCaching the value of the flag
+    */
+    public void setSessionCachingEnabled(boolean enableSessionCaching)
+    {
+        _sessionCachingEnabled = enableSessionCaching;
+    }
+
+    /** Get SSL session cache size.
+     * @return SSL session cache size
+     */
+    public int getSslSessionCacheSize()
+    {
+        return _sslSessionCacheSize;
+    }
+
+    /** SEt SSL session cache size.
+     * @param sslSessionCacheSize SSL session cache size to set
+     */
+    public void setSslSessionCacheSize(int sslSessionCacheSize)
+    {
+        _sslSessionCacheSize = sslSessionCacheSize;
+    }
+
+    /** Get SSL session timeout.
+     * @return SSL session timeout
+     */
+    public int getSslSessionTimeout()
+    {
+        return _sslSessionTimeout;
+    }
+
+    /** Set SSL session timeout.
+     * @param sslSessionTimeout SSL session timeout to set
+     */
+    public void setSslSessionTimeout(int sslSessionTimeout)
+    {
+        _sslSessionTimeout = sslSessionTimeout;
+    }
+
+
+    public SSLServerSocket newSslServerSocket(String host,int port,int backlog) throws IOException
+    {
+        SSLServerSocketFactory factory = _context.getServerSocketFactory();
+
+        SSLServerSocket socket =
+            (SSLServerSocket) (host==null ?
+                        factory.createServerSocket(port,backlog):
+                        factory.createServerSocket(port,backlog,InetAddress.getByName(host)));
+
+        if (getWantClientAuth())
+            socket.setWantClientAuth(getWantClientAuth());
+        if (getNeedClientAuth())
+            socket.setNeedClientAuth(getNeedClientAuth());
+
+        socket.setEnabledCipherSuites(selectCipherSuites(
+                                            socket.getEnabledCipherSuites(),
+                                            socket.getSupportedCipherSuites()));
+        socket.setEnabledProtocols(selectProtocols(socket.getEnabledProtocols(),socket.getSupportedProtocols()));
+
+        return socket;
+    }
+
+    public SSLSocket newSslSocket() throws IOException
+    {
+        SSLSocketFactory factory = _context.getSocketFactory();
+
+        SSLSocket socket = (SSLSocket)factory.createSocket();
+
+        if (getWantClientAuth())
+            socket.setWantClientAuth(getWantClientAuth());
+        if (getNeedClientAuth())
+            socket.setNeedClientAuth(getNeedClientAuth());
+
+        socket.setEnabledCipherSuites(selectCipherSuites(
+                                            socket.getEnabledCipherSuites(),
+                                            socket.getSupportedCipherSuites()));
+        socket.setEnabledProtocols(selectProtocols(socket.getEnabledProtocols(),socket.getSupportedProtocols()));
+
+        return socket;
+    }
+
+    /**
+     * Factory method for "scratch" {@link SSLEngine}s, usually only used for retrieving configuration
+     * information such as the application buffer size or the list of protocols/ciphers.
+     * <p />
+     * This method should not be used for creating {@link SSLEngine}s that are used in actual socket
+     * communication.
+     *
+     * @return a new, "scratch" {@link SSLEngine}
+     */
+    public SSLEngine newSSLEngine()
+    {
+        if (!isRunning())
+            throw new IllegalStateException("!STARTED");
+        SSLEngine sslEngine=_context.createSSLEngine();
+        customize(sslEngine);
+        return sslEngine;
+    }
+
+    /**
+     * General purpose factory method for creating {@link SSLEngine}s, although creation of
+     * {@link SSLEngine}s on the server-side should prefer {@link #newSSLEngine(InetSocketAddress)}.
+     *
+     * @param host the remote host
+     * @param port the remote port
+     * @return a new {@link SSLEngine}
+     */
+    public SSLEngine newSSLEngine(String host, int port)
+    {
+        if (!isRunning())
+            throw new IllegalStateException("!STARTED");
+        SSLEngine sslEngine=isSessionCachingEnabled()
+            ? _context.createSSLEngine(host, port)
+            : _context.createSSLEngine();
+        customize(sslEngine);
+        return sslEngine;
+    }
+
+    /**
+     * Server-side only factory method for creating {@link SSLEngine}s.
+     * <p />
+     * If the given {@code address} is null, it is equivalent to {@link #newSSLEngine()}, otherwise
+     * {@link #newSSLEngine(String, int)} is called.
+     * <p />
+     * If {@link #getNeedClientAuth()} is {@code true}, then the host name is passed to
+     * {@link #newSSLEngine(String, int)}, possibly incurring in a reverse DNS lookup, which takes time
+     * and may hang the selector (since this method is usually called by the selector thread).
+     * <p />
+     * Otherwise, the host address is passed to {@link #newSSLEngine(String, int)} without DNS lookup
+     * penalties.
+     * <p />
+     * Clients that wish to create {@link SSLEngine} instances must use {@link #newSSLEngine(String, int)}.
+     *
+     * @param address the remote peer address
+     * @return a new {@link SSLEngine}
+     */
+    public SSLEngine newSSLEngine(InetSocketAddress address)
+    {
+        if (address == null)
+            return newSSLEngine();
+
+        boolean useHostName = getNeedClientAuth();
+        String hostName = useHostName ? address.getHostName() : address.getAddress().getHostAddress();
+        return newSSLEngine(hostName, address.getPort());
+    }
+
+    public void customize(SSLEngine sslEngine)
+    {
+        SSLParameters sslParams = sslEngine.getSSLParameters();
+        sslParams.setEndpointIdentificationAlgorithm(_endpointIdentificationAlgorithm);
+        sslEngine.setSSLParameters(sslParams);
+
+        if (getWantClientAuth())
+            sslEngine.setWantClientAuth(getWantClientAuth());
+        if (getNeedClientAuth())
+            sslEngine.setNeedClientAuth(getNeedClientAuth());
+
+        sslEngine.setEnabledCipherSuites(selectCipherSuites(
+                sslEngine.getEnabledCipherSuites(),
+                sslEngine.getSupportedCipherSuites()));
+
+        sslEngine.setEnabledProtocols(selectProtocols(sslEngine.getEnabledProtocols(),sslEngine.getSupportedProtocols()));
+    }
+
+    public static X509Certificate[] getCertChain(SSLSession sslSession)
+    {
+        try
+        {
+            Certificate[] javaxCerts=sslSession.getPeerCertificates();
+            if (javaxCerts==null||javaxCerts.length==0)
+                return null;
+
+            int length=javaxCerts.length;
+            X509Certificate[] javaCerts=new X509Certificate[length];
+
+            java.security.cert.CertificateFactory cf=java.security.cert.CertificateFactory.getInstance("X.509");
+            for (int i=0; i<length; i++)
+            {
+                byte bytes[]=javaxCerts[i].getEncoded();
+                ByteArrayInputStream stream=new ByteArrayInputStream(bytes);
+                javaCerts[i]=(X509Certificate)cf.generateCertificate(stream);
+            }
+
+            return javaCerts;
+        }
+        catch (SSLPeerUnverifiedException pue)
+        {
+            return null;
+        }
+        catch (Exception e)
+        {
+            LOG.warn(Log.EXCEPTION,e);
+            return null;
+        }
+    }
+
+    /**
+     * Given the name of a TLS/SSL cipher suite, return an int representing it effective stream
+     * cipher key strength. i.e. How much entropy material is in the key material being fed into the
+     * encryption routines.
+     *
+     * <p>
+     * This is based on the information on effective key lengths in RFC 2246 - The TLS Protocol
+     * Version 1.0, Appendix C. CipherSuite definitions:
+     *
+     * <pre>
+     *                         Effective
+     *     Cipher       Type    Key Bits
+     *
+     *     NULL       * Stream     0
+     *     IDEA_CBC     Block    128
+     *     RC2_CBC_40 * Block     40
+     *     RC4_40     * Stream    40
+     *     RC4_128      Stream   128
+     *     DES40_CBC  * Block     40
+     *     DES_CBC      Block     56
+     *     3DES_EDE_CBC Block    168
+     * </pre>
+     *
+     * @param cipherSuite String name of the TLS cipher suite.
+     * @return int indicating the effective key entropy bit-length.
+     */
+    public static int deduceKeyLength(String cipherSuite)
+    {
+        // Roughly ordered from most common to least common.
+        if (cipherSuite == null)
+            return 0;
+        else if (cipherSuite.contains("WITH_AES_256_"))
+            return 256;
+        else if (cipherSuite.contains("WITH_RC4_128_"))
+            return 128;
+        else if (cipherSuite.contains("WITH_AES_128_"))
+            return 128;
+        else if (cipherSuite.contains("WITH_RC4_40_"))
+            return 40;
+        else if (cipherSuite.contains("WITH_3DES_EDE_CBC_"))
+            return 168;
+        else if (cipherSuite.contains("WITH_IDEA_CBC_"))
+            return 128;
+        else if (cipherSuite.contains("WITH_RC2_CBC_40_"))
+            return 40;
+        else if (cipherSuite.contains("WITH_DES40_CBC_"))
+            return 40;
+        else if (cipherSuite.contains("WITH_DES_CBC_"))
+            return 56;
+        else
+            return 0;
+    }
+
+    @Override
+    public String toString()
+    {
+        return String.format("%s@%x(%s,%s)",
+                getClass().getSimpleName(),
+                hashCode(),
+                _keyStorePath,
+                _trustStorePath);
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/util/ssl/package-info.java b/lib/jetty/org/eclipse/jetty/util/ssl/package-info.java
new file mode 100644 (file)
index 0000000..26ffa87
--- /dev/null
@@ -0,0 +1,23 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+/**
+ * Jetty Util : Common SSL Utility Classes
+ */
+package org.eclipse.jetty.util.ssl;
+
diff --git a/lib/jetty/org/eclipse/jetty/util/statistic/CounterStatistic.java b/lib/jetty/org/eclipse/jetty/util/statistic/CounterStatistic.java
new file mode 100644 (file)
index 0000000..027dfe3
--- /dev/null
@@ -0,0 +1,118 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util.statistic;
+
+import java.util.concurrent.atomic.AtomicLong;
+
+import org.eclipse.jetty.util.Atomics;
+
+
+/* ------------------------------------------------------------ */
+/** Statistics on a counter value.
+ * <p>
+ * Keep total, current and maximum values of a counter that
+ * can be incremented and decremented. The total refers only
+ * to increments.
+ *
+ */
+public class CounterStatistic
+{
+    protected final AtomicLong _max = new AtomicLong();
+    protected final AtomicLong _curr = new AtomicLong();
+    protected final AtomicLong _total = new AtomicLong();
+
+    /* ------------------------------------------------------------ */
+    public void reset()
+    {
+        reset(0);
+    }
+
+    /* ------------------------------------------------------------ */
+    public void reset(final long value)
+    {
+        _max.set(value);
+        _curr.set(value);
+        _total.set(0); // total always set to 0 to properly calculate cumulative total
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param delta the amount to add to the count
+     */
+    public long add(final long delta)
+    {
+        long value=_curr.addAndGet(delta);
+        if (delta > 0)
+        {
+            _total.addAndGet(delta);
+            Atomics.updateMax(_max,value);
+        }
+        return value;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     */
+    public long increment()
+    {
+        return add(1);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     */
+    public long decrement()
+    {
+        return add(-1);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return max value
+     */
+    public long getMax()
+    {
+        return _max.get();
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return current value
+     */
+    public long getCurrent()
+    {
+        return _curr.get();
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return total value
+     */
+    public long getTotal()
+    {
+        return _total.get();
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public String toString()
+    {
+        return String.format("%s@%x{c=%d,m=%d,t=%d}",this.getClass().getSimpleName(),hashCode(),_curr.get(),_max.get(),_total.get());
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/util/statistic/SampleStatistic.java b/lib/jetty/org/eclipse/jetty/util/statistic/SampleStatistic.java
new file mode 100644 (file)
index 0000000..eae7d47
--- /dev/null
@@ -0,0 +1,116 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util.statistic;
+
+import java.util.concurrent.atomic.AtomicLong;
+
+import org.eclipse.jetty.util.Atomics;
+
+
+/* ------------------------------------------------------------ */
+/**
+ * SampledStatistics
+ * <p>
+ * Provides max, total, mean, count, variance, and standard
+ * deviation of continuous sequence of samples.
+ * <p>
+ * Calculates estimates of mean, variance, and standard deviation
+ * characteristics of a sample using a non synchronized
+ * approximation of the on-line algorithm presented
+ * in Donald Knuth's Art of Computer Programming, Volume 2,
+ * Seminumerical Algorithms, 3rd edition, page 232,
+ * Boston: Addison-Wesley. that cites a 1962 paper by B.P. Welford
+ * that can be found by following the link http://www.jstor.org/pss/1266577
+ * <p>
+ * This algorithm is also described in Wikipedia at
+ * http://en.wikipedia.org/w/index.php?title=Algorithms_for_calculating_variance&section=4#On-line_algorithm
+ */
+public class SampleStatistic
+{
+    protected final AtomicLong _max = new AtomicLong();
+    protected final AtomicLong _total = new AtomicLong();
+    protected final AtomicLong _count = new AtomicLong();
+    protected final AtomicLong _totalVariance100 = new AtomicLong();
+
+    public void reset()
+    {
+        _max.set(0);
+        _total.set(0);
+        _count.set(0);
+        _totalVariance100.set(0);
+    }
+
+    public void set(final long sample)
+    {
+        long total = _total.addAndGet(sample);
+        long count = _count.incrementAndGet();
+
+        if (count>1)
+        {
+            long mean10 = total*10/count;
+            long delta10 = sample*10 - mean10;
+            _totalVariance100.addAndGet(delta10*delta10);
+        }
+
+        Atomics.updateMax(_max, sample);
+    }
+
+    /**
+     * @return the max value
+     */
+    public long getMax()
+    {
+        return _max.get();
+    }
+
+    public long getTotal()
+    {
+        return _total.get();
+    }
+
+    public long getCount()
+    {
+        return _count.get();
+    }
+
+    public double getMean()
+    {
+        return (double)_total.get()/_count.get();
+    }
+
+    public double getVariance()
+    {
+        final long variance100 = _totalVariance100.get();
+        final long count = _count.get();
+
+        return count>1?((double)variance100)/100.0/(count-1):0.0;
+    }
+
+    public double getStdDev()
+    {
+        return Math.sqrt(getVariance());
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public String toString()
+    {
+        return String.format("%s@%x{c=%d,m=%d,t=%d,v100=%d}",this.getClass().getSimpleName(),hashCode(),_count.get(),_max.get(),_total.get(),_totalVariance100.get());
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/util/statistic/package-info.java b/lib/jetty/org/eclipse/jetty/util/statistic/package-info.java
new file mode 100644 (file)
index 0000000..fac39e4
--- /dev/null
@@ -0,0 +1,23 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+/**
+ * Jetty Util : Common Statistics Utility classes
+ */
+package org.eclipse.jetty.util.statistic;
+
diff --git a/lib/jetty/org/eclipse/jetty/util/thread/ExecutorThreadPool.java b/lib/jetty/org/eclipse/jetty/util/thread/ExecutorThreadPool.java
new file mode 100644 (file)
index 0000000..4f6b5fe
--- /dev/null
@@ -0,0 +1,192 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util.thread;
+
+import java.util.concurrent.ArrayBlockingQueue;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.RejectedExecutionException;
+import java.util.concurrent.SynchronousQueue;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+
+import org.eclipse.jetty.util.component.AbstractLifeCycle;
+import org.eclipse.jetty.util.component.LifeCycle;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/* ------------------------------------------------------------ */
+/**
+ * Jetty ThreadPool using java 5 ThreadPoolExecutor
+ * This class wraps a {@link ExecutorService} as a {@link ThreadPool} and
+ * {@link LifeCycle} interfaces so that it may be used by the Jetty <code>org.eclipse.jetty.server.Server</code>
+ */
+public class ExecutorThreadPool extends AbstractLifeCycle implements ThreadPool, LifeCycle
+{
+    private static final Logger LOG = Log.getLogger(ExecutorThreadPool.class);
+    private final ExecutorService _executor;
+
+    /* ------------------------------------------------------------ */
+    public ExecutorThreadPool(ExecutorService executor)
+    {
+        _executor = executor;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Wraps an {@link ThreadPoolExecutor}.
+     * Max pool size is 256, pool thread timeout after 60 seconds and
+     * an unbounded {@link LinkedBlockingQueue} is used for the job queue;
+     */
+    public ExecutorThreadPool()
+    {
+        // Using an unbounded queue makes the maxThreads parameter useless
+        // Refer to ThreadPoolExecutor javadocs for details
+        this(new ThreadPoolExecutor(256, 256, 60, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>()));
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Wraps an {@link ThreadPoolExecutor}.
+     * Max pool size is 256, pool thread timeout after 60 seconds, and core pool size is 32 when queueSize >= 0.
+     * @param queueSize can be -1 for using an unbounded {@link LinkedBlockingQueue}, 0 for using a
+     * {@link SynchronousQueue}, greater than 0 for using a {@link ArrayBlockingQueue} of the given size.
+     */
+    public ExecutorThreadPool(int queueSize)
+    {
+        this(queueSize < 0 ? new ThreadPoolExecutor(256, 256, 60, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>()) :
+                queueSize == 0 ? new ThreadPoolExecutor(32, 256, 60, TimeUnit.SECONDS, new SynchronousQueue<Runnable>()) :
+                        new ThreadPoolExecutor(32, 256, 60, TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(queueSize)));
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Wraps an {@link ThreadPoolExecutor} using
+     * an unbounded {@link LinkedBlockingQueue} is used for the jobs queue;
+     * @param corePoolSize must be equal to maximumPoolSize
+     * @param maximumPoolSize the maximum number of threads to allow in the pool
+     * @param keepAliveTime the max time a thread can remain idle, in milliseconds
+     */
+    public ExecutorThreadPool(int corePoolSize, int maximumPoolSize, long keepAliveTime)
+    {
+        this(corePoolSize, maximumPoolSize, keepAliveTime, TimeUnit.MILLISECONDS);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Wraps an {@link ThreadPoolExecutor} using
+     * an unbounded {@link LinkedBlockingQueue} is used for the jobs queue.
+     * @param corePoolSize must be equal to maximumPoolSize
+     * @param maximumPoolSize the maximum number of threads to allow in the pool
+     * @param keepAliveTime the max time a thread can remain idle
+     * @param unit the unit for the keepAliveTime
+     */
+    public ExecutorThreadPool(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit)
+    {
+        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, new LinkedBlockingQueue<Runnable>());
+    }
+
+    /* ------------------------------------------------------------ */
+
+    /**
+     * Wraps an {@link ThreadPoolExecutor}
+     * @param corePoolSize the number of threads to keep in the pool, even if they are idle
+     * @param maximumPoolSize the maximum number of threads to allow in the pool
+     * @param keepAliveTime the max time a thread can remain idle
+     * @param unit the unit for the keepAliveTime
+     * @param workQueue the queue to use for holding tasks before they are executed
+     */
+    public ExecutorThreadPool(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue)
+    {
+        this(new ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue));
+    }
+
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public void execute(Runnable job)
+    {
+        _executor.execute(job);
+    }
+
+    /* ------------------------------------------------------------ */
+    public boolean dispatch(Runnable job)
+    {
+        try
+        {
+            _executor.execute(job);
+            return true;
+        }
+        catch(RejectedExecutionException e)
+        {
+            LOG.warn(e);
+            return false;
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    public int getIdleThreads()
+    {
+        if (_executor instanceof ThreadPoolExecutor)
+        {
+            final ThreadPoolExecutor tpe = (ThreadPoolExecutor)_executor;
+            return tpe.getPoolSize() - tpe.getActiveCount();
+        }
+        return -1;
+    }
+
+    /* ------------------------------------------------------------ */
+    public int getThreads()
+    {
+        if (_executor instanceof ThreadPoolExecutor)
+        {
+            final ThreadPoolExecutor tpe = (ThreadPoolExecutor)_executor;
+            return tpe.getPoolSize();
+        }
+        return -1;
+    }
+
+    /* ------------------------------------------------------------ */
+    public boolean isLowOnThreads()
+    {
+        if (_executor instanceof ThreadPoolExecutor)
+        {
+            final ThreadPoolExecutor tpe = (ThreadPoolExecutor)_executor;
+            // getActiveCount() locks the thread pool, so execute it last
+            return tpe.getPoolSize() == tpe.getMaximumPoolSize() &&
+                    tpe.getQueue().size() >= tpe.getPoolSize() - tpe.getActiveCount();
+        }
+        return false;
+    }
+
+    /* ------------------------------------------------------------ */
+    public void join() throws InterruptedException
+    {
+        _executor.awaitTermination(Long.MAX_VALUE, TimeUnit.MILLISECONDS);
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    protected void doStop() throws Exception
+    {
+        super.doStop();
+        _executor.shutdownNow();
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/util/thread/NonBlockingThread.java b/lib/jetty/org/eclipse/jetty/util/thread/NonBlockingThread.java
new file mode 100644 (file)
index 0000000..4fb75c7
--- /dev/null
@@ -0,0 +1,59 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util.thread;
+
+/**
+ * Marker that wraps a Runnable, indicating that it is running in a thread that must not be blocked.
+ * <p />
+ * Client code can use the thread-local {@link #isNonBlockingThread()} to detect whether they are
+ * in the context of a non-blocking thread, and perform different actions if that's the case.
+ */
+public class NonBlockingThread implements Runnable
+{
+    private final static ThreadLocal<Boolean> __nonBlockingThread = new ThreadLocal<>();
+
+    /**
+     * @return whether the current thread is a thread that must not block.
+     */
+    public static boolean isNonBlockingThread()
+    {
+        return Boolean.TRUE.equals(__nonBlockingThread.get());
+    }
+
+    private final Runnable delegate;
+
+    public NonBlockingThread(Runnable delegate)
+    {
+        this.delegate = delegate;
+    }
+
+    @Override
+    public void run()
+    {
+        try
+        {
+            __nonBlockingThread.set(Boolean.TRUE);
+            delegate.run();
+        }
+        finally
+        {
+            __nonBlockingThread.remove();
+        }
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/util/thread/QueuedThreadPool.java b/lib/jetty/org/eclipse/jetty/util/thread/QueuedThreadPool.java
new file mode 100644 (file)
index 0000000..5cc7512
--- /dev/null
@@ -0,0 +1,666 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+
+package org.eclipse.jetty.util.thread;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.RejectedExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicLong;
+
+import org.eclipse.jetty.util.BlockingArrayQueue;
+import org.eclipse.jetty.util.StringUtil;
+import org.eclipse.jetty.util.annotation.ManagedAttribute;
+import org.eclipse.jetty.util.annotation.ManagedObject;
+import org.eclipse.jetty.util.annotation.ManagedOperation;
+import org.eclipse.jetty.util.annotation.Name;
+import org.eclipse.jetty.util.component.AbstractLifeCycle;
+import org.eclipse.jetty.util.component.ContainerLifeCycle;
+import org.eclipse.jetty.util.component.Dumpable;
+import org.eclipse.jetty.util.component.LifeCycle;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.thread.ThreadPool.SizedThreadPool;
+
+@ManagedObject("A thread pool with no max bound by default")
+public class QueuedThreadPool extends AbstractLifeCycle implements SizedThreadPool, Dumpable
+{
+    private static final Logger LOG = Log.getLogger(QueuedThreadPool.class);
+
+    private final AtomicInteger _threadsStarted = new AtomicInteger();
+    private final AtomicInteger _threadsIdle = new AtomicInteger();
+    private final AtomicLong _lastShrink = new AtomicLong();
+    private final ConcurrentLinkedQueue<Thread> _threads = new ConcurrentLinkedQueue<>();
+    private final Object _joinLock = new Object();
+    private final BlockingQueue<Runnable> _jobs;
+    private String _name = "qtp" + hashCode();
+    private int _idleTimeout;
+    private int _maxThreads;
+    private int _minThreads;
+    private int _priority = Thread.NORM_PRIORITY;
+    private boolean _daemon = false;
+    private boolean _detailedDump = false;
+
+    public QueuedThreadPool()
+    {
+        this(200);
+    }
+
+    public QueuedThreadPool(@Name("maxThreads") int maxThreads)
+    {
+        this(maxThreads, 8);
+    }
+
+    public QueuedThreadPool(@Name("maxThreads") int maxThreads,  @Name("minThreads") int minThreads)
+    {
+        this(maxThreads, minThreads, 60000);
+    }
+
+    public QueuedThreadPool(@Name("maxThreads") int maxThreads,  @Name("minThreads") int minThreads, @Name("idleTimeout")int idleTimeout)
+    {
+        this(maxThreads, minThreads, idleTimeout, null);
+    }
+
+    public QueuedThreadPool(@Name("maxThreads") int maxThreads, @Name("minThreads") int minThreads, @Name("idleTimeout") int idleTimeout, @Name("queue") BlockingQueue<Runnable> queue)
+    {
+        setMinThreads(minThreads);
+        setMaxThreads(maxThreads);
+        setIdleTimeout(idleTimeout);
+        setStopTimeout(5000);
+
+        if (queue==null)
+            queue=new BlockingArrayQueue<>(_minThreads, _minThreads);
+        _jobs=queue;
+
+    }
+
+    @Override
+    protected void doStart() throws Exception
+    {
+        super.doStart();
+        _threadsStarted.set(0);
+
+        startThreads(_minThreads);
+    }
+
+    @Override
+    protected void doStop() throws Exception
+    {
+        super.doStop();
+
+        long timeout = getStopTimeout();
+        BlockingQueue<Runnable> jobs = getQueue();
+
+        // If no stop timeout, clear job queue
+        if (timeout <= 0)
+            jobs.clear();
+
+        // Fill job Q with noop jobs to wakeup idle
+        Runnable noop = new Runnable()
+        {
+            @Override
+            public void run()
+            {
+            }
+        };
+        for (int i = _threadsStarted.get(); i-- > 0; )
+            jobs.offer(noop);
+
+        // try to jobs complete naturally for half our stop time
+        long stopby = System.nanoTime() + TimeUnit.MILLISECONDS.toNanos(timeout) / 2;
+        for (Thread thread : _threads)
+        {
+            long canwait = TimeUnit.NANOSECONDS.toMillis(stopby - System.nanoTime());
+            if (canwait > 0)
+                thread.join(canwait);
+        }
+
+        // If we still have threads running, get a bit more aggressive
+
+        // interrupt remaining threads
+        if (_threadsStarted.get() > 0)
+            for (Thread thread : _threads)
+                thread.interrupt();
+
+        // wait again for the other half of our stop time
+        stopby = System.nanoTime() + TimeUnit.MILLISECONDS.toNanos(timeout) / 2;
+        for (Thread thread : _threads)
+        {
+            long canwait = TimeUnit.NANOSECONDS.toMillis(stopby - System.nanoTime());
+            if (canwait > 0)
+                thread.join(canwait);
+        }
+
+        Thread.yield();
+        int size = _threads.size();
+        if (size > 0)
+        {
+            Thread.yield();
+            
+            if (LOG.isDebugEnabled())
+            {
+                for (Thread unstopped : _threads)
+                {
+                    StringBuilder dmp = new StringBuilder();
+                    for (StackTraceElement element : unstopped.getStackTrace())
+                    {
+                        dmp.append(StringUtil.__LINE_SEPARATOR).append("\tat ").append(element);
+                    }
+                    LOG.warn("Couldn't stop {}{}", unstopped, dmp.toString());
+                }
+            }
+            else
+            {
+                for (Thread unstopped : _threads)
+                    LOG.warn("{} Couldn't stop {}",this,unstopped);
+            }
+        }
+
+        synchronized (_joinLock)
+        {
+            _joinLock.notifyAll();
+        }
+    }
+
+    /**
+     * Delegated to the named or anonymous Pool.
+     */
+    public void setDaemon(boolean daemon)
+    {
+        _daemon = daemon;
+    }
+
+    /**
+     * Set the maximum thread idle time.
+     * Threads that are idle for longer than this period may be
+     * stopped.
+     * Delegated to the named or anonymous Pool.
+     *
+     * @param idleTimeout Max idle time in ms.
+     * @see #getIdleTimeout
+     */
+    public void setIdleTimeout(int idleTimeout)
+    {
+        _idleTimeout = idleTimeout;
+    }
+
+    /**
+     * Set the maximum number of threads.
+     * Delegated to the named or anonymous Pool.
+     *
+     * @param maxThreads maximum number of threads.
+     * @see #getMaxThreads
+     */
+    @Override
+    public void setMaxThreads(int maxThreads)
+    {
+        _maxThreads = maxThreads;
+        if (_minThreads > _maxThreads)
+            _minThreads = _maxThreads;
+    }
+
+    /**
+     * Set the minimum number of threads.
+     * Delegated to the named or anonymous Pool.
+     *
+     * @param minThreads minimum number of threads
+     * @see #getMinThreads
+     */
+    @Override
+    public void setMinThreads(int minThreads)
+    {
+        _minThreads = minThreads;
+
+        if (_minThreads > _maxThreads)
+            _maxThreads = _minThreads;
+
+        int threads = _threadsStarted.get();
+        if (isStarted() && threads < _minThreads)
+            startThreads(_minThreads - threads);
+    }
+
+    /**
+     * @param name Name of this thread pool to use when naming threads.
+     */
+    public void setName(String name)
+    {
+        if (isRunning())
+            throw new IllegalStateException("started");
+        _name = name;
+    }
+
+    /**
+     * Set the priority of the pool threads.
+     *
+     * @param priority the new thread priority.
+     */
+    public void setThreadsPriority(int priority)
+    {
+        _priority = priority;
+    }
+
+    /**
+     * Get the maximum thread idle time.
+     * Delegated to the named or anonymous Pool.
+     *
+     * @return Max idle time in ms.
+     * @see #setIdleTimeout
+     */
+    @ManagedAttribute("maximum time a thread may be idle in ms")
+    public int getIdleTimeout()
+    {
+        return _idleTimeout;
+    }
+
+    /**
+     * Set the maximum number of threads.
+     * Delegated to the named or anonymous Pool.
+     *
+     * @return maximum number of threads.
+     * @see #setMaxThreads
+     */
+    @Override
+    @ManagedAttribute("maximum number of threads in the pool")
+    public int getMaxThreads()
+    {
+        return _maxThreads;
+    }
+
+    /**
+     * Get the minimum number of threads.
+     * Delegated to the named or anonymous Pool.
+     *
+     * @return minimum number of threads.
+     * @see #setMinThreads
+     */
+    @Override
+    @ManagedAttribute("minimum number of threads in the pool")
+    public int getMinThreads()
+    {
+        return _minThreads;
+    }
+
+    /**
+     * @return The name of the this thread pool
+     */
+    @ManagedAttribute("name of the thread pool")
+    public String getName()
+    {
+        return _name;
+    }
+
+    /**
+     * Get the priority of the pool threads.
+     *
+     * @return the priority of the pool threads.
+     */
+    @ManagedAttribute("priority of threads in the pool")
+    public int getThreadsPriority()
+    {
+        return _priority;
+    }
+    
+    /**
+     * Get the size of the job queue.
+     * 
+     * @return Number of jobs queued waiting for a thread
+     */
+    @ManagedAttribute("Size of the job queue")
+    public int getQueueSize()
+    {
+        return _jobs.size();
+    }
+
+    /**
+     * Delegated to the named or anonymous Pool.
+     */
+    @ManagedAttribute("thead pool using a daemon thread")
+    public boolean isDaemon()
+    {
+        return _daemon;
+    }
+
+    public boolean isDetailedDump()
+    {
+        return _detailedDump;
+    }
+
+    public void setDetailedDump(boolean detailedDump)
+    {
+        _detailedDump = detailedDump;
+    }
+    
+    @Override
+    public void execute(Runnable job)
+    {
+        if (!isRunning() || !_jobs.offer(job))
+        {
+            LOG.warn("{} rejected {}", this, job);
+            throw new RejectedExecutionException(job.toString());
+        }
+    }
+
+    /**
+     * Blocks until the thread pool is {@link LifeCycle#stop stopped}.
+     */
+    @Override
+    public void join() throws InterruptedException
+    {
+        synchronized (_joinLock)
+        {
+            while (isRunning())
+                _joinLock.wait();
+        }
+
+        while (isStopping())
+            Thread.sleep(1);
+    }
+
+    /**
+     * @return The total number of threads currently in the pool
+     */
+    @Override
+    @ManagedAttribute("total number of threads currently in the pool")
+    public int getThreads()
+    {
+        return _threadsStarted.get();
+    }
+
+    /**
+     * @return The number of idle threads in the pool
+     */
+    @Override
+    @ManagedAttribute("total number of idle threads in the pool")
+    public int getIdleThreads()
+    {
+        return _threadsIdle.get();
+    }
+
+    /**
+     * @return True if the pool is at maxThreads and there are not more idle threads than queued jobs
+     */
+    @Override
+    @ManagedAttribute("True if the pools is at maxThreads and there are not idle threads than queued jobs")
+    public boolean isLowOnThreads()
+    {
+        return _threadsStarted.get() == _maxThreads && _jobs.size() >= _threadsIdle.get();
+    }
+
+    private boolean startThreads(int threadsToStart)
+    {
+        while (threadsToStart > 0)
+        {
+            int threads = _threadsStarted.get();
+            if (threads >= _maxThreads)
+                return false;
+
+            if (!_threadsStarted.compareAndSet(threads, threads + 1))
+                continue;
+
+            boolean started = false;
+            try
+            {
+                Thread thread = newThread(_runnable);
+                thread.setDaemon(isDaemon());
+                thread.setPriority(getThreadsPriority());
+                thread.setName(_name + "-" + thread.getId());
+                _threads.add(thread);
+
+                thread.start();
+                started = true;
+            }
+            finally
+            {
+                if (!started)
+                    _threadsStarted.decrementAndGet();
+            }
+            if (started)
+                threadsToStart--;
+        }
+        return true;
+    }
+
+    protected Thread newThread(Runnable runnable)
+    {
+        return new Thread(runnable);
+    }
+
+
+    @Override
+    @ManagedOperation("dump thread state")
+    public String dump()
+    {
+        return ContainerLifeCycle.dump(this);
+    }
+
+    @Override
+    public void dump(Appendable out, String indent) throws IOException
+    {
+        List<Object> dump = new ArrayList<>(getMaxThreads());
+        for (final Thread thread : _threads)
+        {
+            final StackTraceElement[] trace = thread.getStackTrace();
+            boolean inIdleJobPoll = false;
+            for (StackTraceElement t : trace)
+            {
+                if ("idleJobPoll".equals(t.getMethodName()))
+                {
+                    inIdleJobPoll = true;
+                    break;
+                }
+            }
+            final boolean idle = inIdleJobPoll;
+
+            if (isDetailedDump())
+            {
+                dump.add(new Dumpable()
+                {
+                    @Override
+                    public void dump(Appendable out, String indent) throws IOException
+                    {
+                        out.append(String.valueOf(thread.getId())).append(' ').append(thread.getName()).append(' ').append(thread.getState().toString()).append(idle ? " IDLE" : "").append('\n');
+                        if (!idle)
+                            ContainerLifeCycle.dump(out, indent, Arrays.asList(trace));
+                    }
+
+                    @Override
+                    public String dump()
+                    {
+                        return null;
+                    }
+                });
+            }
+            else
+            {
+                dump.add(thread.getId() + " " + thread.getName() + " " + thread.getState() + " @ " + (trace.length > 0 ? trace[0] : "???") + (idle ? " IDLE" : ""));
+            }
+        }
+
+        ContainerLifeCycle.dumpObject(out, this);
+        ContainerLifeCycle.dump(out, indent, dump);
+    }
+
+    @Override
+    public String toString()
+    {
+        return String.format("%s{%s,%d<=%d<=%d,i=%d,q=%d}", _name, getState(), getMinThreads(), getThreads(), getMaxThreads(), getIdleThreads(), (_jobs == null ? -1 : _jobs.size()));
+    }
+
+    private Runnable idleJobPoll() throws InterruptedException
+    {
+        return _jobs.poll(_idleTimeout, TimeUnit.MILLISECONDS);
+    }
+
+    private Runnable _runnable = new Runnable()
+    {
+        @Override
+        public void run()
+        {
+            boolean shrink = false;
+            try
+            {
+                Runnable job = _jobs.poll();
+
+                if (job != null && _threadsIdle.get() == 0)
+                {
+                    startThreads(1);
+                }
+
+                loop: while (isRunning())
+                {
+                    // Job loop
+                    while (job != null && isRunning())
+                    {
+                        runJob(job);
+                        if (Thread.interrupted())
+                            break loop;
+                        job = _jobs.poll();
+                    }
+
+                    // Idle loop
+                    try
+                    {
+                        _threadsIdle.incrementAndGet();
+
+                        while (isRunning() && job == null)
+                        {
+                            if (_idleTimeout <= 0)
+                                job = _jobs.take();
+                            else
+                            {
+                                // maybe we should shrink?
+                                final int size = _threadsStarted.get();
+                                if (size > _minThreads)
+                                {
+                                    long last = _lastShrink.get();
+                                    long now = System.nanoTime();
+                                    if (last == 0 || (now - last) > TimeUnit.MILLISECONDS.toNanos(_idleTimeout))
+                                    {
+                                        shrink = _lastShrink.compareAndSet(last, now) &&
+                                                _threadsStarted.compareAndSet(size, size - 1);
+                                        if (shrink)
+                                        {
+                                            return;
+                                        }
+                                    }
+                                }
+                                job = idleJobPoll();
+                            }
+                        }
+                    }
+                    finally
+                    {
+                        if (_threadsIdle.decrementAndGet() == 0)
+                        {
+                            startThreads(1);
+                        }
+                    }
+                }
+            }
+            catch (InterruptedException e)
+            {
+                LOG.ignore(e);
+            }
+            catch (Throwable e)
+            {
+                LOG.warn(e);
+            }
+            finally
+            {
+                if (!shrink)
+                    _threadsStarted.decrementAndGet();
+                _threads.remove(Thread.currentThread());
+            }
+        }
+    };
+
+    /**
+     * <p>Runs the given job in the {@link Thread#currentThread() current thread}.</p>
+     * <p>Subclasses may override to perform pre/post actions before/after the job is run.</p>
+     *
+     * @param job the job to run
+     */
+    protected void runJob(Runnable job)
+    {
+        job.run();
+    }
+
+    /**
+     * @return the job queue
+     */
+    protected BlockingQueue<Runnable> getQueue()
+    {
+        return _jobs;
+    }
+
+    /**
+     * @param queue the job queue
+     */
+    public void setQueue(BlockingQueue<Runnable> queue)
+    {
+        throw new UnsupportedOperationException("Use constructor injection");
+    }
+
+    /**
+     * @param id The thread ID to interrupt.
+     * @return true if the thread was found and interrupted.
+     */
+    @ManagedOperation("interrupt a pool thread")
+    public boolean interruptThread(@Name("id") long id)
+    {
+        for (Thread thread : _threads)
+        {
+            if (thread.getId() == id)
+            {
+                thread.interrupt();
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * @param id The thread ID to interrupt.
+     * @return true if the thread was found and interrupted.
+     */
+    @ManagedOperation("dump a pool thread stack")
+    public String dumpThread(@Name("id") long id)
+    {
+        for (Thread thread : _threads)
+        {
+            if (thread.getId() == id)
+            {
+                StringBuilder buf = new StringBuilder();
+                buf.append(thread.getId()).append(" ").append(thread.getName()).append(" ").append(thread.getState()).append(":\n");
+                for (StackTraceElement element : thread.getStackTrace())
+                    buf.append("  at ").append(element.toString()).append('\n');
+                return buf.toString();
+            }
+        }
+        return null;
+    }
+    
+
+}
diff --git a/lib/jetty/org/eclipse/jetty/util/thread/ScheduledExecutorScheduler.java b/lib/jetty/org/eclipse/jetty/util/thread/ScheduledExecutorScheduler.java
new file mode 100644 (file)
index 0000000..5f8d62a
--- /dev/null
@@ -0,0 +1,100 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util.thread;
+
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.ScheduledThreadPoolExecutor;
+import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.TimeUnit;
+
+import org.eclipse.jetty.util.component.AbstractLifeCycle;
+
+/**
+ * Implementation of {@link Scheduler} based on JDK's {@link ScheduledThreadPoolExecutor}.
+ * <p />
+ * While use of {@link ScheduledThreadPoolExecutor} creates futures that will not be used,
+ * it has the advantage of allowing to set a property to remove cancelled tasks from its
+ * queue even if the task did not fire, which provides a huge benefit in the performance
+ * of garbage collection in young generation.
+ */
+public class ScheduledExecutorScheduler extends AbstractLifeCycle implements Scheduler
+{
+    private final String name;
+    private final boolean daemon;
+    private volatile ScheduledThreadPoolExecutor scheduler;
+
+    public ScheduledExecutorScheduler()
+    {
+        this(null, false);
+    }
+
+    public ScheduledExecutorScheduler(String name, boolean daemon)
+    {
+        this.name = name == null ? "Scheduler-" + hashCode() : name;
+        this.daemon = daemon;
+    }
+
+    @Override
+    protected void doStart() throws Exception
+    {
+        scheduler = new ScheduledThreadPoolExecutor(1, new ThreadFactory()
+        {
+            @Override
+            public Thread newThread(Runnable r)
+            {
+                Thread thread = new Thread(r, name);
+                thread.setDaemon(daemon);
+                return thread;
+            }
+        });
+        scheduler.setRemoveOnCancelPolicy(true);
+        super.doStart();
+    }
+
+    @Override
+    protected void doStop() throws Exception
+    {
+        scheduler.shutdownNow();
+        super.doStop();
+        scheduler = null;
+    }
+
+    @Override
+    public Task schedule(Runnable task, long delay, TimeUnit unit)
+    {
+        ScheduledFuture<?> result = scheduler.schedule(task, delay, unit);
+        return new ScheduledFutureTask(result);
+    }
+
+    private class ScheduledFutureTask implements Task
+    {
+        private final ScheduledFuture<?> scheduledFuture;
+
+        public ScheduledFutureTask(ScheduledFuture<?> scheduledFuture)
+        {
+            this.scheduledFuture = scheduledFuture;
+        }
+
+        @Override
+        public boolean cancel()
+        {
+            return scheduledFuture.cancel(false);
+        }
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/util/thread/Scheduler.java b/lib/jetty/org/eclipse/jetty/util/thread/Scheduler.java
new file mode 100644 (file)
index 0000000..f519184
--- /dev/null
@@ -0,0 +1,33 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util.thread;
+
+import java.util.concurrent.TimeUnit;
+
+import org.eclipse.jetty.util.component.LifeCycle;
+
+public interface Scheduler extends LifeCycle
+{
+    interface Task
+    {
+        boolean cancel();
+    }
+
+    Task schedule(Runnable task, long delay, TimeUnit units);
+}
diff --git a/lib/jetty/org/eclipse/jetty/util/thread/ShutdownThread.java b/lib/jetty/org/eclipse/jetty/util/thread/ShutdownThread.java
new file mode 100644 (file)
index 0000000..168b444
--- /dev/null
@@ -0,0 +1,148 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util.thread;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+import org.eclipse.jetty.util.component.Destroyable;
+import org.eclipse.jetty.util.component.LifeCycle;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+
+/* ------------------------------------------------------------ */
+/**
+ * ShutdownThread is a shutdown hook thread implemented as 
+ * singleton that maintains a list of lifecycle instances
+ * that are registered with it and provides ability to stop
+ * these lifecycles upon shutdown of the Java Virtual Machine 
+ */
+public class ShutdownThread extends Thread
+{
+    private static final Logger LOG = Log.getLogger(ShutdownThread.class);
+    private static final ShutdownThread _thread = new ShutdownThread();
+
+    private boolean _hooked;
+    private final List<LifeCycle> _lifeCycles = new CopyOnWriteArrayList<LifeCycle>();
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Default constructor for the singleton
+     * 
+     * Registers the instance as shutdown hook with the Java Runtime
+     */
+    private ShutdownThread()
+    {
+    }
+    
+    /* ------------------------------------------------------------ */
+    private synchronized void hook()
+    {
+        try
+        {
+            if (!_hooked)
+                Runtime.getRuntime().addShutdownHook(this);
+            _hooked=true;
+        }
+        catch(Exception e)
+        {
+            LOG.ignore(e);
+            LOG.info("shutdown already commenced");
+        }
+    }
+    
+    /* ------------------------------------------------------------ */
+    private synchronized void unhook()
+    {
+        try
+        {
+            _hooked=false;
+            Runtime.getRuntime().removeShutdownHook(this);
+        }
+        catch(Exception e)
+        {
+            LOG.ignore(e);
+            LOG.debug("shutdown already commenced");
+        }
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * Returns the instance of the singleton
+     * 
+     * @return the singleton instance of the {@link ShutdownThread}
+     */
+    public static ShutdownThread getInstance()
+    {
+        return _thread;
+    }
+
+    /* ------------------------------------------------------------ */
+    public static synchronized void register(LifeCycle... lifeCycles)
+    {
+        _thread._lifeCycles.addAll(Arrays.asList(lifeCycles));
+        if (_thread._lifeCycles.size()>0)
+            _thread.hook();
+    }
+
+    /* ------------------------------------------------------------ */
+    public static synchronized void register(int index, LifeCycle... lifeCycles)
+    {
+        _thread._lifeCycles.addAll(index,Arrays.asList(lifeCycles));
+        if (_thread._lifeCycles.size()>0)
+            _thread.hook();
+    }
+    
+    /* ------------------------------------------------------------ */
+    public static synchronized void deregister(LifeCycle lifeCycle)
+    {
+        _thread._lifeCycles.remove(lifeCycle);
+        if (_thread._lifeCycles.size()==0)
+            _thread.unhook();
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public void run()
+    {
+        for (LifeCycle lifeCycle : _thread._lifeCycles)
+        {
+            try
+            {
+                if (lifeCycle.isStarted())
+                {
+                    lifeCycle.stop();
+                    LOG.debug("Stopped {}",lifeCycle);
+                }
+                
+                if (lifeCycle instanceof Destroyable)
+                {
+                    ((Destroyable)lifeCycle).destroy();
+                    LOG.debug("Destroyed {}",lifeCycle);
+                }
+            }
+            catch (Exception ex)
+            {
+                LOG.debug(ex);
+            }
+        }
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/util/thread/ThreadPool.java b/lib/jetty/org/eclipse/jetty/util/thread/ThreadPool.java
new file mode 100644 (file)
index 0000000..c2abcfc
--- /dev/null
@@ -0,0 +1,74 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util.thread;
+
+import java.util.concurrent.Executor;
+
+import org.eclipse.jetty.util.annotation.ManagedAttribute;
+import org.eclipse.jetty.util.annotation.ManagedObject;
+import org.eclipse.jetty.util.component.LifeCycle;
+
+/* ------------------------------------------------------------ */
+/** ThreadPool.
+ * 
+ * A specialization of Executor interface that provides reporting methods (eg {@link #getThreads()})
+ * and the option of configuration methods (e.g. @link {@link SizedThreadPool#setMaxThreads(int)}). 
+ *
+ */
+@ManagedObject("Pool of Threads")
+public interface ThreadPool extends Executor
+{
+    /* ------------------------------------------------------------ */
+    /**
+     * Blocks until the thread pool is {@link LifeCycle#stop stopped}.
+     */
+    public void join() throws InterruptedException;
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return The total number of threads currently in the pool
+     */
+    @ManagedAttribute("number of threads in pool")
+    public int getThreads();
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return The number of idle threads in the pool
+     */
+    @ManagedAttribute("number of idle threads in pool")
+    public int getIdleThreads();
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * @return True if the pool is low on threads
+     */
+    @ManagedAttribute("indicates the pool is low on available threads")
+    public boolean isLowOnThreads();
+    
+
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    public interface SizedThreadPool extends ThreadPool
+    {
+        public int getMinThreads();
+        public int getMaxThreads();
+        public void setMinThreads(int threads);
+        public void setMaxThreads(int threads);
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/util/thread/TimerScheduler.java b/lib/jetty/org/eclipse/jetty/util/thread/TimerScheduler.java
new file mode 100644 (file)
index 0000000..c07390a
--- /dev/null
@@ -0,0 +1,129 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util.thread;
+
+import java.util.Timer;
+import java.util.TimerTask;
+import java.util.concurrent.RejectedExecutionException;
+import java.util.concurrent.TimeUnit;
+
+import org.eclipse.jetty.util.component.AbstractLifeCycle;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+
+/* ------------------------------------------------------------ */
+/** A scheduler based on the the JVM Timer class
+ */
+public class TimerScheduler extends AbstractLifeCycle implements Scheduler, Runnable
+{
+    private static final Logger LOG = Log.getLogger(TimerScheduler.class);
+
+    /*
+     * This class uses the Timer class rather than an ScheduledExecutionService because
+     * it uses the same algorithm internally and the signature is cheaper to use as there are no
+     * Futures involved (which we do not need).
+     * However, Timer is still locking and a concurrent queue would be better.
+     */
+
+    private final String _name;
+    private final boolean _daemon;
+    private Timer _timer;
+
+    public TimerScheduler()
+    {
+        this(null, false);
+    }
+
+    public TimerScheduler(String name, boolean daemon)
+    {
+        _name = name;
+        _daemon = daemon;
+    }
+
+    @Override
+    protected void doStart() throws Exception
+    {
+        _timer = _name == null ? new Timer() : new Timer(_name, _daemon);
+        run();
+        super.doStart();
+    }
+
+    @Override
+    protected void doStop() throws Exception
+    {
+        _timer.cancel();
+        super.doStop();
+        _timer = null;
+    }
+
+    @Override
+    public Task schedule(final Runnable task, final long delay, final TimeUnit units)
+    {
+        Timer timer = _timer;
+        if (timer == null)
+            throw new RejectedExecutionException("STOPPED: " + this);
+        SimpleTask t = new SimpleTask(task);
+        timer.schedule(t, units.toMillis(delay));
+        return t;
+    }
+
+    @Override
+    public void run()
+    {
+        Timer timer = _timer;
+        if (timer != null)
+        {
+            timer.purge();
+            schedule(this, 1, TimeUnit.SECONDS);
+        }
+    }
+
+    private static class SimpleTask extends TimerTask implements Task
+    {
+        private final Runnable _task;
+
+        private SimpleTask(Runnable runnable)
+        {
+            _task = runnable;
+        }
+
+        @Override
+        public void run()
+        {
+            try
+            {
+                _task.run();
+            }
+            catch (Throwable x)
+            {
+                LOG.debug("Exception while executing task " + _task, x);
+            }
+        }
+
+        @Override
+        public String toString()
+        {
+            return String.format("%s.%s@%x",
+                    TimerScheduler.class.getSimpleName(),
+                    SimpleTask.class.getSimpleName(),
+                    hashCode());
+        }
+    }
+}
diff --git a/lib/jetty/org/eclipse/jetty/util/thread/package-info.java b/lib/jetty/org/eclipse/jetty/util/thread/package-info.java
new file mode 100644 (file)
index 0000000..2637e49
--- /dev/null
@@ -0,0 +1,23 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+/**
+ * Jetty Util : Common ThreadPool Utilities
+ */
+package org.eclipse.jetty.util.thread;
+
diff --git a/lib/jtar/org/kamranzafar/jtar/Octal.java b/lib/jtar/org/kamranzafar/jtar/Octal.java
new file mode 100644 (file)
index 0000000..7a40ea1
--- /dev/null
@@ -0,0 +1,141 @@
+/**
+ * Copyright 2012 Kamran Zafar 
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); 
+ * you may not use this file except in compliance with the License. 
+ * You may obtain a copy of the License at 
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0 
+ * 
+ * Unless required by applicable law or agreed to in writing, software 
+ * distributed under the License is distributed on an "AS IS" BASIS, 
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
+ * See the License for the specific language governing permissions and 
+ * limitations under the License. 
+ * 
+ */
+
+package org.kamranzafar.jtar;
+
+/**
+ * @author Kamran Zafar
+ * 
+ */
+public class Octal {
+
+    /**
+     * Parse an octal string from a header buffer. This is used for the file
+     * permission mode value.
+     * 
+     * @param header
+     *            The header buffer from which to parse.
+     * @param offset
+     *            The offset into the buffer from which to parse.
+     * @param length
+     *            The number of header bytes to parse.
+     * 
+     * @return The long value of the octal string.
+     */
+    public static long parseOctal(byte[] header, int offset, int length) {
+        long result = 0;
+        boolean stillPadding = true;
+
+        int end = offset + length;
+        for (int i = offset; i < end; ++i) {
+            if (header[i] == 0)
+                break;
+
+            if (header[i] == (byte) ' ' || header[i] == '0') {
+                if (stillPadding)
+                    continue;
+
+                if (header[i] == (byte) ' ')
+                    break;
+            }
+
+            stillPadding = false;
+
+            result = ( result << 3 ) + ( header[i] - '0' );
+        }
+
+        return result;
+    }
+
+    /**
+     * Parse an octal integer from a header buffer.
+     * 
+     * @param value
+     * @param buf
+     *            The header buffer from which to parse.
+     * @param offset
+     *            The offset into the buffer from which to parse.
+     * @param length
+     *            The number of header bytes to parse.
+     * 
+     * @return The integer value of the octal bytes.
+     */
+    public static int getOctalBytes(long value, byte[] buf, int offset, int length) {
+        int idx = length - 1;
+
+        buf[offset + idx] = 0;
+        --idx;
+        buf[offset + idx] = (byte) ' ';
+        --idx;
+
+        if (value == 0) {
+            buf[offset + idx] = (byte) '0';
+            --idx;
+        } else {
+            for (long val = value; idx >= 0 && val > 0; --idx) {
+                buf[offset + idx] = (byte) ( (byte) '0' + (byte) ( val & 7 ) );
+                val = val >> 3;
+            }
+        }
+
+        for (; idx >= 0; --idx) {
+            buf[offset + idx] = (byte) ' ';
+        }
+
+        return offset + length;
+    }
+
+    /**
+     * Parse the checksum octal integer from a header buffer.
+     * 
+     * @param value
+     * @param buf
+     *            The header buffer from which to parse.
+     * @param offset
+     *            The offset into the buffer from which to parse.
+     * @param length
+     *            The number of header bytes to parse.
+     * @return The integer value of the entry's checksum.
+     */
+    public static int getCheckSumOctalBytes(long value, byte[] buf, int offset, int length) {
+        getOctalBytes( value, buf, offset, length );
+        buf[offset + length - 1] = (byte) ' ';
+        buf[offset + length - 2] = 0;
+        return offset + length;
+    }
+
+    /**
+     * Parse an octal long integer from a header buffer.
+     * 
+     * @param value
+     * @param buf
+     *            The header buffer from which to parse.
+     * @param offset
+     *            The offset into the buffer from which to parse.
+     * @param length
+     *            The number of header bytes to parse.
+     * 
+     * @return The long value of the octal bytes.
+     */
+    public static int getLongOctalBytes(long value, byte[] buf, int offset, int length) {
+        byte[] temp = new byte[length + 1];
+        getOctalBytes( value, temp, 0, length + 1 );
+        System.arraycopy( temp, 0, buf, offset, length );
+        return offset + length;
+    }
+
+}
diff --git a/lib/jtar/org/kamranzafar/jtar/TarConstants.java b/lib/jtar/org/kamranzafar/jtar/TarConstants.java
new file mode 100644 (file)
index 0000000..c85d0a7
--- /dev/null
@@ -0,0 +1,28 @@
+/**
+ * Copyright 2012 Kamran Zafar 
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); 
+ * you may not use this file except in compliance with the License. 
+ * You may obtain a copy of the License at 
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0 
+ * 
+ * Unless required by applicable law or agreed to in writing, software 
+ * distributed under the License is distributed on an "AS IS" BASIS, 
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
+ * See the License for the specific language governing permissions and 
+ * limitations under the License. 
+ * 
+ */
+
+package org.kamranzafar.jtar;
+
+/**
+ * @author Kamran Zafar
+ * 
+ */
+public class TarConstants {
+    public static final int EOF_BLOCK = 1024;
+    public static final int DATA_BLOCK = 512;
+    public static final int HEADER_BLOCK = 512;
+}
diff --git a/lib/jtar/org/kamranzafar/jtar/TarEntry.java b/lib/jtar/org/kamranzafar/jtar/TarEntry.java
new file mode 100644 (file)
index 0000000..c91eac6
--- /dev/null
@@ -0,0 +1,322 @@
+/**
+ * Copyright 2012 Kamran Zafar 
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); 
+ * you may not use this file except in compliance with the License. 
+ * You may obtain a copy of the License at 
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0 
+ * 
+ * Unless required by applicable law or agreed to in writing, software 
+ * distributed under the License is distributed on an "AS IS" BASIS, 
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
+ * See the License for the specific language governing permissions and 
+ * limitations under the License. 
+ * 
+ */
+
+package org.kamranzafar.jtar;
+
+import java.io.File;
+import java.util.Date;
+
+/**
+ * @author Kamran Zafar
+ * 
+ */
+public class TarEntry {
+       protected File file;
+       protected TarHeader header;
+
+       private TarEntry() {
+               this.file = null;
+               header = new TarHeader();
+       }
+
+       public TarEntry(File file, String entryName) {
+               this();
+               this.file = file;
+               this.extractTarHeader(entryName);
+       }
+
+       public TarEntry(byte[] headerBuf) {
+               this();
+               this.parseTarHeader(headerBuf);
+       }
+
+       /**
+        * Constructor to create an entry from an existing TarHeader object.
+        * 
+        * This method is useful to add new entries programmatically (e.g. for
+        * adding files or directories that do not exist in the file system).
+        * 
+        * @param header
+        * 
+        */
+       public TarEntry(TarHeader header) {
+               this.file = null;
+               this.header = header;
+       }
+
+       @Override
+       public boolean equals(Object it) {
+               if (!(it instanceof TarEntry)) {
+                       return false;
+               }
+               return header.name.toString().equals(
+                               ((TarEntry) it).header.name.toString());
+       }
+
+       @Override
+       public int hashCode() {
+               return header.name.hashCode();
+       }
+
+       public boolean isDescendent(TarEntry desc) {
+               return desc.header.name.toString().startsWith(header.name.toString());
+       }
+
+       public TarHeader getHeader() {
+               return header;
+       }
+
+       public String getName() {
+               String name = header.name.toString();
+               if (header.namePrefix != null
+                               && !header.namePrefix.toString().equals("")) {
+                       name = header.namePrefix.toString() + "/" + name;
+               }
+
+               return name;
+       }
+
+       public void setName(String name) {
+               header.name = new StringBuffer(name);
+       }
+
+       public int getUserId() {
+               return header.userId;
+       }
+
+       public void setUserId(int userId) {
+               header.userId = userId;
+       }
+
+       public int getGroupId() {
+               return header.groupId;
+       }
+
+       public void setGroupId(int groupId) {
+               header.groupId = groupId;
+       }
+
+       public String getUserName() {
+               return header.userName.toString();
+       }
+
+       public void setUserName(String userName) {
+               header.userName = new StringBuffer(userName);
+       }
+
+       public String getGroupName() {
+               return header.groupName.toString();
+       }
+
+       public void setGroupName(String groupName) {
+               header.groupName = new StringBuffer(groupName);
+       }
+
+       public void setIds(int userId, int groupId) {
+               this.setUserId(userId);
+               this.setGroupId(groupId);
+       }
+
+       public void setModTime(long time) {
+               header.modTime = time / 1000;
+       }
+
+       public void setModTime(Date time) {
+               header.modTime = time.getTime() / 1000;
+       }
+
+       public Date getModTime() {
+               return new Date(header.modTime * 1000);
+       }
+
+       public File getFile() {
+               return this.file;
+       }
+
+       public long getSize() {
+               return header.size;
+       }
+
+       public void setSize(long size) {
+               header.size = size;
+       }
+
+       /**
+        * Checks if the org.kamrazafar.jtar entry is a directory
+        * 
+        * @return
+        */
+       public boolean isDirectory() {
+               if (this.file != null) {
+                       return this.file.isDirectory();
+               }
+
+               if (header != null) {
+                       if (header.linkFlag == TarHeader.LF_DIR) {
+                               return true;
+                       }
+
+                       if (header.name.toString().endsWith("/")) {
+                               return true;
+                       }
+               }
+
+               return false;
+       }
+
+       /**
+        * Extract header from File
+        * 
+        * @param entryName
+        */
+       public void extractTarHeader(String entryName) {
+               header = TarHeader.createHeader(entryName, file.length(),
+                               file.lastModified() / 1000, file.isDirectory());
+       }
+
+       /**
+        * Calculate checksum
+        * 
+        * @param buf
+        * @return
+        */
+       public long computeCheckSum(byte[] buf) {
+               long sum = 0;
+
+               for (int i = 0; i < buf.length; ++i) {
+                       sum += 255 & buf[i];
+               }
+
+               return sum;
+       }
+
+       /**
+        * Writes the header to the byte buffer
+        * 
+        * @param outbuf
+        */
+       public void writeEntryHeader(byte[] outbuf) {
+               int offset = 0;
+
+               offset = TarHeader.getNameBytes(header.name, outbuf, offset,
+                               TarHeader.NAMELEN);
+               offset = Octal.getOctalBytes(header.mode, outbuf, offset,
+                               TarHeader.MODELEN);
+               offset = Octal.getOctalBytes(header.userId, outbuf, offset,
+                               TarHeader.UIDLEN);
+               offset = Octal.getOctalBytes(header.groupId, outbuf, offset,
+                               TarHeader.GIDLEN);
+
+               long size = header.size;
+
+               offset = Octal.getLongOctalBytes(size, outbuf, offset,
+                               TarHeader.SIZELEN);
+               offset = Octal.getLongOctalBytes(header.modTime, outbuf, offset,
+                               TarHeader.MODTIMELEN);
+
+               int csOffset = offset;
+               for (int c = 0; c < TarHeader.CHKSUMLEN; ++c) {
+                       outbuf[offset++] = (byte) ' ';
+               }
+
+               outbuf[offset++] = header.linkFlag;
+
+               offset = TarHeader.getNameBytes(header.linkName, outbuf, offset,
+                               TarHeader.NAMELEN);
+               offset = TarHeader.getNameBytes(header.magic, outbuf, offset,
+                               TarHeader.USTAR_MAGICLEN);
+               offset = TarHeader.getNameBytes(header.userName, outbuf, offset,
+                               TarHeader.USTAR_USER_NAMELEN);
+               offset = TarHeader.getNameBytes(header.groupName, outbuf, offset,
+                               TarHeader.USTAR_GROUP_NAMELEN);
+               offset = Octal.getOctalBytes(header.devMajor, outbuf, offset,
+                               TarHeader.USTAR_DEVLEN);
+               offset = Octal.getOctalBytes(header.devMinor, outbuf, offset,
+                               TarHeader.USTAR_DEVLEN);
+               offset = TarHeader.getNameBytes(header.namePrefix, outbuf, offset,
+                               TarHeader.USTAR_FILENAME_PREFIX);
+
+               for (; offset < outbuf.length;) {
+                       outbuf[offset++] = 0;
+               }
+
+               long checkSum = this.computeCheckSum(outbuf);
+
+               Octal.getCheckSumOctalBytes(checkSum, outbuf, csOffset,
+                               TarHeader.CHKSUMLEN);
+       }
+
+       /**
+        * Parses the tar header to the byte buffer
+        * 
+        * @param header
+        * @param bh
+        */
+       public void parseTarHeader(byte[] bh) {
+               int offset = 0;
+
+               header.name = TarHeader.parseName(bh, offset, TarHeader.NAMELEN);
+               offset += TarHeader.NAMELEN;
+
+               header.mode = (int) Octal.parseOctal(bh, offset, TarHeader.MODELEN);
+               offset += TarHeader.MODELEN;
+
+               header.userId = (int) Octal.parseOctal(bh, offset, TarHeader.UIDLEN);
+               offset += TarHeader.UIDLEN;
+
+               header.groupId = (int) Octal.parseOctal(bh, offset, TarHeader.GIDLEN);
+               offset += TarHeader.GIDLEN;
+
+               header.size = Octal.parseOctal(bh, offset, TarHeader.SIZELEN);
+               offset += TarHeader.SIZELEN;
+
+               header.modTime = Octal.parseOctal(bh, offset, TarHeader.MODTIMELEN);
+               offset += TarHeader.MODTIMELEN;
+
+               header.checkSum = (int) Octal.parseOctal(bh, offset,
+                               TarHeader.CHKSUMLEN);
+               offset += TarHeader.CHKSUMLEN;
+
+               header.linkFlag = bh[offset++];
+
+               header.linkName = TarHeader.parseName(bh, offset, TarHeader.NAMELEN);
+               offset += TarHeader.NAMELEN;
+
+               header.magic = TarHeader
+                               .parseName(bh, offset, TarHeader.USTAR_MAGICLEN);
+               offset += TarHeader.USTAR_MAGICLEN;
+
+               header.userName = TarHeader.parseName(bh, offset,
+                               TarHeader.USTAR_USER_NAMELEN);
+               offset += TarHeader.USTAR_USER_NAMELEN;
+
+               header.groupName = TarHeader.parseName(bh, offset,
+                               TarHeader.USTAR_GROUP_NAMELEN);
+               offset += TarHeader.USTAR_GROUP_NAMELEN;
+
+               header.devMajor = (int) Octal.parseOctal(bh, offset,
+                               TarHeader.USTAR_DEVLEN);
+               offset += TarHeader.USTAR_DEVLEN;
+
+               header.devMinor = (int) Octal.parseOctal(bh, offset,
+                               TarHeader.USTAR_DEVLEN);
+               offset += TarHeader.USTAR_DEVLEN;
+
+               header.namePrefix = TarHeader.parseName(bh, offset,
+                               TarHeader.USTAR_FILENAME_PREFIX);
+       }
+}
\ No newline at end of file
diff --git a/lib/jtar/org/kamranzafar/jtar/TarHeader.java b/lib/jtar/org/kamranzafar/jtar/TarHeader.java
new file mode 100644 (file)
index 0000000..deecaa0
--- /dev/null
@@ -0,0 +1,243 @@
+/**
+ * Copyright 2012 Kamran Zafar 
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); 
+ * you may not use this file except in compliance with the License. 
+ * You may obtain a copy of the License at 
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0 
+ * 
+ * Unless required by applicable law or agreed to in writing, software 
+ * distributed under the License is distributed on an "AS IS" BASIS, 
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
+ * See the License for the specific language governing permissions and 
+ * limitations under the License. 
+ * 
+ */
+
+package org.kamranzafar.jtar;
+
+import java.io.File;
+
+/**
+ * Header
+ * 
+ * <pre>
+ * Offset  Size     Field
+ * 0       100      File name
+ * 100     8        File mode
+ * 108     8        Owner's numeric user ID
+ * 116     8        Group's numeric user ID
+ * 124     12       File size in bytes
+ * 136     12       Last modification time in numeric Unix time format
+ * 148     8        Checksum for header block
+ * 156     1        Link indicator (file type)
+ * 157     100      Name of linked file
+ * </pre>
+ * 
+ * 
+ * File Types
+ * 
+ * <pre>
+ * Value        Meaning
+ * '0'          Normal file
+ * (ASCII NUL)  Normal file (now obsolete)
+ * '1'          Hard link
+ * '2'          Symbolic link
+ * '3'          Character special
+ * '4'          Block special
+ * '5'          Directory
+ * '6'          FIFO
+ * '7'          Contigous
+ * </pre>
+ * 
+ * 
+ * 
+ * Ustar header
+ * 
+ * <pre>
+ * Offset  Size    Field
+ * 257     6       UStar indicator "ustar"
+ * 263     2       UStar version "00"
+ * 265     32      Owner user name
+ * 297     32      Owner group name
+ * 329     8       Device major number
+ * 337     8       Device minor number
+ * 345     155     Filename prefix
+ * </pre>
+ */
+
+public class TarHeader {
+
+       /*
+        * Header
+        */
+       public static final int NAMELEN = 100;
+       public static final int MODELEN = 8;
+       public static final int UIDLEN = 8;
+       public static final int GIDLEN = 8;
+       public static final int SIZELEN = 12;
+       public static final int MODTIMELEN = 12;
+       public static final int CHKSUMLEN = 8;
+       public static final byte LF_OLDNORM = 0;
+
+       /*
+        * File Types
+        */
+       public static final byte LF_NORMAL = (byte) '0';
+       public static final byte LF_LINK = (byte) '1';
+       public static final byte LF_SYMLINK = (byte) '2';
+       public static final byte LF_CHR = (byte) '3';
+       public static final byte LF_BLK = (byte) '4';
+       public static final byte LF_DIR = (byte) '5';
+       public static final byte LF_FIFO = (byte) '6';
+       public static final byte LF_CONTIG = (byte) '7';
+
+       /*
+        * Ustar header
+        */
+
+       public static final String USTAR_MAGIC = "ustar"; // POSIX
+
+       public static final int USTAR_MAGICLEN = 8;
+       public static final int USTAR_USER_NAMELEN = 32;
+       public static final int USTAR_GROUP_NAMELEN = 32;
+       public static final int USTAR_DEVLEN = 8;
+       public static final int USTAR_FILENAME_PREFIX = 155;
+
+       // Header values
+       public StringBuffer name;
+       public int mode;
+       public int userId;
+       public int groupId;
+       public long size;
+       public long modTime;
+       public int checkSum;
+       public byte linkFlag;
+       public StringBuffer linkName;
+       public StringBuffer magic; // ustar indicator and version
+       public StringBuffer userName;
+       public StringBuffer groupName;
+       public int devMajor;
+       public int devMinor;
+       public StringBuffer namePrefix;
+
+       public TarHeader() {
+               this.magic = new StringBuffer(TarHeader.USTAR_MAGIC);
+
+               this.name = new StringBuffer();
+               this.linkName = new StringBuffer();
+
+               String user = System.getProperty("user.name", "");
+
+               if (user.length() > 31)
+                       user = user.substring(0, 31);
+
+               this.userId = 0;
+               this.groupId = 0;
+               this.userName = new StringBuffer(user);
+               this.groupName = new StringBuffer("");
+               this.namePrefix = new StringBuffer();
+       }
+
+       /**
+        * Parse an entry name from a header buffer.
+        * 
+        * @param name
+        * @param header
+        *            The header buffer from which to parse.
+        * @param offset
+        *            The offset into the buffer from which to parse.
+        * @param length
+        *            The number of header bytes to parse.
+        * @return The header's entry name.
+        */
+       public static StringBuffer parseName(byte[] header, int offset, int length) {
+               StringBuffer result = new StringBuffer(length);
+
+               int end = offset + length;
+               for (int i = offset; i < end; ++i) {
+                       if (header[i] == 0)
+                               break;
+                       result.append((char) header[i]);
+               }
+
+               return result;
+       }
+
+       /**
+        * Determine the number of bytes in an entry name.
+        * 
+        * @param name
+        * @param header
+        *            The header buffer from which to parse.
+        * @param offset
+        *            The offset into the buffer from which to parse.
+        * @param length
+        *            The number of header bytes to parse.
+        * @return The number of bytes in a header's entry name.
+        */
+       public static int getNameBytes(StringBuffer name, byte[] buf, int offset, int length) {
+               int i;
+
+               for (i = 0; i < length && i < name.length(); ++i) {
+                       buf[offset + i] = (byte) name.charAt(i);
+               }
+
+               for (; i < length; ++i) {
+                       buf[offset + i] = 0;
+               }
+
+               return offset + length;
+       }
+
+       /**
+        * Creates a new header for a file/directory entry.
+        * 
+        * 
+        * @param name
+        *            File name
+        * @param size
+        *            File size in bytes
+        * @param modTime
+        *            Last modification time in numeric Unix time format
+        * @param dir
+        *            Is directory
+        * 
+        * @return
+        */
+       public static TarHeader createHeader(String entryName, long size, long modTime, boolean dir) {
+               String name = entryName;
+               name = TarUtils.trim(name.replace(File.separatorChar, '/'), '/');
+
+               TarHeader header = new TarHeader();
+               header.linkName = new StringBuffer("");
+
+               if (name.length() > 100) {
+                       header.namePrefix = new StringBuffer(name.substring(0, name.lastIndexOf('/')));
+                       header.name = new StringBuffer(name.substring(name.lastIndexOf('/') + 1));
+               } else {
+                       header.name = new StringBuffer(name);
+               }
+
+               if (dir) {
+                       header.mode = 040755;
+                       header.linkFlag = TarHeader.LF_DIR;
+                       if (header.name.charAt(header.name.length() - 1) != '/') {
+                               header.name.append("/");
+                       }
+                       header.size = 0;
+               } else {
+                       header.mode = 0100644;
+                       header.linkFlag = TarHeader.LF_NORMAL;
+                       header.size = size;
+               }
+
+               header.modTime = modTime;
+               header.checkSum = 0;
+               header.devMajor = 0;
+               header.devMinor = 0;
+
+               return header;
+       }
+}
\ No newline at end of file
diff --git a/lib/jtar/org/kamranzafar/jtar/TarInputStream.java b/lib/jtar/org/kamranzafar/jtar/TarInputStream.java
new file mode 100644 (file)
index 0000000..cd48ae0
--- /dev/null
@@ -0,0 +1,249 @@
+/**
+ * Copyright 2012 Kamran Zafar 
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); 
+ * you may not use this file except in compliance with the License. 
+ * You may obtain a copy of the License at 
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0 
+ * 
+ * Unless required by applicable law or agreed to in writing, software 
+ * distributed under the License is distributed on an "AS IS" BASIS, 
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
+ * See the License for the specific language governing permissions and 
+ * limitations under the License. 
+ * 
+ */
+
+package org.kamranzafar.jtar;
+
+import java.io.FilterInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * @author Kamran Zafar
+ * 
+ */
+public class TarInputStream extends FilterInputStream {
+
+       private static final int SKIP_BUFFER_SIZE = 2048;
+       private TarEntry currentEntry;
+       private long currentFileSize;
+       private long bytesRead;
+       private boolean defaultSkip = false;
+
+       public TarInputStream(InputStream in) {
+               super(in);
+               currentFileSize = 0;
+               bytesRead = 0;
+       }
+
+       @Override
+       public boolean markSupported() {
+               return false;
+       }
+
+       /**
+        * Not supported
+        * 
+        */
+       @Override
+       public synchronized void mark(int readlimit) {
+       }
+
+       /**
+        * Not supported
+        * 
+        */
+       @Override
+       public synchronized void reset() throws IOException {
+               throw new IOException("mark/reset not supported");
+       }
+
+       /**
+        * Read a byte
+        * 
+        * @see java.io.FilterInputStream#read()
+        */
+       @Override
+       public int read() throws IOException {
+               byte[] buf = new byte[1];
+
+               int res = this.read(buf, 0, 1);
+
+               if (res != -1) {
+                       return 0xFF & buf[0];
+               }
+
+               return res;
+       }
+
+       /**
+        * Checks if the bytes being read exceed the entry size and adjusts the byte
+        * array length. Updates the byte counters
+        * 
+        * 
+        * @see java.io.FilterInputStream#read(byte[], int, int)
+        */
+       @Override
+       public int read(byte[] b, int off, int len) throws IOException {
+               if (currentEntry != null) {
+                       if (currentFileSize == currentEntry.getSize()) {
+                               return -1;
+                       } else if ((currentEntry.getSize() - currentFileSize) < len) {
+                               len = (int) (currentEntry.getSize() - currentFileSize);
+                       }
+               }
+
+               int br = super.read(b, off, len);
+
+               if (br != -1) {
+                       if (currentEntry != null) {
+                               currentFileSize += br;
+                       }
+
+                       bytesRead += br;
+               }
+
+               return br;
+       }
+
+       /**
+        * Returns the next entry in the tar file
+        * 
+        * @return TarEntry
+        * @throws IOException
+        */
+       public TarEntry getNextEntry() throws IOException {
+               closeCurrentEntry();
+
+               byte[] header = new byte[TarConstants.HEADER_BLOCK];
+               byte[] theader = new byte[TarConstants.HEADER_BLOCK];
+               int tr = 0;
+
+               // Read full header
+               while (tr < TarConstants.HEADER_BLOCK) {
+                       int res = read(theader, 0, TarConstants.HEADER_BLOCK - tr);
+
+                       if (res < 0) {
+                               break;
+                       }
+
+                       System.arraycopy(theader, 0, header, tr, res);
+                       tr += res;
+               }
+
+               // Check if record is null
+               boolean eof = true;
+               for (byte b : header) {
+                       if (b != 0) {
+                               eof = false;
+                               break;
+                       }
+               }
+
+               if (!eof) {
+                       currentEntry = new TarEntry(header);
+               }
+
+               return currentEntry;
+       }
+
+       /**
+        * Returns the current offset (in bytes) from the beginning of the stream. 
+        * This can be used to find out at which point in a tar file an entry's content begins, for instance. 
+        */
+       public long getCurrentOffset() {
+               return bytesRead;
+       }
+       
+       /**
+        * Closes the current tar entry
+        * 
+        * @throws IOException
+        */
+       protected void closeCurrentEntry() throws IOException {
+               if (currentEntry != null) {
+                       if (currentEntry.getSize() > currentFileSize) {
+                               // Not fully read, skip rest of the bytes
+                               long bs = 0;
+                               while (bs < currentEntry.getSize() - currentFileSize) {
+                                       long res = skip(currentEntry.getSize() - currentFileSize - bs);
+
+                                       if (res == 0 && currentEntry.getSize() - currentFileSize > 0) {
+                                               // I suspect file corruption
+                                               throw new IOException("Possible tar file corruption");
+                                       }
+
+                                       bs += res;
+                               }
+                       }
+
+                       currentEntry = null;
+                       currentFileSize = 0L;
+                       skipPad();
+               }
+       }
+
+       /**
+        * Skips the pad at the end of each tar entry file content
+        * 
+        * @throws IOException
+        */
+       protected void skipPad() throws IOException {
+               if (bytesRead > 0) {
+                       int extra = (int) (bytesRead % TarConstants.DATA_BLOCK);
+
+                       if (extra > 0) {
+                               long bs = 0;
+                               while (bs < TarConstants.DATA_BLOCK - extra) {
+                                       long res = skip(TarConstants.DATA_BLOCK - extra - bs);
+                                       bs += res;
+                               }
+                       }
+               }
+       }
+
+       /**
+        * Skips 'n' bytes on the InputStream<br>
+        * Overrides default implementation of skip
+        * 
+        */
+       @Override
+       public long skip(long n) throws IOException {
+               if (defaultSkip) {
+                       // use skip method of parent stream
+                       // may not work if skip not implemented by parent
+                       long bs = super.skip(n);
+                       bytesRead += bs;
+
+                       return bs;
+               }
+
+               if (n <= 0) {
+                       return 0;
+               }
+
+               long left = n;
+               byte[] sBuff = new byte[SKIP_BUFFER_SIZE];
+
+               while (left > 0) {
+                       int res = read(sBuff, 0, (int) (left < SKIP_BUFFER_SIZE ? left : SKIP_BUFFER_SIZE));
+                       if (res < 0) {
+                               break;
+                       }
+                       left -= res;
+               }
+
+               return n - left;
+       }
+
+       public boolean isDefaultSkip() {
+               return defaultSkip;
+       }
+
+       public void setDefaultSkip(boolean defaultSkip) {
+               this.defaultSkip = defaultSkip;
+       }
+}
diff --git a/lib/jtar/org/kamranzafar/jtar/TarOutputStream.java b/lib/jtar/org/kamranzafar/jtar/TarOutputStream.java
new file mode 100644 (file)
index 0000000..e17413c
--- /dev/null
@@ -0,0 +1,163 @@
+/**
+ * Copyright 2012 Kamran Zafar 
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); 
+ * you may not use this file except in compliance with the License. 
+ * You may obtain a copy of the License at 
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0 
+ * 
+ * Unless required by applicable law or agreed to in writing, software 
+ * distributed under the License is distributed on an "AS IS" BASIS, 
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
+ * See the License for the specific language governing permissions and 
+ * limitations under the License. 
+ * 
+ */
+
+package org.kamranzafar.jtar;
+
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.RandomAccessFile;
+
+/**
+ * @author Kamran Zafar
+ * 
+ */
+public class TarOutputStream extends OutputStream {
+       private final OutputStream out;
+    private long bytesWritten;
+    private long currentFileSize;
+    private TarEntry currentEntry;
+
+    public TarOutputStream(OutputStream out) {
+        this.out = out;
+        bytesWritten = 0;
+        currentFileSize = 0;
+    }
+
+       public TarOutputStream(final File fout) throws FileNotFoundException {
+               this.out = new BufferedOutputStream(new FileOutputStream(fout));
+               bytesWritten = 0;
+               currentFileSize = 0;
+       }
+
+       /**
+        * Opens a file for writing. 
+        */
+       public TarOutputStream(final File fout, final boolean append) throws IOException {
+               @SuppressWarnings("resource")
+               RandomAccessFile raf = new RandomAccessFile(fout, "rw");
+               final long fileSize = fout.length();
+               if (append && fileSize > TarConstants.EOF_BLOCK) {
+                       raf.seek(fileSize - TarConstants.EOF_BLOCK);
+               }
+               out = new BufferedOutputStream(new FileOutputStream(raf.getFD()));
+       }
+
+    /**
+     * Appends the EOF record and closes the stream
+     * 
+     * @see java.io.FilterOutputStream#close()
+     */
+    @Override
+    public void close() throws IOException {
+        closeCurrentEntry();
+        write( new byte[TarConstants.EOF_BLOCK] );
+        out.close();
+    }
+    /**
+     * Writes a byte to the stream and updates byte counters
+     * 
+     * @see java.io.FilterOutputStream#write(int)
+     */
+    @Override
+    public void write(int b) throws IOException {
+        out.write( b );
+        bytesWritten += 1;
+
+        if (currentEntry != null) {
+            currentFileSize += 1;
+        }
+    }
+
+    /**
+     * Checks if the bytes being written exceed the current entry size.
+     * 
+     * @see java.io.FilterOutputStream#write(byte[], int, int)
+     */
+    @Override
+    public void write(byte[] b, int off, int len) throws IOException {
+        if (currentEntry != null && !currentEntry.isDirectory()) {
+            if (currentEntry.getSize() < currentFileSize + len) {
+                throw new IOException( "The current entry[" + currentEntry.getName() + "] size["
+                        + currentEntry.getSize() + "] is smaller than the bytes[" + ( currentFileSize + len )
+                        + "] being written." );
+            }
+        }
+
+        out.write( b, off, len );
+        
+        bytesWritten += len;
+
+        if (currentEntry != null) {
+            currentFileSize += len;
+        }        
+    }
+
+    /**
+     * Writes the next tar entry header on the stream
+     * 
+     * @param entry
+     * @throws IOException
+     */
+    public void putNextEntry(TarEntry entry) throws IOException {
+        closeCurrentEntry();
+
+        byte[] header = new byte[TarConstants.HEADER_BLOCK];
+        entry.writeEntryHeader( header );
+
+        write( header );
+
+        currentEntry = entry;
+    }
+
+    /**
+     * Closes the current tar entry
+     * 
+     * @throws IOException
+     */
+    protected void closeCurrentEntry() throws IOException {
+        if (currentEntry != null) {
+            if (currentEntry.getSize() > currentFileSize) {
+                throw new IOException( "The current entry[" + currentEntry.getName() + "] of size["
+                        + currentEntry.getSize() + "] has not been fully written." );
+            }
+
+            currentEntry = null;
+            currentFileSize = 0;
+
+            pad();
+        }
+    }
+
+    /**
+     * Pads the last content block
+     * 
+     * @throws IOException
+     */
+    protected void pad() throws IOException {
+        if (bytesWritten > 0) {
+            int extra = (int) ( bytesWritten % TarConstants.DATA_BLOCK );
+
+            if (extra > 0) {
+                write( new byte[TarConstants.DATA_BLOCK - extra] );
+            }
+        }
+    }
+}
diff --git a/lib/jtar/org/kamranzafar/jtar/TarUtils.java b/lib/jtar/org/kamranzafar/jtar/TarUtils.java
new file mode 100644 (file)
index 0000000..8dccc37
--- /dev/null
@@ -0,0 +1,96 @@
+/**
+ * Copyright 2012 Kamran Zafar 
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); 
+ * you may not use this file except in compliance with the License. 
+ * You may obtain a copy of the License at 
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0 
+ * 
+ * Unless required by applicable law or agreed to in writing, software 
+ * distributed under the License is distributed on an "AS IS" BASIS, 
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
+ * See the License for the specific language governing permissions and 
+ * limitations under the License. 
+ * 
+ */
+
+package org.kamranzafar.jtar;
+
+import java.io.File;
+
+/**
+ * @author Kamran
+ * 
+ */
+public class TarUtils {
+       /**
+        * Determines the tar file size of the given folder/file path
+        * 
+        * @param path
+        * @return
+        */
+       public static long calculateTarSize(File path) {
+               return tarSize(path) + TarConstants.EOF_BLOCK;
+       }
+
+       private static long tarSize(File dir) {
+               long size = 0;
+
+               if (dir.isFile()) {
+                       return entrySize(dir.length());
+               } else {
+                       File[] subFiles = dir.listFiles();
+
+                       if (subFiles != null && subFiles.length > 0) {
+                               for (File file : subFiles) {
+                                       if (file.isFile()) {
+                                               size += entrySize(file.length());
+                                       } else {
+                                               size += tarSize(file);
+                                       }
+                               }
+                       } else {
+                               // Empty folder header
+                               return TarConstants.HEADER_BLOCK;
+                       }
+               }
+
+               return size;
+       }
+
+       private static long entrySize(long fileSize) {
+               long size = 0;
+               size += TarConstants.HEADER_BLOCK; // Header
+               size += fileSize; // File size
+
+               long extra = size % TarConstants.DATA_BLOCK;
+
+               if (extra > 0) {
+                       size += (TarConstants.DATA_BLOCK - extra); // pad
+               }
+
+               return size;
+       }
+
+       public static String trim(String s, char c) {
+               StringBuffer tmp = new StringBuffer(s);
+               for (int i = 0; i < tmp.length(); i++) {
+                       if (tmp.charAt(i) != c) {
+                               break;
+                       } else {
+                               tmp.deleteCharAt(i);
+                       }
+               }
+
+               for (int i = tmp.length() - 1; i >= 0; i--) {
+                       if (tmp.charAt(i) != c) {
+                               break;
+                       } else {
+                               tmp.deleteCharAt(i);
+                       }
+               }
+
+               return tmp.toString();
+       }
+}
diff --git a/lib/servlet-api/javax/servlet/AsyncContext.java b/lib/servlet-api/javax/servlet/AsyncContext.java
new file mode 100644 (file)
index 0000000..bc2267e
--- /dev/null
@@ -0,0 +1,83 @@
+/*
+* Licensed to the Apache Software Foundation (ASF) under one or more
+* contributor license agreements.  See the NOTICE file distributed with
+* this work for additional information regarding copyright ownership.
+* The ASF licenses this file to You under the Apache License, Version 2.0
+* (the "License"); you may not use this file except in compliance with
+* the License.  You may obtain a copy of the License at
+*
+*     http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+package javax.servlet;
+
+/**
+ * TODO SERVLET3 - Add comments
+ * @since Servlet 3.0
+ */
+public interface AsyncContext {
+    public static final String ASYNC_REQUEST_URI =
+        "javax.servlet.async.request_uri";
+    public static final String ASYNC_CONTEXT_PATH  =
+        "javax.servlet.async.context_path";
+    public static final String ASYNC_PATH_INFO =
+        "javax.servlet.async.path_info";
+    public static final String ASYNC_SERVLET_PATH =
+        "javax.servlet.async.servlet_path";
+    public static final String ASYNC_QUERY_STRING =
+        "javax.servlet.async.query_string";
+
+    ServletRequest getRequest();
+
+    ServletResponse getResponse();
+
+    boolean hasOriginalRequestAndResponse();
+
+    /**
+     *
+     * @throws IllegalStateException
+     */
+    void dispatch();
+
+    /**
+     *
+     * @param path
+     * @throws IllegalStateException
+     */
+    void dispatch(String path);
+
+    /**
+     *
+     * @param context
+     * @param path
+     * @throws IllegalStateException
+     */
+    void dispatch(ServletContext context, String path);
+
+    void complete();
+
+    void start(Runnable run);
+
+    void addListener(AsyncListener listener);
+
+    void addListener(AsyncListener listener, ServletRequest request,
+            ServletResponse response);
+
+    <T extends AsyncListener> T createListener(Class<T> clazz)
+    throws ServletException;
+
+    /**
+     * Set timeout in milliseconds. 0 or less indicates no timeout.
+     */
+    void setTimeout(long timeout);
+
+    /**
+     * Get timeout in milliseconds. 0 or less indicates no timeout.
+     */
+    long getTimeout();
+}
diff --git a/lib/servlet-api/javax/servlet/AsyncEvent.java b/lib/servlet-api/javax/servlet/AsyncEvent.java
new file mode 100644 (file)
index 0000000..4b0f0b3
--- /dev/null
@@ -0,0 +1,74 @@
+/*
+* Licensed to the Apache Software Foundation (ASF) under one or more
+* contributor license agreements.  See the NOTICE file distributed with
+* this work for additional information regarding copyright ownership.
+* The ASF licenses this file to You under the Apache License, Version 2.0
+* (the "License"); you may not use this file except in compliance with
+* the License.  You may obtain a copy of the License at
+*
+*     http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+package javax.servlet;
+
+/**
+ * TODO SERVLET3 - Add comments
+ * @since Servlet 3.0
+ */
+public class AsyncEvent {
+    private final AsyncContext context;
+    private final ServletRequest request;
+    private final ServletResponse response;
+    private final Throwable throwable;
+
+    public AsyncEvent(AsyncContext context) {
+        this.context = context;
+        this.request = null;
+        this.response = null;
+        this.throwable = null;
+    }
+
+    public AsyncEvent(AsyncContext context, ServletRequest request,
+            ServletResponse response) {
+        this.context = context;
+        this.request = request;
+        this.response = response;
+        this.throwable = null;
+    }
+
+    public AsyncEvent(AsyncContext context, Throwable throwable) {
+        this.context = context;
+        this.throwable = throwable;
+        this.request = null;
+        this.response = null;
+    }
+
+    public AsyncEvent(AsyncContext context, ServletRequest request,
+            ServletResponse response, Throwable throwable) {
+        this.context = context;
+        this.request = request;
+        this.response = response;
+        this.throwable = throwable;
+    }
+
+    public AsyncContext getAsyncContext() {
+        return context;
+    }
+
+    public ServletRequest getSuppliedRequest() {
+        return request;
+    }
+
+    public ServletResponse getSuppliedResponse() {
+        return response;
+    }
+
+    public Throwable getThrowable() {
+        return throwable;
+    }
+}
diff --git a/lib/servlet-api/javax/servlet/AsyncListener.java b/lib/servlet-api/javax/servlet/AsyncListener.java
new file mode 100644 (file)
index 0000000..66f6392
--- /dev/null
@@ -0,0 +1,31 @@
+/*
+* Licensed to the Apache Software Foundation (ASF) under one or more
+* contributor license agreements.  See the NOTICE file distributed with
+* this work for additional information regarding copyright ownership.
+* The ASF licenses this file to You under the Apache License, Version 2.0
+* (the "License"); you may not use this file except in compliance with
+* the License.  You may obtain a copy of the License at
+*
+*     http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+package javax.servlet;
+
+import java.io.IOException;
+import java.util.EventListener;
+
+/**
+ * TODO SERVLET3 - Add comments
+ * @since Servlet 3.0
+ */
+public interface AsyncListener extends EventListener {
+    void onComplete(AsyncEvent event) throws IOException;
+    void onTimeout(AsyncEvent event) throws IOException;
+    void onError(AsyncEvent event) throws IOException;
+    void onStartAsync(AsyncEvent event) throws IOException;
+}
diff --git a/lib/servlet-api/javax/servlet/DispatcherType.java b/lib/servlet-api/javax/servlet/DispatcherType.java
new file mode 100644 (file)
index 0000000..67e5603
--- /dev/null
@@ -0,0 +1,28 @@
+/*
+* Licensed to the Apache Software Foundation (ASF) under one or more
+* contributor license agreements.  See the NOTICE file distributed with
+* this work for additional information regarding copyright ownership.
+* The ASF licenses this file to You under the Apache License, Version 2.0
+* (the "License"); you may not use this file except in compliance with
+* the License.  You may obtain a copy of the License at
+*
+*     http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+package javax.servlet;
+
+/**
+ * @since Servlet 3.0
+ */
+public enum DispatcherType {
+    FORWARD,
+    INCLUDE,
+    REQUEST,
+    ASYNC,
+    ERROR
+}
diff --git a/lib/servlet-api/javax/servlet/Filter.java b/lib/servlet-api/javax/servlet/Filter.java
new file mode 100644 (file)
index 0000000..b36b37d
--- /dev/null
@@ -0,0 +1,100 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package javax.servlet;
+
+import java.io.IOException;
+
+/**
+ * A filter is an object that performs filtering tasks on either the request to
+ * a resource (a servlet or static content), or on the response from a resource,
+ * or both. <br>
+ * <br>
+ * Filters perform filtering in the <code>doFilter</code> method. Every Filter
+ * has access to a FilterConfig object from which it can obtain its
+ * initialization parameters, a reference to the ServletContext which it can
+ * use, for example, to load resources needed for filtering tasks.
+ * <p>
+ * Filters are configured in the deployment descriptor of a web application
+ * <p>
+ * Examples that have been identified for this design are<br>
+ * 1) Authentication Filters <br>
+ * 2) Logging and Auditing Filters <br>
+ * 3) Image conversion Filters <br>
+ * 4) Data compression Filters <br>
+ * 5) Encryption Filters <br>
+ * 6) Tokenizing Filters <br>
+ * 7) Filters that trigger resource access events <br>
+ * 8) XSL/T filters <br>
+ * 9) Mime-type chain Filter <br>
+ *
+ * @since Servlet 2.3
+ */
+public interface Filter {
+
+    /**
+     * Called by the web container to indicate to a filter that it is being
+     * placed into service. The servlet container calls the init method exactly
+     * once after instantiating the filter. The init method must complete
+     * successfully before the filter is asked to do any filtering work. <br>
+     * <br>
+     * The web container cannot place the filter into service if the init method
+     * either<br>
+     * 1.Throws a ServletException <br>
+     * 2.Does not return within a time period defined by the web container
+     */
+    public void init(FilterConfig filterConfig) throws ServletException;
+
+    /**
+     * The <code>doFilter</code> method of the Filter is called by the container
+     * each time a request/response pair is passed through the chain due to a
+     * client request for a resource at the end of the chain. The FilterChain
+     * passed in to this method allows the Filter to pass on the request and
+     * response to the next entity in the chain.
+     * <p>
+     * A typical implementation of this method would follow the following
+     * pattern:- <br>
+     * 1. Examine the request<br>
+     * 2. Optionally wrap the request object with a custom implementation to
+     * filter content or headers for input filtering <br>
+     * 3. Optionally wrap the response object with a custom implementation to
+     * filter content or headers for output filtering <br>
+     * 4. a) <strong>Either</strong> invoke the next entity in the chain using
+     * the FilterChain object (<code>chain.doFilter()</code>), <br>
+     * 4. b) <strong>or</strong> not pass on the request/response pair to the
+     * next entity in the filter chain to block the request processing<br>
+     * 5. Directly set headers on the response after invocation of the next
+     * entity in the filter chain.
+     **/
+    public void doFilter(ServletRequest request, ServletResponse response,
+            FilterChain chain) throws IOException, ServletException;
+
+    /**
+     * Called by the web container to indicate to a filter that it is being
+     * taken out of service. This method is only called once all threads within
+     * the filter's doFilter method have exited or after a timeout period has
+     * passed. After the web container calls this method, it will not call the
+     * doFilter method again on this instance of the filter. <br>
+     * <br>
+     *
+     * This method gives the filter an opportunity to clean up any resources
+     * that are being held (for example, memory, file handles, threads) and make
+     * sure that any persistent state is synchronized with the filter's current
+     * state in memory.
+     */
+    public void destroy();
+
+}
diff --git a/lib/servlet-api/javax/servlet/FilterChain.java b/lib/servlet-api/javax/servlet/FilterChain.java
new file mode 100644 (file)
index 0000000..cdc5206
--- /dev/null
@@ -0,0 +1,49 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package javax.servlet;
+
+import java.io.IOException;
+
+/**
+ * A FilterChain is an object provided by the servlet container to the developer
+ * giving a view into the invocation chain of a filtered request for a resource.
+ * Filters use the FilterChain to invoke the next filter in the chain, or if the
+ * calling filter is the last filter in the chain, to invoke the resource at the
+ * end of the chain.
+ *
+ * @see Filter
+ * @since Servlet 2.3
+ **/
+
+public interface FilterChain {
+
+    /**
+     * Causes the next filter in the chain to be invoked, or if the calling
+     * filter is the last filter in the chain, causes the resource at the end of
+     * the chain to be invoked.
+     *
+     * @param request
+     *            the request to pass along the chain.
+     * @param response
+     *            the response to pass along the chain.
+     *
+     * @since 2.3
+     */
+    public void doFilter(ServletRequest request, ServletResponse response)
+            throws IOException, ServletException;
+
+}
diff --git a/lib/servlet-api/javax/servlet/FilterConfig.java b/lib/servlet-api/javax/servlet/FilterConfig.java
new file mode 100644 (file)
index 0000000..4a0a65b
--- /dev/null
@@ -0,0 +1,73 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package javax.servlet;
+
+import java.util.Enumeration;
+
+/**
+ *
+ * A filter configuration object used by a servlet container to pass information
+ * to a filter during initialization.
+ *
+ * @see Filter
+ * @since Servlet 2.3
+ */
+public interface FilterConfig {
+
+    /**
+     * Returns the filter-name of this filter as defined in the deployment
+     * descriptor.
+     */
+    public String getFilterName();
+
+    /**
+     * Returns a reference to the {@link ServletContext} in which the caller is
+     * executing.
+     *
+     * @return {@link ServletContext} object, used by the caller to interact
+     *         with its servlet container
+     *
+     * @see ServletContext
+     */
+    public ServletContext getServletContext();
+
+    /**
+     * Returns a <code>String</code> containing the value of the named
+     * initialization parameter, or <code>null</code> if the parameter does not
+     * exist.
+     *
+     * @param name
+     *            <code>String</code> specifying the name of the initialization
+     *            parameter
+     *
+     * @return <code>String</code> containing the value of the initialization
+     *         parameter
+     */
+    public String getInitParameter(String name);
+
+    /**
+     * Returns the names of the filter's initialization parameters as an
+     * <code>Enumeration</code> of <code>String</code> objects, or an empty
+     * <code>Enumeration</code> if the filter has no initialization parameters.
+     *
+     * @return <code>Enumeration</code> of <code>String</code> objects
+     *         containing the names of the filter's initialization parameters
+     */
+    public Enumeration<String> getInitParameterNames();
+
+}
diff --git a/lib/servlet-api/javax/servlet/FilterRegistration.java b/lib/servlet-api/javax/servlet/FilterRegistration.java
new file mode 100644 (file)
index 0000000..52d9596
--- /dev/null
@@ -0,0 +1,67 @@
+/*
+* Licensed to the Apache Software Foundation (ASF) under one or more
+* contributor license agreements.  See the NOTICE file distributed with
+* this work for additional information regarding copyright ownership.
+* The ASF licenses this file to You under the Apache License, Version 2.0
+* (the "License"); you may not use this file except in compliance with
+* the License.  You may obtain a copy of the License at
+*
+*     http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+package javax.servlet;
+
+import java.util.Collection;
+import java.util.EnumSet;
+
+/**
+ * @since Servlet 3.0
+ * TODO SERVLET3 - Add comments
+ */
+public interface FilterRegistration extends Registration {
+
+    /**
+     *
+     * @param dispatcherTypes
+     * @param isMatchAfter
+     * @param servletNames
+     * @throws IllegalArgumentException
+     * @throws IllegalStateException
+     */
+    public void addMappingForServletNames(
+            EnumSet<DispatcherType> dispatcherTypes,
+            boolean isMatchAfter, String... servletNames);
+    /**
+     *
+     * @return TODO
+     */
+    public Collection<String> getServletNameMappings();
+
+    /**
+     *
+     * @param dispatcherTypes
+     * @param isMatchAfter
+     * @param urlPatterns
+     * @throws IllegalArgumentException
+     * @throws IllegalStateException
+     */
+    public void addMappingForUrlPatterns(
+            EnumSet<DispatcherType> dispatcherTypes,
+            boolean isMatchAfter, String... urlPatterns);
+
+    /**
+     *
+     * @return TODO
+     */
+    public Collection<String> getUrlPatternMappings();
+
+    public static interface Dynamic
+    extends FilterRegistration, Registration.Dynamic {
+        // No additional methods
+    }
+}
diff --git a/lib/servlet-api/javax/servlet/GenericServlet.java b/lib/servlet-api/javax/servlet/GenericServlet.java
new file mode 100644 (file)
index 0000000..f040cb7
--- /dev/null
@@ -0,0 +1,238 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package javax.servlet;
+
+import java.io.IOException;
+import java.util.Enumeration;
+
+/**
+ * Defines a generic, protocol-independent servlet. To write an HTTP servlet for
+ * use on the Web, extend {@link javax.servlet.http.HttpServlet} instead.
+ * <p>
+ * <code>GenericServlet</code> implements the <code>Servlet</code> and
+ * <code>ServletConfig</code> interfaces. <code>GenericServlet</code> may be
+ * directly extended by a servlet, although it's more common to extend a
+ * protocol-specific subclass such as <code>HttpServlet</code>.
+ * <p>
+ * <code>GenericServlet</code> makes writing servlets easier. It provides simple
+ * versions of the lifecycle methods <code>init</code> and <code>destroy</code>
+ * and of the methods in the <code>ServletConfig</code> interface.
+ * <code>GenericServlet</code> also implements the <code>log</code> method,
+ * declared in the <code>ServletContext</code> interface.
+ * <p>
+ * To write a generic servlet, you need only override the abstract
+ * <code>service</code> method.
+ */
+public abstract class GenericServlet implements Servlet, ServletConfig,
+        java.io.Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    private transient ServletConfig config;
+
+    /**
+     * Does nothing. All of the servlet initialization is done by one of the
+     * <code>init</code> methods.
+     */
+    public GenericServlet() {
+        // NOOP
+    }
+
+    /**
+     * Called by the servlet container to indicate to a servlet that the servlet
+     * is being taken out of service. See {@link Servlet#destroy}.
+     */
+    @Override
+    public void destroy() {
+        // NOOP by default
+    }
+
+    /**
+     * Returns a <code>String</code> containing the value of the named
+     * initialization parameter, or <code>null</code> if the parameter does not
+     * exist. See {@link ServletConfig#getInitParameter}.
+     * <p>
+     * This method is supplied for convenience. It gets the value of the named
+     * parameter from the servlet's <code>ServletConfig</code> object.
+     *
+     * @param name
+     *            a <code>String</code> specifying the name of the
+     *            initialization parameter
+     * @return String a <code>String</code> containing the value of the
+     *         initialization parameter
+     */
+    @Override
+    public String getInitParameter(String name) {
+        return getServletConfig().getInitParameter(name);
+    }
+
+    /**
+     * Returns the names of the servlet's initialization parameters as an
+     * <code>Enumeration</code> of <code>String</code> objects, or an empty
+     * <code>Enumeration</code> if the servlet has no initialization parameters.
+     * See {@link ServletConfig#getInitParameterNames}.
+     * <p>
+     * This method is supplied for convenience. It gets the parameter names from
+     * the servlet's <code>ServletConfig</code> object.
+     *
+     * @return Enumeration an enumeration of <code>String</code> objects
+     *         containing the names of the servlet's initialization parameters
+     */
+    @Override
+    public Enumeration<String> getInitParameterNames() {
+        return getServletConfig().getInitParameterNames();
+    }
+
+    /**
+     * Returns this servlet's {@link ServletConfig} object.
+     *
+     * @return ServletConfig the <code>ServletConfig</code> object that
+     *         initialized this servlet
+     */
+    @Override
+    public ServletConfig getServletConfig() {
+        return config;
+    }
+
+    /**
+     * Returns a reference to the {@link ServletContext} in which this servlet
+     * is running. See {@link ServletConfig#getServletContext}.
+     * <p>
+     * This method is supplied for convenience. It gets the context from the
+     * servlet's <code>ServletConfig</code> object.
+     *
+     * @return ServletContext the <code>ServletContext</code> object passed to
+     *         this servlet by the <code>init</code> method
+     */
+    @Override
+    public ServletContext getServletContext() {
+        return getServletConfig().getServletContext();
+    }
+
+    /**
+     * Returns information about the servlet, such as author, version, and
+     * copyright. By default, this method returns an empty string. Override this
+     * method to have it return a meaningful value. See
+     * {@link Servlet#getServletInfo}.
+     *
+     * @return String information about this servlet, by default an empty string
+     */
+    @Override
+    public String getServletInfo() {
+        return "";
+    }
+
+    /**
+     * Called by the servlet container to indicate to a servlet that the servlet
+     * is being placed into service. See {@link Servlet#init}.
+     * <p>
+     * This implementation stores the {@link ServletConfig} object it receives
+     * from the servlet container for later use. When overriding this form of
+     * the method, call <code>super.init(config)</code>.
+     *
+     * @param config
+     *            the <code>ServletConfig</code> object that contains
+     *            configuration information for this servlet
+     * @exception ServletException
+     *                if an exception occurs that interrupts the servlet's
+     *                normal operation
+     * @see UnavailableException
+     */
+    @Override
+    public void init(ServletConfig config) throws ServletException {
+        this.config = config;
+        this.init();
+    }
+
+    /**
+     * A convenience method which can be overridden so that there's no need to
+     * call <code>super.init(config)</code>.
+     * <p>
+     * Instead of overriding {@link #init(ServletConfig)}, simply override this
+     * method and it will be called by
+     * <code>GenericServlet.init(ServletConfig config)</code>. The
+     * <code>ServletConfig</code> object can still be retrieved via
+     * {@link #getServletConfig}.
+     *
+     * @exception ServletException
+     *                if an exception occurs that interrupts the servlet's
+     *                normal operation
+     */
+    public void init() throws ServletException {
+        // NOOP by default
+    }
+
+    /**
+     * Writes the specified message to a servlet log file, prepended by the
+     * servlet's name. See {@link ServletContext#log(String)}.
+     *
+     * @param msg
+     *            a <code>String</code> specifying the message to be written to
+     *            the log file
+     */
+    public void log(String msg) {
+        getServletContext().log(getServletName() + ": " + msg);
+    }
+
+    /**
+     * Writes an explanatory message and a stack trace for a given
+     * <code>Throwable</code> exception to the servlet log file, prepended by
+     * the servlet's name. See {@link ServletContext#log(String, Throwable)}.
+     *
+     * @param message
+     *            a <code>String</code> that describes the error or exception
+     * @param t
+     *            the <code>java.lang.Throwable</code> error or exception
+     */
+    public void log(String message, Throwable t) {
+        getServletContext().log(getServletName() + ": " + message, t);
+    }
+
+    /**
+     * Called by the servlet container to allow the servlet to respond to a
+     * request. See {@link Servlet#service}.
+     * <p>
+     * This method is declared abstract so subclasses, such as
+     * <code>HttpServlet</code>, must override it.
+     *
+     * @param req
+     *            the <code>ServletRequest</code> object that contains the
+     *            client's request
+     * @param res
+     *            the <code>ServletResponse</code> object that will contain the
+     *            servlet's response
+     * @exception ServletException
+     *                if an exception occurs that interferes with the servlet's
+     *                normal operation occurred
+     * @exception IOException
+     *                if an input or output exception occurs
+     */
+    @Override
+    public abstract void service(ServletRequest req, ServletResponse res)
+            throws ServletException, IOException;
+
+    /**
+     * Returns the name of this servlet instance. See
+     * {@link ServletConfig#getServletName}.
+     *
+     * @return the name of this servlet instance
+     */
+    @Override
+    public String getServletName() {
+        return config.getServletName();
+    }
+}
diff --git a/lib/servlet-api/javax/servlet/HttpConstraintElement.java b/lib/servlet-api/javax/servlet/HttpConstraintElement.java
new file mode 100644 (file)
index 0000000..c7a4f93
--- /dev/null
@@ -0,0 +1,98 @@
+/*
+* Licensed to the Apache Software Foundation (ASF) under one or more
+* contributor license agreements.  See the NOTICE file distributed with
+* this work for additional information regarding copyright ownership.
+* The ASF licenses this file to You under the Apache License, Version 2.0
+* (the "License"); you may not use this file except in compliance with
+* the License.  You may obtain a copy of the License at
+*
+*     http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+package javax.servlet;
+
+import java.util.ResourceBundle;
+
+import javax.servlet.annotation.ServletSecurity.EmptyRoleSemantic;
+import javax.servlet.annotation.ServletSecurity.TransportGuarantee;
+
+/**
+ * @since Servlet 3.0
+ * TODO SERVLET3 - Add comments
+ */
+public class HttpConstraintElement {
+
+    private static final String LSTRING_FILE = "javax.servlet.LocalStrings";
+    private static final ResourceBundle lStrings =
+        ResourceBundle.getBundle(LSTRING_FILE);
+
+    private final EmptyRoleSemantic emptyRoleSemantic;// = EmptyRoleSemantic.PERMIT;
+    private final TransportGuarantee transportGuarantee;// = TransportGuarantee.NONE;
+    private final String[] rolesAllowed;// = new String[0];
+
+    /**
+     * Default constraint is permit with no transport guarantee.
+     */
+    public HttpConstraintElement() {
+        // Default constructor
+        this.emptyRoleSemantic = EmptyRoleSemantic.PERMIT;
+        this.transportGuarantee = TransportGuarantee.NONE;
+        this.rolesAllowed = new String[0];
+    }
+
+    /**
+     * Convenience constructor for {@link EmptyRoleSemantic#DENY}.
+     *
+     */
+    public HttpConstraintElement(EmptyRoleSemantic emptyRoleSemantic) {
+        this.emptyRoleSemantic = emptyRoleSemantic;
+        this.transportGuarantee = TransportGuarantee.NONE;
+        this.rolesAllowed = new String[0];
+    }
+
+    /**
+     * Convenience constructor to specify transport guarantee and/or roles.
+     */
+    public HttpConstraintElement(TransportGuarantee transportGuarantee,
+            String... rolesAllowed) {
+        this.emptyRoleSemantic = EmptyRoleSemantic.PERMIT;
+        this.transportGuarantee = transportGuarantee;
+        this.rolesAllowed = rolesAllowed;
+    }
+
+    /**
+     *
+     * @param emptyRoleSemantic
+     * @param transportGuarantee
+     * @param rolesAllowed
+     * @throws IllegalArgumentException if roles are specified when DENY is used
+     */
+    public HttpConstraintElement(EmptyRoleSemantic emptyRoleSemantic,
+            TransportGuarantee transportGuarantee, String... rolesAllowed) {
+        if (rolesAllowed != null && rolesAllowed.length > 0 &&
+                EmptyRoleSemantic.DENY.equals(emptyRoleSemantic)) {
+            throw new IllegalArgumentException(lStrings.getString(
+                    "httpConstraintElement.invalidRolesDeny"));
+        }
+        this.emptyRoleSemantic = emptyRoleSemantic;
+        this.transportGuarantee = transportGuarantee;
+        this.rolesAllowed = rolesAllowed;
+    }
+
+    public EmptyRoleSemantic getEmptyRoleSemantic() {
+        return emptyRoleSemantic;
+    }
+
+    public TransportGuarantee getTransportGuarantee() {
+        return transportGuarantee;
+    }
+
+    public String[] getRolesAllowed() {
+        return rolesAllowed;
+    }
+}
\ No newline at end of file
diff --git a/lib/servlet-api/javax/servlet/HttpMethodConstraintElement.java b/lib/servlet-api/javax/servlet/HttpMethodConstraintElement.java
new file mode 100644 (file)
index 0000000..5de2ad9
--- /dev/null
@@ -0,0 +1,57 @@
+/*
+* Licensed to the Apache Software Foundation (ASF) under one or more
+* contributor license agreements.  See the NOTICE file distributed with
+* this work for additional information regarding copyright ownership.
+* The ASF licenses this file to You under the Apache License, Version 2.0
+* (the "License"); you may not use this file except in compliance with
+* the License.  You may obtain a copy of the License at
+*
+*     http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+package javax.servlet;
+
+import java.util.ResourceBundle;
+
+/**
+ * @since Servlet 3.0
+ * TODO SERVLET3 - Add comments
+ */
+public class HttpMethodConstraintElement extends HttpConstraintElement {
+
+    // Can't inherit from HttpConstraintElement as API does not allow it
+    private static final String LSTRING_FILE = "javax.servlet.LocalStrings";
+    private static final ResourceBundle lStrings =
+        ResourceBundle.getBundle(LSTRING_FILE);
+
+    private final String methodName;
+
+    public HttpMethodConstraintElement(String methodName) {
+        if (methodName == null || methodName.length() == 0) {
+            throw new IllegalArgumentException(lStrings.getString(
+                    "httpMethodConstraintElement.invalidMethod"));
+        }
+        this.methodName = methodName;
+    }
+
+    public HttpMethodConstraintElement(String methodName,
+            HttpConstraintElement constraint) {
+        super(constraint.getEmptyRoleSemantic(),
+                constraint.getTransportGuarantee(),
+                constraint.getRolesAllowed());
+        if (methodName == null || methodName.length() == 0) {
+            throw new IllegalArgumentException(lStrings.getString(
+                    "httpMethodConstraintElement.invalidMethod"));
+        }
+        this.methodName = methodName;
+    }
+
+    public String getMethodName() {
+        return methodName;
+    }
+}
\ No newline at end of file
diff --git a/lib/servlet-api/javax/servlet/LocalStrings.properties b/lib/servlet-api/javax/servlet/LocalStrings.properties
new file mode 100644 (file)
index 0000000..0f60d51
--- /dev/null
@@ -0,0 +1,24 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Default localized string information
+# Localized for Locale en_US
+
+err.not_iso8859_1=Not an ISO 8859-1 character: {0}
+value.true=true
+value.false=false
+
+httpConstraintElement.invalidRolesDeny=Roles may not be specified when using DENY
+httpMethodConstraintElement.invalidMethod=Invalid HTTP method
\ No newline at end of file
diff --git a/lib/servlet-api/javax/servlet/LocalStrings_es.properties b/lib/servlet-api/javax/servlet/LocalStrings_es.properties
new file mode 100644 (file)
index 0000000..497c0dc
--- /dev/null
@@ -0,0 +1,19 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+err.not_iso8859_1 = No es un car\u00E1cter ISO 8859-1\: {0}
+value.true = true
+value.false = false
+httpConstraintElement.invalidRolesDeny = No se pueden especificar Roles al utilizar DENY (DENEGAR)
+httpMethodConstraintElement.invalidMethod = M\u00E9todo HTTP inv\u00E1lido
diff --git a/lib/servlet-api/javax/servlet/LocalStrings_fr.properties b/lib/servlet-api/javax/servlet/LocalStrings_fr.properties
new file mode 100644 (file)
index 0000000..05f75ec
--- /dev/null
@@ -0,0 +1,23 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Default localized string information
+# Localized for Locale fr_FR
+
+err.not_iso8859_1={0} n''est pas un caract\u00e8re ISO 8859-1
+value.true=true
+value.false=false
+
+
diff --git a/lib/servlet-api/javax/servlet/LocalStrings_ja.properties b/lib/servlet-api/javax/servlet/LocalStrings_ja.properties
new file mode 100644 (file)
index 0000000..2a87482
--- /dev/null
@@ -0,0 +1,21 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Default localized string information
+# Localized for Locale ja_JP
+
+err.not_iso8859_1=ISO 8859-1 \u306e\u6587\u5b57\u3067\u306f\u3042\u308a\u307e\u305b\u3093: {0}
+value.true=true
+value.false=false
diff --git a/lib/servlet-api/javax/servlet/MultipartConfigElement.java b/lib/servlet-api/javax/servlet/MultipartConfigElement.java
new file mode 100644 (file)
index 0000000..39b9f8b
--- /dev/null
@@ -0,0 +1,79 @@
+/*
+* Licensed to the Apache Software Foundation (ASF) under one or more
+* contributor license agreements.  See the NOTICE file distributed with
+* this work for additional information regarding copyright ownership.
+* The ASF licenses this file to You under the Apache License, Version 2.0
+* (the "License"); you may not use this file except in compliance with
+* the License.  You may obtain a copy of the License at
+*
+*     http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+package javax.servlet;
+
+import javax.servlet.annotation.MultipartConfig;
+
+/**
+ * @since Servlet 3.0
+ * TODO SERVLET3 - Add comments
+ */
+public class MultipartConfigElement {
+
+    private final String location;// = "";
+    private final long maxFileSize;// = -1;
+    private final long maxRequestSize;// = -1;
+    private final int fileSizeThreshold;// = 0;
+
+    public MultipartConfigElement(String location) {
+        // Keep empty string default if location is null
+        if (location != null) {
+            this.location = location;
+        } else {
+            this.location = "";
+        }
+        this.maxFileSize = -1;
+        this.maxRequestSize = -1;
+        this.fileSizeThreshold = 0;
+    }
+
+    public MultipartConfigElement(String location, long maxFileSize,
+            long maxRequestSize, int fileSizeThreshold) {
+        // Keep empty string default if location is null
+        if (location != null) {
+            this.location = location;
+        } else {
+            this.location = "";
+        }
+        this.maxFileSize = maxFileSize;
+        this.maxRequestSize = maxRequestSize;
+        this.fileSizeThreshold = fileSizeThreshold;
+    }
+
+    public MultipartConfigElement(MultipartConfig annotation) {
+        location = annotation.location();
+        maxFileSize = annotation.maxFileSize();
+        maxRequestSize = annotation.maxRequestSize();
+        fileSizeThreshold = annotation.fileSizeThreshold();
+    }
+
+    public String getLocation() {
+        return location;
+    }
+
+    public long getMaxFileSize() {
+        return maxFileSize;
+    }
+
+    public long getMaxRequestSize() {
+        return maxRequestSize;
+    }
+
+    public int getFileSizeThreshold() {
+        return fileSizeThreshold;
+    }
+}
\ No newline at end of file
diff --git a/lib/servlet-api/javax/servlet/ReadListener.java b/lib/servlet-api/javax/servlet/ReadListener.java
new file mode 100644 (file)
index 0000000..1d51c6c
--- /dev/null
@@ -0,0 +1,52 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package javax.servlet;
+
+import java.io.IOException;
+
+/**
+ * Receives notification of read events when using non-blocking IO.
+ *
+ * @since Servlet 3.1
+ */
+public interface ReadListener extends java.util.EventListener{
+
+    /**
+     * Invoked when data is available to read. The container will invoke this
+     * method the first time for a request as soon as there is data to read.
+     * Subsequent invocations will only occur if a call to
+     * {@link ServletInputStream#isReady()} has returned false and data has
+     * subsequently become available to read.
+     *
+     * @throws IOException
+     */
+    public abstract void onDataAvailable() throws IOException;
+
+    /**
+     * Invoked when the request body has been fully read.
+     *
+     * @throws IOException
+     */
+    public abstract void onAllDataRead() throws IOException;
+
+    /**
+     * Invoked if an error occurs while reading the request body.
+     *
+     * @param throwable The exception that occurred
+     */
+    public abstract void onError(java.lang.Throwable throwable);
+}
diff --git a/lib/servlet-api/javax/servlet/Registration.java b/lib/servlet-api/javax/servlet/Registration.java
new file mode 100644 (file)
index 0000000..fa9f4ec
--- /dev/null
@@ -0,0 +1,64 @@
+/*
+* Licensed to the Apache Software Foundation (ASF) under one or more
+* contributor license agreements.  See the NOTICE file distributed with
+* this work for additional information regarding copyright ownership.
+* The ASF licenses this file to You under the Apache License, Version 2.0
+* (the "License"); you may not use this file except in compliance with
+* the License.  You may obtain a copy of the License at
+*
+*     http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+package javax.servlet;
+
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * TODO SERVLET3 - Add comments
+ * @since Servlet 3.0
+ */
+public interface Registration {
+
+    public String getName();
+
+    public String getClassName();
+
+    /**
+     *
+     * @param name
+     * @param value
+     * @return TODO
+     * @throws IllegalArgumentException
+     * @throws IllegalStateException
+     */
+    public boolean setInitParameter(String name, String value);
+
+    public String getInitParameter(String name);
+
+    /**
+     *
+     * @param initParameters
+     * @return TODO
+     * @throws IllegalArgumentException
+     * @throws IllegalStateException
+     */
+    public Set<String> setInitParameters(Map<String,String> initParameters);
+
+    public Map<String, String> getInitParameters();
+
+    public interface Dynamic extends Registration {
+
+        /**
+         *
+         * @param isAsyncSupported
+         * @throws IllegalStateException
+         */
+        public void setAsyncSupported(boolean isAsyncSupported);
+    }
+}
diff --git a/lib/servlet-api/javax/servlet/RequestDispatcher.java b/lib/servlet-api/javax/servlet/RequestDispatcher.java
new file mode 100644 (file)
index 0000000..99ceeef
--- /dev/null
@@ -0,0 +1,293 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package javax.servlet;
+
+import java.io.IOException;
+
+/**
+ * Defines an object that receives requests from the client and sends them to
+ * any resource (such as a servlet, HTML file, or JSP file) on the server. The
+ * servlet container creates the <code>RequestDispatcher</code> object, which is
+ * used as a wrapper around a server resource located at a particular path or
+ * given by a particular name.
+ *
+ * <p>
+ * This interface is intended to wrap servlets, but a servlet container can
+ * create <code>RequestDispatcher</code> objects to wrap any type of resource.
+ *
+ * @see ServletContext#getRequestDispatcher(java.lang.String)
+ * @see ServletContext#getNamedDispatcher(java.lang.String)
+ * @see ServletRequest#getRequestDispatcher(java.lang.String)
+ *
+ */
+public interface RequestDispatcher {
+
+    /**
+     * The name of the request attribute that should be set by the container
+     * when the {@link #forward(ServletRequest, ServletResponse)} method is
+     * called. It provides the original value of a path-related property of the
+     * request. See the chapter "Forwarded Request Parameters" in the Servlet
+     * Specification for details.
+     *
+     * @since Servlet 3.0
+     */
+    static final String FORWARD_REQUEST_URI = "javax.servlet.forward.request_uri";
+
+    /**
+     * The name of the request attribute that should be set by the container
+     * when the {@link #forward(ServletRequest, ServletResponse)} method is
+     * called. It provides the original value of a path-related property of the
+     * request. See the chapter "Forwarded Request Parameters" in the Servlet
+     * Specification for details.
+     *
+     * @since Servlet 3.0
+     */
+    static final String FORWARD_CONTEXT_PATH = "javax.servlet.forward.context_path";
+
+    /**
+     * The name of the request attribute that should be set by the container
+     * when the {@link #forward(ServletRequest, ServletResponse)} method is
+     * called. It provides the original value of a path-related property of the
+     * request. See the chapter "Forwarded Request Parameters" in the Servlet
+     * Specification for details.
+     *
+     * @since Servlet 3.0
+     */
+    static final String FORWARD_PATH_INFO = "javax.servlet.forward.path_info";
+
+    /**
+     * The name of the request attribute that should be set by the container
+     * when the {@link #forward(ServletRequest, ServletResponse)} method is
+     * called. It provides the original value of a path-related property of the
+     * request. See the chapter "Forwarded Request Parameters" in the Servlet
+     * Specification for details.
+     *
+     * @since Servlet 3.0
+     */
+    static final String FORWARD_SERVLET_PATH = "javax.servlet.forward.servlet_path";
+
+    /**
+     * The name of the request attribute that should be set by the container
+     * when the {@link #forward(ServletRequest, ServletResponse)} method is
+     * called. It provides the original value of a path-related property of the
+     * request. See the chapter "Forwarded Request Parameters" in the Servlet
+     * Specification for details.
+     *
+     * @since Servlet 3.0
+     */
+    static final String FORWARD_QUERY_STRING = "javax.servlet.forward.query_string";
+
+    /**
+     * The name of the request attribute that should be set by the container
+     * when the {@link #include(ServletRequest, ServletResponse)} method is
+     * called on the {@code RequestDispatcher} obtained by a path and not by a
+     * name. It provides information on the path that was used to obtain the
+     * {@code RequestDispatcher} instance for this include call. See the chapter
+     * "Included Request Parameters" in the Servlet Specification for details.
+     *
+     * @since Servlet 3.0
+     */
+    static final String INCLUDE_REQUEST_URI = "javax.servlet.include.request_uri";
+
+    /**
+     * The name of the request attribute that should be set by the container
+     * when the {@link #include(ServletRequest, ServletResponse)} method is
+     * called on the {@code RequestDispatcher} obtained by a path and not by a
+     * name. It provides information on the path that was used to obtain the
+     * {@code RequestDispatcher} instance for this include call. See the chapter
+     * "Included Request Parameters" in the Servlet Specification for details.
+     *
+     * @since Servlet 3.0
+     */
+    static final String INCLUDE_CONTEXT_PATH = "javax.servlet.include.context_path";
+
+    /**
+     * The name of the request attribute that should be set by the container
+     * when the {@link #include(ServletRequest, ServletResponse)} method is
+     * called on the {@code RequestDispatcher} obtained by a path and not by a
+     * name. It provides information on the path that was used to obtain the
+     * {@code RequestDispatcher} instance for this include call. See the chapter
+     * "Included Request Parameters" in the Servlet Specification for details.
+     *
+     * @since Servlet 3.0
+     */
+    static final String INCLUDE_PATH_INFO = "javax.servlet.include.path_info";
+
+    /**
+     * The name of the request attribute that should be set by the container
+     * when the {@link #include(ServletRequest, ServletResponse)} method is
+     * called on the {@code RequestDispatcher} obtained by a path and not by a
+     * name. It provides information on the path that was used to obtain the
+     * {@code RequestDispatcher} instance for this include call. See the chapter
+     * "Included Request Parameters" in the Servlet Specification for details.
+     *
+     * @since Servlet 3.0
+     */
+    static final String INCLUDE_SERVLET_PATH = "javax.servlet.include.servlet_path";
+
+    /**
+     * The name of the request attribute that should be set by the container
+     * when the {@link #include(ServletRequest, ServletResponse)} method is
+     * called on the {@code RequestDispatcher} obtained by a path and not by a
+     * name. It provides information on the path that was used to obtain the
+     * {@code RequestDispatcher} instance for this include call. See the chapter
+     * "Included Request Parameters" in the Servlet Specification for details.
+     *
+     * @since Servlet 3.0
+     */
+    static final String INCLUDE_QUERY_STRING = "javax.servlet.include.query_string";
+
+    /**
+     * The name of the request attribute that should be set by the container
+     * when custom error-handling servlet or JSP page is invoked. The value of
+     * the attribute is of type {@code java.lang.Throwable}. See the chapter
+     * "Error Handling" in the Servlet Specification for details.
+     *
+     * @since Servlet 3.0
+     */
+    public static final String ERROR_EXCEPTION = "javax.servlet.error.exception";
+
+    /**
+     * The name of the request attribute that should be set by the container
+     * when custom error-handling servlet or JSP page is invoked. The value of
+     * the attribute is of type {@code java.lang.Class}. See the chapter
+     * "Error Handling" in the Servlet Specification for details.
+     *
+     * @since Servlet 3.0
+     */
+    public static final String ERROR_EXCEPTION_TYPE = "javax.servlet.error.exception_type";
+
+    /**
+     * The name of the request attribute that should be set by the container
+     * when custom error-handling servlet or JSP page is invoked. The value of
+     * the attribute is of type {@code java.lang.String}. See the chapter
+     * "Error Handling" in the Servlet Specification for details.
+     *
+     * @since Servlet 3.0
+     */
+    public static final String ERROR_MESSAGE = "javax.servlet.error.message";
+
+    /**
+     * The name of the request attribute that should be set by the container
+     * when custom error-handling servlet or JSP page is invoked. The value of
+     * the attribute is of type {@code java.lang.String}. See the chapter
+     * "Error Handling" in the Servlet Specification for details.
+     *
+     * @since Servlet 3.0
+     */
+    public static final String ERROR_REQUEST_URI = "javax.servlet.error.request_uri";
+
+    /**
+     * The name of the request attribute that should be set by the container
+     * when custom error-handling servlet or JSP page is invoked. The value of
+     * the attribute is of type {@code java.lang.String}. See the chapter
+     * "Error Handling" in the Servlet Specification for details.
+     *
+     * @since Servlet 3.0
+     */
+    public static final String ERROR_SERVLET_NAME = "javax.servlet.error.servlet_name";
+
+    /**
+     * The name of the request attribute that should be set by the container
+     * when custom error-handling servlet or JSP page is invoked. The value of
+     * the attribute is of type {@code java.lang.Integer}. See the chapter
+     * "Error Handling" in the Servlet Specification for details.
+     *
+     * @since Servlet 3.0
+     */
+    public static final String ERROR_STATUS_CODE = "javax.servlet.error.status_code";
+
+    /**
+     * Forwards a request from a servlet to another resource (servlet, JSP file,
+     * or HTML file) on the server. This method allows one servlet to do
+     * preliminary processing of a request and another resource to generate the
+     * response.
+     *
+     * <p>
+     * For a <code>RequestDispatcher</code> obtained via
+     * <code>getRequestDispatcher()</code>, the <code>ServletRequest</code>
+     * object has its path elements and parameters adjusted to match the path of
+     * the target resource.
+     *
+     * <p>
+     * <code>forward</code> should be called before the response has been
+     * committed to the client (before response body output has been flushed).
+     * If the response already has been committed, this method throws an
+     * <code>IllegalStateException</code>. Uncommitted output in the response
+     * buffer is automatically cleared before the forward.
+     *
+     * <p>
+     * The request and response parameters must be either the same objects as
+     * were passed to the calling servlet's service method or be subclasses of
+     * the {@link ServletRequestWrapper} or {@link ServletResponseWrapper}
+     * classes that wrap them.
+     *
+     *
+     * @param request
+     *            a {@link ServletRequest} object that represents the request
+     *            the client makes of the servlet
+     *
+     * @param response
+     *            a {@link ServletResponse} object that represents the response
+     *            the servlet returns to the client
+     *
+     * @exception ServletException
+     *                if the target resource throws this exception
+     *
+     * @exception IOException
+     *                if the target resource throws this exception
+     *
+     * @exception IllegalStateException
+     *                if the response was already committed
+     */
+    public void forward(ServletRequest request, ServletResponse response)
+            throws ServletException, IOException;
+
+    /**
+     * Includes the content of a resource (servlet, JSP page, HTML file) in the
+     * response. In essence, this method enables programmatic server-side
+     * includes.
+     *
+     * <p>
+     * The {@link ServletResponse} object has its path elements and parameters
+     * remain unchanged from the caller's. The included servlet cannot change
+     * the response status code or set headers; any attempt to make a change is
+     * ignored.
+     *
+     * <p>
+     * The request and response parameters must be either the same objects as
+     * were passed to the calling servlet's service method or be subclasses of
+     * the {@link ServletRequestWrapper} or {@link ServletResponseWrapper}
+     * classes that wrap them.
+     *
+     * @param request
+     *            a {@link ServletRequest} object that contains the client's
+     *            request
+     *
+     * @param response
+     *            a {@link ServletResponse} object that contains the servlet's
+     *            response
+     *
+     * @exception ServletException
+     *                if the included resource throws this exception
+     *
+     * @exception IOException
+     *                if the included resource throws this exception
+     */
+    public void include(ServletRequest request, ServletResponse response)
+            throws ServletException, IOException;
+}
diff --git a/lib/servlet-api/javax/servlet/Servlet.java b/lib/servlet-api/javax/servlet/Servlet.java
new file mode 100644 (file)
index 0000000..73d30bb
--- /dev/null
@@ -0,0 +1,178 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package javax.servlet;
+
+import java.io.IOException;
+
+/**
+ * Defines methods that all servlets must implement.
+ *
+ * <p>
+ * A servlet is a small Java program that runs within a Web server. Servlets
+ * receive and respond to requests from Web clients, usually across HTTP, the
+ * HyperText Transfer Protocol.
+ *
+ * <p>
+ * To implement this interface, you can write a generic servlet that extends
+ * <code>javax.servlet.GenericServlet</code> or an HTTP servlet that extends
+ * <code>javax.servlet.http.HttpServlet</code>.
+ *
+ * <p>
+ * This interface defines methods to initialize a servlet, to service requests,
+ * and to remove a servlet from the server. These are known as life-cycle
+ * methods and are called in the following sequence:
+ * <ol>
+ * <li>The servlet is constructed, then initialized with the <code>init</code>
+ * method.
+ * <li>Any calls from clients to the <code>service</code> method are handled.
+ * <li>The servlet is taken out of service, then destroyed with the
+ * <code>destroy</code> method, then garbage collected and finalized.
+ * </ol>
+ *
+ * <p>
+ * In addition to the life-cycle methods, this interface provides the
+ * <code>getServletConfig</code> method, which the servlet can use to get any
+ * startup information, and the <code>getServletInfo</code> method, which allows
+ * the servlet to return basic information about itself, such as author,
+ * version, and copyright.
+ *
+ * @see GenericServlet
+ * @see javax.servlet.http.HttpServlet
+ */
+public interface Servlet {
+
+    /**
+     * Called by the servlet container to indicate to a servlet that the servlet
+     * is being placed into service.
+     *
+     * <p>
+     * The servlet container calls the <code>init</code> method exactly once
+     * after instantiating the servlet. The <code>init</code> method must
+     * complete successfully before the servlet can receive any requests.
+     *
+     * <p>
+     * The servlet container cannot place the servlet into service if the
+     * <code>init</code> method
+     * <ol>
+     * <li>Throws a <code>ServletException</code>
+     * <li>Does not return within a time period defined by the Web server
+     * </ol>
+     *
+     *
+     * @param config
+     *            a <code>ServletConfig</code> object containing the servlet's
+     *            configuration and initialization parameters
+     *
+     * @exception ServletException
+     *                if an exception has occurred that interferes with the
+     *                servlet's normal operation
+     *
+     * @see UnavailableException
+     * @see #getServletConfig
+     */
+    public void init(ServletConfig config) throws ServletException;
+
+    /**
+     *
+     * Returns a {@link ServletConfig} object, which contains initialization and
+     * startup parameters for this servlet. The <code>ServletConfig</code>
+     * object returned is the one passed to the <code>init</code> method.
+     *
+     * <p>
+     * Implementations of this interface are responsible for storing the
+     * <code>ServletConfig</code> object so that this method can return it. The
+     * {@link GenericServlet} class, which implements this interface, already
+     * does this.
+     *
+     * @return the <code>ServletConfig</code> object that initializes this
+     *         servlet
+     *
+     * @see #init
+     */
+    public ServletConfig getServletConfig();
+
+    /**
+     * Called by the servlet container to allow the servlet to respond to a
+     * request.
+     *
+     * <p>
+     * This method is only called after the servlet's <code>init()</code> method
+     * has completed successfully.
+     *
+     * <p>
+     * The status code of the response always should be set for a servlet that
+     * throws or sends an error.
+     *
+     *
+     * <p>
+     * Servlets typically run inside multithreaded servlet containers that can
+     * handle multiple requests concurrently. Developers must be aware to
+     * synchronize access to any shared resources such as files, network
+     * connections, and as well as the servlet's class and instance variables.
+     * More information on multithreaded programming in Java is available in <a
+     * href
+     * ="http://java.sun.com/Series/Tutorial/java/threads/multithreaded.html">
+     * the Java tutorial on multi-threaded programming</a>.
+     *
+     *
+     * @param req
+     *            the <code>ServletRequest</code> object that contains the
+     *            client's request
+     *
+     * @param res
+     *            the <code>ServletResponse</code> object that contains the
+     *            servlet's response
+     *
+     * @exception ServletException
+     *                if an exception occurs that interferes with the servlet's
+     *                normal operation
+     *
+     * @exception IOException
+     *                if an input or output exception occurs
+     */
+    public void service(ServletRequest req, ServletResponse res)
+            throws ServletException, IOException;
+
+    /**
+     * Returns information about the servlet, such as author, version, and
+     * copyright.
+     *
+     * <p>
+     * The string that this method returns should be plain text and not markup
+     * of any kind (such as HTML, XML, etc.).
+     *
+     * @return a <code>String</code> containing servlet information
+     */
+    public String getServletInfo();
+
+    /**
+     * Called by the servlet container to indicate to a servlet that the servlet
+     * is being taken out of service. This method is only called once all
+     * threads within the servlet's <code>service</code> method have exited or
+     * after a timeout period has passed. After the servlet container calls this
+     * method, it will not call the <code>service</code> method again on this
+     * servlet.
+     *
+     * <p>
+     * This method gives the servlet an opportunity to clean up any resources
+     * that are being held (for example, memory, file handles, threads) and make
+     * sure that any persistent state is synchronized with the servlet's current
+     * state in memory.
+     */
+    public void destroy();
+}
diff --git a/lib/servlet-api/javax/servlet/ServletConfig.java b/lib/servlet-api/javax/servlet/ServletConfig.java
new file mode 100644 (file)
index 0000000..be61656
--- /dev/null
@@ -0,0 +1,69 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package javax.servlet;
+
+import java.util.Enumeration;
+
+/**
+ * A servlet configuration object used by a servlet container to pass
+ * information to a servlet during initialization.
+ */
+public interface ServletConfig {
+
+    /**
+     * Returns the name of this servlet instance. The name may be provided via
+     * server administration, assigned in the web application deployment
+     * descriptor, or for an unregistered (and thus unnamed) servlet instance it
+     * will be the servlet's class name.
+     *
+     * @return the name of the servlet instance
+     */
+    public String getServletName();
+
+    /**
+     * Returns a reference to the {@link ServletContext} in which the caller is
+     * executing.
+     *
+     * @return a {@link ServletContext} object, used by the caller to interact
+     *         with its servlet container
+     * @see ServletContext
+     */
+    public ServletContext getServletContext();
+
+    /**
+     * Returns a <code>String</code> containing the value of the named
+     * initialization parameter, or <code>null</code> if the parameter does not
+     * exist.
+     *
+     * @param name
+     *            a <code>String</code> specifying the name of the
+     *            initialization parameter
+     * @return a <code>String</code> containing the value of the initialization
+     *         parameter
+     */
+    public String getInitParameter(String name);
+
+    /**
+     * Returns the names of the servlet's initialization parameters as an
+     * <code>Enumeration</code> of <code>String</code> objects, or an empty
+     * <code>Enumeration</code> if the servlet has no initialization parameters.
+     *
+     * @return an <code>Enumeration</code> of <code>String</code> objects
+     *         containing the names of the servlet's initialization parameters
+     */
+    public Enumeration<String> getInitParameterNames();
+}
diff --git a/lib/servlet-api/javax/servlet/ServletContainerInitializer.java b/lib/servlet-api/javax/servlet/ServletContainerInitializer.java
new file mode 100644 (file)
index 0000000..2029c8a
--- /dev/null
@@ -0,0 +1,53 @@
+/*
+* Licensed to the Apache Software Foundation (ASF) under one or more
+* contributor license agreements.  See the NOTICE file distributed with
+* this work for additional information regarding copyright ownership.
+* The ASF licenses this file to You under the Apache License, Version 2.0
+* (the "License"); you may not use this file except in compliance with
+* the License.  You may obtain a copy of the License at
+*
+*     http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+package javax.servlet;
+
+import java.util.Set;
+
+/**
+ * ServletContainerInitializers (SCIs) are registered via an entry in the
+ * file META-INF/services/javax.servlet.ServletContainerInitializer that must be
+ * included in the JAR file that contains the SCI implementation.
+ * <p>
+ * SCI processing is performed regardless of the setting of metadata-complete.
+ * SCI processing can be controlled per JAR file via fragment ordering. If an
+ * absolute ordering is defined, the only those JARs included in the ordering
+ * will be processed for SCIs. To disable SCI processing completely, an empty
+ * absolute ordering may be defined.
+ * <p>
+ * SCIs register an interest in annotations (class, method or field) and/or
+ * types via the {@link javax.servlet.annotation.HandlesTypes} annotation which
+ * is added to the class.
+ *
+ * @since Servlet 3.0
+ */
+public interface ServletContainerInitializer {
+
+    /**
+     * Receives notification during startup of a web application of the classes
+     * within the web application that matched the criteria defined via the
+     * {@link javax.servlet.annotation.HandlesTypes} annotation.
+     *
+     * @param c     The (possibly null) set of classes that met the specified
+     *              criteria
+     * @param ctx   The ServletContext of the web application in which the
+     *              classes were discovered
+     *
+     * @throws ServletException If an error occurs
+     */
+    void onStartup(Set<Class<?>> c, ServletContext ctx) throws ServletException;
+}
diff --git a/lib/servlet-api/javax/servlet/ServletContext.java b/lib/servlet-api/javax/servlet/ServletContext.java
new file mode 100644 (file)
index 0000000..9aa1177
--- /dev/null
@@ -0,0 +1,895 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package javax.servlet;
+
+import java.io.InputStream;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.Enumeration;
+import java.util.EventListener;
+import java.util.Map;
+import java.util.Set;
+
+import javax.servlet.descriptor.JspConfigDescriptor;
+
+/**
+ * Defines a set of methods that a servlet uses to communicate with its servlet
+ * container, for example, to get the MIME type of a file, dispatch requests, or
+ * write to a log file.
+ * <p>
+ * There is one context per "web application" per Java Virtual Machine. (A
+ * "web application" is a collection of servlets and content installed under a
+ * specific subset of the server's URL namespace such as <code>/catalog</code>
+ * and possibly installed via a <code>.war</code> file.)
+ * <p>
+ * In the case of a web application marked "distributed" in its deployment
+ * descriptor, there will be one context instance for each virtual machine. In
+ * this situation, the context cannot be used as a location to share global
+ * information (because the information won't be truly global). Use an external
+ * resource like a database instead.
+ * <p>
+ * The <code>ServletContext</code> object is contained within the
+ * {@link ServletConfig} object, which the Web server provides the servlet when
+ * the servlet is initialized.
+ *
+ * @see Servlet#getServletConfig
+ * @see ServletConfig#getServletContext
+ */
+public interface ServletContext {
+
+    public static final String TEMPDIR = "javax.servlet.context.tempdir";
+
+    /**
+     * @since Servlet 3.0
+     */
+    public static final String ORDERED_LIBS = "javax.servlet.context.orderedLibs";
+
+    public String getContextPath();
+
+    /**
+     * Returns a <code>ServletContext</code> object that corresponds to a
+     * specified URL on the server.
+     * <p>
+     * This method allows servlets to gain access to the context for various
+     * parts of the server, and as needed obtain {@link RequestDispatcher}
+     * objects from the context. The given path must be begin with "/", is
+     * interpreted relative to the server's document root and is matched against
+     * the context roots of other web applications hosted on this container.
+     * <p>
+     * In a security conscious environment, the servlet container may return
+     * <code>null</code> for a given URL.
+     *
+     * @param uripath
+     *            a <code>String</code> specifying the context path of another
+     *            web application in the container.
+     * @return the <code>ServletContext</code> object that corresponds to the
+     *         named URL, or null if either none exists or the container wishes
+     *         to restrict this access.
+     * @see RequestDispatcher
+     */
+    public ServletContext getContext(String uripath);
+
+    /**
+     * Returns the major version of the Java Servlet API that this servlet
+     * container supports. All implementations that comply with Version 3.1 must
+     * have this method return the integer 3.
+     *
+     * @return 3
+     */
+    public int getMajorVersion();
+
+    /**
+     * Returns the minor version of the Servlet API that this servlet container
+     * supports. All implementations that comply with Version 3.1 must have this
+     * method return the integer 1.
+     *
+     * @return 1
+     */
+    public int getMinorVersion();
+
+    /**
+     * @return TODO
+     * @throws UnsupportedOperationException    If called from a
+     *    {@link ServletContextListener#contextInitialized(ServletContextEvent)}
+     *    method of a {@link ServletContextListener} that was not defined in a
+     *    web.xml file, a web-fragment.xml file nor annotated with
+     *    {@link javax.servlet.annotation.WebListener}. For example, a
+     *    {@link ServletContextListener} defined in a TLD would not be able to
+     *    use this method.
+     *
+     * @since Servlet 3.0 TODO SERVLET3 - Add comments
+     */
+    public int getEffectiveMajorVersion();
+
+    /**
+     * @return TODO
+     * @throws UnsupportedOperationException    If called from a
+     *    {@link ServletContextListener#contextInitialized(ServletContextEvent)}
+     *    method of a {@link ServletContextListener} that was not defined in a
+     *    web.xml file, a web-fragment.xml file nor annotated with
+     *    {@link javax.servlet.annotation.WebListener}. For example, a
+     *    {@link ServletContextListener} defined in a TLD would not be able to
+     *    use this method.
+     * @since Servlet 3.0 TODO SERVLET3 - Add comments
+     */
+    public int getEffectiveMinorVersion();
+
+    /**
+     * Returns the MIME type of the specified file, or <code>null</code> if the
+     * MIME type is not known. The MIME type is determined by the configuration
+     * of the servlet container, and may be specified in a web application
+     * deployment descriptor. Common MIME types are <code>"text/html"</code> and
+     * <code>"image/gif"</code>.
+     *
+     * @param file
+     *            a <code>String</code> specifying the name of a file
+     * @return a <code>String</code> specifying the file's MIME type
+     */
+    public String getMimeType(String file);
+
+    /**
+     * Returns a directory-like listing of all the paths to resources within the
+     * web application whose longest sub-path matches the supplied path
+     * argument. Paths indicating subdirectory paths end with a '/'. The
+     * returned paths are all relative to the root of the web application and
+     * have a leading '/'. For example, for a web application containing<br>
+     * <br>
+     * /welcome.html<br>
+     * /catalog/index.html<br>
+     * /catalog/products.html<br>
+     * /catalog/offers/books.html<br>
+     * /catalog/offers/music.html<br>
+     * /customer/login.jsp<br>
+     * /WEB-INF/web.xml<br>
+     * /WEB-INF/classes/com.acme.OrderServlet.class,<br>
+     * <br>
+     * getResourcePaths("/") returns {"/welcome.html", "/catalog/",
+     * "/customer/", "/WEB-INF/"}<br>
+     * getResourcePaths("/catalog/") returns {"/catalog/index.html",
+     * "/catalog/products.html", "/catalog/offers/"}.<br>
+     *
+     * @param path
+     *            the partial path used to match the resources, which must start
+     *            with a /
+     * @return a Set containing the directory listing, or null if there are no
+     *         resources in the web application whose path begins with the
+     *         supplied path.
+     * @since Servlet 2.3
+     */
+    public Set<String> getResourcePaths(String path);
+
+    /**
+     * Returns a URL to the resource that is mapped to a specified path. The
+     * path must begin with a "/" and is interpreted as relative to the current
+     * context root.
+     * <p>
+     * This method allows the servlet container to make a resource available to
+     * servlets from any source. Resources can be located on a local or remote
+     * file system, in a database, or in a <code>.war</code> file.
+     * <p>
+     * The servlet container must implement the URL handlers and
+     * <code>URLConnection</code> objects that are necessary to access the
+     * resource.
+     * <p>
+     * This method returns <code>null</code> if no resource is mapped to the
+     * pathname.
+     * <p>
+     * Some containers may allow writing to the URL returned by this method
+     * using the methods of the URL class.
+     * <p>
+     * The resource content is returned directly, so be aware that requesting a
+     * <code>.jsp</code> page returns the JSP source code. Use a
+     * <code>RequestDispatcher</code> instead to include results of an
+     * execution.
+     * <p>
+     * This method has a different purpose than
+     * <code>java.lang.Class.getResource</code>, which looks up resources based
+     * on a class loader. This method does not use class loaders.
+     *
+     * @param path
+     *            a <code>String</code> specifying the path to the resource
+     * @return the resource located at the named path, or <code>null</code> if
+     *         there is no resource at that path
+     * @exception MalformedURLException
+     *                if the pathname is not given in the correct form
+     */
+    public URL getResource(String path) throws MalformedURLException;
+
+    /**
+     * Returns the resource located at the named path as an
+     * <code>InputStream</code> object.
+     * <p>
+     * The data in the <code>InputStream</code> can be of any type or length.
+     * The path must be specified according to the rules given in
+     * <code>getResource</code>. This method returns <code>null</code> if no
+     * resource exists at the specified path.
+     * <p>
+     * Meta-information such as content length and content type that is
+     * available via <code>getResource</code> method is lost when using this
+     * method.
+     * <p>
+     * The servlet container must implement the URL handlers and
+     * <code>URLConnection</code> objects necessary to access the resource.
+     * <p>
+     * This method is different from
+     * <code>java.lang.Class.getResourceAsStream</code>, which uses a class
+     * loader. This method allows servlet containers to make a resource
+     * available to a servlet from any location, without using a class loader.
+     *
+     * @param path
+     *            a <code>String</code> specifying the path to the resource
+     * @return the <code>InputStream</code> returned to the servlet, or
+     *         <code>null</code> if no resource exists at the specified path
+     */
+    public InputStream getResourceAsStream(String path);
+
+    /**
+     * Returns a {@link RequestDispatcher} object that acts as a wrapper for the
+     * resource located at the given path. A <code>RequestDispatcher</code>
+     * object can be used to forward a request to the resource or to include the
+     * resource in a response. The resource can be dynamic or static.
+     * <p>
+     * The pathname must begin with a "/" and is interpreted as relative to the
+     * current context root. Use <code>getContext</code> to obtain a
+     * <code>RequestDispatcher</code> for resources in foreign contexts. This
+     * method returns <code>null</code> if the <code>ServletContext</code>
+     * cannot return a <code>RequestDispatcher</code>.
+     *
+     * @param path
+     *            a <code>String</code> specifying the pathname to the resource
+     * @return a <code>RequestDispatcher</code> object that acts as a wrapper for
+     *         the resource at the specified path, or <code>null</code> if the
+     *         <code>ServletContext</code> cannot return a
+     *         <code>RequestDispatcher</code>
+     * @see RequestDispatcher
+     * @see ServletContext#getContext
+     */
+    public RequestDispatcher getRequestDispatcher(String path);
+
+    /**
+     * Returns a {@link RequestDispatcher} object that acts as a wrapper for the
+     * named servlet.
+     * <p>
+     * Servlets (and JSP pages also) may be given names via server
+     * administration or via a web application deployment descriptor. A servlet
+     * instance can determine its name using
+     * {@link ServletConfig#getServletName}.
+     * <p>
+     * This method returns <code>null</code> if the <code>ServletContext</code>
+     * cannot return a <code>RequestDispatcher</code> for any reason.
+     *
+     * @param name
+     *            a <code>String</code> specifying the name of a servlet to wrap
+     * @return a <code>RequestDispatcher</code> object that acts as a wrapper for
+     *         the named servlet, or <code>null</code> if the
+     *         <code>ServletContext</code> cannot return a
+     *         <code>RequestDispatcher</code>
+     * @see RequestDispatcher
+     * @see ServletContext#getContext
+     * @see ServletConfig#getServletName
+     */
+    public RequestDispatcher getNamedDispatcher(String name);
+
+    /**
+     * @deprecated As of Java Servlet API 2.1, with no direct replacement.
+     *             <p>
+     *             This method was originally defined to retrieve a servlet from
+     *             a <code>ServletContext</code>. In this version, this method
+     *             always returns <code>null</code> and remains only to preserve
+     *             binary compatibility. This method will be permanently removed
+     *             in a future version of the Java Servlet API.
+     *             <p>
+     *             In lieu of this method, servlets can share information using
+     *             the <code>ServletContext</code> class and can perform shared
+     *             business logic by invoking methods on common non-servlet
+     *             classes.
+     */
+    @SuppressWarnings("dep-ann")
+    // Spec API does not use @Deprecated
+    public Servlet getServlet(String name) throws ServletException;
+
+    /**
+     * @deprecated As of Java Servlet API 2.0, with no replacement.
+     *             <p>
+     *             This method was originally defined to return an
+     *             <code>Enumeration</code> of all the servlets known to this
+     *             servlet context. In this version, this method always returns
+     *             an empty enumeration and remains only to preserve binary
+     *             compatibility. This method will be permanently removed in a
+     *             future version of the Java Servlet API.
+     */
+    @SuppressWarnings("dep-ann")
+    // Spec API does not use @Deprecated
+    public Enumeration<Servlet> getServlets();
+
+    /**
+     * @deprecated As of Java Servlet API 2.1, with no replacement.
+     *             <p>
+     *             This method was originally defined to return an
+     *             <code>Enumeration</code> of all the servlet names known to
+     *             this context. In this version, this method always returns an
+     *             empty <code>Enumeration</code> and remains only to preserve
+     *             binary compatibility. This method will be permanently removed
+     *             in a future version of the Java Servlet API.
+     */
+    @SuppressWarnings("dep-ann")
+    // Spec API does not use @Deprecated
+    public Enumeration<String> getServletNames();
+
+    /**
+     * Writes the specified message to a servlet log file, usually an event log.
+     * The name and type of the servlet log file is specific to the servlet
+     * container.
+     *
+     * @param msg
+     *            a <code>String</code> specifying the message to be written to
+     *            the log file
+     */
+    public void log(String msg);
+
+    /**
+     * @deprecated As of Java Servlet API 2.1, use
+     *             {@link #log(String message, Throwable throwable)} instead.
+     *             <p>
+     *             This method was originally defined to write an exception's
+     *             stack trace and an explanatory error message to the servlet
+     *             log file.
+     */
+    @SuppressWarnings("dep-ann")
+    // Spec API does not use @Deprecated
+    public void log(Exception exception, String msg);
+
+    /**
+     * Writes an explanatory message and a stack trace for a given
+     * <code>Throwable</code> exception to the servlet log file. The name and
+     * type of the servlet log file is specific to the servlet container,
+     * usually an event log.
+     *
+     * @param message
+     *            a <code>String</code> that describes the error or exception
+     * @param throwable
+     *            the <code>Throwable</code> error or exception
+     */
+    public void log(String message, Throwable throwable);
+
+    /**
+     * Returns a <code>String</code> containing the real path for a given
+     * virtual path. For example, the path "/index.html" returns the absolute
+     * file path on the server's filesystem would be served by a request for
+     * "http://host/contextPath/index.html", where contextPath is the context
+     * path of this ServletContext..
+     * <p>
+     * The real path returned will be in a form appropriate to the computer and
+     * operating system on which the servlet container is running, including the
+     * proper path separators. This method returns <code>null</code> if the
+     * servlet container cannot translate the virtual path to a real path for
+     * any reason (such as when the content is being made available from a
+     * <code>.war</code> archive).
+     *
+     * @param path
+     *            a <code>String</code> specifying a virtual path
+     * @return a <code>String</code> specifying the real path, or null if the
+     *         translation cannot be performed
+     */
+    public String getRealPath(String path);
+
+    /**
+     * Returns the name and version of the servlet container on which the
+     * servlet is running.
+     * <p>
+     * The form of the returned string is
+     * <i>servername</i>/<i>versionnumber</i>. For example, the JavaServer Web
+     * Development Kit may return the string
+     * <code>JavaServer Web Dev Kit/1.0</code>.
+     * <p>
+     * The servlet container may return other optional information after the
+     * primary string in parentheses, for example,
+     * <code>JavaServer Web Dev Kit/1.0 (JDK 1.1.6; Windows NT 4.0 x86)</code>.
+     *
+     * @return a <code>String</code> containing at least the servlet container
+     *         name and version number
+     */
+    public String getServerInfo();
+
+    /**
+     * Returns a <code>String</code> containing the value of the named
+     * context-wide initialization parameter, or <code>null</code> if the
+     * parameter does not exist.
+     * <p>
+     * This method can make available configuration information useful to an
+     * entire "web application". For example, it can provide a webmaster's email
+     * address or the name of a system that holds critical data.
+     *
+     * @param name
+     *            a <code>String</code> containing the name of the parameter
+     *            whose value is requested
+     * @return a <code>String</code> containing the value of the initialization
+     *         parameter
+     * @see ServletConfig#getInitParameter
+     */
+    public String getInitParameter(String name);
+
+    /**
+     * Returns the names of the context's initialization parameters as an
+     * <code>Enumeration</code> of <code>String</code> objects, or an empty
+     * <code>Enumeration</code> if the context has no initialization parameters.
+     *
+     * @return an <code>Enumeration</code> of <code>String</code> objects
+     *         containing the names of the context's initialization parameters
+     * @see ServletConfig#getInitParameter
+     */
+
+    public Enumeration<String> getInitParameterNames();
+
+    /**
+     * @param name
+     * @param value
+     * @return TODO
+     * @throws IllegalStateException
+     * @throws UnsupportedOperationException    If called from a
+     *    {@link ServletContextListener#contextInitialized(ServletContextEvent)}
+     *    method of a {@link ServletContextListener} that was not defined in a
+     *    web.xml file, a web-fragment.xml file nor annotated with
+     *    {@link javax.servlet.annotation.WebListener}. For example, a
+     *    {@link ServletContextListener} defined in a TLD would not be able to
+     *    use this method.
+     * @since Servlet 3.0 TODO SERVLET3 - Add comments
+     */
+    public boolean setInitParameter(String name, String value);
+
+    /**
+     * Returns the servlet container attribute with the given name, or
+     * <code>null</code> if there is no attribute by that name. An attribute
+     * allows a servlet container to give the servlet additional information not
+     * already provided by this interface. See your server documentation for
+     * information about its attributes. A list of supported attributes can be
+     * retrieved using <code>getAttributeNames</code>.
+     * <p>
+     * The attribute is returned as a <code>java.lang.Object</code> or some
+     * subclass. Attribute names should follow the same convention as package
+     * names. The Java Servlet API specification reserves names matching
+     * <code>java.*</code>, <code>javax.*</code>, and <code>sun.*</code>.
+     *
+     * @param name
+     *            a <code>String</code> specifying the name of the attribute
+     * @return an <code>Object</code> containing the value of the attribute, or
+     *         <code>null</code> if no attribute exists matching the given name
+     * @see ServletContext#getAttributeNames
+     */
+    public Object getAttribute(String name);
+
+    /**
+     * Returns an <code>Enumeration</code> containing the attribute names
+     * available within this servlet context. Use the {@link #getAttribute}
+     * method with an attribute name to get the value of an attribute.
+     *
+     * @return an <code>Enumeration</code> of attribute names
+     * @see #getAttribute
+     */
+    public Enumeration<String> getAttributeNames();
+
+    /**
+     * Binds an object to a given attribute name in this servlet context. If the
+     * name specified is already used for an attribute, this method will replace
+     * the attribute with the new to the new attribute.
+     * <p>
+     * If listeners are configured on the <code>ServletContext</code> the
+     * container notifies them accordingly.
+     * <p>
+     * If a null value is passed, the effect is the same as calling
+     * <code>removeAttribute()</code>.
+     * <p>
+     * Attribute names should follow the same convention as package names. The
+     * Java Servlet API specification reserves names matching
+     * <code>java.*</code>, <code>javax.*</code>, and <code>sun.*</code>.
+     *
+     * @param name
+     *            a <code>String</code> specifying the name of the attribute
+     * @param object
+     *            an <code>Object</code> representing the attribute to be bound
+     */
+    public void setAttribute(String name, Object object);
+
+    /**
+     * Removes the attribute with the given name from the servlet context. After
+     * removal, subsequent calls to {@link #getAttribute} to retrieve the
+     * attribute's value will return <code>null</code>.
+     * <p>
+     * If listeners are configured on the <code>ServletContext</code> the
+     * container notifies them accordingly.
+     *
+     * @param name
+     *            a <code>String</code> specifying the name of the attribute to
+     *            be removed
+     */
+    public void removeAttribute(String name);
+
+    /**
+     * Returns the name of this web application corresponding to this
+     * ServletContext as specified in the deployment descriptor for this web
+     * application by the display-name element.
+     *
+     * @return The name of the web application or null if no name has been
+     *         declared in the deployment descriptor.
+     * @since Servlet 2.3
+     */
+    public String getServletContextName();
+
+    /**
+     * @param servletName
+     * @param className
+     * @return TODO
+     * @throws IllegalStateException
+     *             If the context has already been initialised
+     * @throws UnsupportedOperationException    If called from a
+     *    {@link ServletContextListener#contextInitialized(ServletContextEvent)}
+     *    method of a {@link ServletContextListener} that was not defined in a
+     *    web.xml file, a web-fragment.xml file nor annotated with
+     *    {@link javax.servlet.annotation.WebListener}. For example, a
+     *    {@link ServletContextListener} defined in a TLD would not be able to
+     *    use this method.
+     * @since Servlet 3.0 TODO SERVLET3 - Add comments
+     */
+    public ServletRegistration.Dynamic addServlet(String servletName,
+            String className);
+
+    /**
+     * @param servletName
+     * @param servlet
+     * @return TODO
+     * @throws IllegalStateException
+     *             If the context has already been initialised
+     * @throws UnsupportedOperationException    If called from a
+     *    {@link ServletContextListener#contextInitialized(ServletContextEvent)}
+     *    method of a {@link ServletContextListener} that was not defined in a
+     *    web.xml file, a web-fragment.xml file nor annotated with
+     *    {@link javax.servlet.annotation.WebListener}. For example, a
+     *    {@link ServletContextListener} defined in a TLD would not be able to
+     *    use this method.
+     * @since Servlet 3.0 TODO SERVLET3 - Add comments
+     */
+    public ServletRegistration.Dynamic addServlet(String servletName,
+            Servlet servlet);
+
+    /**
+     * @param servletName
+     * @param servletClass
+     * @return TODO
+     * @throws IllegalStateException
+     *             If the context has already been initialised
+     * @throws UnsupportedOperationException    If called from a
+     *    {@link ServletContextListener#contextInitialized(ServletContextEvent)}
+     *    method of a {@link ServletContextListener} that was not defined in a
+     *    web.xml file, a web-fragment.xml file nor annotated with
+     *    {@link javax.servlet.annotation.WebListener}. For example, a
+     *    {@link ServletContextListener} defined in a TLD would not be able to
+     *    use this method.
+     * @since Servlet 3.0 TODO SERVLET3 - Add comments
+     */
+    public ServletRegistration.Dynamic addServlet(String servletName,
+            Class<? extends Servlet> servletClass);
+
+    /**
+     * @param c
+     * @return TODO
+     * @throws ServletException
+     * @throws UnsupportedOperationException    If called from a
+     *    {@link ServletContextListener#contextInitialized(ServletContextEvent)}
+     *    method of a {@link ServletContextListener} that was not defined in a
+     *    web.xml file, a web-fragment.xml file nor annotated with
+     *    {@link javax.servlet.annotation.WebListener}. For example, a
+     *    {@link ServletContextListener} defined in a TLD would not be able to
+     *    use this method.
+     * @since Servlet 3.0 TODO SERVLET3 - Add comments
+     */
+    public <T extends Servlet> T createServlet(Class<T> c)
+            throws ServletException;
+
+    /**
+     * Obtain the details of the named servlet.
+     *
+     * @param servletName   The name of the Servlet of interest
+     *
+     * @return  The registration details for the named Servlet or
+     *          <code>null</code> if no Servlet has been registered with the
+     *          given name
+     *
+     * @throws UnsupportedOperationException    If called from a
+     *    {@link ServletContextListener#contextInitialized(ServletContextEvent)}
+     *    method of a {@link ServletContextListener} that was not defined in a
+     *    web.xml file, a web-fragment.xml file nor annotated with
+     *    {@link javax.servlet.annotation.WebListener}. For example, a
+     *    {@link ServletContextListener} defined in a TLD would not be able to
+     *    use this method.
+     *
+     * @since Servlet 3.0
+     */
+    public ServletRegistration getServletRegistration(String servletName);
+
+    /**
+     * @return TODO
+     * @throws UnsupportedOperationException    If called from a
+     *    {@link ServletContextListener#contextInitialized(ServletContextEvent)}
+     *    method of a {@link ServletContextListener} that was not defined in a
+     *    web.xml file, a web-fragment.xml file nor annotated with
+     *    {@link javax.servlet.annotation.WebListener}. For example, a
+     *    {@link ServletContextListener} defined in a TLD would not be able to
+     *    use this method.
+     * @since Servlet 3.0 TODO SERVLET3 - Add comments
+     */
+    public Map<String, ? extends ServletRegistration> getServletRegistrations();
+
+    /**
+     * @param filterName
+     * @param className
+     * @return TODO
+     * @throws UnsupportedOperationException    If called from a
+     *    {@link ServletContextListener#contextInitialized(ServletContextEvent)}
+     *    method of a {@link ServletContextListener} that was not defined in a
+     *    web.xml file, a web-fragment.xml file nor annotated with
+     *    {@link javax.servlet.annotation.WebListener}. For example, a
+     *    {@link ServletContextListener} defined in a TLD would not be able to
+     *    use this method.
+     * @throws IllegalStateException
+     *             If the context has already been initialised
+     * @since Servlet 3.0 TODO SERVLET3 - Add comments
+     */
+    public FilterRegistration.Dynamic addFilter(String filterName,
+            String className);
+
+    /**
+     * @param filterName
+     * @param filter
+     * @return TODO
+     * @throws UnsupportedOperationException    If called from a
+     *    {@link ServletContextListener#contextInitialized(ServletContextEvent)}
+     *    method of a {@link ServletContextListener} that was not defined in a
+     *    web.xml file, a web-fragment.xml file nor annotated with
+     *    {@link javax.servlet.annotation.WebListener}. For example, a
+     *    {@link ServletContextListener} defined in a TLD would not be able to
+     *    use this method.
+     * @throws IllegalStateException
+     *             If the context has already been initialised
+     * @since Servlet 3.0 TODO SERVLET3 - Add comments
+     */
+    public FilterRegistration.Dynamic addFilter(String filterName, Filter filter);
+
+    /**
+     * @param filterName
+     * @param filterClass
+     * @return TODO
+     * @throws UnsupportedOperationException    If called from a
+     *    {@link ServletContextListener#contextInitialized(ServletContextEvent)}
+     *    method of a {@link ServletContextListener} that was not defined in a
+     *    web.xml file, a web-fragment.xml file nor annotated with
+     *    {@link javax.servlet.annotation.WebListener}. For example, a
+     *    {@link ServletContextListener} defined in a TLD would not be able to
+     *    use this method.
+     * @throws IllegalStateException
+     *             If the context has already been initialised
+     * @since Servlet 3.0 TODO SERVLET3 - Add comments
+     */
+    public FilterRegistration.Dynamic addFilter(String filterName,
+            Class<? extends Filter> filterClass);
+
+    /**
+     * @param c
+     * @return TODO
+     * @throws UnsupportedOperationException    If called from a
+     *    {@link ServletContextListener#contextInitialized(ServletContextEvent)}
+     *    method of a {@link ServletContextListener} that was not defined in a
+     *    web.xml file, a web-fragment.xml file nor annotated with
+     *    {@link javax.servlet.annotation.WebListener}. For example, a
+     *    {@link ServletContextListener} defined in a TLD would not be able to
+     *    use this method.
+     * @throws ServletException
+     * @since Servlet 3.0 TODO SERVLET3 - Add comments
+     */
+    public <T extends Filter> T createFilter(Class<T> c)
+            throws ServletException;
+
+    /**
+     * @param filterName
+     * @return TODO
+     * @throws UnsupportedOperationException    If called from a
+     *    {@link ServletContextListener#contextInitialized(ServletContextEvent)}
+     *    method of a {@link ServletContextListener} that was not defined in a
+     *    web.xml file, a web-fragment.xml file nor annotated with
+     *    {@link javax.servlet.annotation.WebListener}. For example, a
+     *    {@link ServletContextListener} defined in a TLD would not be able to
+     *    use this method.
+     * @since Servlet 3.0 TODO SERVLET3 - Add comments
+     */
+    public FilterRegistration getFilterRegistration(String filterName);
+
+    /**
+     * @return TODO
+     * @throws UnsupportedOperationException    If called from a
+     *    {@link ServletContextListener#contextInitialized(ServletContextEvent)}
+     *    method of a {@link ServletContextListener} that was not defined in a
+     *    web.xml file, a web-fragment.xml file nor annotated with
+     *    {@link javax.servlet.annotation.WebListener}. For example, a
+     *    {@link ServletContextListener} defined in a TLD would not be able to
+     *    use this method.
+     * @since Servlet 3.0 TODO SERVLET3 - Add comments
+     */
+    public Map<String, ? extends FilterRegistration> getFilterRegistrations();
+
+    /**
+     * @return TODO
+     * @throws UnsupportedOperationException    If called from a
+     *    {@link ServletContextListener#contextInitialized(ServletContextEvent)}
+     *    method of a {@link ServletContextListener} that was not defined in a
+     *    web.xml file, a web-fragment.xml file nor annotated with
+     *    {@link javax.servlet.annotation.WebListener}. For example, a
+     *    {@link ServletContextListener} defined in a TLD would not be able to
+     *    use this method.
+     * @since Servlet 3.0 TODO SERVLET3 - Add comments
+     */
+    public SessionCookieConfig getSessionCookieConfig();
+
+    /**
+     * @param sessionTrackingModes
+     * @throws IllegalArgumentException
+     *             If sessionTrackingModes specifies
+     *             {@link SessionTrackingMode#SSL} in combination with any other
+     *             {@link SessionTrackingMode}
+     * @throws IllegalStateException
+     *             If the context has already been initialised
+     * @throws UnsupportedOperationException    If called from a
+     *    {@link ServletContextListener#contextInitialized(ServletContextEvent)}
+     *    method of a {@link ServletContextListener} that was not defined in a
+     *    web.xml file, a web-fragment.xml file nor annotated with
+     *    {@link javax.servlet.annotation.WebListener}. For example, a
+     *    {@link ServletContextListener} defined in a TLD would not be able to
+     *    use this method.
+     * @since Servlet 3.0 TODO SERVLET3 - Add comments
+     */
+    public void setSessionTrackingModes(
+            Set<SessionTrackingMode> sessionTrackingModes);
+
+    /**
+     * @return TODO
+     * @throws UnsupportedOperationException    If called from a
+     *    {@link ServletContextListener#contextInitialized(ServletContextEvent)}
+     *    method of a {@link ServletContextListener} that was not defined in a
+     *    web.xml file, a web-fragment.xml file nor annotated with
+     *    {@link javax.servlet.annotation.WebListener}. For example, a
+     *    {@link ServletContextListener} defined in a TLD would not be able to
+     *    use this method.
+     * @since Servlet 3.0 TODO SERVLET3 - Add comments
+     */
+    public Set<SessionTrackingMode> getDefaultSessionTrackingModes();
+
+    /**
+     * @return TODO
+     * @throws UnsupportedOperationException    If called from a
+     *    {@link ServletContextListener#contextInitialized(ServletContextEvent)}
+     *    method of a {@link ServletContextListener} that was not defined in a
+     *    web.xml file, a web-fragment.xml file nor annotated with
+     *    {@link javax.servlet.annotation.WebListener}. For example, a
+     *    {@link ServletContextListener} defined in a TLD would not be able to
+     *    use this method.
+     * @since Servlet 3.0 TODO SERVLET3 - Add comments
+     */
+    public Set<SessionTrackingMode> getEffectiveSessionTrackingModes();
+
+    /**
+     * @param className
+     * @throws UnsupportedOperationException    If called from a
+     *    {@link ServletContextListener#contextInitialized(ServletContextEvent)}
+     *    method of a {@link ServletContextListener} that was not defined in a
+     *    web.xml file, a web-fragment.xml file nor annotated with
+     *    {@link javax.servlet.annotation.WebListener}. For example, a
+     *    {@link ServletContextListener} defined in a TLD would not be able to
+     *    use this method.
+     * @since Servlet 3.0 TODO SERVLET3 - Add comments
+     */
+    public void addListener(String className);
+
+    /**
+     * @param <T>
+     * @param t
+     * @throws UnsupportedOperationException    If called from a
+     *    {@link ServletContextListener#contextInitialized(ServletContextEvent)}
+     *    method of a {@link ServletContextListener} that was not defined in a
+     *    web.xml file, a web-fragment.xml file nor annotated with
+     *    {@link javax.servlet.annotation.WebListener}. For example, a
+     *    {@link ServletContextListener} defined in a TLD would not be able to
+     *    use this method.
+     * @since Servlet 3.0 TODO SERVLET3 - Add comments
+     */
+    public <T extends EventListener> void addListener(T t);
+
+    /**
+     * @param listenerClass
+     * @throws UnsupportedOperationException    If called from a
+     *    {@link ServletContextListener#contextInitialized(ServletContextEvent)}
+     *    method of a {@link ServletContextListener} that was not defined in a
+     *    web.xml file, a web-fragment.xml file nor annotated with
+     *    {@link javax.servlet.annotation.WebListener}. For example, a
+     *    {@link ServletContextListener} defined in a TLD would not be able to
+     *    use this method.
+     * @since Servlet 3.0 TODO SERVLET3 - Add comments
+     */
+    public void addListener(Class<? extends EventListener> listenerClass);
+
+    /**
+     * @param <T>
+     * @param c
+     * @return TODO
+     * @throws ServletException
+     * @throws UnsupportedOperationException    If called from a
+     *    {@link ServletContextListener#contextInitialized(ServletContextEvent)}
+     *    method of a {@link ServletContextListener} that was not defined in a
+     *    web.xml file, a web-fragment.xml file nor annotated with
+     *    {@link javax.servlet.annotation.WebListener}. For example, a
+     *    {@link ServletContextListener} defined in a TLD would not be able to
+     *    use this method.
+     * @since Servlet 3.0 TODO SERVLET3 - Add comments
+     */
+    public <T extends EventListener> T createListener(Class<T> c)
+            throws ServletException;
+
+    /**
+     * @return TODO
+     * @throws UnsupportedOperationException    If called from a
+     *    {@link ServletContextListener#contextInitialized(ServletContextEvent)}
+     *    method of a {@link ServletContextListener} that was not defined in a
+     *    web.xml file, a web-fragment.xml file nor annotated with
+     *    {@link javax.servlet.annotation.WebListener}. For example, a
+     *    {@link ServletContextListener} defined in a TLD would not be able to
+     *    use this method.
+     * @since Servlet 3.0 TODO SERVLET3 - Add comments
+     */
+    public JspConfigDescriptor getJspConfigDescriptor();
+
+    /**
+     * @return TODO
+     * @throws UnsupportedOperationException    If called from a
+     *    {@link ServletContextListener#contextInitialized(ServletContextEvent)}
+     *    method of a {@link ServletContextListener} that was not defined in a
+     *    web.xml file, a web-fragment.xml file nor annotated with
+     *    {@link javax.servlet.annotation.WebListener}. For example, a
+     *    {@link ServletContextListener} defined in a TLD would not be able to
+     *    use this method.
+     * @throws SecurityException
+     * @since Servlet 3.0 TODO SERVLET3 - Add comments
+     */
+    public ClassLoader getClassLoader();
+
+    /**
+     * @param roleNames
+     * @throws UnsupportedOperationException    If called from a
+     *    {@link ServletContextListener#contextInitialized(ServletContextEvent)}
+     *    method of a {@link ServletContextListener} that was not defined in a
+     *    web.xml file, a web-fragment.xml file nor annotated with
+     *    {@link javax.servlet.annotation.WebListener}. For example, a
+     *    {@link ServletContextListener} defined in a TLD would not be able to
+     *    use this method.
+     * @throws IllegalArgumentException
+     * @throws IllegalStateException
+     * @since Servlet 3.0 TODO SERVLET3 - Add comments
+     */
+    public void declareRoles(String... roleNames);
+
+    /**
+     * Returns the primary name of the virtual host on which this context is
+     * deployed. The name may or may not be a valid host name.
+     */
+    public String getVirtualServerName();
+}
diff --git a/lib/servlet-api/javax/servlet/ServletContextAttributeEvent.java b/lib/servlet-api/javax/servlet/ServletContextAttributeEvent.java
new file mode 100644 (file)
index 0000000..b1bf727
--- /dev/null
@@ -0,0 +1,59 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package javax.servlet;
+
+/**
+ * This is the event class for notifications about changes to the attributes of
+ * the servlet context of a web application.
+ *
+ * @see ServletContextAttributeListener
+ * @since v 2.3
+ */
+public class ServletContextAttributeEvent extends ServletContextEvent {
+    private static final long serialVersionUID = 1L;
+
+    private final String name;
+    private final Object value;
+
+    /**
+     * Construct a ServletContextAttributeEvent from the given context for the
+     * given attribute name and attribute value.
+     */
+    public ServletContextAttributeEvent(ServletContext source, String name,
+            Object value) {
+        super(source);
+        this.name = name;
+        this.value = value;
+    }
+
+    /**
+     * Return the name of the attribute that changed on the ServletContext.
+     */
+    public String getName() {
+        return this.name;
+    }
+
+    /**
+     * Returns the value of the attribute that has been added, removed, or
+     * replaced. If the attribute was added, this is the value of the attribute.
+     * If the attribute was removed, this is the value of the removed attribute.
+     * If the attribute was replaced, this is the old value of the attribute.
+     */
+    public Object getValue() {
+        return this.value;
+    }
+}
diff --git a/lib/servlet-api/javax/servlet/ServletContextAttributeListener.java b/lib/servlet-api/javax/servlet/ServletContextAttributeListener.java
new file mode 100644 (file)
index 0000000..60cd3ab
--- /dev/null
@@ -0,0 +1,49 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package javax.servlet;
+
+import java.util.EventListener;
+
+/**
+ * Implementations of this interface receive notifications of changes to the
+ * attribute list on the servlet context of a web application. To receive
+ * notification events, the implementation class must be configured in the
+ * deployment descriptor for the web application.
+ *
+ * @see ServletContextAttributeEvent
+ * @since v 2.3
+ */
+
+public interface ServletContextAttributeListener extends EventListener {
+    /**
+     * Notification that a new attribute was added to the servlet context.
+     * Called after the attribute is added.
+     */
+    public void attributeAdded(ServletContextAttributeEvent scab);
+
+    /**
+     * Notification that an existing attribute has been removed from the servlet
+     * context. Called after the attribute is removed.
+     */
+    public void attributeRemoved(ServletContextAttributeEvent scab);
+
+    /**
+     * Notification that an attribute on the servlet context has been replaced.
+     * Called after the attribute is replaced.
+     */
+    public void attributeReplaced(ServletContextAttributeEvent scab);
+}
diff --git a/lib/servlet-api/javax/servlet/ServletContextEvent.java b/lib/servlet-api/javax/servlet/ServletContextEvent.java
new file mode 100644 (file)
index 0000000..92a9120
--- /dev/null
@@ -0,0 +1,48 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package javax.servlet;
+
+/**
+ * This is the event class for notifications about changes to the servlet
+ * context of a web application.
+ *
+ * @see ServletContextListener
+ * @since v 2.3
+ */
+public class ServletContextEvent extends java.util.EventObject {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * Construct a ServletContextEvent from the given context.
+     *
+     * @param source
+     *            - the ServletContext that is sending the event.
+     */
+    public ServletContextEvent(ServletContext source) {
+        super(source);
+    }
+
+    /**
+     * Return the ServletContext that changed.
+     *
+     * @return the ServletContext that sent the event.
+     */
+    public ServletContext getServletContext() {
+        return (ServletContext) super.getSource();
+    }
+}
diff --git a/lib/servlet-api/javax/servlet/ServletContextListener.java b/lib/servlet-api/javax/servlet/ServletContextListener.java
new file mode 100644 (file)
index 0000000..7d1b8e6
--- /dev/null
@@ -0,0 +1,46 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package javax.servlet;
+
+import java.util.EventListener;
+
+/**
+ * Implementations of this interface receive notifications about changes to the
+ * servlet context of the web application they are part of. To receive
+ * notification events, the implementation class must be configured in the
+ * deployment descriptor for the web application.
+ *
+ * @see ServletContextEvent
+ * @since v 2.3
+ */
+
+public interface ServletContextListener extends EventListener {
+
+    /**
+     ** Notification that the web application initialization process is starting.
+     * All ServletContextListeners are notified of context initialization before
+     * any filter or servlet in the web application is initialized.
+     */
+    public void contextInitialized(ServletContextEvent sce);
+
+    /**
+     ** Notification that the servlet context is about to be shut down. All
+     * servlets and filters have been destroy()ed before any
+     * ServletContextListeners are notified of context destruction.
+     */
+    public void contextDestroyed(ServletContextEvent sce);
+}
diff --git a/lib/servlet-api/javax/servlet/ServletException.java b/lib/servlet-api/javax/servlet/ServletException.java
new file mode 100644 (file)
index 0000000..96e3e5c
--- /dev/null
@@ -0,0 +1,91 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package javax.servlet;
+
+/**
+ * Defines a general exception a servlet can throw when it encounters
+ * difficulty.
+ */
+public class ServletException extends Exception {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * Constructs a new servlet exception.
+     */
+    public ServletException() {
+        super();
+    }
+
+    /**
+     * Constructs a new servlet exception with the specified message. The
+     * message can be written to the server log and/or displayed for the user.
+     *
+     * @param message
+     *            a <code>String</code> specifying the text of the exception
+     *            message
+     */
+    public ServletException(String message) {
+        super(message);
+    }
+
+    /**
+     * Constructs a new servlet exception when the servlet needs to throw an
+     * exception and include a message about the "root cause" exception that
+     * interfered with its normal operation, including a description message.
+     *
+     * @param message
+     *            a <code>String</code> containing the text of the exception
+     *            message
+     * @param rootCause
+     *            the <code>Throwable</code> exception that interfered with the
+     *            servlet's normal operation, making this servlet exception
+     *            necessary
+     */
+    public ServletException(String message, Throwable rootCause) {
+        super(message, rootCause);
+    }
+
+    /**
+     * Constructs a new servlet exception when the servlet needs to throw an
+     * exception and include a message about the "root cause" exception that
+     * interfered with its normal operation. The exception's message is based on
+     * the localized message of the underlying exception.
+     * <p>
+     * This method calls the <code>getLocalizedMessage</code> method on the
+     * <code>Throwable</code> exception to get a localized exception message.
+     * When subclassing <code>ServletException</code>, this method can be
+     * overridden to create an exception message designed for a specific locale.
+     *
+     * @param rootCause
+     *            the <code>Throwable</code> exception that interfered with the
+     *            servlet's normal operation, making the servlet exception
+     *            necessary
+     */
+    public ServletException(Throwable rootCause) {
+        super(rootCause);
+    }
+
+    /**
+     * Returns the exception that caused this servlet exception.
+     *
+     * @return the <code>Throwable</code> that caused this servlet exception
+     */
+    public Throwable getRootCause() {
+        return getCause();
+    }
+}
diff --git a/lib/servlet-api/javax/servlet/ServletInputStream.java b/lib/servlet-api/javax/servlet/ServletInputStream.java
new file mode 100644 (file)
index 0000000..d116e32
--- /dev/null
@@ -0,0 +1,117 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package javax.servlet;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * Provides an input stream for reading binary data from a client request,
+ * including an efficient <code>readLine</code> method for reading data one line
+ * at a time. With some protocols, such as HTTP POST and PUT, a
+ * <code>ServletInputStream</code> object can be used to read data sent from the
+ * client.
+ * <p>
+ * A <code>ServletInputStream</code> object is normally retrieved via the
+ * {@link ServletRequest#getInputStream} method.
+ * <p>
+ * This is an abstract class that a servlet container implements. Subclasses of
+ * this class must implement the <code>java.io.InputStream.read()</code> method.
+ *
+ * @see ServletRequest
+ */
+public abstract class ServletInputStream extends InputStream {
+
+    /**
+     * Does nothing, because this is an abstract class.
+     */
+    protected ServletInputStream() {
+        // NOOP
+    }
+
+    /**
+     * Reads the input stream, one line at a time. Starting at an offset, reads
+     * bytes into an array, until it reads a certain number of bytes or reaches
+     * a newline character, which it reads into the array as well.
+     * <p>
+     * This method returns -1 if it reaches the end of the input stream before
+     * reading the maximum number of bytes.
+     *
+     * @param b
+     *            an array of bytes into which data is read
+     * @param off
+     *            an integer specifying the character at which this method
+     *            begins reading
+     * @param len
+     *            an integer specifying the maximum number of bytes to read
+     * @return an integer specifying the actual number of bytes read, or -1 if
+     *         the end of the stream is reached
+     * @exception IOException
+     *                if an input or output exception has occurred
+     */
+    public int readLine(byte[] b, int off, int len) throws IOException {
+
+        if (len <= 0) {
+            return 0;
+        }
+        int count = 0, c;
+
+        while ((c = read()) != -1) {
+            b[off++] = (byte) c;
+            count++;
+            if (c == '\n' || count == len) {
+                break;
+            }
+        }
+        return count > 0 ? count : -1;
+    }
+
+    /**
+     * Returns <code>true</code> if all the data has been read from the stream,
+     * else <code>false</code>.
+     *
+     * @since Servlet 3.1
+     */
+    public abstract boolean isFinished();
+
+    /**
+     * Returns <code>true</code> if data can be read without blocking, else
+     * <code>false</code>. If this method is called and returns false, the
+     * container will invoke {@link ReadListener#onDataAvailable()} when data is
+     * available.
+     *
+     * @since Servlet 3.1
+     */
+    public abstract boolean isReady();
+
+    /**
+     * Sets the {@link ReadListener} for this {@link ServletInputStream} and
+     * thereby switches to non-blocking IO. It is only valid to switch to
+     * non-blocking IO within async processing or HTTP upgrade processing.
+     *
+     * @param listener  The non-blocking IO read listener
+     *
+     * @throws IllegalStateException    If this method is called if neither
+     *                                  async nor HTTP upgrade is in progress or
+     *                                  if the {@link ReadListener} has already
+     *                                  been set
+     * @throws NullPointerException     If listener is null
+     *
+     * @since Servlet 3.1
+     */
+    public abstract void setReadListener(ReadListener listener);
+}
diff --git a/lib/servlet-api/javax/servlet/ServletOutputStream.java b/lib/servlet-api/javax/servlet/ServletOutputStream.java
new file mode 100644 (file)
index 0000000..3498e2d
--- /dev/null
@@ -0,0 +1,302 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package javax.servlet;
+
+import java.io.CharConversionException;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.text.MessageFormat;
+import java.util.ResourceBundle;
+
+/**
+ * Provides an output stream for sending binary data to the client. A
+ * <code>ServletOutputStream</code> object is normally retrieved via the
+ * {@link ServletResponse#getOutputStream} method.
+ * <p>
+ * This is an abstract class that the servlet container implements. Subclasses
+ * of this class must implement the <code>java.io.OutputStream.write(int)</code>
+ * method.
+ *
+ * @see ServletResponse
+ */
+public abstract class ServletOutputStream extends OutputStream {
+
+    private static final String LSTRING_FILE = "javax.servlet.LocalStrings";
+    private static final ResourceBundle lStrings = ResourceBundle.getBundle(LSTRING_FILE);
+
+    /**
+     * Does nothing, because this is an abstract class.
+     */
+    protected ServletOutputStream() {
+        // NOOP
+    }
+
+    /**
+     * Writes a <code>String</code> to the client, without a carriage
+     * return-line feed (CRLF) character at the end.
+     *
+     * @param s
+     *            the <code>String</code> to send to the client
+     * @exception IOException
+     *                if an input or output exception occurred
+     */
+    public void print(String s) throws IOException {
+        if (s == null)
+            s = "null";
+        int len = s.length();
+        for (int i = 0; i < len; i++) {
+            char c = s.charAt(i);
+
+            //
+            // XXX NOTE: This is clearly incorrect for many strings,
+            // but is the only consistent approach within the current
+            // servlet framework. It must suffice until servlet output
+            // streams properly encode their output.
+            //
+            if ((c & 0xff00) != 0) { // high order byte must be zero
+                String errMsg = lStrings.getString("err.not_iso8859_1");
+                Object[] errArgs = new Object[1];
+                errArgs[0] = Character.valueOf(c);
+                errMsg = MessageFormat.format(errMsg, errArgs);
+                throw new CharConversionException(errMsg);
+            }
+            write(c);
+        }
+    }
+
+    /**
+     * Writes a <code>boolean</code> value to the client, with no carriage
+     * return-line feed (CRLF) character at the end.
+     *
+     * @param b
+     *            the <code>boolean</code> value to send to the client
+     * @exception IOException
+     *                if an input or output exception occurred
+     */
+    public void print(boolean b) throws IOException {
+        String msg;
+        if (b) {
+            msg = lStrings.getString("value.true");
+        } else {
+            msg = lStrings.getString("value.false");
+        }
+        print(msg);
+    }
+
+    /**
+     * Writes a character to the client, with no carriage return-line feed
+     * (CRLF) at the end.
+     *
+     * @param c
+     *            the character to send to the client
+     * @exception IOException
+     *                if an input or output exception occurred
+     */
+    public void print(char c) throws IOException {
+        print(String.valueOf(c));
+    }
+
+    /**
+     * Writes an int to the client, with no carriage return-line feed (CRLF) at
+     * the end.
+     *
+     * @param i
+     *            the int to send to the client
+     * @exception IOException
+     *                if an input or output exception occurred
+     */
+    public void print(int i) throws IOException {
+        print(String.valueOf(i));
+    }
+
+    /**
+     * Writes a <code>long</code> value to the client, with no carriage
+     * return-line feed (CRLF) at the end.
+     *
+     * @param l
+     *            the <code>long</code> value to send to the client
+     * @exception IOException
+     *                if an input or output exception occurred
+     */
+    public void print(long l) throws IOException {
+        print(String.valueOf(l));
+    }
+
+    /**
+     * Writes a <code>float</code> value to the client, with no carriage
+     * return-line feed (CRLF) at the end.
+     *
+     * @param f
+     *            the <code>float</code> value to send to the client
+     * @exception IOException
+     *                if an input or output exception occurred
+     */
+    public void print(float f) throws IOException {
+        print(String.valueOf(f));
+    }
+
+    /**
+     * Writes a <code>double</code> value to the client, with no carriage
+     * return-line feed (CRLF) at the end.
+     *
+     * @param d
+     *            the <code>double</code> value to send to the client
+     * @exception IOException
+     *                if an input or output exception occurred
+     */
+    public void print(double d) throws IOException {
+        print(String.valueOf(d));
+    }
+
+    /**
+     * Writes a carriage return-line feed (CRLF) to the client.
+     *
+     * @exception IOException
+     *                if an input or output exception occurred
+     */
+    public void println() throws IOException {
+        print("\r\n");
+    }
+
+    /**
+     * Writes a <code>String</code> to the client, followed by a carriage
+     * return-line feed (CRLF).
+     *
+     * @param s
+     *            the <code>String</code> to write to the client
+     * @exception IOException
+     *                if an input or output exception occurred
+     */
+    public void println(String s) throws IOException {
+        print(s);
+        println();
+    }
+
+    /**
+     * Writes a <code>boolean</code> value to the client, followed by a carriage
+     * return-line feed (CRLF).
+     *
+     * @param b
+     *            the <code>boolean</code> value to write to the client
+     * @exception IOException
+     *                if an input or output exception occurred
+     */
+    public void println(boolean b) throws IOException {
+        print(b);
+        println();
+    }
+
+    /**
+     * Writes a character to the client, followed by a carriage return-line feed
+     * (CRLF).
+     *
+     * @param c
+     *            the character to write to the client
+     * @exception IOException
+     *                if an input or output exception occurred
+     */
+    public void println(char c) throws IOException {
+        print(c);
+        println();
+    }
+
+    /**
+     * Writes an int to the client, followed by a carriage return-line feed
+     * (CRLF) character.
+     *
+     * @param i
+     *            the int to write to the client
+     * @exception IOException
+     *                if an input or output exception occurred
+     */
+    public void println(int i) throws IOException {
+        print(i);
+        println();
+    }
+
+    /**
+     * Writes a <code>long</code> value to the client, followed by a carriage
+     * return-line feed (CRLF).
+     *
+     * @param l
+     *            the <code>long</code> value to write to the client
+     * @exception IOException
+     *                if an input or output exception occurred
+     */
+    public void println(long l) throws IOException {
+        print(l);
+        println();
+    }
+
+    /**
+     * Writes a <code>float</code> value to the client, followed by a carriage
+     * return-line feed (CRLF).
+     *
+     * @param f
+     *            the <code>float</code> value to write to the client
+     * @exception IOException
+     *                if an input or output exception occurred
+     */
+    public void println(float f) throws IOException {
+        print(f);
+        println();
+    }
+
+    /**
+     * Writes a <code>double</code> value to the client, followed by a carriage
+     * return-line feed (CRLF).
+     *
+     * @param d
+     *            the <code>double</code> value to write to the client
+     * @exception IOException
+     *                if an input or output exception occurred
+     */
+    public void println(double d) throws IOException {
+        print(d);
+        println();
+    }
+
+    /**
+     * Checks if a non-blocking write will succeed. If this returns
+     * <code>false</code>, it will cause a callback to
+     * {@link WriteListener#onWritePossible()} when the buffer has emptied. If
+     * this method returns <code>false</code> no further data must be written
+     * until the contain calls {@link WriteListener#onWritePossible()}.
+     *
+     * @return <code>true</code> if data can be written, else <code>false</code>
+     *
+     * @since Servlet 3.1
+     */
+    public abstract boolean isReady();
+
+    /**
+     * Sets the {@link WriteListener} for this {@link ServletOutputStream} and
+     * thereby switches to non-blocking IO. It is only valid to switch to
+     * non-blocking IO within async processing or HTTP upgrade processing.
+     *
+     * @param listener  The non-blocking IO write listener
+     *
+     * @throws IllegalStateException    If this method is called if neither
+     *                                  async nor HTTP upgrade is in progress or
+     *                                  if the {@link WriteListener} has already
+     *                                  been set
+     * @throws NullPointerException     If listener is null
+     *
+     * @since Servlet 3.1
+     */
+    public abstract void setWriteListener(javax.servlet.WriteListener listener);
+}
diff --git a/lib/servlet-api/javax/servlet/ServletRegistration.java b/lib/servlet-api/javax/servlet/ServletRegistration.java
new file mode 100644 (file)
index 0000000..a0b5b34
--- /dev/null
@@ -0,0 +1,49 @@
+/*
+* Licensed to the Apache Software Foundation (ASF) under one or more
+* contributor license agreements.  See the NOTICE file distributed with
+* this work for additional information regarding copyright ownership.
+* The ASF licenses this file to You under the Apache License, Version 2.0
+* (the "License"); you may not use this file except in compliance with
+* the License.  You may obtain a copy of the License at
+*
+*     http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+package javax.servlet;
+
+import java.util.Collection;
+import java.util.Set;
+
+/**
+ * TODO SERVLET3 - Add comments
+ * @since Servlet 3.0
+ */
+public interface ServletRegistration extends Registration {
+
+    /**
+     *
+     * @param urlPatterns
+     * @return TODO
+     * @throws IllegalArgumentException if urlPattern is null or empty
+     * @throws IllegalStateException if the associated ServletContext has
+     *                                  already been initialised
+     */
+    public Set<String> addMapping(String... urlPatterns);
+
+    public Collection<String> getMappings();
+
+    public String getRunAsRole();
+
+    public static interface Dynamic
+    extends ServletRegistration, Registration.Dynamic {
+        public void setLoadOnStartup(int loadOnStartup);
+        public void setMultipartConfig(MultipartConfigElement multipartConfig);
+        public void setRunAsRole(String roleName);
+        public Set<String> setServletSecurity(ServletSecurityElement constraint);
+    }
+}
diff --git a/lib/servlet-api/javax/servlet/ServletRequest.java b/lib/servlet-api/javax/servlet/ServletRequest.java
new file mode 100644 (file)
index 0000000..f32dff3
--- /dev/null
@@ -0,0 +1,492 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package javax.servlet;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.util.Enumeration;
+import java.util.Locale;
+import java.util.Map;
+
+/**
+ * Defines an object to provide client request information to a servlet. The
+ * servlet container creates a <code>ServletRequest</code> object and passes it
+ * as an argument to the servlet's <code>service</code> method.
+ * <p>
+ * A <code>ServletRequest</code> object provides data including parameter name
+ * and values, attributes, and an input stream. Interfaces that extend
+ * <code>ServletRequest</code> can provide additional protocol-specific data
+ * (for example, HTTP data is provided by
+ * {@link javax.servlet.http.HttpServletRequest}.
+ *
+ * @see javax.servlet.http.HttpServletRequest
+ */
+public interface ServletRequest {
+
+    /**
+     * Returns the value of the named attribute as an <code>Object</code>, or
+     * <code>null</code> if no attribute of the given name exists.
+     * <p>
+     * Attributes can be set two ways. The servlet container may set attributes
+     * to make available custom information about a request. For example, for
+     * requests made using HTTPS, the attribute
+     * <code>javax.servlet.request.X509Certificate</code> can be used to
+     * retrieve information on the certificate of the client. Attributes can
+     * also be set programatically using {@link ServletRequest#setAttribute}.
+     * This allows information to be embedded into a request before a
+     * {@link RequestDispatcher} call.
+     * <p>
+     * Attribute names should follow the same conventions as package names.
+     * Names beginning with <code>java.*</code> and <code>javax.*</code> are
+     * reserved for use by the Servlet specification. Names beginning with
+     * <code>sun.*</code>, <code>com.sun.*</code>, <code>oracle.*</code> and
+     * <code>com.oracle.*</code>) are reserved for use by Oracle Corporation.
+     *
+     * @param name
+     *            a <code>String</code> specifying the name of the attribute
+     * @return an <code>Object</code> containing the value of the attribute, or
+     *         <code>null</code> if the attribute does not exist
+     */
+    public Object getAttribute(String name);
+
+    /**
+     * Returns an <code>Enumeration</code> containing the names of the
+     * attributes available to this request. This method returns an empty
+     * <code>Enumeration</code> if the request has no attributes available to
+     * it.
+     *
+     * @return an <code>Enumeration</code> of strings containing the names of the
+     *         request's attributes
+     */
+    public Enumeration<String> getAttributeNames();
+
+    /**
+     * Returns the name of the character encoding used in the body of this
+     * request. This method returns <code>null</code> if the request does not
+     * specify a character encoding
+     *
+     * @return a <code>String</code> containing the name of the character
+     *         encoding, or <code>null</code> if the request does not specify a
+     *         character encoding
+     */
+    public String getCharacterEncoding();
+
+    /**
+     * Overrides the name of the character encoding used in the body of this
+     * request. This method must be called prior to reading request parameters
+     * or reading input using getReader().
+     *
+     * @param env
+     *            a <code>String</code> containing the name of the character
+     *            encoding.
+     * @throws java.io.UnsupportedEncodingException
+     *             if this is not a valid encoding
+     */
+    public void setCharacterEncoding(String env)
+            throws java.io.UnsupportedEncodingException;
+
+    /**
+     * Returns the length, in bytes, of the request body and made available by
+     * the input stream, or -1 if the length is not known. For HTTP servlets,
+     * same as the value of the CGI variable CONTENT_LENGTH.
+     *
+     * @return an integer containing the length of the request body or -1 if the
+     *         length is not known or is greater than {@link Integer#MAX_VALUE}
+     */
+    public int getContentLength();
+
+    /**
+     * Returns the length, in bytes, of the request body and made available by
+     * the input stream, or -1 if the length is not known. For HTTP servlets,
+     * same as the value of the CGI variable CONTENT_LENGTH.
+     *
+     * @return a long integer containing the length of the request body or -1 if
+     *         the length is not known
+     */
+    public long getContentLengthLong();
+
+    /**
+     * Returns the MIME type of the body of the request, or <code>null</code> if
+     * the type is not known. For HTTP servlets, same as the value of the CGI
+     * variable CONTENT_TYPE.
+     *
+     * @return a <code>String</code> containing the name of the MIME type of the
+     *         request, or null if the type is not known
+     */
+    public String getContentType();
+
+    /**
+     * Retrieves the body of the request as binary data using a
+     * {@link ServletInputStream}. Either this method or {@link #getReader} may
+     * be called to read the body, not both.
+     *
+     * @return a {@link ServletInputStream} object containing the body of the
+     *         request
+     * @exception IllegalStateException
+     *                if the {@link #getReader} method has already been called
+     *                for this request
+     * @exception IOException
+     *                if an input or output exception occurred
+     */
+    public ServletInputStream getInputStream() throws IOException;
+
+    /**
+     * Returns the value of a request parameter as a <code>String</code>, or
+     * <code>null</code> if the parameter does not exist. Request parameters are
+     * extra information sent with the request. For HTTP servlets, parameters
+     * are contained in the query string or posted form data.
+     * <p>
+     * You should only use this method when you are sure the parameter has only
+     * one value. If the parameter might have more than one value, use
+     * {@link #getParameterValues}.
+     * <p>
+     * If you use this method with a multivalued parameter, the value returned
+     * is equal to the first value in the array returned by
+     * <code>getParameterValues</code>.
+     * <p>
+     * If the parameter data was sent in the request body, such as occurs with
+     * an HTTP POST request, then reading the body directly via
+     * {@link #getInputStream} or {@link #getReader} can interfere with the
+     * execution of this method.
+     *
+     * @param name
+     *            a <code>String</code> specifying the name of the parameter
+     * @return a <code>String</code> representing the single value of the
+     *         parameter
+     * @see #getParameterValues
+     */
+    public String getParameter(String name);
+
+    /**
+     * Returns an <code>Enumeration</code> of <code>String</code> objects
+     * containing the names of the parameters contained in this request. If the
+     * request has no parameters, the method returns an empty
+     * <code>Enumeration</code>.
+     *
+     * @return an <code>Enumeration</code> of <code>String</code> objects, each
+     *         <code>String</code> containing the name of a request parameter;
+     *         or an empty <code>Enumeration</code> if the request has no
+     *         parameters
+     */
+    public Enumeration<String> getParameterNames();
+
+    /**
+     * Returns an array of <code>String</code> objects containing all of the
+     * values the given request parameter has, or <code>null</code> if the
+     * parameter does not exist.
+     * <p>
+     * If the parameter has a single value, the array has a length of 1.
+     *
+     * @param name
+     *            a <code>String</code> containing the name of the parameter
+     *            whose value is requested
+     * @return an array of <code>String</code> objects containing the parameter's
+     *         values
+     * @see #getParameter
+     */
+    public String[] getParameterValues(String name);
+
+    /**
+     * Returns a java.util.Map of the parameters of this request. Request
+     * parameters are extra information sent with the request. For HTTP
+     * servlets, parameters are contained in the query string or posted form
+     * data.
+     *
+     * @return an immutable java.util.Map containing parameter names as keys and
+     *         parameter values as map values. The keys in the parameter map are
+     *         of type String. The values in the parameter map are of type
+     *         String array.
+     */
+    public Map<String, String[]> getParameterMap();
+
+    /**
+     * Returns the name and version of the protocol the request uses in the form
+     * <i>protocol/majorVersion.minorVersion</i>, for example, HTTP/1.1. For
+     * HTTP servlets, the value returned is the same as the value of the CGI
+     * variable <code>SERVER_PROTOCOL</code>.
+     *
+     * @return a <code>String</code> containing the protocol name and version
+     *         number
+     */
+    public String getProtocol();
+
+    /**
+     * Returns the name of the scheme used to make this request, for example,
+     * <code>http</code>, <code>https</code>, or <code>ftp</code>. Different
+     * schemes have different rules for constructing URLs, as noted in RFC 1738.
+     *
+     * @return a <code>String</code> containing the name of the scheme used to
+     *         make this request
+     */
+    public String getScheme();
+
+    /**
+     * Returns the host name of the server to which the request was sent. It is
+     * the value of the part before ":" in the <code>Host</code> header value,
+     * if any, or the resolved server name, or the server IP address.
+     *
+     * @return a <code>String</code> containing the name of the server
+     */
+    public String getServerName();
+
+    /**
+     * Returns the port number to which the request was sent. It is the value of
+     * the part after ":" in the <code>Host</code> header value, if any, or the
+     * server port where the client connection was accepted on.
+     *
+     * @return an integer specifying the port number
+     */
+    public int getServerPort();
+
+    /**
+     * Retrieves the body of the request as character data using a
+     * <code>BufferedReader</code>. The reader translates the character data
+     * according to the character encoding used on the body. Either this method
+     * or {@link #getInputStream} may be called to read the body, not both.
+     *
+     * @return a <code>BufferedReader</code> containing the body of the request
+     * @exception java.io.UnsupportedEncodingException
+     *                if the character set encoding used is not supported and
+     *                the text cannot be decoded
+     * @exception IllegalStateException
+     *                if {@link #getInputStream} method has been called on this
+     *                request
+     * @exception IOException
+     *                if an input or output exception occurred
+     * @see #getInputStream
+     */
+    public BufferedReader getReader() throws IOException;
+
+    /**
+     * Returns the Internet Protocol (IP) address of the client or last proxy
+     * that sent the request. For HTTP servlets, same as the value of the CGI
+     * variable <code>REMOTE_ADDR</code>.
+     *
+     * @return a <code>String</code> containing the IP address of the client
+     *         that sent the request
+     */
+    public String getRemoteAddr();
+
+    /**
+     * Returns the fully qualified name of the client or the last proxy that
+     * sent the request. If the engine cannot or chooses not to resolve the
+     * hostname (to improve performance), this method returns the dotted-string
+     * form of the IP address. For HTTP servlets, same as the value of the CGI
+     * variable <code>REMOTE_HOST</code>.
+     *
+     * @return a <code>String</code> containing the fully qualified name of the
+     *         client
+     */
+    public String getRemoteHost();
+
+    /**
+     * Stores an attribute in this request. Attributes are reset between
+     * requests. This method is most often used in conjunction with
+     * {@link RequestDispatcher}.
+     * <p>
+     * Attribute names should follow the same conventions as package names.
+     * Names beginning with <code>java.*</code> and <code>javax.*</code> are
+     * reserved for use by the Servlet specification. Names beginning with
+     * <code>sun.*</code>, <code>com.sun.*</code>, <code>oracle.*</code> and
+     * <code>com.oracle.*</code>) are reserved for use by Oracle Corporation.
+     * <br>
+     * If the object passed in is null, the effect is the same as calling
+     * {@link #removeAttribute}. <br>
+     * It is warned that when the request is dispatched from the servlet resides
+     * in a different web application by <code>RequestDispatcher</code>, the
+     * object set by this method may not be correctly retrieved in the caller
+     * servlet.
+     *
+     * @param name
+     *            a <code>String</code> specifying the name of the attribute
+     * @param o
+     *            the <code>Object</code> to be stored
+     */
+    public void setAttribute(String name, Object o);
+
+    /**
+     * Removes an attribute from this request. This method is not generally
+     * needed as attributes only persist as long as the request is being
+     * handled.
+     * <p>
+     * Attribute names should follow the same conventions as package names.
+     * Names beginning with <code>java.*</code> and <code>javax.*</code> are
+     * reserved for use by the Servlet specification. Names beginning with
+     * <code>sun.*</code>, <code>com.sun.*</code>, <code>oracle.*</code> and
+     * <code>com.oracle.*</code>) are reserved for use by Oracle Corporation.
+     *
+     * @param name
+     *            a <code>String</code> specifying the name of the attribute to
+     *            remove
+     */
+    public void removeAttribute(String name);
+
+    /**
+     * Returns the preferred <code>Locale</code> that the client will accept
+     * content in, based on the Accept-Language header. If the client request
+     * doesn't provide an Accept-Language header, this method returns the
+     * default locale for the server.
+     *
+     * @return the preferred <code>Locale</code> for the client
+     */
+    public Locale getLocale();
+
+    /**
+     * Returns an <code>Enumeration</code> of <code>Locale</code> objects
+     * indicating, in decreasing order starting with the preferred locale, the
+     * locales that are acceptable to the client based on the Accept-Language
+     * header. If the client request doesn't provide an Accept-Language header,
+     * this method returns an <code>Enumeration</code> containing one
+     * <code>Locale</code>, the default locale for the server.
+     *
+     * @return an <code>Enumeration</code> of preferred <code>Locale</code>
+     *         objects for the client
+     */
+    public Enumeration<Locale> getLocales();
+
+    /**
+     * Returns a boolean indicating whether this request was made using a secure
+     * channel, such as HTTPS.
+     *
+     * @return a boolean indicating if the request was made using a secure
+     *         channel
+     */
+    public boolean isSecure();
+
+    /**
+     * Returns a {@link RequestDispatcher} object that acts as a wrapper for the
+     * resource located at the given path. A <code>RequestDispatcher</code>
+     * object can be used to forward a request to the resource or to include the
+     * resource in a response. The resource can be dynamic or static.
+     * <p>
+     * The pathname specified may be relative, although it cannot extend outside
+     * the current servlet context. If the path begins with a "/" it is
+     * interpreted as relative to the current context root. This method returns
+     * <code>null</code> if the servlet container cannot return a
+     * <code>RequestDispatcher</code>.
+     * <p>
+     * The difference between this method and
+     * {@link ServletContext#getRequestDispatcher} is that this method can take
+     * a relative path.
+     *
+     * @param path
+     *            a <code>String</code> specifying the pathname to the resource.
+     *            If it is relative, it must be relative against the current
+     *            servlet.
+     * @return a <code>RequestDispatcher</code> object that acts as a wrapper for
+     *         the resource at the specified path, or <code>null</code> if the
+     *         servlet container cannot return a <code>RequestDispatcher</code>
+     * @see RequestDispatcher
+     * @see ServletContext#getRequestDispatcher
+     */
+    public RequestDispatcher getRequestDispatcher(String path);
+
+    /**
+     * @deprecated As of Version 2.1 of the Java Servlet API, use
+     *             {@link ServletContext#getRealPath} instead.
+     */
+    @SuppressWarnings("dep-ann")
+    // Spec API does not use @Deprecated
+    public String getRealPath(String path);
+
+    /**
+     * Returns the Internet Protocol (IP) source port of the client or last
+     * proxy that sent the request.
+     *
+     * @return an integer specifying the port number
+     * @since 2.4
+     */
+    public int getRemotePort();
+
+    /**
+     * Returns the host name of the Internet Protocol (IP) interface on which
+     * the request was received.
+     *
+     * @return a <code>String</code> containing the host name of the IP on which
+     *         the request was received.
+     * @since 2.4
+     */
+    public String getLocalName();
+
+    /**
+     * Returns the Internet Protocol (IP) address of the interface on which the
+     * request was received.
+     *
+     * @return a <code>String</code> containing the IP address on which the
+     *         request was received.
+     * @since 2.4
+     */
+    public String getLocalAddr();
+
+    /**
+     * Returns the Internet Protocol (IP) port number of the interface on which
+     * the request was received.
+     *
+     * @return an integer specifying the port number
+     * @since 2.4
+     */
+    public int getLocalPort();
+
+    /**
+     * @return TODO
+     * @since Servlet 3.0 TODO SERVLET3 - Add comments
+     */
+    public ServletContext getServletContext();
+
+    /**
+     * @return TODO
+     * @throws java.lang.IllegalStateException
+     *             If async is not supported for this request
+     * @since Servlet 3.0 TODO SERVLET3 - Add comments
+     */
+    public AsyncContext startAsync() throws IllegalStateException;
+
+    /**
+     * @param servletRequest
+     * @param servletResponse
+     * @return TODO
+     * @throws java.lang.IllegalStateException
+     * @since Servlet 3.0 TODO SERVLET3 - Add comments
+     */
+    public AsyncContext startAsync(ServletRequest servletRequest,
+            ServletResponse servletResponse) throws IllegalStateException;
+
+    /**
+     * @return TODO
+     * @since Servlet 3.0 TODO SERVLET3 - Add comments
+     */
+    public boolean isAsyncStarted();
+
+    /**
+     * @return TODO
+     * @since Servlet 3.0 TODO SERVLET3 - Add comments
+     */
+    public boolean isAsyncSupported();
+
+    /**
+     * @return TODO
+     * @throws java.lang.IllegalStateException
+     * @since Servlet 3.0 TODO SERVLET3 - Add comments
+     */
+    public AsyncContext getAsyncContext();
+
+    /**
+     * @return TODO
+     * @since Servlet 3.0 TODO SERVLET3 - Add comments
+     */
+    public DispatcherType getDispatcherType();
+}
diff --git a/lib/servlet-api/javax/servlet/ServletRequestAttributeEvent.java b/lib/servlet-api/javax/servlet/ServletRequestAttributeEvent.java
new file mode 100644 (file)
index 0000000..9b25184
--- /dev/null
@@ -0,0 +1,73 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package javax.servlet;
+
+/**
+ * This is the event class for notifications of changes to the attributes of the
+ * servlet request in an application.
+ *
+ * @see ServletRequestAttributeListener
+ * @since Servlet 2.4
+ */
+public class ServletRequestAttributeEvent extends ServletRequestEvent {
+    private static final long serialVersionUID = 1L;
+
+    private final String name;
+    private final Object value;
+
+    /**
+     * Construct a ServletRequestAttributeEvent giving the servlet context of
+     * this web application, the ServletRequest whose attributes are changing
+     * and the name and value of the attribute.
+     *
+     * @param sc
+     *            the ServletContext that is sending the event.
+     * @param request
+     *            the ServletRequest that is sending the event.
+     * @param name
+     *            the name of the request attribute.
+     * @param value
+     *            the value of the request attribute.
+     */
+    public ServletRequestAttributeEvent(ServletContext sc,
+            ServletRequest request, String name, Object value) {
+        super(sc, request);
+        this.name = name;
+        this.value = value;
+    }
+
+    /**
+     * Return the name of the attribute that changed on the ServletRequest.
+     *
+     * @return the name of the changed request attribute
+     */
+    public String getName() {
+        return this.name;
+    }
+
+    /**
+     * Returns the value of the attribute that has been added, removed or
+     * replaced. If the attribute was added, this is the value of the attribute.
+     * If the attribute was removed, this is the value of the removed attribute.
+     * If the attribute was replaced, this is the old value of the attribute.
+     *
+     * @return the value of the changed request attribute
+     */
+    public Object getValue() {
+        return this.value;
+    }
+}
diff --git a/lib/servlet-api/javax/servlet/ServletRequestAttributeListener.java b/lib/servlet-api/javax/servlet/ServletRequestAttributeListener.java
new file mode 100644 (file)
index 0000000..8d65fa1
--- /dev/null
@@ -0,0 +1,50 @@
+/*
+* Licensed to the Apache Software Foundation (ASF) under one or more
+* contributor license agreements.  See the NOTICE file distributed with
+* this work for additional information regarding copyright ownership.
+* The ASF licenses this file to You under the Apache License, Version 2.0
+* (the "License"); you may not use this file except in compliance with
+* the License.  You may obtain a copy of the License at
+*
+*     http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+package javax.servlet;
+
+import java.util.EventListener;
+
+    /**
+     * A ServletRequestAttributeListener can be implemented by the
+     * developer interested in being notified of request attribute
+     * changes. Notifications will be generated while the request
+     * is within the scope of the web application in which the listener
+     * is registered. A request is defined as coming into scope when
+     * it is about to enter the first servlet or filter in each web
+     * application, as going out of scope when it exits the last servlet
+     * or the first filter in the chain.
+     *
+     * @since Servlet 2.4
+     */
+
+public interface ServletRequestAttributeListener extends EventListener {
+    /** Notification that a new attribute was added to the
+     ** servlet request. Called after the attribute is added.
+     */
+    public void attributeAdded(ServletRequestAttributeEvent srae);
+
+    /** Notification that an existing attribute has been removed from the
+     ** servlet request. Called after the attribute is removed.
+     */
+    public void attributeRemoved(ServletRequestAttributeEvent srae);
+
+    /** Notification that an attribute was replaced on the
+     ** servlet request. Called after the attribute is replaced.
+     */
+    public void attributeReplaced(ServletRequestAttributeEvent srae);
+}
+
diff --git a/lib/servlet-api/javax/servlet/ServletRequestEvent.java b/lib/servlet-api/javax/servlet/ServletRequestEvent.java
new file mode 100644 (file)
index 0000000..5d95eab
--- /dev/null
@@ -0,0 +1,58 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package javax.servlet;
+
+/**
+ * Events of this kind indicate lifecycle events for a ServletRequest. The
+ * source of the event is the ServletContext of this web application.
+ *
+ * @see ServletRequestListener
+ * @since Servlet 2.4
+ */
+public class ServletRequestEvent extends java.util.EventObject {
+    private static final long serialVersionUID = 1L;
+
+    private final transient ServletRequest request;
+
+    /**
+     * Construct a ServletRequestEvent for the given ServletContext and
+     * ServletRequest.
+     *
+     * @param sc
+     *            the ServletContext of the web application.
+     * @param request
+     *            the ServletRequest that is sending the event.
+     */
+    public ServletRequestEvent(ServletContext sc, ServletRequest request) {
+        super(sc);
+        this.request = request;
+    }
+
+    /**
+     * Returns the ServletRequest that is changing.
+     */
+    public ServletRequest getServletRequest() {
+        return this.request;
+    }
+
+    /**
+     * Returns the ServletContext of this web application.
+     */
+    public ServletContext getServletContext() {
+        return (ServletContext) super.getSource();
+    }
+}
diff --git a/lib/servlet-api/javax/servlet/ServletRequestListener.java b/lib/servlet-api/javax/servlet/ServletRequestListener.java
new file mode 100644 (file)
index 0000000..4ac7055
--- /dev/null
@@ -0,0 +1,40 @@
+/*
+* Licensed to the Apache Software Foundation (ASF) under one or more
+* contributor license agreements.  See the NOTICE file distributed with
+* this work for additional information regarding copyright ownership.
+* The ASF licenses this file to You under the Apache License, Version 2.0
+* (the "License"); you may not use this file except in compliance with
+* the License.  You may obtain a copy of the License at
+*
+*     http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+package javax.servlet;
+
+import java.util.EventListener;
+
+    /**
+     * A ServletRequestListener can be implemented by the developer
+     * interested in being notified of requests coming in and out of
+     * scope in a web component. A request is defined as coming into
+     * scope when it is about to enter the first servlet or filter
+     * in each web application, as going out of scope when it exits
+     * the last servlet or the first filter in the chain.
+     *
+     * @since Servlet 2.4
+     */
+
+
+public interface ServletRequestListener extends EventListener {
+
+    /** The request is about to go out of scope of the web application. */
+    public void requestDestroyed ( ServletRequestEvent sre );
+
+    /** The request is about to come into scope of the web application. */
+    public void requestInitialized ( ServletRequestEvent sre );
+}
diff --git a/lib/servlet-api/javax/servlet/ServletRequestWrapper.java b/lib/servlet-api/javax/servlet/ServletRequestWrapper.java
new file mode 100644 (file)
index 0000000..308a29f
--- /dev/null
@@ -0,0 +1,458 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package javax.servlet;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.util.Enumeration;
+import java.util.Locale;
+import java.util.Map;
+
+/**
+ * Provides a convenient implementation of the ServletRequest interface that can
+ * be subclassed by developers wishing to adapt the request to a Servlet. This
+ * class implements the Wrapper or Decorator pattern. Methods default to calling
+ * through to the wrapped request object.
+ *
+ * @since v 2.3
+ * @see javax.servlet.ServletRequest
+ */
+public class ServletRequestWrapper implements ServletRequest {
+    private ServletRequest request;
+
+    /**
+     * Creates a ServletRequest adaptor wrapping the given request object.
+     *
+     * @throws java.lang.IllegalArgumentException
+     *             if the request is null
+     */
+    public ServletRequestWrapper(ServletRequest request) {
+        if (request == null) {
+            throw new IllegalArgumentException("Request cannot be null");
+        }
+        this.request = request;
+    }
+
+    /**
+     * Return the wrapped request object.
+     */
+    public ServletRequest getRequest() {
+        return this.request;
+    }
+
+    /**
+     * Sets the request object being wrapped.
+     *
+     * @throws java.lang.IllegalArgumentException
+     *             if the request is null.
+     */
+    public void setRequest(ServletRequest request) {
+        if (request == null) {
+            throw new IllegalArgumentException("Request cannot be null");
+        }
+        this.request = request;
+    }
+
+    /**
+     * The default behavior of this method is to call getAttribute(String name)
+     * on the wrapped request object.
+     */
+    @Override
+    public Object getAttribute(String name) {
+        return this.request.getAttribute(name);
+    }
+
+    /**
+     * The default behavior of this method is to return getAttributeNames() on
+     * the wrapped request object.
+     */
+    @Override
+    public Enumeration<String> getAttributeNames() {
+        return this.request.getAttributeNames();
+    }
+
+    /**
+     * The default behavior of this method is to return getCharacterEncoding()
+     * on the wrapped request object.
+     */
+    @Override
+    public String getCharacterEncoding() {
+        return this.request.getCharacterEncoding();
+    }
+
+    /**
+     * The default behavior of this method is to set the character encoding on
+     * the wrapped request object.
+     */
+    @Override
+    public void setCharacterEncoding(String enc)
+            throws java.io.UnsupportedEncodingException {
+        this.request.setCharacterEncoding(enc);
+    }
+
+    /**
+     * The default behavior of this method is to return getContentLength() on
+     * the wrapped request object.
+     */
+    @Override
+    public int getContentLength() {
+        return this.request.getContentLength();
+    }
+
+    @Override
+    public long getContentLengthLong() {
+        return this.request.getContentLengthLong();
+    }
+
+    /**
+     * The default behavior of this method is to return getContentType() on the
+     * wrapped request object.
+     */
+    @Override
+    public String getContentType() {
+        return this.request.getContentType();
+    }
+
+    /**
+     * The default behavior of this method is to return getInputStream() on the
+     * wrapped request object.
+     */
+    @Override
+    public ServletInputStream getInputStream() throws IOException {
+        return this.request.getInputStream();
+    }
+
+    /**
+     * The default behavior of this method is to return getParameter(String
+     * name) on the wrapped request object.
+     */
+    @Override
+    public String getParameter(String name) {
+        return this.request.getParameter(name);
+    }
+
+    /**
+     * The default behavior of this method is to return getParameterMap() on the
+     * wrapped request object.
+     */
+    @Override
+    public Map<String, String[]> getParameterMap() {
+        return this.request.getParameterMap();
+    }
+
+    /**
+     * The default behavior of this method is to return getParameterNames() on
+     * the wrapped request object.
+     */
+    @Override
+    public Enumeration<String> getParameterNames() {
+        return this.request.getParameterNames();
+    }
+
+    /**
+     * The default behavior of this method is to return
+     * getParameterValues(String name) on the wrapped request object.
+     */
+    @Override
+    public String[] getParameterValues(String name) {
+        return this.request.getParameterValues(name);
+    }
+
+    /**
+     * The default behavior of this method is to return getProtocol() on the
+     * wrapped request object.
+     */
+    @Override
+    public String getProtocol() {
+        return this.request.getProtocol();
+    }
+
+    /**
+     * The default behavior of this method is to return getScheme() on the
+     * wrapped request object.
+     */
+    @Override
+    public String getScheme() {
+        return this.request.getScheme();
+    }
+
+    /**
+     * The default behavior of this method is to return getServerName() on the
+     * wrapped request object.
+     */
+    @Override
+    public String getServerName() {
+        return this.request.getServerName();
+    }
+
+    /**
+     * The default behavior of this method is to return getServerPort() on the
+     * wrapped request object.
+     */
+    @Override
+    public int getServerPort() {
+        return this.request.getServerPort();
+    }
+
+    /**
+     * The default behavior of this method is to return getReader() on the
+     * wrapped request object.
+     */
+    @Override
+    public BufferedReader getReader() throws IOException {
+        return this.request.getReader();
+    }
+
+    /**
+     * The default behavior of this method is to return getRemoteAddr() on the
+     * wrapped request object.
+     */
+    @Override
+    public String getRemoteAddr() {
+        return this.request.getRemoteAddr();
+    }
+
+    /**
+     * The default behavior of this method is to return getRemoteHost() on the
+     * wrapped request object.
+     */
+    @Override
+    public String getRemoteHost() {
+        return this.request.getRemoteHost();
+    }
+
+    /**
+     * The default behavior of this method is to return setAttribute(String
+     * name, Object o) on the wrapped request object.
+     */
+    @Override
+    public void setAttribute(String name, Object o) {
+        this.request.setAttribute(name, o);
+    }
+
+    /**
+     * The default behavior of this method is to call removeAttribute(String
+     * name) on the wrapped request object.
+     */
+    @Override
+    public void removeAttribute(String name) {
+        this.request.removeAttribute(name);
+    }
+
+    /**
+     * The default behavior of this method is to return getLocale() on the
+     * wrapped request object.
+     */
+    @Override
+    public Locale getLocale() {
+        return this.request.getLocale();
+    }
+
+    /**
+     * The default behavior of this method is to return getLocales() on the
+     * wrapped request object.
+     */
+    @Override
+    public Enumeration<Locale> getLocales() {
+        return this.request.getLocales();
+    }
+
+    /**
+     * The default behavior of this method is to return isSecure() on the
+     * wrapped request object.
+     */
+    @Override
+    public boolean isSecure() {
+        return this.request.isSecure();
+    }
+
+    /**
+     * The default behavior of this method is to return
+     * getRequestDispatcher(String path) on the wrapped request object.
+     */
+    @Override
+    public RequestDispatcher getRequestDispatcher(String path) {
+        return this.request.getRequestDispatcher(path);
+    }
+
+    /**
+     * The default behavior of this method is to return getRealPath(String path)
+     * on the wrapped request object.
+     *
+     * @deprecated As of Version 3.0 of the Java Servlet API
+     */
+    @Override
+    @SuppressWarnings("dep-ann")
+    // Spec API does not use @Deprecated
+    public String getRealPath(String path) {
+        return this.request.getRealPath(path);
+    }
+
+    /**
+     * The default behavior of this method is to return getRemotePort() on the
+     * wrapped request object.
+     *
+     * @since 2.4
+     */
+    @Override
+    public int getRemotePort() {
+        return this.request.getRemotePort();
+    }
+
+    /**
+     * The default behavior of this method is to return getLocalName() on the
+     * wrapped request object.
+     *
+     * @since 2.4
+     */
+    @Override
+    public String getLocalName() {
+        return this.request.getLocalName();
+    }
+
+    /**
+     * The default behavior of this method is to return getLocalAddr() on the
+     * wrapped request object.
+     *
+     * @since 2.4
+     */
+    @Override
+    public String getLocalAddr() {
+        return this.request.getLocalAddr();
+    }
+
+    /**
+     * The default behavior of this method is to return getLocalPort() on the
+     * wrapped request object.
+     *
+     * @since 2.4
+     */
+    @Override
+    public int getLocalPort() {
+        return this.request.getLocalPort();
+    }
+
+    /**
+     * The default behavior of this method is to return getServletContext() on
+     * the wrapped request object.
+     *
+     * @since Servlet 3.0
+     */
+    @Override
+    public ServletContext getServletContext() {
+        return request.getServletContext();
+    }
+
+    /**
+     * The default behavior of this method is to return startAsync() on the
+     * wrapped request object.
+     *
+     * @throws java.lang.IllegalStateException
+     * @since Servlet 3.0
+     */
+    @Override
+    public AsyncContext startAsync() throws IllegalStateException {
+        return request.startAsync();
+    }
+
+    /**
+     * The default behavior of this method is to return startAsync(Runnable) on
+     * the wrapped request object.
+     *
+     * @param servletRequest
+     * @param servletResponse
+     * @throws java.lang.IllegalStateException
+     * @since Servlet 3.0
+     */
+    @Override
+    public AsyncContext startAsync(ServletRequest servletRequest,
+            ServletResponse servletResponse) throws IllegalStateException {
+        return request.startAsync(servletRequest, servletResponse);
+    }
+
+    /**
+     * The default behavior of this method is to return isAsyncStarted() on the
+     * wrapped request object.
+     *
+     * @since Servlet 3.0
+     */
+    @Override
+    public boolean isAsyncStarted() {
+        return request.isAsyncStarted();
+    }
+
+    /**
+     * The default behavior of this method is to return isAsyncSupported() on
+     * the wrapped request object.
+     *
+     * @since Servlet 3.0
+     */
+    @Override
+    public boolean isAsyncSupported() {
+        return request.isAsyncSupported();
+    }
+
+    /**
+     * The default behavior of this method is to return getAsyncContext() on the
+     * wrapped request object.
+     *
+     * @since Servlet 3.0
+     */
+    @Override
+    public AsyncContext getAsyncContext() {
+        return request.getAsyncContext();
+    }
+
+    /**
+     * @param wrapped
+     * @since Servlet 3.0 TODO SERVLET3 - Add comments
+     */
+    public boolean isWrapperFor(ServletRequest wrapped) {
+        if (request == wrapped) {
+            return true;
+        }
+        if (request instanceof ServletRequestWrapper) {
+            return ((ServletRequestWrapper) request).isWrapperFor(wrapped);
+        }
+        return false;
+    }
+
+    /**
+     * @param wrappedType
+     * @since Servlet 3.0 TODO SERVLET3 - Add comments
+     */
+    public boolean isWrapperFor(Class<?> wrappedType) {
+        if (wrappedType.isAssignableFrom(request.getClass())) {
+            return true;
+        }
+        if (request instanceof ServletRequestWrapper) {
+            return ((ServletRequestWrapper) request).isWrapperFor(wrappedType);
+        }
+        return false;
+    }
+
+    /**
+     * The default behavior of this method is to call getDispatcherType() on the
+     * wrapped request object.
+     *
+     * @since Servlet 3.0
+     */
+    @Override
+    public DispatcherType getDispatcherType() {
+        return this.request.getDispatcherType();
+    }
+}
diff --git a/lib/servlet-api/javax/servlet/ServletResponse.java b/lib/servlet-api/javax/servlet/ServletResponse.java
new file mode 100644 (file)
index 0000000..f96e24e
--- /dev/null
@@ -0,0 +1,342 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package javax.servlet;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.Locale;
+
+/**
+ * Defines an object to assist a servlet in sending a response to the client.
+ * The servlet container creates a <code>ServletResponse</code> object and
+ * passes it as an argument to the servlet's <code>service</code> method.
+ * <p>
+ * To send binary data in a MIME body response, use the
+ * {@link ServletOutputStream} returned by {@link #getOutputStream}. To send
+ * character data, use the <code>PrintWriter</code> object returned by
+ * {@link #getWriter}. To mix binary and text data, for example, to create a
+ * multipart response, use a <code>ServletOutputStream</code> and manage the
+ * character sections manually.
+ * <p>
+ * The charset for the MIME body response can be specified explicitly using the
+ * {@link #setCharacterEncoding} and {@link #setContentType} methods, or
+ * implicitly using the {@link #setLocale} method. Explicit specifications take
+ * precedence over implicit specifications. If no charset is specified,
+ * ISO-8859-1 will be used. The <code>setCharacterEncoding</code>,
+ * <code>setContentType</code>, or <code>setLocale</code> method must be called
+ * before <code>getWriter</code> and before committing the response for the
+ * character encoding to be used.
+ * <p>
+ * See the Internet RFCs such as <a href="http://www.ietf.org/rfc/rfc2045.txt">
+ * RFC 2045</a> for more information on MIME. Protocols such as SMTP and HTTP
+ * define profiles of MIME, and those standards are still evolving.
+ *
+ * @see ServletOutputStream
+ */
+public interface ServletResponse {
+
+    /**
+     * Returns the name of the character encoding (MIME charset) used for the
+     * body sent in this response. The character encoding may have been
+     * specified explicitly using the {@link #setCharacterEncoding} or
+     * {@link #setContentType} methods, or implicitly using the
+     * {@link #setLocale} method. Explicit specifications take precedence over
+     * implicit specifications. Calls made to these methods after
+     * <code>getWriter</code> has been called or after the response has been
+     * committed have no effect on the character encoding. If no character
+     * encoding has been specified, <code>ISO-8859-1</code> is returned.
+     * <p>
+     * See RFC 2047 (http://www.ietf.org/rfc/rfc2047.txt) for more information
+     * about character encoding and MIME.
+     *
+     * @return a <code>String</code> specifying the name of the character
+     *         encoding, for example, <code>UTF-8</code>
+     */
+    public String getCharacterEncoding();
+
+    /**
+     * Returns the content type used for the MIME body sent in this response.
+     * The content type proper must have been specified using
+     * {@link #setContentType} before the response is committed. If no content
+     * type has been specified, this method returns null. If a content type has
+     * been specified and a character encoding has been explicitly or implicitly
+     * specified as described in {@link #getCharacterEncoding}, the charset
+     * parameter is included in the string returned. If no character encoding
+     * has been specified, the charset parameter is omitted.
+     *
+     * @return a <code>String</code> specifying the content type, for example,
+     *         <code>text/html; charset=UTF-8</code>, or null
+     * @since 2.4
+     */
+    public String getContentType();
+
+    /**
+     * Returns a {@link ServletOutputStream} suitable for writing binary data in
+     * the response. The servlet container does not encode the binary data.
+     * <p>
+     * Calling flush() on the ServletOutputStream commits the response. Either
+     * this method or {@link #getWriter} may be called to write the body, not
+     * both.
+     *
+     * @return a {@link ServletOutputStream} for writing binary data
+     * @exception IllegalStateException
+     *                if the <code>getWriter</code> method has been called on
+     *                this response
+     * @exception IOException
+     *                if an input or output exception occurred
+     * @see #getWriter
+     */
+    public ServletOutputStream getOutputStream() throws IOException;
+
+    /**
+     * Returns a <code>PrintWriter</code> object that can send character text to
+     * the client. The <code>PrintWriter</code> uses the character encoding
+     * returned by {@link #getCharacterEncoding}. If the response's character
+     * encoding has not been specified as described in
+     * <code>getCharacterEncoding</code> (i.e., the method just returns the
+     * default value <code>ISO-8859-1</code>), <code>getWriter</code> updates it
+     * to <code>ISO-8859-1</code>.
+     * <p>
+     * Calling flush() on the <code>PrintWriter</code> commits the response.
+     * <p>
+     * Either this method or {@link #getOutputStream} may be called to write the
+     * body, not both.
+     *
+     * @return a <code>PrintWriter</code> object that can return character data
+     *         to the client
+     * @exception java.io.UnsupportedEncodingException
+     *                if the character encoding returned by
+     *                <code>getCharacterEncoding</code> cannot be used
+     * @exception IllegalStateException
+     *                if the <code>getOutputStream</code> method has already
+     *                been called for this response object
+     * @exception IOException
+     *                if an input or output exception occurred
+     * @see #getOutputStream
+     * @see #setCharacterEncoding
+     */
+    public PrintWriter getWriter() throws IOException;
+
+    /**
+     * Sets the character encoding (MIME charset) of the response being sent to
+     * the client, for example, to UTF-8. If the character encoding has already
+     * been set by {@link #setContentType} or {@link #setLocale}, this method
+     * overrides it. Calling {@link #setContentType} with the
+     * <code>String</code> of <code>text/html</code> and calling this method
+     * with the <code>String</code> of <code>UTF-8</code> is equivalent with
+     * calling <code>setContentType</code> with the <code>String</code> of
+     * <code>text/html; charset=UTF-8</code>.
+     * <p>
+     * This method can be called repeatedly to change the character encoding.
+     * This method has no effect if it is called after <code>getWriter</code>
+     * has been called or after the response has been committed.
+     * <p>
+     * Containers must communicate the character encoding used for the servlet
+     * response's writer to the client if the protocol provides a way for doing
+     * so. In the case of HTTP, the character encoding is communicated as part
+     * of the <code>Content-Type</code> header for text media types. Note that
+     * the character encoding cannot be communicated via HTTP headers if the
+     * servlet does not specify a content type; however, it is still used to
+     * encode text written via the servlet response's writer.
+     *
+     * @param charset
+     *            a String specifying only the character set defined by IANA
+     *            Character Sets
+     *            (http://www.iana.org/assignments/character-sets)
+     * @see #setContentType #setLocale
+     * @since 2.4
+     */
+    public void setCharacterEncoding(String charset);
+
+    /**
+     * Sets the length of the content body in the response In HTTP servlets,
+     * this method sets the HTTP Content-Length header.
+     *
+     * @param len
+     *            an integer specifying the length of the content being returned
+     *            to the client; sets the Content-Length header
+     */
+    public void setContentLength(int len);
+
+    /**
+     * TODO SERVLET 3.1
+     */
+    public void setContentLengthLong(long length);
+
+    /**
+     * Sets the content type of the response being sent to the client, if the
+     * response has not been committed yet. The given content type may include a
+     * character encoding specification, for example,
+     * <code>text/html;charset=UTF-8</code>. The response's character encoding
+     * is only set from the given content type if this method is called before
+     * <code>getWriter</code> is called.
+     * <p>
+     * This method may be called repeatedly to change content type and character
+     * encoding. This method has no effect if called after the response has been
+     * committed. It does not set the response's character encoding if it is
+     * called after <code>getWriter</code> has been called or after the response
+     * has been committed.
+     * <p>
+     * Containers must communicate the content type and the character encoding
+     * used for the servlet response's writer to the client if the protocol
+     * provides a way for doing so. In the case of HTTP, the
+     * <code>Content-Type</code> header is used.
+     *
+     * @param type
+     *            a <code>String</code> specifying the MIME type of the content
+     * @see #setLocale
+     * @see #setCharacterEncoding
+     * @see #getOutputStream
+     * @see #getWriter
+     */
+    public void setContentType(String type);
+
+    /**
+     * Sets the preferred buffer size for the body of the response. The servlet
+     * container will use a buffer at least as large as the size requested. The
+     * actual buffer size used can be found using <code>getBufferSize</code>.
+     * <p>
+     * A larger buffer allows more content to be written before anything is
+     * actually sent, thus providing the servlet with more time to set
+     * appropriate status codes and headers. A smaller buffer decreases server
+     * memory load and allows the client to start receiving data more quickly.
+     * <p>
+     * This method must be called before any response body content is written;
+     * if content has been written or the response object has been committed,
+     * this method throws an <code>IllegalStateException</code>.
+     *
+     * @param size
+     *            the preferred buffer size
+     * @exception IllegalStateException
+     *                if this method is called after content has been written
+     * @see #getBufferSize
+     * @see #flushBuffer
+     * @see #isCommitted
+     * @see #reset
+     */
+    public void setBufferSize(int size);
+
+    /**
+     * Returns the actual buffer size used for the response. If no buffering is
+     * used, this method returns 0.
+     *
+     * @return the actual buffer size used
+     * @see #setBufferSize
+     * @see #flushBuffer
+     * @see #isCommitted
+     * @see #reset
+     */
+    public int getBufferSize();
+
+    /**
+     * Forces any content in the buffer to be written to the client. A call to
+     * this method automatically commits the response, meaning the status code
+     * and headers will be written.
+     *
+     * @see #setBufferSize
+     * @see #getBufferSize
+     * @see #isCommitted
+     * @see #reset
+     */
+    public void flushBuffer() throws IOException;
+
+    /**
+     * Clears the content of the underlying buffer in the response without
+     * clearing headers or status code. If the response has been committed, this
+     * method throws an <code>IllegalStateException</code>.
+     *
+     * @see #setBufferSize
+     * @see #getBufferSize
+     * @see #isCommitted
+     * @see #reset
+     * @since 2.3
+     */
+    public void resetBuffer();
+
+    /**
+     * Returns a boolean indicating if the response has been committed. A
+     * committed response has already had its status code and headers written.
+     *
+     * @return a boolean indicating if the response has been committed
+     * @see #setBufferSize
+     * @see #getBufferSize
+     * @see #flushBuffer
+     * @see #reset
+     */
+    public boolean isCommitted();
+
+    /**
+     * Clears any data that exists in the buffer as well as the status code and
+     * headers. If the response has been committed, this method throws an
+     * <code>IllegalStateException</code>.
+     *
+     * @exception IllegalStateException
+     *                if the response has already been committed
+     * @see #setBufferSize
+     * @see #getBufferSize
+     * @see #flushBuffer
+     * @see #isCommitted
+     */
+    public void reset();
+
+    /**
+     * Sets the locale of the response, if the response has not been committed
+     * yet. It also sets the response's character encoding appropriately for the
+     * locale, if the character encoding has not been explicitly set using
+     * {@link #setContentType} or {@link #setCharacterEncoding},
+     * <code>getWriter</code> hasn't been called yet, and the response hasn't
+     * been committed yet. If the deployment descriptor contains a
+     * <code>locale-encoding-mapping-list</code> element, and that element
+     * provides a mapping for the given locale, that mapping is used. Otherwise,
+     * the mapping from locale to character encoding is container dependent.
+     * <p>
+     * This method may be called repeatedly to change locale and character
+     * encoding. The method has no effect if called after the response has been
+     * committed. It does not set the response's character encoding if it is
+     * called after {@link #setContentType} has been called with a charset
+     * specification, after {@link #setCharacterEncoding} has been called, after
+     * <code>getWriter</code> has been called, or after the response has been
+     * committed.
+     * <p>
+     * Containers must communicate the locale and the character encoding used
+     * for the servlet response's writer to the client if the protocol provides
+     * a way for doing so. In the case of HTTP, the locale is communicated via
+     * the <code>Content-Language</code> header, the character encoding as part
+     * of the <code>Content-Type</code> header for text media types. Note that
+     * the character encoding cannot be communicated via HTTP headers if the
+     * servlet does not specify a content type; however, it is still used to
+     * encode text written via the servlet response's writer.
+     *
+     * @param loc
+     *            the locale of the response
+     * @see #getLocale
+     * @see #setContentType
+     * @see #setCharacterEncoding
+     */
+    public void setLocale(Locale loc);
+
+    /**
+     * Returns the locale specified for this response using the
+     * {@link #setLocale} method. Calls made to <code>setLocale</code> after the
+     * response is committed have no effect. If no locale has been specified,
+     * the container's default locale is returned.
+     *
+     * @see #setLocale
+     */
+    public Locale getLocale();
+
+}
diff --git a/lib/servlet-api/javax/servlet/ServletResponseWrapper.java b/lib/servlet-api/javax/servlet/ServletResponseWrapper.java
new file mode 100644 (file)
index 0000000..9b440dd
--- /dev/null
@@ -0,0 +1,244 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package javax.servlet;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.Locale;
+
+/**
+ * Provides a convenient implementation of the ServletResponse interface that
+ * can be subclassed by developers wishing to adapt the response from a Servlet.
+ * This class implements the Wrapper or Decorator pattern. Methods default to
+ * calling through to the wrapped response object.
+ *
+ * @since v 2.3
+ * @see javax.servlet.ServletResponse
+ */
+public class ServletResponseWrapper implements ServletResponse {
+    private ServletResponse response;
+
+    /**
+     * Creates a ServletResponse adaptor wrapping the given response object.
+     *
+     * @throws java.lang.IllegalArgumentException
+     *             if the response is null.
+     */
+    public ServletResponseWrapper(ServletResponse response) {
+        if (response == null) {
+            throw new IllegalArgumentException("Response cannot be null");
+        }
+        this.response = response;
+    }
+
+    /**
+     * Return the wrapped ServletResponse object.
+     */
+    public ServletResponse getResponse() {
+        return this.response;
+    }
+
+    /**
+     * Sets the response being wrapped.
+     *
+     * @throws java.lang.IllegalArgumentException
+     *             if the response is null.
+     */
+    public void setResponse(ServletResponse response) {
+        if (response == null) {
+            throw new IllegalArgumentException("Response cannot be null");
+        }
+        this.response = response;
+    }
+
+    /**
+     * The default behavior of this method is to call
+     * setCharacterEncoding(String charset) on the wrapped response object.
+     *
+     * @since 2.4
+     */
+    @Override
+    public void setCharacterEncoding(String charset) {
+        this.response.setCharacterEncoding(charset);
+    }
+
+    /**
+     * The default behavior of this method is to return getCharacterEncoding()
+     * on the wrapped response object.
+     */
+    @Override
+    public String getCharacterEncoding() {
+        return this.response.getCharacterEncoding();
+    }
+
+    /**
+     * The default behavior of this method is to return getOutputStream() on the
+     * wrapped response object.
+     */
+    @Override
+    public ServletOutputStream getOutputStream() throws IOException {
+        return this.response.getOutputStream();
+    }
+
+    /**
+     * The default behavior of this method is to return getWriter() on the
+     * wrapped response object.
+     */
+    @Override
+    public PrintWriter getWriter() throws IOException {
+        return this.response.getWriter();
+    }
+
+    /**
+     * The default behavior of this method is to call setContentLength(int len)
+     * on the wrapped response object.
+     */
+    @Override
+    public void setContentLength(int len) {
+        this.response.setContentLength(len);
+    }
+
+    /**
+     * The default behavior of this method is to call setContentLength(long len)
+     * on the wrapped response object.
+     */
+    @Override
+    public void setContentLengthLong(long length) {
+        this.response.setContentLengthLong(length);
+    }
+
+    /**
+     * The default behavior of this method is to call setContentType(String
+     * type) on the wrapped response object.
+     */
+    @Override
+    public void setContentType(String type) {
+        this.response.setContentType(type);
+    }
+
+    /**
+     * The default behavior of this method is to return getContentType() on the
+     * wrapped response object.
+     *
+     * @since 2.4
+     */
+    @Override
+    public String getContentType() {
+        return this.response.getContentType();
+    }
+
+    /**
+     * The default behavior of this method is to call setBufferSize(int size) on
+     * the wrapped response object.
+     */
+    @Override
+    public void setBufferSize(int size) {
+        this.response.setBufferSize(size);
+    }
+
+    /**
+     * The default behavior of this method is to return getBufferSize() on the
+     * wrapped response object.
+     */
+    @Override
+    public int getBufferSize() {
+        return this.response.getBufferSize();
+    }
+
+    /**
+     * The default behavior of this method is to call flushBuffer() on the
+     * wrapped response object.
+     */
+    @Override
+    public void flushBuffer() throws IOException {
+        this.response.flushBuffer();
+    }
+
+    /**
+     * The default behavior of this method is to return isCommitted() on the
+     * wrapped response object.
+     */
+    @Override
+    public boolean isCommitted() {
+        return this.response.isCommitted();
+    }
+
+    /**
+     * The default behavior of this method is to call reset() on the wrapped
+     * response object.
+     */
+    @Override
+    public void reset() {
+        this.response.reset();
+    }
+
+    /**
+     * The default behavior of this method is to call resetBuffer() on the
+     * wrapped response object.
+     */
+    @Override
+    public void resetBuffer() {
+        this.response.resetBuffer();
+    }
+
+    /**
+     * The default behavior of this method is to call setLocale(Locale loc) on
+     * the wrapped response object.
+     */
+    @Override
+    public void setLocale(Locale loc) {
+        this.response.setLocale(loc);
+    }
+
+    /**
+     * The default behavior of this method is to return getLocale() on the
+     * wrapped response object.
+     */
+    @Override
+    public Locale getLocale() {
+        return this.response.getLocale();
+    }
+
+    /**
+     * @param wrapped
+     * @since Servlet 3.0 TODO SERVLET3 - Add comments
+     */
+    public boolean isWrapperFor(ServletResponse wrapped) {
+        if (response == wrapped) {
+            return true;
+        }
+        if (response instanceof ServletResponseWrapper) {
+            return ((ServletResponseWrapper) response).isWrapperFor(wrapped);
+        }
+        return false;
+    }
+
+    /**
+     * @param wrappedType
+     * @since Servlet 3.0 TODO SERVLET3 - Add comments
+     */
+    public boolean isWrapperFor(Class<?> wrappedType) {
+        if (wrappedType.isAssignableFrom(response.getClass())) {
+            return true;
+        }
+        if (response instanceof ServletResponseWrapper) {
+            return ((ServletResponseWrapper) response).isWrapperFor(wrappedType);
+        }
+        return false;
+    }
+
+}
diff --git a/lib/servlet-api/javax/servlet/ServletSecurityElement.java b/lib/servlet-api/javax/servlet/ServletSecurityElement.java
new file mode 100644 (file)
index 0000000..cf2832f
--- /dev/null
@@ -0,0 +1,135 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package javax.servlet;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+
+import javax.servlet.annotation.HttpMethodConstraint;
+import javax.servlet.annotation.ServletSecurity;
+
+/**
+ *
+ * @since Servlet 3.0
+ * TODO SERVLET3 - Add comments
+ */
+public class ServletSecurityElement extends HttpConstraintElement {
+
+    private final Map<String,HttpMethodConstraintElement> methodConstraints =
+        new HashMap<>();
+
+    /**
+     * Use default HttpConstraint.
+     */
+    public ServletSecurityElement() {
+        super();
+    }
+
+    /**
+     * Use specified HttpConstraintElement.
+     * @param httpConstraintElement
+     */
+    public ServletSecurityElement(HttpConstraintElement httpConstraintElement) {
+        this (httpConstraintElement, null);
+    }
+
+    /**
+     * Use specific constraints for specified methods and default
+     * HttpConstraintElement for all other methods.
+     * @param httpMethodConstraints
+     * @throws IllegalArgumentException if a method name is specified more than
+     * once
+     */
+    public ServletSecurityElement(
+            Collection<HttpMethodConstraintElement> httpMethodConstraints) {
+        super();
+        addHttpMethodConstraints(httpMethodConstraints);
+    }
+
+
+    /**
+     * Use specified HttpConstraintElement as default and specific constraints
+     * for specified methods.
+     * @param httpConstraintElement
+     * @param httpMethodConstraints
+     * @throws IllegalArgumentException if a method name is specified more than
+     */
+    public ServletSecurityElement(HttpConstraintElement httpConstraintElement,
+            Collection<HttpMethodConstraintElement> httpMethodConstraints) {
+        super(httpConstraintElement.getEmptyRoleSemantic(),
+                httpConstraintElement.getTransportGuarantee(),
+                httpConstraintElement.getRolesAllowed());
+        addHttpMethodConstraints(httpMethodConstraints);
+    }
+
+    /**
+     * Create from an annotation.
+     * @param annotation
+     * @throws IllegalArgumentException if a method name is specified more than
+     */
+    public ServletSecurityElement(ServletSecurity annotation) {
+        this(new HttpConstraintElement(annotation.value().value(),
+                annotation.value().transportGuarantee(),
+                annotation.value().rolesAllowed()));
+
+        List<HttpMethodConstraintElement> l = new ArrayList<>();
+        HttpMethodConstraint[] constraints = annotation.httpMethodConstraints();
+        if (constraints != null) {
+            for (int i = 0; i < constraints.length; i++) {
+                HttpMethodConstraintElement e =
+                    new HttpMethodConstraintElement(constraints[i].value(),
+                            new HttpConstraintElement(
+                                    constraints[i].emptyRoleSemantic(),
+                                    constraints[i].transportGuarantee(),
+                                    constraints[i].rolesAllowed()));
+                l.add(e);
+            }
+        }
+        addHttpMethodConstraints(l);
+    }
+
+    public Collection<HttpMethodConstraintElement> getHttpMethodConstraints() {
+        Collection<HttpMethodConstraintElement> result = new HashSet<>();
+        result.addAll(methodConstraints.values());
+        return result;
+    }
+
+    public Collection<String> getMethodNames() {
+        Collection<String> result = new HashSet<>();
+        result.addAll(methodConstraints.keySet());
+        return result;
+    }
+
+    private void addHttpMethodConstraints(
+            Collection<HttpMethodConstraintElement> httpMethodConstraints) {
+        if (httpMethodConstraints == null) {
+            return;
+        }
+        for (HttpMethodConstraintElement constraint : httpMethodConstraints) {
+            String method = constraint.getMethodName();
+            if (methodConstraints.containsKey(method)) {
+                throw new IllegalArgumentException(
+                        "Duplicate method name: " + method);
+            }
+            methodConstraints.put(method, constraint);
+        }
+    }
+}
diff --git a/lib/servlet-api/javax/servlet/SessionCookieConfig.java b/lib/servlet-api/javax/servlet/SessionCookieConfig.java
new file mode 100644 (file)
index 0000000..11567c0
--- /dev/null
@@ -0,0 +1,90 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package javax.servlet;
+
+/**
+ *
+ * TODO SERVLET3 - Add comments
+ * @since Servlet 3.0
+ */
+public interface SessionCookieConfig {
+
+    /**
+     *
+     * @param name
+     * @throws IllegalStateException
+     */
+    public void setName(String name);
+
+    public String getName();
+
+    /**
+     *
+     * @param domain
+     * @throws IllegalStateException
+     */
+    public void setDomain(String domain);
+
+    public String getDomain();
+
+    /**
+     *
+     * @param path
+     * @throws IllegalStateException
+     */
+    public void setPath(String path);
+
+    public String getPath();
+
+    /**
+     *
+     * @param comment
+     * @throws IllegalStateException
+     */
+    public void setComment(String comment);
+
+    public String getComment();
+
+    /**
+     *
+     * @param httpOnly
+     * @throws IllegalStateException
+     */
+    public void setHttpOnly(boolean httpOnly);
+
+    public boolean isHttpOnly();
+
+    /**
+     *
+     * @param secure
+     * @throws IllegalStateException
+     */
+    public void setSecure(boolean secure);
+
+    public boolean isSecure();
+
+    /**
+     * Sets the maximum age.
+     *
+     * @param MaxAge the maximum age to set
+     * @throws IllegalStateException
+     */
+    public void setMaxAge(int MaxAge);
+
+    public int getMaxAge();
+
+}
diff --git a/lib/servlet-api/javax/servlet/SessionTrackingMode.java b/lib/servlet-api/javax/servlet/SessionTrackingMode.java
new file mode 100644 (file)
index 0000000..c59fd0c
--- /dev/null
@@ -0,0 +1,26 @@
+/*
+* Licensed to the Apache Software Foundation (ASF) under one or more
+* contributor license agreements.  See the NOTICE file distributed with
+* this work for additional information regarding copyright ownership.
+* The ASF licenses this file to You under the Apache License, Version 2.0
+* (the "License"); you may not use this file except in compliance with
+* the License.  You may obtain a copy of the License at
+*
+*     http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+package javax.servlet;
+
+/**
+ * @since Servlet 3.0
+ */
+public enum SessionTrackingMode {
+    COOKIE,
+    URL,
+    SSL
+}
diff --git a/lib/servlet-api/javax/servlet/SingleThreadModel.java b/lib/servlet-api/javax/servlet/SingleThreadModel.java
new file mode 100644 (file)
index 0000000..6e8437e
--- /dev/null
@@ -0,0 +1,44 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package javax.servlet;
+
+/**
+ * Ensures that servlets handle only one request at a time. This interface has
+ * no methods.
+ * <p>
+ * If a servlet implements this interface, you are <i>guaranteed</i> that no two
+ * threads will execute concurrently in the servlet's <code>service</code>
+ * method. The servlet container can make this guarantee by synchronizing access
+ * to a single instance of the servlet, or by maintaining a pool of servlet
+ * instances and dispatching each new request to a free servlet.
+ * <p>
+ * Note that SingleThreadModel does not solve all thread safety issues. For
+ * example, session attributes and static variables can still be accessed by
+ * multiple requests on multiple threads at the same time, even when
+ * SingleThreadModel servlets are used. It is recommended that a developer take
+ * other means to resolve those issues instead of implementing this interface,
+ * such as avoiding the usage of an instance variable or synchronizing the block
+ * of the code accessing those resources. This interface is deprecated in
+ * Servlet API version 2.4.
+ *
+ * @deprecated As of Java Servlet API 2.4, with no direct replacement.
+ */
+@SuppressWarnings("dep-ann")
+// Spec API does not use @Deprecated
+public interface SingleThreadModel {
+    // No methods
+}
diff --git a/lib/servlet-api/javax/servlet/UnavailableException.java b/lib/servlet-api/javax/servlet/UnavailableException.java
new file mode 100644 (file)
index 0000000..c03c6d2
--- /dev/null
@@ -0,0 +1,178 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package javax.servlet;
+
+/**
+ * Defines an exception that a servlet or filter throws to indicate that it is
+ * permanently or temporarily unavailable.
+ * <p>
+ * When a servlet or filter is permanently unavailable, something is wrong with
+ * it, and it cannot handle requests until some action is taken. For example, a
+ * servlet might be configured incorrectly, or a filter's state may be
+ * corrupted. The component should log both the error and the corrective action
+ * that is needed.
+ * <p>
+ * A servlet or filter is temporarily unavailable if it cannot handle requests
+ * momentarily due to some system-wide problem. For example, a third-tier server
+ * might not be accessible, or there may be insufficient memory or disk storage
+ * to handle requests. A system administrator may need to take corrective
+ * action.
+ * <p>
+ * Servlet containers can safely treat both types of unavailable exceptions in
+ * the same way. However, treating temporary unavailability effectively makes
+ * the servlet container more robust. Specifically, the servlet container might
+ * block requests to the servlet or filter for a period of time suggested by the
+ * exception, rather than rejecting them until the servlet container restarts.
+ */
+public class UnavailableException extends ServletException {
+
+    private static final long serialVersionUID = 1L;
+
+    private final Servlet servlet; // what's unavailable
+    private final boolean permanent; // needs admin action?
+    private final int seconds; // unavailability estimate
+
+    /**
+     * @param servlet
+     *            the <code>Servlet</code> instance that is unavailable
+     * @param msg
+     *            a <code>String</code> specifying the descriptive message
+     * @deprecated As of Java Servlet API 2.2, use
+     *             {@link #UnavailableException(String)} instead.
+     */
+    @SuppressWarnings("dep-ann")
+    // Spec API does not use @Deprecated
+    public UnavailableException(Servlet servlet, String msg) {
+        super(msg);
+        this.servlet = servlet;
+        permanent = true;
+        this.seconds = 0;
+    }
+
+    /**
+     * @param seconds
+     *            an integer specifying the number of seconds the servlet
+     *            expects to be unavailable; if zero or negative, indicates that
+     *            the servlet can't make an estimate
+     * @param servlet
+     *            the <code>Servlet</code> that is unavailable
+     * @param msg
+     *            a <code>String</code> specifying the descriptive message,
+     *            which can be written to a log file or displayed for the user.
+     * @deprecated As of Java Servlet API 2.2, use
+     *             {@link #UnavailableException(String, int)} instead.
+     */
+    @SuppressWarnings("dep-ann")
+    // Spec API does not use @Deprecated
+    public UnavailableException(int seconds, Servlet servlet, String msg) {
+        super(msg);
+        this.servlet = servlet;
+        if (seconds <= 0)
+            this.seconds = -1;
+        else
+            this.seconds = seconds;
+        permanent = false;
+    }
+
+    /**
+     * Constructs a new exception with a descriptive message indicating that the
+     * servlet is permanently unavailable.
+     *
+     * @param msg
+     *            a <code>String</code> specifying the descriptive message
+     */
+    public UnavailableException(String msg) {
+        super(msg);
+        seconds = 0;
+        servlet = null;
+        permanent = true;
+    }
+
+    /**
+     * Constructs a new exception with a descriptive message indicating that the
+     * servlet is temporarily unavailable and giving an estimate of how long it
+     * will be unavailable.
+     * <p>
+     * In some cases, the servlet cannot make an estimate. For example, the
+     * servlet might know that a server it needs is not running, but not be able
+     * to report how long it will take to be restored to functionality. This can
+     * be indicated with a negative or zero value for the <code>seconds</code>
+     * argument.
+     *
+     * @param msg
+     *            a <code>String</code> specifying the descriptive message,
+     *            which can be written to a log file or displayed for the user.
+     * @param seconds
+     *            an integer specifying the number of seconds the servlet
+     *            expects to be unavailable; if zero or negative, indicates that
+     *            the servlet can't make an estimate
+     */
+    public UnavailableException(String msg, int seconds) {
+        super(msg);
+
+        if (seconds <= 0)
+            this.seconds = -1;
+        else
+            this.seconds = seconds;
+        servlet = null;
+        permanent = false;
+    }
+
+    /**
+     * Returns a <code>boolean</code> indicating whether the servlet is
+     * permanently unavailable. If so, something is wrong with the servlet, and
+     * the system administrator must take some corrective action.
+     *
+     * @return <code>true</code> if the servlet is permanently unavailable;
+     *         <code>false</code> if the servlet is available or temporarily
+     *         unavailable
+     */
+    public boolean isPermanent() {
+        return permanent;
+    }
+
+    /**
+     * Returns the servlet that is reporting its unavailability.
+     *
+     * @return the <code>Servlet</code> object that is throwing the
+     *         <code>UnavailableException</code>
+     * @deprecated As of Java Servlet API 2.2, with no replacement.
+     */
+    @SuppressWarnings("dep-ann")
+    // Spec API does not use @Deprecated
+    public Servlet getServlet() {
+        return servlet;
+    }
+
+    /**
+     * Returns the number of seconds the servlet expects to be temporarily
+     * unavailable.
+     * <p>
+     * If this method returns a negative number, the servlet is permanently
+     * unavailable or cannot provide an estimate of how long it will be
+     * unavailable. No effort is made to correct for the time elapsed since the
+     * exception was first reported.
+     *
+     * @return an integer specifying the number of seconds the servlet will be
+     *         temporarily unavailable, or a negative number if the servlet is
+     *         permanently unavailable or cannot make an estimate
+     */
+    public int getUnavailableSeconds() {
+        return permanent ? -1 : seconds;
+    }
+}
diff --git a/lib/servlet-api/javax/servlet/WriteListener.java b/lib/servlet-api/javax/servlet/WriteListener.java
new file mode 100644 (file)
index 0000000..488111f
--- /dev/null
@@ -0,0 +1,45 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package javax.servlet;
+
+import java.io.IOException;
+
+/**
+ * Receives notification of write events when using non-blocking IO.
+ *
+ * @since Servlet 3.1
+ */
+public interface WriteListener extends java.util.EventListener{
+
+    /**
+     * Invoked when it it possible to write data without blocking. The container
+     * will invoke this method the first time for a request as soon as data can
+     * be written. Subsequent invocations will only occur if a call to
+     * {@link ServletOutputStream#isReady()} has returned false and it has since
+     * become possible to write data.
+     *
+     * @throws IOException
+     */
+    public void onWritePossible() throws IOException;
+
+    /**
+     * Invoked if an error occurs while writing the response.
+     *
+     * @param throwable
+     */
+    public void onError(java.lang.Throwable throwable);
+}
\ No newline at end of file
diff --git a/lib/servlet-api/javax/servlet/annotation/HandlesTypes.java b/lib/servlet-api/javax/servlet/annotation/HandlesTypes.java
new file mode 100644 (file)
index 0000000..c5f26b0
--- /dev/null
@@ -0,0 +1,39 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package javax.servlet.annotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * This annotation is used to declare an array of application classes which are
+ * passed to a {@link javax.servlet.ServletContainerInitializer}.
+ *
+ * @since Servlet 3.0
+ */
+@Target({ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+public @interface HandlesTypes {
+
+    /**
+     * @return array of classes
+     */
+    Class<?>[] value();
+
+}
diff --git a/lib/servlet-api/javax/servlet/annotation/HttpConstraint.java b/lib/servlet-api/javax/servlet/annotation/HttpConstraint.java
new file mode 100644 (file)
index 0000000..fd4366c
--- /dev/null
@@ -0,0 +1,69 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package javax.servlet.annotation;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+import javax.servlet.annotation.ServletSecurity.EmptyRoleSemantic;
+import javax.servlet.annotation.ServletSecurity.TransportGuarantee;
+
+/**
+ * This annotation represents the security constraints that are applied to all
+ * requests with HTTP protocol method types that are not otherwise represented
+ * by a corresponding {@link javax.servlet.annotation.HttpMethodConstraint} in a
+ * {@link javax.servlet.annotation.ServletSecurity} annotation.
+ *
+ * @since Servlet 3.0
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+public @interface HttpConstraint {
+
+    /**
+     * The EmptyRoleSemantic determines the behaviour when the rolesAllowed list
+     * is empty.
+     *
+     * @return empty role semantic
+     */
+    EmptyRoleSemantic value() default EmptyRoleSemantic.PERMIT;
+
+    /**
+     * Determines whether SSL/TLS is required to process the current request.
+     *
+     * @return transport guarantee
+     */
+    TransportGuarantee transportGuarantee() default TransportGuarantee.NONE;
+
+    /**
+     * The authorized roles' names. The container may discard duplicate role
+     * names during processing of the annotation. N.B. The String "*" does not
+     * have a special meaning if it occurs as a role name.
+     *
+     * @return array of names. The array may be of zero length, in which case
+     *         the EmptyRoleSemantic applies; the returned value determines
+     *         whether access is to be permitted or denied regardless of the
+     *         identity and authentication state in either case, PERMIT or DENY.<br />
+     *         Otherwise, when the array contains one or more role names access
+     *         is permitted if the user a member of at least one of the named
+     *         roles. The EmptyRoleSemantic is not applied in this case.
+     *
+     */
+    String[] rolesAllowed() default {};
+
+}
diff --git a/lib/servlet-api/javax/servlet/annotation/HttpMethodConstraint.java b/lib/servlet-api/javax/servlet/annotation/HttpMethodConstraint.java
new file mode 100644 (file)
index 0000000..84e7c1d
--- /dev/null
@@ -0,0 +1,74 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package javax.servlet.annotation;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+import javax.servlet.annotation.ServletSecurity.EmptyRoleSemantic;
+import javax.servlet.annotation.ServletSecurity.TransportGuarantee;
+
+/**
+ * Specific security constraints can be applied to different types of request,
+ * differentiated by the HTTP protocol method type by using this annotation
+ * inside the {@link javax.servlet.annotation.ServletSecurity} annotation.
+ *
+ * @since Servlet 3.0
+ *
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+public @interface HttpMethodConstraint {
+
+    /**
+     * HTTP Protocol method name (e.g. POST, PUT)
+     *
+     * @return method name
+     */
+    String value();
+
+    /**
+     * The EmptyRoleSemantic determines the behaviour when the rolesAllowed list
+     * is empty.
+     *
+     * @return empty role semantic
+     */
+    EmptyRoleSemantic emptyRoleSemantic() default EmptyRoleSemantic.PERMIT;
+
+    /**
+     * Determines whether SSL/TLS is required to process the current request.
+     *
+     * @return transport guarantee
+     */
+    TransportGuarantee transportGuarantee() default TransportGuarantee.NONE;
+
+    /**
+     * The authorized roles' names. The container may discard duplicate role
+     * names during processing of the annotation. N.B. The String "*" does not
+     * have a special meaning if it occurs as a role name.
+     *
+     * @return array of names. The array may be of zero length, in which case
+     *         the EmptyRoleSemantic applies; the returned value determines
+     *         whether access is to be permitted or denied regardless of the
+     *         identity and authentication state in either case, PERMIT or DENY.<br />
+     *         Otherwise, when the array contains one or more role names access
+     *         is permitted if the user a member of at least one of the named
+     *         roles. The EmptyRoleSemantic is not applied in this case.
+     */
+    String[] rolesAllowed() default {};
+}
diff --git a/lib/servlet-api/javax/servlet/annotation/MultipartConfig.java b/lib/servlet-api/javax/servlet/annotation/MultipartConfig.java
new file mode 100644 (file)
index 0000000..a86f86e
--- /dev/null
@@ -0,0 +1,68 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package javax.servlet.annotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * This annotation is used to indicate that the {@link javax.servlet.Servlet} on
+ * which it is declared expects requests to made using the {@code
+ * multipart/form-data} MIME type. <br />
+ * <br />
+ *
+ * {@link javax.servlet.http.Part} components of a given {@code
+ * multipart/form-data} request are retrieved by a Servlet annotated with
+ * {@code MultipartConfig} by calling
+ * {@link javax.servlet.http.HttpServletRequest#getPart} or
+ * {@link javax.servlet.http.HttpServletRequest#getParts}.<br />
+ * <br />
+ *
+ * E.g. <code>@WebServlet("/upload")}</code><br />
+ *
+ * <code>@MultipartConfig()</code> <code>public class UploadServlet extends
+ * HttpServlet ... } </code><br />
+ *
+ * @since Servlet 3.0
+ */
+@Target({ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+public @interface MultipartConfig {
+
+    /**
+     * @return location in which the Container stores temporary files
+     */
+    String location() default "";
+
+    /**
+     * @return the maximum size allowed for uploaded files (in bytes)
+     */
+    long maxFileSize() default -1L;
+
+    /**
+     * @return the maximum size of the request allowed for {@code
+     *         multipart/form-data}
+     */
+    long maxRequestSize() default -1L;
+
+    /**
+     * @return the size threshold at which the file will be written to the disk
+     */
+    int fileSizeThreshold() default 0;
+}
diff --git a/lib/servlet-api/javax/servlet/annotation/ServletSecurity.java b/lib/servlet-api/javax/servlet/annotation/ServletSecurity.java
new file mode 100644 (file)
index 0000000..7ed432e
--- /dev/null
@@ -0,0 +1,90 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package javax.servlet.annotation;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Declare this annotation on a {@link javax.servlet.Servlet} implementation
+ * class to enforce security constraints on HTTP protocol requests.<br />
+ * The container applies constraints to the URL patterns mapped to each Servlet
+ * which declares this annotation.<br />
+ * <br />
+ *
+ * @since Servlet 3.0
+ */
+@Inherited
+@Target({ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+public @interface ServletSecurity {
+
+    /**
+     * Represents the two possible values of the empty role semantic, active
+     * when a list of role names is empty.
+     */
+    enum EmptyRoleSemantic {
+
+        /**
+         * Access MUST be permitted, regardless of authentication state or
+         * identity
+         */
+        PERMIT,
+
+        /**
+         * Access MUST be denied, regardless of authentication state or identity
+         */
+        DENY
+    }
+
+    /**
+     * Represents the two possible values of data transport, encrypted or not.
+     */
+    enum TransportGuarantee {
+
+        /**
+         * User data must not be encrypted by the container during transport
+         */
+        NONE,
+
+        /**
+         * The container MUST encrypt user data during transport
+         */
+        CONFIDENTIAL
+    }
+
+    /**
+     * The default constraint to apply to requests not handled by specific
+     * method constraints
+     *
+     * @return http constraint
+     */
+    HttpConstraint value() default @HttpConstraint;
+
+    /**
+     * An array of HttpMethodContraint objects to which the security constraint
+     * will be applied
+     *
+     * @return array of http method constraint
+     */
+    HttpMethodConstraint[] httpMethodConstraints() default {};
+}
diff --git a/lib/servlet-api/javax/servlet/annotation/WebFilter.java b/lib/servlet-api/javax/servlet/annotation/WebFilter.java
new file mode 100644 (file)
index 0000000..08cbfdb
--- /dev/null
@@ -0,0 +1,122 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package javax.servlet.annotation;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+import javax.servlet.DispatcherType;
+
+/**
+ * The annotation used to declare a Servlet {@link javax.servlet.Filter}. <br />
+ * <br />
+ *
+ * This annotation will be processed by the container during deployment, the
+ * Filter class in which it is found will be created as per the configuration
+ * and applied to the URL patterns, {@link javax.servlet.Servlet}s and
+ * {@link javax.servlet.DispatcherType}s.<br />
+ * <br/>
+ *
+ * If the name attribute is not defined, the fully qualified name of the class
+ * is used.<br/>
+ * <br/>
+ *
+ * At least one URL pattern MUST be declared in either the {@code value} or
+ * {@code urlPattern} attribute of the annotation, but not both.<br/>
+ * <br/>
+ *
+ * The {@code value} attribute is recommended for use when the URL pattern is
+ * the only attribute being set, otherwise the {@code urlPattern} attribute
+ * should be used.<br />
+ * <br />
+ *
+ * The annotated class MUST implement {@link javax.servlet.Filter}.
+ *
+ * E.g.
+ *
+ * <code>@WebFilter("/path/*")</code><br />
+ * <code>public class AnExampleFilter implements Filter { ... </code><br />
+ *
+ * @since Servlet 3.0 (Section 8.1.2)
+ *
+ */
+@Target({ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+public @interface WebFilter {
+
+    /**
+     * @return description of the Filter, if present
+     */
+    String description() default "";
+
+    /**
+     * @return display name of the Filter, if present
+     */
+    String displayName() default "";
+
+    /**
+     * @return array of initialization params for this Filter
+     */
+    WebInitParam[] initParams() default {};
+
+    /**
+     * @return name of the Filter, if present
+     */
+    String filterName() default "";
+
+    /**
+     * @return small icon for this Filter, if present
+     */
+    String smallIcon() default "";
+
+    /**
+     * @return the large icon for this Filter, if present
+     */
+    String largeIcon() default "";
+
+    /**
+     * @return array of Servlet names to which this Filter applies
+     */
+    String[] servletNames() default {};
+
+    /**
+     * A convenience method, to allow extremely simple annotation of a class.
+     *
+     * @return array of URL patterns
+     * @see #urlPatterns()
+     */
+    String[] value() default {};
+
+    /**
+     * @return array of URL patterns to which this Filter applies
+     */
+    String[] urlPatterns() default {};
+
+    /**
+     * @return array of DispatcherTypes to which this filter applies
+     */
+    DispatcherType[] dispatcherTypes() default {DispatcherType.REQUEST};
+
+    /**
+     * @return asynchronous operation supported by this Filter
+     */
+    boolean asyncSupported() default false;
+}
diff --git a/lib/servlet-api/javax/servlet/annotation/WebInitParam.java b/lib/servlet-api/javax/servlet/annotation/WebInitParam.java
new file mode 100644 (file)
index 0000000..e5fb73f
--- /dev/null
@@ -0,0 +1,57 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package javax.servlet.annotation;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * The annotation used to declare an initialization parameter on a
+ * {@link javax.servlet.Servlet} or {@link javax.servlet.Filter}, within a
+ * {@link javax.servlet.annotation.WebFilter} or
+ * {@link javax.servlet.annotation.WebServlet} annotation.<br />
+ * <br />
+ *
+ * E.g.
+ * <code>&amp;#064;WebServlet(name="TestServlet", urlPatterns={"/test"},initParams={&amp;#064;WebInitParam(name="test", value="true")})
+ * public class TestServlet extends HttpServlet { ... </code><br />
+ *
+ * @since Servlet 3.0
+ */
+@Target({ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+public @interface WebInitParam {
+
+    /**
+     * @return name of the initialization parameter
+     */
+    String name();
+
+    /**
+     * @return value of the initialization parameter
+     */
+    String value();
+
+    /**
+     * @return description of the initialization parameter
+     */
+    String description() default "";
+}
diff --git a/lib/servlet-api/javax/servlet/annotation/WebListener.java b/lib/servlet-api/javax/servlet/annotation/WebListener.java
new file mode 100644 (file)
index 0000000..81f5ff7
--- /dev/null
@@ -0,0 +1,54 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package javax.servlet.annotation;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * The annotation used to declare a listener for various types of event, in a
+ * given web application context.<br />
+ * <br />
+ *
+ * The class annotated MUST implement one, (or more), of the following
+ * interfaces: {@link javax.servlet.http.HttpSessionAttributeListener},
+ * {@link javax.servlet.http.HttpSessionListener},
+ * {@link javax.servlet.ServletContextAttributeListener},
+ * {@link javax.servlet.ServletContextListener},
+ * {@link javax.servlet.ServletRequestAttributeListener},
+ * {@link javax.servlet.ServletRequestListener} or
+ * {@link javax.servlet.http.HttpSessionIdListener}
+ * <br />
+ *
+ * E.g. <code>@WebListener</code><br />
+ * <code>public TestListener implements ServletContextListener {</code><br />
+ *
+ * @since Servlet 3.0
+ */
+@Target({ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+public @interface WebListener {
+
+    /**
+     * @return description of the listener, if present
+     */
+    String value() default "";
+}
diff --git a/lib/servlet-api/javax/servlet/annotation/WebServlet.java b/lib/servlet-api/javax/servlet/annotation/WebServlet.java
new file mode 100644 (file)
index 0000000..eebf8ba
--- /dev/null
@@ -0,0 +1,113 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package javax.servlet.annotation;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * This annotation is used to declare the configuration of an
+ * {@link javax.servlet.Servlet}. <br/>
+ *
+ * If the name attribute is not defined, the fully qualified name of the class
+ * is used.<br/>
+ * <br/>
+ *
+ * At least one URL pattern MUST be declared in either the {@code value} or
+ * {@code urlPattern} attribute of the annotation, but not both.<br/>
+ * <br/>
+ *
+ * The {@code value} attribute is recommended for use when the URL pattern is
+ * the only attribute being set, otherwise the {@code urlPattern} attribute
+ * should be used.<br />
+ * <br />
+ *
+ * The class on which this annotation is declared MUST extend
+ * {@link javax.servlet.http.HttpServlet}. <br />
+ * <br />
+ *
+ * E.g. <code>@WebServlet("/path")}<br />
+ * public class TestServlet extends HttpServlet ... {</code><br />
+ *
+ * E.g.
+ * <code>@WebServlet(name="TestServlet", urlPatterns={"/path", "/alt"}) <br />
+ * public class TestServlet extends HttpServlet ... {</code><br />
+ *
+ * @since Servlet 3.0 (Section 8.1.1)
+ *
+ */
+@Target({ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+public @interface WebServlet {
+
+    /**
+     * @return name of the Servlet
+     */
+    String name() default "";
+
+    /**
+     * A convenience method, to allow extremely simple annotation of a class.
+     *
+     * @return array of URL patterns
+     * @see #urlPatterns()
+     */
+    String[] value() default {};
+
+    /**
+     * @return array of URL patterns to which this Filter applies
+     */
+    String[] urlPatterns() default {};
+
+    /**
+     * @return load on startup ordering hint
+     */
+    int loadOnStartup() default -1;
+
+    /**
+     * @return array of initialization params for this Servlet
+     */
+    WebInitParam[] initParams() default {};
+
+    /**
+     * @return asynchronous operation supported by this Servlet
+     */
+    boolean asyncSupported() default false;
+
+    /**
+     * @return small icon for this Servlet, if present
+     */
+    String smallIcon() default "";
+
+    /**
+     * @return large icon for this Servlet, if present
+     */
+    String largeIcon() default "";
+
+    /**
+     * @return description of this Servlet, if present
+     */
+    String description() default "";
+
+    /**
+     * @return display name of this Servlet, if present
+     */
+    String displayName() default "";
+}
diff --git a/lib/servlet-api/javax/servlet/descriptor/JspConfigDescriptor.java b/lib/servlet-api/javax/servlet/descriptor/JspConfigDescriptor.java
new file mode 100644 (file)
index 0000000..27b3cc7
--- /dev/null
@@ -0,0 +1,28 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package javax.servlet.descriptor;
+
+import java.util.Collection;
+
+/**
+ * @since Servlet 3.0
+ * TODO SERVLET3 - Add comments
+ */
+public interface JspConfigDescriptor {
+    public Collection<TaglibDescriptor> getTaglibs();
+    public Collection<JspPropertyGroupDescriptor> getJspPropertyGroups();
+}
diff --git a/lib/servlet-api/javax/servlet/descriptor/JspPropertyGroupDescriptor.java b/lib/servlet-api/javax/servlet/descriptor/JspPropertyGroupDescriptor.java
new file mode 100644 (file)
index 0000000..ddee69b
--- /dev/null
@@ -0,0 +1,38 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package javax.servlet.descriptor;
+
+import java.util.Collection;
+
+/**
+ * @since Servlet 3.0
+ * TODO SERVLET3 - Add comments
+ */
+public interface JspPropertyGroupDescriptor {
+    public Collection<String> getUrlPatterns();
+    public String getElIgnored();
+    public String getPageEncoding();
+    public String getScriptingInvalid();
+    public String getIsXml();
+    public Collection<String> getIncludePreludes();
+    public Collection<String> getIncludeCodas();
+    public String getDeferredSyntaxAllowedAsLiteral();
+    public String getTrimDirectiveWhitespaces();
+    public String getDefaultContentType();
+    public String getBuffer();
+    public String getErrorOnUndeclaredNamespace();
+}
diff --git a/lib/servlet-api/javax/servlet/descriptor/TaglibDescriptor.java b/lib/servlet-api/javax/servlet/descriptor/TaglibDescriptor.java
new file mode 100644 (file)
index 0000000..b2e8e98
--- /dev/null
@@ -0,0 +1,26 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package javax.servlet.descriptor;
+
+/**
+ * @since Servlet 3.0
+ * TODO SERVLET3 - Add comments
+ */
+public interface TaglibDescriptor {
+    public String getTaglibURI();
+    public String getTaglibLocation();
+}
diff --git a/lib/servlet-api/javax/servlet/http/Cookie.java b/lib/servlet-api/javax/servlet/http/Cookie.java
new file mode 100644 (file)
index 0000000..1774555
--- /dev/null
@@ -0,0 +1,458 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package javax.servlet.http;
+
+import java.io.Serializable;
+import java.text.MessageFormat;
+import java.util.BitSet;
+import java.util.Locale;
+import java.util.ResourceBundle;
+
+/**
+ * Creates a cookie, a small amount of information sent by a servlet to a Web
+ * browser, saved by the browser, and later sent back to the server. A cookie's
+ * value can uniquely identify a client, so cookies are commonly used for
+ * session management.
+ * <p>
+ * A cookie has a name, a single value, and optional attributes such as a
+ * comment, path and domain qualifiers, a maximum age, and a version number.
+ * Some Web browsers have bugs in how they handle the optional attributes, so
+ * use them sparingly to improve the interoperability of your servlets.
+ * <p>
+ * The servlet sends cookies to the browser by using the
+ * {@link HttpServletResponse#addCookie} method, which adds fields to HTTP
+ * response headers to send cookies to the browser, one at a time. The browser
+ * is expected to support 20 cookies for each Web server, 300 cookies total, and
+ * may limit cookie size to 4 KB each.
+ * <p>
+ * The browser returns cookies to the servlet by adding fields to HTTP request
+ * headers. Cookies can be retrieved from a request by using the
+ * {@link HttpServletRequest#getCookies} method. Several cookies might have the
+ * same name but different path attributes.
+ * <p>
+ * Cookies affect the caching of the Web pages that use them. HTTP 1.0 does not
+ * cache pages that use cookies created with this class. This class does not
+ * support the cache control defined with HTTP 1.1.
+ * <p>
+ * This class supports both the Version 0 (by Netscape) and Version 1 (by RFC
+ * 2109) cookie specifications. By default, cookies are created using Version 0
+ * to ensure the best interoperability.
+ */
+public class Cookie implements Cloneable, Serializable {
+
+    private static final CookieNameValidator validation;
+    static {
+        boolean strictNaming;
+        String prop = System.getProperty("org.apache.tomcat.util.http.ServerCookie.STRICT_NAMING");
+        if (prop != null) {
+            strictNaming = Boolean.parseBoolean(prop);
+        } else {
+            strictNaming = Boolean.getBoolean("org.apache.catalina.STRICT_SERVLET_COMPLIANCE");
+        }
+
+        if (strictNaming) {
+            validation = new RFC2109Validator();
+        }
+        else {
+            validation = new NetscapeValidator();
+        }
+    }
+
+    private static final long serialVersionUID = 1L;
+
+    private final String name;
+    private String value;
+
+    private int version = 0; // ;Version=1 ... means RFC 2109 style
+
+    //
+    // Attributes encoded in the header's cookie fields.
+    //
+    private String comment; // ;Comment=VALUE ... describes cookie's use
+    private String domain; // ;Domain=VALUE ... domain that sees cookie
+    private int maxAge = -1; // ;Max-Age=VALUE ... cookies auto-expire
+    private String path; // ;Path=VALUE ... URLs that see the cookie
+    private boolean secure; // ;Secure ... e.g. use SSL
+    private boolean httpOnly; // Not in cookie specs, but supported by browsers
+
+    /**
+     * Constructs a cookie with a specified name and value.
+     * <p>
+     * The name must conform to RFC 2109. That means it can contain only ASCII
+     * alphanumeric characters and cannot contain commas, semicolons, or white
+     * space or begin with a $ character. The cookie's name cannot be changed
+     * after creation.
+     * <p>
+     * The value can be anything the server chooses to send. Its value is
+     * probably of interest only to the server. The cookie's value can be
+     * changed after creation with the <code>setValue</code> method.
+     * <p>
+     * By default, cookies are created according to the Netscape cookie
+     * specification. The version can be changed with the
+     * <code>setVersion</code> method.
+     *
+     * @param name
+     *            a <code>String</code> specifying the name of the cookie
+     * @param value
+     *            a <code>String</code> specifying the value of the cookie
+     * @throws IllegalArgumentException
+     *             if the cookie name contains illegal characters (for example,
+     *             a comma, space, or semicolon) or it is one of the tokens
+     *             reserved for use by the cookie protocol
+     * @see #setValue
+     * @see #setVersion
+     */
+    public Cookie(String name, String value) {
+        validation.validate(name);
+        this.name = name;
+        this.value = value;
+    }
+
+    /**
+     * Specifies a comment that describes a cookie's purpose. The comment is
+     * useful if the browser presents the cookie to the user. Comments are not
+     * supported by Netscape Version 0 cookies.
+     *
+     * @param purpose
+     *            a <code>String</code> specifying the comment to display to the
+     *            user
+     * @see #getComment
+     */
+    public void setComment(String purpose) {
+        comment = purpose;
+    }
+
+    /**
+     * Returns the comment describing the purpose of this cookie, or
+     * <code>null</code> if the cookie has no comment.
+     *
+     * @return a <code>String</code> containing the comment, or
+     *         <code>null</code> if none
+     * @see #setComment
+     */
+    public String getComment() {
+        return comment;
+    }
+
+    /**
+     * Specifies the domain within which this cookie should be presented.
+     * <p>
+     * The form of the domain name is specified by RFC 2109. A domain name
+     * begins with a dot (<code>.foo.com</code>) and means that the cookie is
+     * visible to servers in a specified Domain Name System (DNS) zone (for
+     * example, <code>www.foo.com</code>, but not <code>a.b.foo.com</code>). By
+     * default, cookies are only returned to the server that sent them.
+     *
+     * @param pattern
+     *            a <code>String</code> containing the domain name within which
+     *            this cookie is visible; form is according to RFC 2109
+     * @see #getDomain
+     */
+    public void setDomain(String pattern) {
+        domain = pattern.toLowerCase(Locale.ENGLISH); // IE allegedly needs this
+    }
+
+    /**
+     * Returns the domain name set for this cookie. The form of the domain name
+     * is set by RFC 2109.
+     *
+     * @return a <code>String</code> containing the domain name
+     * @see #setDomain
+     */
+    public String getDomain() {
+        return domain;
+    }
+
+    /**
+     * Sets the maximum age of the cookie in seconds.
+     * <p>
+     * A positive value indicates that the cookie will expire after that many
+     * seconds have passed. Note that the value is the <i>maximum</i> age when
+     * the cookie will expire, not the cookie's current age.
+     * <p>
+     * A negative value means that the cookie is not stored persistently and
+     * will be deleted when the Web browser exits. A zero value causes the
+     * cookie to be deleted.
+     *
+     * @param expiry
+     *            an integer specifying the maximum age of the cookie in
+     *            seconds; if negative, means the cookie is not stored; if zero,
+     *            deletes the cookie
+     * @see #getMaxAge
+     */
+    public void setMaxAge(int expiry) {
+        maxAge = expiry;
+    }
+
+    /**
+     * Returns the maximum age of the cookie, specified in seconds, By default,
+     * <code>-1</code> indicating the cookie will persist until browser
+     * shutdown.
+     *
+     * @return an integer specifying the maximum age of the cookie in seconds; if
+     *         negative, means the cookie persists until browser shutdown
+     * @see #setMaxAge
+     */
+    public int getMaxAge() {
+        return maxAge;
+    }
+
+    /**
+     * Specifies a path for the cookie to which the client should return the
+     * cookie.
+     * <p>
+     * The cookie is visible to all the pages in the directory you specify, and
+     * all the pages in that directory's subdirectories. A cookie's path must
+     * include the servlet that set the cookie, for example, <i>/catalog</i>,
+     * which makes the cookie visible to all directories on the server under
+     * <i>/catalog</i>.
+     * <p>
+     * Consult RFC 2109 (available on the Internet) for more information on
+     * setting path names for cookies.
+     *
+     * @param uri
+     *            a <code>String</code> specifying a path
+     * @see #getPath
+     */
+    public void setPath(String uri) {
+        path = uri;
+    }
+
+    /**
+     * Returns the path on the server to which the browser returns this cookie.
+     * The cookie is visible to all subpaths on the server.
+     *
+     * @return a <code>String</code> specifying a path that contains a servlet
+     *         name, for example, <i>/catalog</i>
+     * @see #setPath
+     */
+    public String getPath() {
+        return path;
+    }
+
+    /**
+     * Indicates to the browser whether the cookie should only be sent using a
+     * secure protocol, such as HTTPS or SSL.
+     * <p>
+     * The default value is <code>false</code>.
+     *
+     * @param flag
+     *            if <code>true</code>, sends the cookie from the browser to the
+     *            server only when using a secure protocol; if
+     *            <code>false</code>, sent on any protocol
+     * @see #getSecure
+     */
+    public void setSecure(boolean flag) {
+        secure = flag;
+    }
+
+    /**
+     * Returns <code>true</code> if the browser is sending cookies only over a
+     * secure protocol, or <code>false</code> if the browser can send cookies
+     * using any protocol.
+     *
+     * @return <code>true</code> if the browser uses a secure protocol;
+     *         otherwise, <code>true</code>
+     * @see #setSecure
+     */
+    public boolean getSecure() {
+        return secure;
+    }
+
+    /**
+     * Returns the name of the cookie. The name cannot be changed after
+     * creation.
+     *
+     * @return a <code>String</code> specifying the cookie's name
+     */
+    public String getName() {
+        return name;
+    }
+
+    /**
+     * Assigns a new value to a cookie after the cookie is created. If you use a
+     * binary value, you may want to use BASE64 encoding.
+     * <p>
+     * With Version 0 cookies, values should not contain white space, brackets,
+     * parentheses, equals signs, commas, double quotes, slashes, question
+     * marks, at signs, colons, and semicolons. Empty values may not behave the
+     * same way on all browsers.
+     *
+     * @param newValue
+     *            a <code>String</code> specifying the new value
+     * @see #getValue
+     * @see Cookie
+     */
+    public void setValue(String newValue) {
+        value = newValue;
+    }
+
+    /**
+     * Returns the value of the cookie.
+     *
+     * @return a <code>String</code> containing the cookie's present value
+     * @see #setValue
+     * @see Cookie
+     */
+    public String getValue() {
+        return value;
+    }
+
+    /**
+     * Returns the version of the protocol this cookie complies with. Version 1
+     * complies with RFC 2109, and version 0 complies with the original cookie
+     * specification drafted by Netscape. Cookies provided by a browser use and
+     * identify the browser's cookie version.
+     *
+     * @return 0 if the cookie complies with the original Netscape specification;
+     *         1 if the cookie complies with RFC 2109
+     * @see #setVersion
+     */
+    public int getVersion() {
+        return version;
+    }
+
+    /**
+     * Sets the version of the cookie protocol this cookie complies with.
+     * Version 0 complies with the original Netscape cookie specification.
+     * Version 1 complies with RFC 2109.
+     * <p>
+     * Since RFC 2109 is still somewhat new, consider version 1 as experimental;
+     * do not use it yet on production sites.
+     *
+     * @param v
+     *            0 if the cookie should comply with the original Netscape
+     *            specification; 1 if the cookie should comply with RFC 2109
+     * @see #getVersion
+     */
+    public void setVersion(int v) {
+        version = v;
+    }
+
+    /**
+     * Overrides the standard <code>java.lang.Object.clone</code> method to
+     * return a copy of this cookie.
+     */
+    @Override
+    public Object clone() {
+        try {
+            return super.clone();
+        } catch (CloneNotSupportedException e) {
+            throw new RuntimeException(e.getMessage());
+        }
+    }
+
+    /**
+     * Sets the flag that controls if this cookie will be hidden from scripts on
+     * the client side.
+     *
+     * @param httpOnly  The new value of the flag
+     *
+     * @since Servlet 3.0
+     */
+    public void setHttpOnly(boolean httpOnly) {
+        this.httpOnly = httpOnly;
+    }
+
+    /**
+     * Gets the flag that controls if this cookie will be hidden from scripts on
+     * the client side.
+     *
+     * @return  <code>true</code> if the cookie is hidden from scripts, else
+     *          <code>false</code>
+     * @since Servlet 3.0
+     */
+    public boolean isHttpOnly() {
+        return httpOnly;
+    }
+}
+
+
+class CookieNameValidator {
+    private static final String LSTRING_FILE = "javax.servlet.http.LocalStrings";
+    private static final ResourceBundle lStrings = ResourceBundle.getBundle(LSTRING_FILE);
+
+    protected final BitSet allowed;
+
+    protected CookieNameValidator(String separators) {
+        allowed = new BitSet(128);
+        allowed.set(0x20, 0x7f); // any CHAR except CTLs or separators
+        for (int i = 0; i < separators.length(); i++) {
+            char ch = separators.charAt(i);
+            allowed.clear(ch);
+        }
+    }
+
+    void validate(String name) {
+        if (name == null || name.length() == 0) {
+            throw new IllegalArgumentException(lStrings.getString("err.cookie_name_blank"));
+        }
+        if (!isToken(name) ||
+                name.equalsIgnoreCase("Comment") ||
+                name.equalsIgnoreCase("Discard") ||
+                name.equalsIgnoreCase("Domain") ||
+                name.equalsIgnoreCase("Expires") ||
+                name.equalsIgnoreCase("Max-Age") ||
+                name.equalsIgnoreCase("Path") ||
+                name.equalsIgnoreCase("Secure") ||
+                name.equalsIgnoreCase("Version") ||
+                name.startsWith("$")) {
+            String errMsg = lStrings.getString("err.cookie_name_is_token");
+            throw new IllegalArgumentException(MessageFormat.format(errMsg, name));
+        }
+    }
+
+    private boolean isToken(String possibleToken) {
+        int len = possibleToken.length();
+
+        for (int i = 0; i < len; i++) {
+            char c = possibleToken.charAt(i);
+            if (!allowed.get(c)) {
+                return false;
+            }
+        }
+        return true;
+    }
+}
+
+class NetscapeValidator extends CookieNameValidator {
+    private static final String NETSCAPE_SEPARATORS = ",; ";
+
+    NetscapeValidator() {
+        super(NETSCAPE_SEPARATORS);
+    }
+}
+
+class RFC2109Validator extends CookieNameValidator {
+    private static final String RFC2616_SEPARATORS = "()<>@,;:\\\"/[]?={} \t";
+
+    RFC2109Validator() {
+        super(RFC2616_SEPARATORS);
+
+        // special treatment to allow for FWD_SLASH_IS_SEPARATOR property
+        boolean allowSlash;
+        String prop = System.getProperty("org.apache.tomcat.util.http.ServerCookie.FWD_SLASH_IS_SEPARATOR");
+        if (prop != null) {
+            allowSlash = !Boolean.parseBoolean(prop);
+        } else {
+            allowSlash = !Boolean.getBoolean("org.apache.catalina.STRICT_SERVLET_COMPLIANCE");
+        }
+        if (allowSlash) {
+            allowed.set('/');
+        }
+    }
+}
diff --git a/lib/servlet-api/javax/servlet/http/HttpServlet.java b/lib/servlet-api/javax/servlet/http/HttpServlet.java
new file mode 100644 (file)
index 0000000..f44ebcd
--- /dev/null
@@ -0,0 +1,881 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package javax.servlet.http;
+
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.io.PrintWriter;
+import java.io.UnsupportedEncodingException;
+import java.lang.reflect.Method;
+import java.text.MessageFormat;
+import java.util.Enumeration;
+import java.util.ResourceBundle;
+
+import javax.servlet.GenericServlet;
+import javax.servlet.ServletException;
+import javax.servlet.ServletOutputStream;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+
+
+/**
+ * Provides an abstract class to be subclassed to create
+ * an HTTP servlet suitable for a Web site. A subclass of
+ * <code>HttpServlet</code> must override at least
+ * one method, usually one of these:
+ *
+ * <ul>
+ * <li> <code>doGet</code>, if the servlet supports HTTP GET requests
+ * <li> <code>doPost</code>, for HTTP POST requests
+ * <li> <code>doPut</code>, for HTTP PUT requests
+ * <li> <code>doDelete</code>, for HTTP DELETE requests
+ * <li> <code>init</code> and <code>destroy</code>,
+ * to manage resources that are held for the life of the servlet
+ * <li> <code>getServletInfo</code>, which the servlet uses to
+ * provide information about itself
+ * </ul>
+ *
+ * <p>There's almost no reason to override the <code>service</code>
+ * method. <code>service</code> handles standard HTTP
+ * requests by dispatching them to the handler methods
+ * for each HTTP request type (the <code>do</code><i>Method</i>
+ * methods listed above).
+ *
+ * <p>Likewise, there's almost no reason to override the
+ * <code>doOptions</code> and <code>doTrace</code> methods.
+ *
+ * <p>Servlets typically run on multithreaded servers,
+ * so be aware that a servlet must handle concurrent
+ * requests and be careful to synchronize access to shared resources.
+ * Shared resources include in-memory data such as
+ * instance or class variables and external objects
+ * such as files, database connections, and network
+ * connections.
+ * See the
+ * <a href="http://java.sun.com/Series/Tutorial/java/threads/multithreaded.html">
+ * Java Tutorial on Multithreaded Programming</a> for more
+ * information on handling multiple threads in a Java program.
+ */
+public abstract class HttpServlet extends GenericServlet {
+
+    private static final long serialVersionUID = 1L;
+
+    private static final String METHOD_DELETE = "DELETE";
+    private static final String METHOD_HEAD = "HEAD";
+    private static final String METHOD_GET = "GET";
+    private static final String METHOD_OPTIONS = "OPTIONS";
+    private static final String METHOD_POST = "POST";
+    private static final String METHOD_PUT = "PUT";
+    private static final String METHOD_TRACE = "TRACE";
+
+    private static final String HEADER_IFMODSINCE = "If-Modified-Since";
+    private static final String HEADER_LASTMOD = "Last-Modified";
+
+    private static final String LSTRING_FILE =
+        "javax.servlet.http.LocalStrings";
+    private static final ResourceBundle lStrings =
+        ResourceBundle.getBundle(LSTRING_FILE);
+
+
+    /**
+     * Does nothing, because this is an abstract class.
+     */
+    public HttpServlet() {
+        // NOOP
+    }
+
+
+    /**
+     * Called by the server (via the <code>service</code> method) to
+     * allow a servlet to handle a GET request.
+     *
+     * <p>Overriding this method to support a GET request also
+     * automatically supports an HTTP HEAD request. A HEAD
+     * request is a GET request that returns no body in the
+     * response, only the request header fields.
+     *
+     * <p>When overriding this method, read the request data,
+     * write the response headers, get the response's writer or
+     * output stream object, and finally, write the response data.
+     * It's best to include content type and encoding. When using
+     * a <code>PrintWriter</code> object to return the response,
+     * set the content type before accessing the
+     * <code>PrintWriter</code> object.
+     *
+     * <p>The servlet container must write the headers before
+     * committing the response, because in HTTP the headers must be sent
+     * before the response body.
+     *
+     * <p>Where possible, set the Content-Length header (with the
+     * {@link javax.servlet.ServletResponse#setContentLength} method),
+     * to allow the servlet container to use a persistent connection
+     * to return its response to the client, improving performance.
+     * The content length is automatically set if the entire response fits
+     * inside the response buffer.
+     *
+     * <p>When using HTTP 1.1 chunked encoding (which means that the response
+     * has a Transfer-Encoding header), do not set the Content-Length header.
+     *
+     * <p>The GET method should be safe, that is, without
+     * any side effects for which users are held responsible.
+     * For example, most form queries have no side effects.
+     * If a client request is intended to change stored data,
+     * the request should use some other HTTP method.
+     *
+     * <p>The GET method should also be idempotent, meaning
+     * that it can be safely repeated. Sometimes making a
+     * method safe also makes it idempotent. For example,
+     * repeating queries is both safe and idempotent, but
+     * buying a product online or modifying data is neither
+     * safe nor idempotent.
+     *
+     * <p>If the request is incorrectly formatted, <code>doGet</code>
+     * returns an HTTP "Bad Request" message.
+     *
+     * @param req   an {@link HttpServletRequest} object that
+     *                  contains the request the client has made
+     *                  of the servlet
+     *
+     * @param resp  an {@link HttpServletResponse} object that
+     *                  contains the response the servlet sends
+     *                  to the client
+     *
+     * @exception IOException   if an input or output error is
+     *                              detected when the servlet handles
+     *                              the GET request
+     *
+     * @exception ServletException  if the request for the GET
+     *                                  could not be handled
+     *
+     * @see javax.servlet.ServletResponse#setContentType
+     */
+    protected void doGet(HttpServletRequest req, HttpServletResponse resp)
+        throws ServletException, IOException
+    {
+        String protocol = req.getProtocol();
+        String msg = lStrings.getString("http.method_get_not_supported");
+        if (protocol.endsWith("1.1")) {
+            resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED, msg);
+        } else {
+            resp.sendError(HttpServletResponse.SC_BAD_REQUEST, msg);
+        }
+    }
+
+
+    /**
+     * Returns the time the <code>HttpServletRequest</code>
+     * object was last modified,
+     * in milliseconds since midnight January 1, 1970 GMT.
+     * If the time is unknown, this method returns a negative
+     * number (the default).
+     *
+     * <p>Servlets that support HTTP GET requests and can quickly determine
+     * their last modification time should override this method.
+     * This makes browser and proxy caches work more effectively,
+     * reducing the load on server and network resources.
+     *
+     * @param req   the <code>HttpServletRequest</code>
+     *                  object that is sent to the servlet
+     *
+     * @return  a <code>long</code> integer specifying
+     *              the time the <code>HttpServletRequest</code>
+     *              object was last modified, in milliseconds
+     *              since midnight, January 1, 1970 GMT, or
+     *              -1 if the time is not known
+     */
+    protected long getLastModified(HttpServletRequest req) {
+        return -1;
+    }
+
+
+    /**
+     * <p>Receives an HTTP HEAD request from the protected
+     * <code>service</code> method and handles the
+     * request.
+     * The client sends a HEAD request when it wants
+     * to see only the headers of a response, such as
+     * Content-Type or Content-Length. The HTTP HEAD
+     * method counts the output bytes in the response
+     * to set the Content-Length header accurately.
+     *
+     * <p>If you override this method, you can avoid computing
+     * the response body and just set the response headers
+     * directly to improve performance. Make sure that the
+     * <code>doHead</code> method you write is both safe
+     * and idempotent (that is, protects itself from being
+     * called multiple times for one HTTP HEAD request).
+     *
+     * <p>If the HTTP HEAD request is incorrectly formatted,
+     * <code>doHead</code> returns an HTTP "Bad Request"
+     * message.
+     *
+     * @param req   the request object that is passed to the servlet
+     *
+     * @param resp  the response object that the servlet
+     *                  uses to return the headers to the client
+     *
+     * @exception IOException   if an input or output error occurs
+     *
+     * @exception ServletException  if the request for the HEAD
+     *                                  could not be handled
+     */
+    protected void doHead(HttpServletRequest req, HttpServletResponse resp)
+        throws ServletException, IOException {
+
+        NoBodyResponse response = new NoBodyResponse(resp);
+
+        doGet(req, response);
+        response.setContentLength();
+    }
+
+
+    /**
+     * Called by the server (via the <code>service</code> method)
+     * to allow a servlet to handle a POST request.
+     *
+     * The HTTP POST method allows the client to send
+     * data of unlimited length to the Web server a single time
+     * and is useful when posting information such as
+     * credit card numbers.
+     *
+     * <p>When overriding this method, read the request data,
+     * write the response headers, get the response's writer or output
+     * stream object, and finally, write the response data. It's best
+     * to include content type and encoding. When using a
+     * <code>PrintWriter</code> object to return the response, set the
+     * content type before accessing the <code>PrintWriter</code> object.
+     *
+     * <p>The servlet container must write the headers before committing the
+     * response, because in HTTP the headers must be sent before the
+     * response body.
+     *
+     * <p>Where possible, set the Content-Length header (with the
+     * {@link javax.servlet.ServletResponse#setContentLength} method),
+     * to allow the servlet container to use a persistent connection
+     * to return its response to the client, improving performance.
+     * The content length is automatically set if the entire response fits
+     * inside the response buffer.
+     *
+     * <p>When using HTTP 1.1 chunked encoding (which means that the response
+     * has a Transfer-Encoding header), do not set the Content-Length header.
+     *
+     * <p>This method does not need to be either safe or idempotent.
+     * Operations requested through POST can have side effects for
+     * which the user can be held accountable, for example,
+     * updating stored data or buying items online.
+     *
+     * <p>If the HTTP POST request is incorrectly formatted,
+     * <code>doPost</code> returns an HTTP "Bad Request" message.
+     *
+     *
+     * @param req   an {@link HttpServletRequest} object that
+     *                  contains the request the client has made
+     *                  of the servlet
+     *
+     * @param resp  an {@link HttpServletResponse} object that
+     *                  contains the response the servlet sends
+     *                  to the client
+     *
+     * @exception IOException   if an input or output error is
+     *                              detected when the servlet handles
+     *                              the request
+     *
+     * @exception ServletException  if the request for the POST
+     *                                  could not be handled
+     *
+     * @see javax.servlet.ServletOutputStream
+     * @see javax.servlet.ServletResponse#setContentType
+     */
+    protected void doPost(HttpServletRequest req, HttpServletResponse resp)
+        throws ServletException, IOException {
+
+        String protocol = req.getProtocol();
+        String msg = lStrings.getString("http.method_post_not_supported");
+        if (protocol.endsWith("1.1")) {
+            resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED, msg);
+        } else {
+            resp.sendError(HttpServletResponse.SC_BAD_REQUEST, msg);
+        }
+    }
+
+
+    /**
+     * Called by the server (via the <code>service</code> method)
+     * to allow a servlet to handle a PUT request.
+     *
+     * The PUT operation allows a client to
+     * place a file on the server and is similar to
+     * sending a file by FTP.
+     *
+     * <p>When overriding this method, leave intact
+     * any content headers sent with the request (including
+     * Content-Length, Content-Type, Content-Transfer-Encoding,
+     * Content-Encoding, Content-Base, Content-Language, Content-Location,
+     * Content-MD5, and Content-Range). If your method cannot
+     * handle a content header, it must issue an error message
+     * (HTTP 501 - Not Implemented) and discard the request.
+     * For more information on HTTP 1.1, see RFC 2616
+     * <a href="http://www.ietf.org/rfc/rfc2616.txt"></a>.
+     *
+     * <p>This method does not need to be either safe or idempotent.
+     * Operations that <code>doPut</code> performs can have side
+     * effects for which the user can be held accountable. When using
+     * this method, it may be useful to save a copy of the
+     * affected URL in temporary storage.
+     *
+     * <p>If the HTTP PUT request is incorrectly formatted,
+     * <code>doPut</code> returns an HTTP "Bad Request" message.
+     *
+     * @param req   the {@link HttpServletRequest} object that
+     *                  contains the request the client made of
+     *                  the servlet
+     *
+     * @param resp  the {@link HttpServletResponse} object that
+     *                  contains the response the servlet returns
+     *                  to the client
+     *
+     * @exception IOException   if an input or output error occurs
+     *                              while the servlet is handling the
+     *                              PUT request
+     *
+     * @exception ServletException  if the request for the PUT
+     *                                  cannot be handled
+     */
+    protected void doPut(HttpServletRequest req, HttpServletResponse resp)
+        throws ServletException, IOException {
+
+        String protocol = req.getProtocol();
+        String msg = lStrings.getString("http.method_put_not_supported");
+        if (protocol.endsWith("1.1")) {
+            resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED, msg);
+        } else {
+            resp.sendError(HttpServletResponse.SC_BAD_REQUEST, msg);
+        }
+    }
+
+
+    /**
+     * Called by the server (via the <code>service</code> method)
+     * to allow a servlet to handle a DELETE request.
+     *
+     * The DELETE operation allows a client to remove a document
+     * or Web page from the server.
+     *
+     * <p>This method does not need to be either safe
+     * or idempotent. Operations requested through
+     * DELETE can have side effects for which users
+     * can be held accountable. When using
+     * this method, it may be useful to save a copy of the
+     * affected URL in temporary storage.
+     *
+     * <p>If the HTTP DELETE request is incorrectly formatted,
+     * <code>doDelete</code> returns an HTTP "Bad Request"
+     * message.
+     *
+     * @param req   the {@link HttpServletRequest} object that
+     *                  contains the request the client made of
+     *                  the servlet
+     *
+     *
+     * @param resp  the {@link HttpServletResponse} object that
+     *                  contains the response the servlet returns
+     *                  to the client
+     *
+     * @exception IOException   if an input or output error occurs
+     *                              while the servlet is handling the
+     *                              DELETE request
+     *
+     * @exception ServletException  if the request for the
+     *                                  DELETE cannot be handled
+     */
+    protected void doDelete(HttpServletRequest req,
+                            HttpServletResponse resp)
+        throws ServletException, IOException {
+
+        String protocol = req.getProtocol();
+        String msg = lStrings.getString("http.method_delete_not_supported");
+        if (protocol.endsWith("1.1")) {
+            resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED, msg);
+        } else {
+            resp.sendError(HttpServletResponse.SC_BAD_REQUEST, msg);
+        }
+    }
+
+
+    private static Method[] getAllDeclaredMethods(Class<?> c) {
+
+        if (c.equals(javax.servlet.http.HttpServlet.class)) {
+            return null;
+        }
+
+        Method[] parentMethods = getAllDeclaredMethods(c.getSuperclass());
+        Method[] thisMethods = c.getDeclaredMethods();
+
+        if ((parentMethods != null) && (parentMethods.length > 0)) {
+            Method[] allMethods =
+                new Method[parentMethods.length + thisMethods.length];
+            System.arraycopy(parentMethods, 0, allMethods, 0,
+                             parentMethods.length);
+            System.arraycopy(thisMethods, 0, allMethods, parentMethods.length,
+                             thisMethods.length);
+
+            thisMethods = allMethods;
+        }
+
+        return thisMethods;
+    }
+
+
+    /**
+     * Called by the server (via the <code>service</code> method)
+     * to allow a servlet to handle a OPTIONS request.
+     *
+     * The OPTIONS request determines which HTTP methods
+     * the server supports and
+     * returns an appropriate header. For example, if a servlet
+     * overrides <code>doGet</code>, this method returns the
+     * following header:
+     *
+     * <p><code>Allow: GET, HEAD, TRACE, OPTIONS</code>
+     *
+     * <p>There's no need to override this method unless the
+     * servlet implements new HTTP methods, beyond those
+     * implemented by HTTP 1.1.
+     *
+     * @param req   the {@link HttpServletRequest} object that
+     *                  contains the request the client made of
+     *                  the servlet
+     *
+     * @param resp  the {@link HttpServletResponse} object that
+     *                  contains the response the servlet returns
+     *                  to the client
+     *
+     * @exception IOException   if an input or output error occurs
+     *                              while the servlet is handling the
+     *                              OPTIONS request
+     *
+     * @exception ServletException  if the request for the
+     *                                  OPTIONS cannot be handled
+     */
+    protected void doOptions(HttpServletRequest req,
+            HttpServletResponse resp)
+        throws ServletException, IOException {
+
+        Method[] methods = getAllDeclaredMethods(this.getClass());
+
+        boolean ALLOW_GET = false;
+        boolean ALLOW_HEAD = false;
+        boolean ALLOW_POST = false;
+        boolean ALLOW_PUT = false;
+        boolean ALLOW_DELETE = false;
+        boolean ALLOW_TRACE = true;
+        boolean ALLOW_OPTIONS = true;
+
+        for (int i=0; i<methods.length; i++) {
+            Method m = methods[i];
+
+            if (m.getName().equals("doGet")) {
+                ALLOW_GET = true;
+                ALLOW_HEAD = true;
+            }
+            if (m.getName().equals("doPost"))
+                ALLOW_POST = true;
+            if (m.getName().equals("doPut"))
+                ALLOW_PUT = true;
+            if (m.getName().equals("doDelete"))
+                ALLOW_DELETE = true;
+        }
+
+        String allow = null;
+        if (ALLOW_GET)
+            allow=METHOD_GET;
+        if (ALLOW_HEAD)
+            if (allow==null) allow=METHOD_HEAD;
+            else allow += ", " + METHOD_HEAD;
+        if (ALLOW_POST)
+            if (allow==null) allow=METHOD_POST;
+            else allow += ", " + METHOD_POST;
+        if (ALLOW_PUT)
+            if (allow==null) allow=METHOD_PUT;
+            else allow += ", " + METHOD_PUT;
+        if (ALLOW_DELETE)
+            if (allow==null) allow=METHOD_DELETE;
+            else allow += ", " + METHOD_DELETE;
+        if (ALLOW_TRACE)
+            if (allow==null) allow=METHOD_TRACE;
+            else allow += ", " + METHOD_TRACE;
+        if (ALLOW_OPTIONS)
+            if (allow==null) allow=METHOD_OPTIONS;
+            else allow += ", " + METHOD_OPTIONS;
+
+        resp.setHeader("Allow", allow);
+    }
+
+
+    /**
+     * Called by the server (via the <code>service</code> method)
+     * to allow a servlet to handle a TRACE request.
+     *
+     * A TRACE returns the headers sent with the TRACE
+     * request to the client, so that they can be used in
+     * debugging. There's no need to override this method.
+     *
+     * @param req   the {@link HttpServletRequest} object that
+     *                  contains the request the client made of
+     *                  the servlet
+     *
+     * @param resp  the {@link HttpServletResponse} object that
+     *                  contains the response the servlet returns
+     *                  to the client
+     *
+     * @exception IOException   if an input or output error occurs
+     *                              while the servlet is handling the
+     *                              TRACE request
+     *
+     * @exception ServletException  if the request for the
+     *                                  TRACE cannot be handled
+     */
+    protected void doTrace(HttpServletRequest req, HttpServletResponse resp)
+        throws ServletException, IOException
+    {
+
+        int responseLength;
+
+        String CRLF = "\r\n";
+        StringBuilder buffer = new StringBuilder("TRACE ").append(req.getRequestURI())
+            .append(" ").append(req.getProtocol());
+
+        Enumeration<String> reqHeaderEnum = req.getHeaderNames();
+
+        while( reqHeaderEnum.hasMoreElements() ) {
+            String headerName = reqHeaderEnum.nextElement();
+            buffer.append(CRLF).append(headerName).append(": ")
+                .append(req.getHeader(headerName));
+        }
+
+        buffer.append(CRLF);
+
+        responseLength = buffer.length();
+
+        resp.setContentType("message/http");
+        resp.setContentLength(responseLength);
+        ServletOutputStream out = resp.getOutputStream();
+        out.print(buffer.toString());
+        out.close();
+        return;
+    }
+
+
+    /**
+     * Receives standard HTTP requests from the public
+     * <code>service</code> method and dispatches
+     * them to the <code>do</code><i>Method</i> methods defined in
+     * this class. This method is an HTTP-specific version of the
+     * {@link javax.servlet.Servlet#service} method. There's no
+     * need to override this method.
+     *
+     * @param req   the {@link HttpServletRequest} object that
+     *                  contains the request the client made of
+     *                  the servlet
+     *
+     * @param resp  the {@link HttpServletResponse} object that
+     *                  contains the response the servlet returns
+     *                  to the client
+     *
+     * @exception IOException   if an input or output error occurs
+     *                              while the servlet is handling the
+     *                              HTTP request
+     *
+     * @exception ServletException  if the HTTP request
+     *                                  cannot be handled
+     *
+     * @see javax.servlet.Servlet#service
+     */
+    protected void service(HttpServletRequest req, HttpServletResponse resp)
+        throws ServletException, IOException {
+
+        String method = req.getMethod();
+
+        if (method.equals(METHOD_GET)) {
+            long lastModified = getLastModified(req);
+            if (lastModified == -1) {
+                // servlet doesn't support if-modified-since, no reason
+                // to go through further expensive logic
+                doGet(req, resp);
+            } else {
+                long ifModifiedSince;
+                try {
+                    ifModifiedSince = req.getDateHeader(HEADER_IFMODSINCE);
+                } catch (IllegalArgumentException iae) {
+                    // Invalid date header - proceed as if none was set
+                    ifModifiedSince = -1;
+                }
+                if (ifModifiedSince < (lastModified / 1000 * 1000)) {
+                    // If the servlet mod time is later, call doGet()
+                    // Round down to the nearest second for a proper compare
+                    // A ifModifiedSince of -1 will always be less
+                    maybeSetLastModified(resp, lastModified);
+                    doGet(req, resp);
+                } else {
+                    resp.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
+                }
+            }
+
+        } else if (method.equals(METHOD_HEAD)) {
+            long lastModified = getLastModified(req);
+            maybeSetLastModified(resp, lastModified);
+            doHead(req, resp);
+
+        } else if (method.equals(METHOD_POST)) {
+            doPost(req, resp);
+
+        } else if (method.equals(METHOD_PUT)) {
+            doPut(req, resp);
+
+        } else if (method.equals(METHOD_DELETE)) {
+            doDelete(req, resp);
+
+        } else if (method.equals(METHOD_OPTIONS)) {
+            doOptions(req,resp);
+
+        } else if (method.equals(METHOD_TRACE)) {
+            doTrace(req,resp);
+
+        } else {
+            //
+            // Note that this means NO servlet supports whatever
+            // method was requested, anywhere on this server.
+            //
+
+            String errMsg = lStrings.getString("http.method_not_implemented");
+            Object[] errArgs = new Object[1];
+            errArgs[0] = method;
+            errMsg = MessageFormat.format(errMsg, errArgs);
+
+            resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED, errMsg);
+        }
+    }
+
+
+    /*
+     * Sets the Last-Modified entity header field, if it has not
+     * already been set and if the value is meaningful.  Called before
+     * doGet, to ensure that headers are set before response data is
+     * written.  A subclass might have set this header already, so we
+     * check.
+     */
+    private void maybeSetLastModified(HttpServletResponse resp,
+                                      long lastModified) {
+        if (resp.containsHeader(HEADER_LASTMOD))
+            return;
+        if (lastModified >= 0)
+            resp.setDateHeader(HEADER_LASTMOD, lastModified);
+    }
+
+
+    /**
+     * Dispatches client requests to the protected
+     * <code>service</code> method. There's no need to
+     * override this method.
+     *
+     * @param req   the {@link HttpServletRequest} object that
+     *                  contains the request the client made of
+     *                  the servlet
+     *
+     * @param res   the {@link HttpServletResponse} object that
+     *                  contains the response the servlet returns
+     *                  to the client
+     *
+     * @exception IOException   if an input or output error occurs
+     *                              while the servlet is handling the
+     *                              HTTP request
+     *
+     * @exception ServletException  if the HTTP request cannot
+     *                                  be handled
+     *
+     * @see javax.servlet.Servlet#service
+     */
+    @Override
+    public void service(ServletRequest req, ServletResponse res)
+        throws ServletException, IOException {
+
+        HttpServletRequest  request;
+        HttpServletResponse response;
+
+        try {
+            request = (HttpServletRequest) req;
+            response = (HttpServletResponse) res;
+        } catch (ClassCastException e) {
+            throw new ServletException("non-HTTP request or response");
+        }
+        service(request, response);
+    }
+}
+
+
+/*
+ * A response wrapper for use in (dumb) "HEAD" support.
+ * This just swallows that body, counting the bytes in order to set
+ * the content length appropriately.  All other methods delegate to the
+ * wrapped HTTP Servlet Response object.
+ */
+// file private
+class NoBodyResponse extends HttpServletResponseWrapper {
+    private final NoBodyOutputStream noBody;
+    private PrintWriter writer;
+    private boolean didSetContentLength;
+
+    // file private
+    NoBodyResponse(HttpServletResponse r) {
+        super(r);
+        noBody = new NoBodyOutputStream();
+    }
+
+    // file private
+    void setContentLength() {
+        if (!didSetContentLength) {
+            if (writer != null) {
+                writer.flush();
+            }
+            super.setContentLength(noBody.getContentLength());
+        }
+    }
+
+
+    // SERVLET RESPONSE interface methods
+
+    @Override
+    public void setContentLength(int len) {
+        super.setContentLength(len);
+        didSetContentLength = true;
+    }
+
+    @Override
+    public void setContentLengthLong(long len) {
+        super.setContentLengthLong(len);
+        didSetContentLength = true;
+    }
+
+    @Override
+    public void setHeader(String name, String value) {
+        super.setHeader(name, value);
+        checkHeader(name);
+    }
+
+    @Override
+    public void addHeader(String name, String value) {
+        super.addHeader(name, value);
+        checkHeader(name);
+    }
+
+    @Override
+    public void setIntHeader(String name, int value) {
+        super.setIntHeader(name, value);
+        checkHeader(name);
+    }
+
+    @Override
+    public void addIntHeader(String name, int value) {
+        super.addIntHeader(name, value);
+        checkHeader(name);
+    }
+
+    private void checkHeader(String name) {
+        if ("content-length".equalsIgnoreCase(name)) {
+            didSetContentLength = true;
+        }
+    }
+
+    @Override
+    public ServletOutputStream getOutputStream() throws IOException {
+        return noBody;
+    }
+
+    @Override
+    public PrintWriter getWriter() throws UnsupportedEncodingException {
+
+        if (writer == null) {
+            OutputStreamWriter w;
+
+            w = new OutputStreamWriter(noBody, getCharacterEncoding());
+            writer = new PrintWriter(w);
+        }
+        return writer;
+    }
+}
+
+
+/*
+ * Servlet output stream that gobbles up all its data.
+ */
+
+// file private
+class NoBodyOutputStream extends ServletOutputStream {
+
+    private static final String LSTRING_FILE =
+        "javax.servlet.http.LocalStrings";
+    private static final ResourceBundle lStrings =
+        ResourceBundle.getBundle(LSTRING_FILE);
+
+    private int contentLength = 0;
+
+    // file private
+    NoBodyOutputStream() {
+        // NOOP
+    }
+
+    // file private
+    int getContentLength() {
+        return contentLength;
+    }
+
+    @Override
+    public void write(int b) {
+        contentLength++;
+    }
+
+    @Override
+    public void write(byte buf[], int offset, int len) throws IOException {
+        if (buf == null) {
+            throw new NullPointerException(
+                    lStrings.getString("err.io.nullArray"));
+        }
+
+        if (offset < 0 || len < 0 || offset+len > buf.length) {
+            String msg = lStrings.getString("err.io.indexOutOfBounds");
+            Object[] msgArgs = new Object[3];
+            msgArgs[0] = Integer.valueOf(offset);
+            msgArgs[1] = Integer.valueOf(len);
+            msgArgs[2] = Integer.valueOf(buf.length);
+            msg = MessageFormat.format(msg, msgArgs);
+            throw new IndexOutOfBoundsException(msg);
+        }
+
+        contentLength += len;
+    }
+
+    @Override
+    public boolean isReady() {
+        // TODO SERVLET 3.1
+        return false;
+    }
+
+    @Override
+    public void setWriteListener(javax.servlet.WriteListener listener) {
+        // TODO SERVLET 3.1
+    }
+}
diff --git a/lib/servlet-api/javax/servlet/http/HttpServletRequest.java b/lib/servlet-api/javax/servlet/http/HttpServletRequest.java
new file mode 100644 (file)
index 0000000..3c1f6a1
--- /dev/null
@@ -0,0 +1,522 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package javax.servlet.http;
+
+import java.io.IOException;
+import java.util.Collection;
+import java.util.Enumeration;
+
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+
+/**
+ * Extends the {@link javax.servlet.ServletRequest} interface to provide request
+ * information for HTTP servlets.
+ * <p>
+ * The servlet container creates an <code>HttpServletRequest</code> object and
+ * passes it as an argument to the servlet's service methods
+ * (<code>doGet</code>, <code>doPost</code>, etc).
+ */
+public interface HttpServletRequest extends ServletRequest {
+
+    /**
+     * String identifier for Basic authentication. Value "BASIC"
+     */
+    public static final String BASIC_AUTH = "BASIC";
+    /**
+     * String identifier for Form authentication. Value "FORM"
+     */
+    public static final String FORM_AUTH = "FORM";
+    /**
+     * String identifier for Client Certificate authentication. Value
+     * "CLIENT_CERT"
+     */
+    public static final String CLIENT_CERT_AUTH = "CLIENT_CERT";
+    /**
+     * String identifier for Digest authentication. Value "DIGEST"
+     */
+    public static final String DIGEST_AUTH = "DIGEST";
+
+    /**
+     * Returns the name of the authentication scheme used to protect the
+     * servlet. All servlet containers support basic, form and client
+     * certificate authentication, and may additionally support digest
+     * authentication. If the servlet is not authenticated <code>null</code> is
+     * returned.
+     * <p>
+     * Same as the value of the CGI variable AUTH_TYPE.
+     *
+     * @return one of the static members BASIC_AUTH, FORM_AUTH, CLIENT_CERT_AUTH,
+     *         DIGEST_AUTH (suitable for == comparison) or the
+     *         container-specific string indicating the authentication scheme,
+     *         or <code>null</code> if the request was not authenticated.
+     */
+    public String getAuthType();
+
+    /**
+     * Returns an array containing all of the <code>Cookie</code> objects the
+     * client sent with this request. This method returns <code>null</code> if
+     * no cookies were sent.
+     *
+     * @return an array of all the <code>Cookies</code> included with this
+     *         request, or <code>null</code> if the request has no cookies
+     */
+    public Cookie[] getCookies();
+
+    /**
+     * Returns the value of the specified request header as a <code>long</code>
+     * value that represents a <code>Date</code> object. Use this method with
+     * headers that contain dates, such as <code>If-Modified-Since</code>.
+     * <p>
+     * The date is returned as the number of milliseconds since January 1, 1970
+     * GMT. The header name is case insensitive.
+     * <p>
+     * If the request did not have a header of the specified name, this method
+     * returns -1. If the header can't be converted to a date, the method throws
+     * an <code>IllegalArgumentException</code>.
+     *
+     * @param name
+     *            a <code>String</code> specifying the name of the header
+     * @return a <code>long</code> value representing the date specified in the
+     *         header expressed as the number of milliseconds since January 1,
+     *         1970 GMT, or -1 if the named header was not included with the
+     *         request
+     * @exception IllegalArgumentException
+     *                If the header value can't be converted to a date
+     */
+    public long getDateHeader(String name);
+
+    /**
+     * Returns the value of the specified request header as a
+     * <code>String</code>. If the request did not include a header of the
+     * specified name, this method returns <code>null</code>. If there are
+     * multiple headers with the same name, this method returns the first head
+     * in the request. The header name is case insensitive. You can use this
+     * method with any request header.
+     *
+     * @param name
+     *            a <code>String</code> specifying the header name
+     * @return a <code>String</code> containing the value of the requested
+     *         header, or <code>null</code> if the request does not have a
+     *         header of that name
+     */
+    public String getHeader(String name);
+
+    /**
+     * Returns all the values of the specified request header as an
+     * <code>Enumeration</code> of <code>String</code> objects.
+     * <p>
+     * Some headers, such as <code>Accept-Language</code> can be sent by clients
+     * as several headers each with a different value rather than sending the
+     * header as a comma separated list.
+     * <p>
+     * If the request did not include any headers of the specified name, this
+     * method returns an empty <code>Enumeration</code>. The header name is case
+     * insensitive. You can use this method with any request header.
+     *
+     * @param name
+     *            a <code>String</code> specifying the header name
+     * @return an <code>Enumeration</code> containing the values of the requested
+     *         header. If the request does not have any headers of that name
+     *         return an empty enumeration. If the container does not allow
+     *         access to header information, return null
+     */
+    public Enumeration<String> getHeaders(String name);
+
+    /**
+     * Returns an enumeration of all the header names this request contains. If
+     * the request has no headers, this method returns an empty enumeration.
+     * <p>
+     * Some servlet containers do not allow servlets to access headers using
+     * this method, in which case this method returns <code>null</code>
+     *
+     * @return an enumeration of all the header names sent with this request; if
+     *         the request has no headers, an empty enumeration; if the servlet
+     *         container does not allow servlets to use this method,
+     *         <code>null</code>
+     */
+    public Enumeration<String> getHeaderNames();
+
+    /**
+     * Returns the value of the specified request header as an <code>int</code>.
+     * If the request does not have a header of the specified name, this method
+     * returns -1. If the header cannot be converted to an integer, this method
+     * throws a <code>NumberFormatException</code>.
+     * <p>
+     * The header name is case insensitive.
+     *
+     * @param name
+     *            a <code>String</code> specifying the name of a request header
+     * @return an integer expressing the value of the request header or -1 if the
+     *         request doesn't have a header of this name
+     * @exception NumberFormatException
+     *                If the header value can't be converted to an
+     *                <code>int</code>
+     */
+    public int getIntHeader(String name);
+
+    /**
+     * Returns the name of the HTTP method with which this request was made, for
+     * example, GET, POST, or PUT. Same as the value of the CGI variable
+     * REQUEST_METHOD.
+     *
+     * @return a <code>String</code> specifying the name of the method with
+     *         which this request was made
+     */
+    public String getMethod();
+
+    /**
+     * Returns any extra path information associated with the URL the client
+     * sent when it made this request. The extra path information follows the
+     * servlet path but precedes the query string and will start with a "/"
+     * character.
+     * <p>
+     * This method returns <code>null</code> if there was no extra path
+     * information.
+     * <p>
+     * Same as the value of the CGI variable PATH_INFO.
+     *
+     * @return a <code>String</code>, decoded by the web container, specifying
+     *         extra path information that comes after the servlet path but
+     *         before the query string in the request URL; or <code>null</code>
+     *         if the URL does not have any extra path information
+     */
+    public String getPathInfo();
+
+    /**
+     * Returns any extra path information after the servlet name but before the
+     * query string, and translates it to a real path. Same as the value of the
+     * CGI variable PATH_TRANSLATED.
+     * <p>
+     * If the URL does not have any extra path information, this method returns
+     * <code>null</code> or the servlet container cannot translate the virtual
+     * path to a real path for any reason (such as when the web application is
+     * executed from an archive). The web container does not decode this string.
+     *
+     * @return a <code>String</code> specifying the real path, or
+     *         <code>null</code> if the URL does not have any extra path
+     *         information
+     */
+    public String getPathTranslated();
+
+    /**
+     * Returns the portion of the request URI that indicates the context of the
+     * request. The context path always comes first in a request URI. The path
+     * starts with a "/" character but does not end with a "/" character. For
+     * servlets in the default (root) context, this method returns "". The
+     * container does not decode this string.
+     *
+     * @return a <code>String</code> specifying the portion of the request URI
+     *         that indicates the context of the request
+     */
+    public String getContextPath();
+
+    /**
+     * Returns the query string that is contained in the request URL after the
+     * path. This method returns <code>null</code> if the URL does not have a
+     * query string. Same as the value of the CGI variable QUERY_STRING.
+     *
+     * @return a <code>String</code> containing the query string or
+     *         <code>null</code> if the URL contains no query string. The value
+     *         is not decoded by the container.
+     */
+    public String getQueryString();
+
+    /**
+     * Returns the login of the user making this request, if the user has been
+     * authenticated, or <code>null</code> if the user has not been
+     * authenticated. Whether the user name is sent with each subsequent request
+     * depends on the browser and type of authentication. Same as the value of
+     * the CGI variable REMOTE_USER.
+     *
+     * @return a <code>String</code> specifying the login of the user making
+     *         this request, or <code>null</code> if the user login is not known
+     */
+    public String getRemoteUser();
+
+    /**
+     * Returns a boolean indicating whether the authenticated user is included
+     * in the specified logical "role". Roles and role membership can be defined
+     * using deployment descriptors. If the user has not been authenticated, the
+     * method returns <code>false</code>.
+     *
+     * @param role
+     *            a <code>String</code> specifying the name of the role
+     * @return a <code>boolean</code> indicating whether the user making this
+     *         request belongs to a given role; <code>false</code> if the user
+     *         has not been authenticated
+     */
+    public boolean isUserInRole(String role);
+
+    /**
+     * Returns a <code>java.security.Principal</code> object containing the name
+     * of the current authenticated user. If the user has not been
+     * authenticated, the method returns <code>null</code>.
+     *
+     * @return a <code>java.security.Principal</code> containing the name of the
+     *         user making this request; <code>null</code> if the user has not
+     *         been authenticated
+     */
+    public java.security.Principal getUserPrincipal();
+
+    /**
+     * Returns the session ID specified by the client. This may not be the same
+     * as the ID of the current valid session for this request. If the client
+     * did not specify a session ID, this method returns <code>null</code>.
+     *
+     * @return a <code>String</code> specifying the session ID, or
+     *         <code>null</code> if the request did not specify a session ID
+     * @see #isRequestedSessionIdValid
+     */
+    public String getRequestedSessionId();
+
+    /**
+     * Returns the part of this request's URL from the protocol name up to the
+     * query string in the first line of the HTTP request. The web container
+     * does not decode this String. For example:
+     * <table summary="Examples of Returned Values">
+     * <tr align=left>
+     * <th>First line of HTTP request</th>
+     * <th>Returned Value</th>
+     * <tr>
+     * <td>POST /some/path.html HTTP/1.1
+     * <td>
+     * <td>/some/path.html
+     * <tr>
+     * <td>GET http://foo.bar/a.html HTTP/1.0
+     * <td>
+     * <td>/a.html
+     * <tr>
+     * <td>HEAD /xyz?a=b HTTP/1.1
+     * <td>
+     * <td>/xyz
+     * </table>
+     * <p>
+     * To reconstruct an URL with a scheme and host, use
+     * {@link #getRequestURL}.
+     *
+     * @return a <code>String</code> containing the part of the URL from the
+     *         protocol name up to the query string
+     * @see #getRequestURL
+     */
+    public String getRequestURI();
+
+    /**
+     * Reconstructs the URL the client used to make the request. The returned
+     * URL contains a protocol, server name, port number, and server path, but
+     * it does not include query string parameters.
+     * <p>
+     * Because this method returns a <code>StringBuffer</code>, not a string,
+     * you can modify the URL easily, for example, to append query parameters.
+     * <p>
+     * This method is useful for creating redirect messages and for reporting
+     * errors.
+     *
+     * @return a <code>StringBuffer</code> object containing the reconstructed
+     *         URL
+     */
+    public StringBuffer getRequestURL();
+
+    /**
+     * Returns the part of this request's URL that calls the servlet. This path
+     * starts with a "/" character and includes either the servlet name or a
+     * path to the servlet, but does not include any extra path information or a
+     * query string. Same as the value of the CGI variable SCRIPT_NAME.
+     * <p>
+     * This method will return an empty string ("") if the servlet used to
+     * process this request was matched using the "/*" pattern.
+     *
+     * @return a <code>String</code> containing the name or path of the servlet
+     *         being called, as specified in the request URL, decoded, or an
+     *         empty string if the servlet used to process the request is
+     *         matched using the "/*" pattern.
+     */
+    public String getServletPath();
+
+    /**
+     * Returns the current <code>HttpSession</code> associated with this request
+     * or, if there is no current session and <code>create</code> is true,
+     * returns a new session.
+     * <p>
+     * If <code>create</code> is <code>false</code> and the request has no valid
+     * <code>HttpSession</code>, this method returns <code>null</code>.
+     * <p>
+     * To make sure the session is properly maintained, you must call this
+     * method before the response is committed. If the container is using
+     * cookies to maintain session integrity and is asked to create a new
+     * session when the response is committed, an IllegalStateException is
+     * thrown.
+     *
+     * @param create
+     *            <code>true</code> to create a new session for this request if
+     *            necessary; <code>false</code> to return <code>null</code> if
+     *            there's no current session
+     * @return the <code>HttpSession</code> associated with this request or
+     *         <code>null</code> if <code>create</code> is <code>false</code>
+     *         and the request has no valid session
+     * @see #getSession()
+     */
+    public HttpSession getSession(boolean create);
+
+    /**
+     * Returns the current session associated with this request, or if the
+     * request does not have a session, creates one.
+     *
+     * @return the <code>HttpSession</code> associated with this request
+     * @see #getSession(boolean)
+     */
+    public HttpSession getSession();
+
+    /**
+     * Changes the session ID of the session associated with this request. This
+     * method does not create a new session object it only changes the ID of the
+     * current session.
+     *
+     * @return the new session ID allocated to the session
+     * @see HttpSessionIdListener
+     * @since Servlet 3.1
+     */
+    public String changeSessionId();
+
+    /**
+     * Checks whether the requested session ID is still valid.
+     *
+     * @return <code>true</code> if this request has an id for a valid session
+     *         in the current session context; <code>false</code> otherwise
+     * @see #getRequestedSessionId
+     * @see #getSession
+     */
+    public boolean isRequestedSessionIdValid();
+
+    /**
+     * Checks whether the requested session ID came in as a cookie.
+     *
+     * @return <code>true</code> if the session ID came in as a cookie;
+     *         otherwise, <code>false</code>
+     * @see #getSession
+     */
+    public boolean isRequestedSessionIdFromCookie();
+
+    /**
+     * Checks whether the requested session ID came in as part of the request
+     * URL.
+     *
+     * @return <code>true</code> if the session ID came in as part of a URL;
+     *         otherwise, <code>false</code>
+     * @see #getSession
+     */
+    public boolean isRequestedSessionIdFromURL();
+
+    /**
+     * @deprecated As of Version 2.1 of the Java Servlet API, use
+     *             {@link #isRequestedSessionIdFromURL} instead.
+     */
+    @SuppressWarnings("dep-ann")
+    // Spec API does not use @Deprecated
+    public boolean isRequestedSessionIdFromUrl();
+
+    /**
+     * Triggers the same authentication process as would be triggered if the
+     * request is for a resource that is protected by a security constraint.
+     *
+     * @param response  The response to use to return any authentication
+     *                  challenge
+     * @return <code>true</code> if the user is successfully authenticated and
+     *         <code>false</code> if not
+     *
+     * @since Servlet 3.0
+     */
+    public boolean authenticate(HttpServletResponse response)
+            throws IOException, ServletException;
+
+    /**
+     * Authenticate the provided user name and password and then associated the
+     * authenticated user with the request.
+     *
+     * @param username  The user name to authenticate
+     * @param password  The password to use to authenticate the user
+     *
+     * @throws ServletException
+     *             If any of {@link #getRemoteUser()},
+     *             {@link #getUserPrincipal()} or {@link #getAuthType()} are
+     *             non-null, if the configured authenticator does not support
+     *             user name and password authentication or if the
+     *             authentication fails
+     * @since Servlet 3.0
+     */
+    public void login(String username, String password) throws ServletException;
+
+    /**
+     * Removes any authenticated user from the request.
+     *
+     * @throws ServletException
+     *             If the logout fails
+     * @since Servlet 3.0
+     */
+    public void logout() throws ServletException;
+
+    /**
+     * Return a collection of all uploaded Parts.
+     *
+     * @return A collection of all uploaded Parts.
+     * @throws IOException
+     *             if an I/O error occurs
+     * @throws IllegalStateException
+     *             if size limits are exceeded or no multipart configuration is
+     *             provided
+     * @throws ServletException
+     *             if the request is not multipart/form-data
+     * @since Servlet 3.0
+     */
+    public Collection<Part> getParts() throws IOException,
+            ServletException;
+
+    /**
+     * Gets the named Part or null if the Part does not exist. Triggers upload
+     * of all Parts.
+     *
+     * @param name
+     * @return The named Part or null if the Part does not exist
+     * @throws IOException
+     *             if an I/O error occurs
+     * @throws IllegalStateException
+     *             if size limits are exceeded
+     * @throws ServletException
+     *             if the request is not multipart/form-data
+     * @since Servlet 3.0
+     */
+    public Part getPart(String name) throws IOException,
+            ServletException;
+
+    /**
+     * Start the HTTP upgrade process and pass the connection to the provided
+     * protocol handler once the current request/response pair has completed
+     * processing. Calling this method sets the response status to {@link
+     * HttpServletResponse#SC_SWITCHING_PROTOCOLS} and flushes the response.
+     * Protocol specific headers must have already been set before this method
+     * is called.
+     *
+     * @throws IOException
+     *             if an I/O error occurred during the upgrade
+     * @throws ServletException
+     *             if the given httpUpgradeHandlerClass fails to be instantiated
+     * @since Servlet 3.1
+     */
+    public <T extends HttpUpgradeHandler> T upgrade(
+            Class<T> httpUpgradeHandlerClass) throws java.io.IOException, ServletException;
+}
diff --git a/lib/servlet-api/javax/servlet/http/HttpServletRequestWrapper.java b/lib/servlet-api/javax/servlet/http/HttpServletRequestWrapper.java
new file mode 100644 (file)
index 0000000..a72a244
--- /dev/null
@@ -0,0 +1,376 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package javax.servlet.http;
+
+import java.io.IOException;
+import java.util.Collection;
+import java.util.Enumeration;
+
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequestWrapper;
+
+/**
+ * Provides a convenient implementation of the HttpServletRequest interface that
+ * can be subclassed by developers wishing to adapt the request to a Servlet.
+ * This class implements the Wrapper or Decorator pattern. Methods default to
+ * calling through to the wrapped request object.
+ *
+ * @see javax.servlet.http.HttpServletRequest
+ * @since v 2.3
+ */
+public class HttpServletRequestWrapper extends ServletRequestWrapper implements
+        HttpServletRequest {
+
+    /**
+     * Constructs a request object wrapping the given request.
+     *
+     * @throws java.lang.IllegalArgumentException
+     *             if the request is null
+     */
+    public HttpServletRequestWrapper(HttpServletRequest request) {
+        super(request);
+    }
+
+    private HttpServletRequest _getHttpServletRequest() {
+        return (HttpServletRequest) super.getRequest();
+    }
+
+    /**
+     * The default behavior of this method is to return getAuthType() on the
+     * wrapped request object.
+     */
+    @Override
+    public String getAuthType() {
+        return this._getHttpServletRequest().getAuthType();
+    }
+
+    /**
+     * The default behavior of this method is to return getCookies() on the
+     * wrapped request object.
+     */
+    @Override
+    public Cookie[] getCookies() {
+        return this._getHttpServletRequest().getCookies();
+    }
+
+    /**
+     * The default behavior of this method is to return getDateHeader(String
+     * name) on the wrapped request object.
+     */
+    @Override
+    public long getDateHeader(String name) {
+        return this._getHttpServletRequest().getDateHeader(name);
+    }
+
+    /**
+     * The default behavior of this method is to return getHeader(String name)
+     * on the wrapped request object.
+     */
+    @Override
+    public String getHeader(String name) {
+        return this._getHttpServletRequest().getHeader(name);
+    }
+
+    /**
+     * The default behavior of this method is to return getHeaders(String name)
+     * on the wrapped request object.
+     */
+    @Override
+    public Enumeration<String> getHeaders(String name) {
+        return this._getHttpServletRequest().getHeaders(name);
+    }
+
+    /**
+     * The default behavior of this method is to return getHeaderNames() on the
+     * wrapped request object.
+     */
+    @Override
+    public Enumeration<String> getHeaderNames() {
+        return this._getHttpServletRequest().getHeaderNames();
+    }
+
+    /**
+     * The default behavior of this method is to return getIntHeader(String
+     * name) on the wrapped request object.
+     */
+    @Override
+    public int getIntHeader(String name) {
+        return this._getHttpServletRequest().getIntHeader(name);
+    }
+
+    /**
+     * The default behavior of this method is to return getMethod() on the
+     * wrapped request object.
+     */
+    @Override
+    public String getMethod() {
+        return this._getHttpServletRequest().getMethod();
+    }
+
+    /**
+     * The default behavior of this method is to return getPathInfo() on the
+     * wrapped request object.
+     */
+    @Override
+    public String getPathInfo() {
+        return this._getHttpServletRequest().getPathInfo();
+    }
+
+    /**
+     * The default behavior of this method is to return getPathTranslated() on
+     * the wrapped request object.
+     */
+    @Override
+    public String getPathTranslated() {
+        return this._getHttpServletRequest().getPathTranslated();
+    }
+
+    /**
+     * The default behavior of this method is to return getContextPath() on the
+     * wrapped request object.
+     */
+    @Override
+    public String getContextPath() {
+        return this._getHttpServletRequest().getContextPath();
+    }
+
+    /**
+     * The default behavior of this method is to return getQueryString() on the
+     * wrapped request object.
+     */
+    @Override
+    public String getQueryString() {
+        return this._getHttpServletRequest().getQueryString();
+    }
+
+    /**
+     * The default behavior of this method is to return getRemoteUser() on the
+     * wrapped request object.
+     */
+    @Override
+    public String getRemoteUser() {
+        return this._getHttpServletRequest().getRemoteUser();
+    }
+
+    /**
+     * The default behavior of this method is to return isUserInRole(String
+     * role) on the wrapped request object.
+     */
+    @Override
+    public boolean isUserInRole(String role) {
+        return this._getHttpServletRequest().isUserInRole(role);
+    }
+
+    /**
+     * The default behavior of this method is to return getUserPrincipal() on
+     * the wrapped request object.
+     */
+    @Override
+    public java.security.Principal getUserPrincipal() {
+        return this._getHttpServletRequest().getUserPrincipal();
+    }
+
+    /**
+     * The default behavior of this method is to return getRequestedSessionId()
+     * on the wrapped request object.
+     */
+    @Override
+    public String getRequestedSessionId() {
+        return this._getHttpServletRequest().getRequestedSessionId();
+    }
+
+    /**
+     * The default behavior of this method is to return getRequestURI() on the
+     * wrapped request object.
+     */
+    @Override
+    public String getRequestURI() {
+        return this._getHttpServletRequest().getRequestURI();
+    }
+
+    /**
+     * The default behavior of this method is to return getRequestURL() on the
+     * wrapped request object.
+     */
+    @Override
+    public StringBuffer getRequestURL() {
+        return this._getHttpServletRequest().getRequestURL();
+    }
+
+    /**
+     * The default behavior of this method is to return getServletPath() on the
+     * wrapped request object.
+     */
+    @Override
+    public String getServletPath() {
+        return this._getHttpServletRequest().getServletPath();
+    }
+
+    /**
+     * The default behavior of this method is to return getSession(boolean
+     * create) on the wrapped request object.
+     */
+    @Override
+    public HttpSession getSession(boolean create) {
+        return this._getHttpServletRequest().getSession(create);
+    }
+
+    /**
+     * The default behavior of this method is to return getSession() on the
+     * wrapped request object.
+     */
+    @Override
+    public HttpSession getSession() {
+        return this._getHttpServletRequest().getSession();
+    }
+
+    /**
+     * The default behavior of this method is to call changeSessionId() on the
+     * wrapped request object.
+     */
+    @Override
+    public String changeSessionId() {
+        return this._getHttpServletRequest().changeSessionId();
+    }
+
+    /**
+     * The default behavior of this method is to return
+     * isRequestedSessionIdValid() on the wrapped request object.
+     */
+    @Override
+    public boolean isRequestedSessionIdValid() {
+        return this._getHttpServletRequest().isRequestedSessionIdValid();
+    }
+
+    /**
+     * The default behavior of this method is to return
+     * isRequestedSessionIdFromCookie() on the wrapped request object.
+     */
+    @Override
+    public boolean isRequestedSessionIdFromCookie() {
+        return this._getHttpServletRequest().isRequestedSessionIdFromCookie();
+    }
+
+    /**
+     * The default behavior of this method is to return
+     * isRequestedSessionIdFromURL() on the wrapped request object.
+     */
+    @Override
+    public boolean isRequestedSessionIdFromURL() {
+        return this._getHttpServletRequest().isRequestedSessionIdFromURL();
+    }
+
+    /**
+     * The default behavior of this method is to return
+     * isRequestedSessionIdFromUrl() on the wrapped request object.
+     *
+     * @deprecated As of Version 3.0 of the Java Servlet API
+     */
+    @Override
+    @SuppressWarnings("dep-ann")
+    // Spec API does not use @Deprecated
+    public boolean isRequestedSessionIdFromUrl() {
+        return this._getHttpServletRequest().isRequestedSessionIdFromUrl();
+    }
+
+    /**
+     * {@inheritDoc}
+     * <p>
+     * The default behavior of this method is to return
+     * {@link HttpServletRequest#authenticate(HttpServletResponse)}
+     * on the wrapped request object.
+     *
+     * @since Servlet 3.0
+     */
+    @Override
+    public boolean authenticate(HttpServletResponse response)
+            throws IOException, ServletException {
+        return this._getHttpServletRequest().authenticate(response);
+    }
+
+    /**
+     * {@inheritDoc}
+     * <p>
+     * The default behavior of this method is to return
+     * {@link HttpServletRequest#login(String, String)}
+     * on the wrapped request object.
+     *
+     * @since Servlet 3.0
+     */
+    @Override
+    public void login(String username, String password) throws ServletException {
+        this._getHttpServletRequest().login(username, password);
+    }
+
+    /**
+     * {@inheritDoc}
+     * <p>
+     * The default behavior of this method is to return
+     * {@link HttpServletRequest#logout()}
+     * on the wrapped request object.
+     *
+     * @since Servlet 3.0
+     */
+    @Override
+    public void logout() throws ServletException {
+        this._getHttpServletRequest().logout();
+    }
+
+    /**
+     * {@inheritDoc}
+     * <p>
+     * The default behavior of this method is to return
+     * {@link HttpServletRequest#getParts()}
+     * on the wrapped request object.
+     *
+     * @since Servlet 3.0
+     */
+    @Override
+    public Collection<Part> getParts() throws IOException,
+            ServletException {
+        return this._getHttpServletRequest().getParts();
+    }
+
+    /**
+     * {@inheritDoc}
+     * <p>
+     * The default behavior of this method is to return
+     * {@link HttpServletRequest#getPart(String)}
+     * on the wrapped request object.
+     *
+     * @since Servlet 3.0
+     */
+    @Override
+    public Part getPart(String name) throws IOException,
+            ServletException {
+        return this._getHttpServletRequest().getPart(name);
+    }
+
+    /**
+     * {@inheritDoc}
+     * <p>
+     * The default behavior of this method is to return
+     * {@link HttpServletRequest#upgrade(Class)} on the wrapped request object.
+     *
+     * @since Servlet 3.1
+     */
+    @Override
+    public <T extends HttpUpgradeHandler> T upgrade(
+            Class<T> httpUpgradeHandlerClass) throws IOException, ServletException {
+        return this._getHttpServletRequest().upgrade(httpUpgradeHandlerClass);
+    }
+}
diff --git a/lib/servlet-api/javax/servlet/http/HttpServletResponse.java b/lib/servlet-api/javax/servlet/http/HttpServletResponse.java
new file mode 100644 (file)
index 0000000..c5c6f82
--- /dev/null
@@ -0,0 +1,597 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package javax.servlet.http;
+
+import java.io.IOException;
+import java.util.Collection;
+
+import javax.servlet.ServletResponse;
+
+/**
+ * Extends the {@link ServletResponse} interface to provide HTTP-specific
+ * functionality in sending a response. For example, it has methods to access
+ * HTTP headers and cookies.
+ * <p>
+ * The servlet container creates an <code>HttpServletResponse</code> object and
+ * passes it as an argument to the servlet's service methods (<code>doGet</code>, <code>doPost</code>, etc).
+ *
+ * @see javax.servlet.ServletResponse
+ */
+public interface HttpServletResponse extends ServletResponse {
+
+    /**
+     * Adds the specified cookie to the response. This method can be called
+     * multiple times to set more than one cookie.
+     *
+     * @param cookie
+     *            the Cookie to return to the client
+     */
+    public void addCookie(Cookie cookie);
+
+    /**
+     * Returns a boolean indicating whether the named response header has
+     * already been set.
+     *
+     * @param name
+     *            the header name
+     * @return <code>true</code> if the named response header has already been
+     *         set; <code>false</code> otherwise
+     */
+    public boolean containsHeader(String name);
+
+    /**
+     * Encodes the specified URL by including the session ID in it, or, if
+     * encoding is not needed, returns the URL unchanged. The implementation of
+     * this method includes the logic to determine whether the session ID needs
+     * to be encoded in the URL. For example, if the browser supports cookies,
+     * or session tracking is turned off, URL encoding is unnecessary.
+     * <p>
+     * For robust session tracking, all URLs emitted by a servlet should be run
+     * through this method. Otherwise, URL rewriting cannot be used with
+     * browsers which do not support cookies.
+     *
+     * @param url
+     *            the url to be encoded.
+     * @return the encoded URL if encoding is needed; the unchanged URL
+     *         otherwise.
+     */
+    public String encodeURL(String url);
+
+    /**
+     * Encodes the specified URL for use in the <code>sendRedirect</code> method
+     * or, if encoding is not needed, returns the URL unchanged. The
+     * implementation of this method includes the logic to determine whether the
+     * session ID needs to be encoded in the URL. Because the rules for making
+     * this determination can differ from those used to decide whether to encode
+     * a normal link, this method is separated from the <code>encodeURL</code>
+     * method.
+     * <p>
+     * All URLs sent to the <code>HttpServletResponse.sendRedirect</code> method
+     * should be run through this method. Otherwise, URL rewriting cannot be
+     * used with browsers which do not support cookies.
+     *
+     * @param url
+     *            the url to be encoded.
+     * @return the encoded URL if encoding is needed; the unchanged URL
+     *         otherwise.
+     * @see #sendRedirect
+     * @see #encodeUrl
+     */
+    public String encodeRedirectURL(String url);
+
+    /**
+     * @param url
+     *            the url to be encoded.
+     * @return the encoded URL if encoding is needed; the unchanged URL
+     *         otherwise.
+     * @deprecated As of version 2.1, use encodeURL(String url) instead
+     */
+    @SuppressWarnings("dep-ann")
+    // Spec API does not use @Deprecated
+    public String encodeUrl(String url);
+
+    /**
+     * @param url
+     *            the url to be encoded.
+     * @return the encoded URL if encoding is needed; the unchanged URL
+     *         otherwise.
+     * @deprecated As of version 2.1, use encodeRedirectURL(String url) instead
+     */
+    @SuppressWarnings("dep-ann")
+    // Spec API does not use @Deprecated
+    public String encodeRedirectUrl(String url);
+
+    /**
+     * Sends an error response to the client using the specified status code and
+     * clears the output buffer. The server defaults to creating the response to
+     * look like an HTML-formatted server error page containing the specified
+     * message, setting the content type to "text/html", leaving cookies and
+     * other headers unmodified. If an error-page declaration has been made for
+     * the web application corresponding to the status code passed in, it will
+     * be served back in preference to the suggested msg parameter.
+     * <p>
+     * If the response has already been committed, this method throws an
+     * IllegalStateException. After using this method, the response should be
+     * considered to be committed and should not be written to.
+     *
+     * @param sc
+     *            the error status code
+     * @param msg
+     *            the descriptive message
+     * @exception IOException
+     *                If an input or output exception occurs
+     * @exception IllegalStateException
+     *                If the response was committed
+     */
+    public void sendError(int sc, String msg) throws IOException;
+
+    /**
+     * Sends an error response to the client using the specified status code and
+     * clears the buffer. This is equivalent to calling {@link #sendError(int,
+     * String)} with the same status code and <code>null</code> for the message.
+     *
+     * @param sc
+     *            the error status code
+     * @exception IOException
+     *                If an input or output exception occurs
+     * @exception IllegalStateException
+     *                If the response was committed before this method call
+     */
+    public void sendError(int sc) throws IOException;
+
+    /**
+     * Sends a temporary redirect response to the client using the specified
+     * redirect location URL. This method can accept relative URLs; the servlet
+     * container must convert the relative URL to an absolute URL before sending
+     * the response to the client. If the location is relative without a leading
+     * '/' the container interprets it as relative to the current request URI.
+     * If the location is relative with a leading '/' the container interprets
+     * it as relative to the servlet container root.
+     * <p>
+     * If the response has already been committed, this method throws an
+     * IllegalStateException. After using this method, the response should be
+     * considered to be committed and should not be written to.
+     *
+     * @param location
+     *            the redirect location URL
+     * @exception IOException
+     *                If an input or output exception occurs
+     * @exception IllegalStateException
+     *                If the response was committed or if a partial URL is given
+     *                and cannot be converted into a valid URL
+     */
+    public void sendRedirect(String location) throws IOException;
+
+    /**
+     * Sets a response header with the given name and date-value. The date is
+     * specified in terms of milliseconds since the epoch. If the header had
+     * already been set, the new value overwrites the previous one. The
+     * <code>containsHeader</code> method can be used to test for the presence
+     * of a header before setting its value.
+     *
+     * @param name
+     *            the name of the header to set
+     * @param date
+     *            the assigned date value
+     * @see #containsHeader
+     * @see #addDateHeader
+     */
+    public void setDateHeader(String name, long date);
+
+    /**
+     * Adds a response header with the given name and date-value. The date is
+     * specified in terms of milliseconds since the epoch. This method allows
+     * response headers to have multiple values.
+     *
+     * @param name
+     *            the name of the header to set
+     * @param date
+     *            the additional date value
+     * @see #setDateHeader
+     */
+    public void addDateHeader(String name, long date);
+
+    /**
+     * Sets a response header with the given name and value. If the header had
+     * already been set, the new value overwrites the previous one. The
+     * <code>containsHeader</code> method can be used to test for the presence
+     * of a header before setting its value.
+     *
+     * @param name
+     *            the name of the header
+     * @param value
+     *            the header value If it contains octet string, it should be
+     *            encoded according to RFC 2047
+     *            (http://www.ietf.org/rfc/rfc2047.txt)
+     * @see #containsHeader
+     * @see #addHeader
+     */
+    public void setHeader(String name, String value);
+
+    /**
+     * Adds a response header with the given name and value. This method allows
+     * response headers to have multiple values.
+     *
+     * @param name
+     *            the name of the header
+     * @param value
+     *            the additional header value If it contains octet string, it
+     *            should be encoded according to RFC 2047
+     *            (http://www.ietf.org/rfc/rfc2047.txt)
+     * @see #setHeader
+     */
+    public void addHeader(String name, String value);
+
+    /**
+     * Sets a response header with the given name and integer value. If the
+     * header had already been set, the new value overwrites the previous one.
+     * The <code>containsHeader</code> method can be used to test for the
+     * presence of a header before setting its value.
+     *
+     * @param name
+     *            the name of the header
+     * @param value
+     *            the assigned integer value
+     * @see #containsHeader
+     * @see #addIntHeader
+     */
+    public void setIntHeader(String name, int value);
+
+    /**
+     * Adds a response header with the given name and integer value. This method
+     * allows response headers to have multiple values.
+     *
+     * @param name
+     *            the name of the header
+     * @param value
+     *            the assigned integer value
+     * @see #setIntHeader
+     */
+    public void addIntHeader(String name, int value);
+
+    /**
+     * Sets the status code for this response. This method is used to set the
+     * return status code when there is no error (for example, for the status
+     * codes SC_OK or SC_MOVED_TEMPORARILY). If there is an error, and the
+     * caller wishes to invoke an error-page defined in the web application, the
+     * <code>sendError</code> method should be used instead.
+     * <p>
+     * The container clears the buffer and sets the Location header, preserving
+     * cookies and other headers.
+     *
+     * @param sc
+     *            the status code
+     * @see #sendError
+     */
+    public void setStatus(int sc);
+
+    /**
+     * Sets the status code and message for this response.
+     *
+     * @param sc
+     *            the status code
+     * @param sm
+     *            the status message
+     * @deprecated As of version 2.1, due to ambiguous meaning of the message
+     *             parameter. To set a status code use
+     *             <code>setStatus(int)</code>, to send an error with a
+     *             description use <code>sendError(int, String)</code>.
+     */
+    @SuppressWarnings("dep-ann")
+    // Spec API does not use @Deprecated
+    public void setStatus(int sc, String sm);
+
+    /**
+     * Return the HTTP status code associated with this Response.
+     *
+     * @since Servlet 3.0
+     */
+    public int getStatus();
+
+    /**
+     * Return the value for the specified header, or <code>null</code> if this
+     * header has not been set.  If more than one value was added for this
+     * name, only the first is returned; use {@link #getHeaders(String)} to
+     * retrieve all of them.
+     *
+     * @param name Header name to look up
+     *
+     * @since Servlet 3.0
+     */
+    public String getHeader(String name);
+
+    /**
+     * Return a Collection of all the header values associated with the
+     * specified header name.
+     *
+     * @param name Header name to look up
+     *
+     * @since Servlet 3.0
+     */
+    public Collection<String> getHeaders(String name);
+
+    /**
+     * Return an Iterable of all the header names set for this response.
+     *
+     * @since Servlet 3.0
+     */
+    public Collection<String> getHeaderNames();
+
+    /*
+     * Server status codes; see RFC 2068.
+     */
+
+    /**
+     * Status code (100) indicating the client can continue.
+     */
+    public static final int SC_CONTINUE = 100;
+
+    /**
+     * Status code (101) indicating the server is switching protocols according
+     * to Upgrade header.
+     */
+    public static final int SC_SWITCHING_PROTOCOLS = 101;
+
+    /**
+     * Status code (200) indicating the request succeeded normally.
+     */
+    public static final int SC_OK = 200;
+
+    /**
+     * Status code (201) indicating the request succeeded and created a new
+     * resource on the server.
+     */
+    public static final int SC_CREATED = 201;
+
+    /**
+     * Status code (202) indicating that a request was accepted for processing,
+     * but was not completed.
+     */
+    public static final int SC_ACCEPTED = 202;
+
+    /**
+     * Status code (203) indicating that the meta information presented by the
+     * client did not originate from the server.
+     */
+    public static final int SC_NON_AUTHORITATIVE_INFORMATION = 203;
+
+    /**
+     * Status code (204) indicating that the request succeeded but that there
+     * was no new information to return.
+     */
+    public static final int SC_NO_CONTENT = 204;
+
+    /**
+     * Status code (205) indicating that the agent <em>SHOULD</em> reset the
+     * document view which caused the request to be sent.
+     */
+    public static final int SC_RESET_CONTENT = 205;
+
+    /**
+     * Status code (206) indicating that the server has fulfilled the partial
+     * GET request for the resource.
+     */
+    public static final int SC_PARTIAL_CONTENT = 206;
+
+    /**
+     * Status code (300) indicating that the requested resource corresponds to
+     * any one of a set of representations, each with its own specific location.
+     */
+    public static final int SC_MULTIPLE_CHOICES = 300;
+
+    /**
+     * Status code (301) indicating that the resource has permanently moved to a
+     * new location, and that future references should use a new URI with their
+     * requests.
+     */
+    public static final int SC_MOVED_PERMANENTLY = 301;
+
+    /**
+     * Status code (302) indicating that the resource has temporarily moved to
+     * another location, but that future references should still use the
+     * original URI to access the resource. This definition is being retained
+     * for backwards compatibility. SC_FOUND is now the preferred definition.
+     */
+    public static final int SC_MOVED_TEMPORARILY = 302;
+
+    /**
+     * Status code (302) indicating that the resource reside temporarily under a
+     * different URI. Since the redirection might be altered on occasion, the
+     * client should continue to use the Request-URI for future
+     * requests.(HTTP/1.1) To represent the status code (302), it is recommended
+     * to use this variable.
+     */
+    public static final int SC_FOUND = 302;
+
+    /**
+     * Status code (303) indicating that the response to the request can be
+     * found under a different URI.
+     */
+    public static final int SC_SEE_OTHER = 303;
+
+    /**
+     * Status code (304) indicating that a conditional GET operation found that
+     * the resource was available and not modified.
+     */
+    public static final int SC_NOT_MODIFIED = 304;
+
+    /**
+     * Status code (305) indicating that the requested resource <em>MUST</em> be
+     * accessed through the proxy given by the <code><em>Location</em></code>
+     * field.
+     */
+    public static final int SC_USE_PROXY = 305;
+
+    /**
+     * Status code (307) indicating that the requested resource resides
+     * temporarily under a different URI. The temporary URI <em>SHOULD</em> be
+     * given by the <code><em>Location</em></code> field in the response.
+     */
+    public static final int SC_TEMPORARY_REDIRECT = 307;
+
+    /**
+     * Status code (400) indicating the request sent by the client was
+     * syntactically incorrect.
+     */
+    public static final int SC_BAD_REQUEST = 400;
+
+    /**
+     * Status code (401) indicating that the request requires HTTP
+     * authentication.
+     */
+    public static final int SC_UNAUTHORIZED = 401;
+
+    /**
+     * Status code (402) reserved for future use.
+     */
+    public static final int SC_PAYMENT_REQUIRED = 402;
+
+    /**
+     * Status code (403) indicating the server understood the request but
+     * refused to fulfill it.
+     */
+    public static final int SC_FORBIDDEN = 403;
+
+    /**
+     * Status code (404) indicating that the requested resource is not
+     * available.
+     */
+    public static final int SC_NOT_FOUND = 404;
+
+    /**
+     * Status code (405) indicating that the method specified in the
+     * <code><em>Request-Line</em></code> is not allowed for the resource
+     * identified by the <code><em>Request-URI</em></code>.
+     */
+    public static final int SC_METHOD_NOT_ALLOWED = 405;
+
+    /**
+     * Status code (406) indicating that the resource identified by the request
+     * is only capable of generating response entities which have content
+     * characteristics not acceptable according to the accept headers sent in
+     * the request.
+     */
+    public static final int SC_NOT_ACCEPTABLE = 406;
+
+    /**
+     * Status code (407) indicating that the client <em>MUST</em> first
+     * authenticate itself with the proxy.
+     */
+    public static final int SC_PROXY_AUTHENTICATION_REQUIRED = 407;
+
+    /**
+     * Status code (408) indicating that the client did not produce a request
+     * within the time that the server was prepared to wait.
+     */
+    public static final int SC_REQUEST_TIMEOUT = 408;
+
+    /**
+     * Status code (409) indicating that the request could not be completed due
+     * to a conflict with the current state of the resource.
+     */
+    public static final int SC_CONFLICT = 409;
+
+    /**
+     * Status code (410) indicating that the resource is no longer available at
+     * the server and no forwarding address is known. This condition
+     * <em>SHOULD</em> be considered permanent.
+     */
+    public static final int SC_GONE = 410;
+
+    /**
+     * Status code (411) indicating that the request cannot be handled without a
+     * defined <code><em>Content-Length</em></code>.
+     */
+    public static final int SC_LENGTH_REQUIRED = 411;
+
+    /**
+     * Status code (412) indicating that the precondition given in one or more
+     * of the request-header fields evaluated to false when it was tested on the
+     * server.
+     */
+    public static final int SC_PRECONDITION_FAILED = 412;
+
+    /**
+     * Status code (413) indicating that the server is refusing to process the
+     * request because the request entity is larger than the server is willing
+     * or able to process.
+     */
+    public static final int SC_REQUEST_ENTITY_TOO_LARGE = 413;
+
+    /**
+     * Status code (414) indicating that the server is refusing to service the
+     * request because the <code><em>Request-URI</em></code> is longer than the
+     * server is willing to interpret.
+     */
+    public static final int SC_REQUEST_URI_TOO_LONG = 414;
+
+    /**
+     * Status code (415) indicating that the server is refusing to service the
+     * request because the entity of the request is in a format not supported by
+     * the requested resource for the requested method.
+     */
+    public static final int SC_UNSUPPORTED_MEDIA_TYPE = 415;
+
+    /**
+     * Status code (416) indicating that the server cannot serve the requested
+     * byte range.
+     */
+    public static final int SC_REQUESTED_RANGE_NOT_SATISFIABLE = 416;
+
+    /**
+     * Status code (417) indicating that the server could not meet the
+     * expectation given in the Expect request header.
+     */
+    public static final int SC_EXPECTATION_FAILED = 417;
+
+    /**
+     * Status code (500) indicating an error inside the HTTP server which
+     * prevented it from fulfilling the request.
+     */
+    public static final int SC_INTERNAL_SERVER_ERROR = 500;
+
+    /**
+     * Status code (501) indicating the HTTP server does not support the
+     * functionality needed to fulfill the request.
+     */
+    public static final int SC_NOT_IMPLEMENTED = 501;
+
+    /**
+     * Status code (502) indicating that the HTTP server received an invalid
+     * response from a server it consulted when acting as a proxy or gateway.
+     */
+    public static final int SC_BAD_GATEWAY = 502;
+
+    /**
+     * Status code (503) indicating that the HTTP server is temporarily
+     * overloaded, and unable to handle the request.
+     */
+    public static final int SC_SERVICE_UNAVAILABLE = 503;
+
+    /**
+     * Status code (504) indicating that the server did not receive a timely
+     * response from the upstream server while acting as a gateway or proxy.
+     */
+    public static final int SC_GATEWAY_TIMEOUT = 504;
+
+    /**
+     * Status code (505) indicating that the server does not support or refuses
+     * to support the HTTP protocol version that was used in the request
+     * message.
+     */
+    public static final int SC_HTTP_VERSION_NOT_SUPPORTED = 505;
+}
diff --git a/lib/servlet-api/javax/servlet/http/HttpServletResponseWrapper.java b/lib/servlet-api/javax/servlet/http/HttpServletResponseWrapper.java
new file mode 100644 (file)
index 0000000..aafc95f
--- /dev/null
@@ -0,0 +1,270 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package javax.servlet.http;
+
+import java.io.IOException;
+import java.util.Collection;
+
+import javax.servlet.ServletResponseWrapper;
+
+/**
+ * Provides a convenient implementation of the HttpServletResponse interface
+ * that can be subclassed by developers wishing to adapt the response from a
+ * Servlet. This class implements the Wrapper or Decorator pattern. Methods
+ * default to calling through to the wrapped response object.
+ *
+ * @since v 2.3
+ * @see javax.servlet.http.HttpServletResponse
+ */
+public class HttpServletResponseWrapper extends ServletResponseWrapper
+        implements HttpServletResponse {
+
+    /**
+     * Constructs a response adaptor wrapping the given response.
+     *
+     * @throws java.lang.IllegalArgumentException
+     *             if the response is null
+     */
+    public HttpServletResponseWrapper(HttpServletResponse response) {
+        super(response);
+    }
+
+    private HttpServletResponse _getHttpServletResponse() {
+        return (HttpServletResponse) super.getResponse();
+    }
+
+    /**
+     * The default behavior of this method is to call addCookie(Cookie cookie)
+     * on the wrapped response object.
+     */
+    @Override
+    public void addCookie(Cookie cookie) {
+        this._getHttpServletResponse().addCookie(cookie);
+    }
+
+    /**
+     * The default behavior of this method is to call containsHeader(String
+     * name) on the wrapped response object.
+     */
+    @Override
+    public boolean containsHeader(String name) {
+        return this._getHttpServletResponse().containsHeader(name);
+    }
+
+    /**
+     * The default behavior of this method is to call encodeURL(String url) on
+     * the wrapped response object.
+     */
+    @Override
+    public String encodeURL(String url) {
+        return this._getHttpServletResponse().encodeURL(url);
+    }
+
+    /**
+     * The default behavior of this method is to return encodeRedirectURL(String
+     * url) on the wrapped response object.
+     */
+    @Override
+    public String encodeRedirectURL(String url) {
+        return this._getHttpServletResponse().encodeRedirectURL(url);
+    }
+
+    /**
+     * The default behavior of this method is to call encodeUrl(String url) on
+     * the wrapped response object.
+     *
+     * @deprecated As of Version 3.0 of the Java Servlet API
+     */
+    @Override
+    @SuppressWarnings("dep-ann")
+    // Spec API does not use @Deprecated
+    public String encodeUrl(String url) {
+        return this._getHttpServletResponse().encodeUrl(url);
+    }
+
+    /**
+     * The default behavior of this method is to return encodeRedirectUrl(String
+     * url) on the wrapped response object.
+     *
+     * @deprecated As of Version 3.0 of the Java Servlet API
+     */
+    @Override
+    @SuppressWarnings("dep-ann")
+    // Spec API does not use @Deprecated
+    public String encodeRedirectUrl(String url) {
+        return this._getHttpServletResponse().encodeRedirectUrl(url);
+    }
+
+    /**
+     * The default behavior of this method is to call sendError(int sc, String
+     * msg) on the wrapped response object.
+     */
+    @Override
+    public void sendError(int sc, String msg) throws IOException {
+        this._getHttpServletResponse().sendError(sc, msg);
+    }
+
+    /**
+     * The default behavior of this method is to call sendError(int sc) on the
+     * wrapped response object.
+     */
+    @Override
+    public void sendError(int sc) throws IOException {
+        this._getHttpServletResponse().sendError(sc);
+    }
+
+    /**
+     * The default behavior of this method is to return sendRedirect(String
+     * location) on the wrapped response object.
+     */
+    @Override
+    public void sendRedirect(String location) throws IOException {
+        this._getHttpServletResponse().sendRedirect(location);
+    }
+
+    /**
+     * The default behavior of this method is to call setDateHeader(String name,
+     * long date) on the wrapped response object.
+     */
+    @Override
+    public void setDateHeader(String name, long date) {
+        this._getHttpServletResponse().setDateHeader(name, date);
+    }
+
+    /**
+     * The default behavior of this method is to call addDateHeader(String name,
+     * long date) on the wrapped response object.
+     */
+    @Override
+    public void addDateHeader(String name, long date) {
+        this._getHttpServletResponse().addDateHeader(name, date);
+    }
+
+    /**
+     * The default behavior of this method is to return setHeader(String name,
+     * String value) on the wrapped response object.
+     */
+    @Override
+    public void setHeader(String name, String value) {
+        this._getHttpServletResponse().setHeader(name, value);
+    }
+
+    /**
+     * The default behavior of this method is to return addHeader(String name,
+     * String value) on the wrapped response object.
+     */
+    @Override
+    public void addHeader(String name, String value) {
+        this._getHttpServletResponse().addHeader(name, value);
+    }
+
+    /**
+     * The default behavior of this method is to call setIntHeader(String name,
+     * int value) on the wrapped response object.
+     */
+    @Override
+    public void setIntHeader(String name, int value) {
+        this._getHttpServletResponse().setIntHeader(name, value);
+    }
+
+    /**
+     * The default behavior of this method is to call addIntHeader(String name,
+     * int value) on the wrapped response object.
+     */
+    @Override
+    public void addIntHeader(String name, int value) {
+        this._getHttpServletResponse().addIntHeader(name, value);
+    }
+
+    /**
+     * The default behavior of this method is to call setStatus(int sc) on the
+     * wrapped response object.
+     */
+    @Override
+    public void setStatus(int sc) {
+        this._getHttpServletResponse().setStatus(sc);
+    }
+
+    /**
+     * The default behavior of this method is to call setStatus(int sc, String
+     * sm) on the wrapped response object.
+     *
+     * @deprecated As of Version 3.0 of the Java Servlet API
+     */
+    @Override
+    @SuppressWarnings("dep-ann")
+    // Spec API does not use @Deprecated
+    public void setStatus(int sc, String sm) {
+        this._getHttpServletResponse().setStatus(sc, sm);
+    }
+
+    /**
+     * {@inheritDoc}
+     * <p>
+     * The default implementation is to call
+     * {@link HttpServletResponse#getStatus()}
+     * on the wrapper {@link HttpServletResponse}.
+     *
+     * @since Servlet 3.0
+     */
+    @Override
+    public int getStatus() {
+        return this._getHttpServletResponse().getStatus();
+    }
+
+    /**
+     * {@inheritDoc}
+     * <p>
+     * The default implementation is to call
+     * {@link HttpServletResponse#getHeader(String)}
+     * on the wrapper {@link HttpServletResponse}.
+     *
+     * @since Servlet 3.0
+     */
+    @Override
+    public String getHeader(String name) {
+        return this._getHttpServletResponse().getHeader(name);
+    }
+
+    /**
+     * {@inheritDoc}
+     * <p>
+     * The default implementation is to call
+     * {@link HttpServletResponse#getHeaders(String)}
+     * on the wrapper {@link HttpServletResponse}.
+     *
+     * @since Servlet 3.0
+     */
+    @Override
+    public Collection<String> getHeaders(String name) {
+        return this._getHttpServletResponse().getHeaders(name);
+    }
+
+    /**
+     * {@inheritDoc}
+     * <p>
+     * The default implementation is to call
+     * {@link HttpServletResponse#getHeaderNames()}
+     * on the wrapper {@link HttpServletResponse}.
+     *
+     * @since Servlet 3.0
+     */
+    @Override
+    public Collection<String> getHeaderNames() {
+        return this._getHttpServletResponse().getHeaderNames();
+    }
+}
diff --git a/lib/servlet-api/javax/servlet/http/HttpSession.java b/lib/servlet-api/javax/servlet/http/HttpSession.java
new file mode 100644 (file)
index 0000000..d15b97c
--- /dev/null
@@ -0,0 +1,284 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package javax.servlet.http;
+
+import java.util.Enumeration;
+
+import javax.servlet.ServletContext;
+
+/**
+ * Provides a way to identify a user across more than one page request or visit
+ * to a Web site and to store information about that user.
+ * <p>
+ * The servlet container uses this interface to create a session between an HTTP
+ * client and an HTTP server. The session persists for a specified time period,
+ * across more than one connection or page request from the user. A session
+ * usually corresponds to one user, who may visit a site many times. The server
+ * can maintain a session in many ways such as using cookies or rewriting URLs.
+ * <p>
+ * This interface allows servlets to
+ * <ul>
+ * <li>View and manipulate information about a session, such as the session
+ * identifier, creation time, and last accessed time
+ * <li>Bind objects to sessions, allowing user information to persist across
+ * multiple user connections
+ * </ul>
+ * <p>
+ * When an application stores an object in or removes an object from a session,
+ * the session checks whether the object implements
+ * {@link HttpSessionBindingListener}. If it does, the servlet notifies the
+ * object that it has been bound to or unbound from the session. Notifications
+ * are sent after the binding methods complete. For session that are invalidated
+ * or expire, notifications are sent after the session has been invalidated or
+ * expired.
+ * <p>
+ * When container migrates a session between VMs in a distributed container
+ * setting, all session attributes implementing the
+ * {@link HttpSessionActivationListener} interface are notified.
+ * <p>
+ * A servlet should be able to handle cases in which the client does not choose
+ * to join a session, such as when cookies are intentionally turned off. Until
+ * the client joins the session, <code>isNew</code> returns <code>true</code>.
+ * If the client chooses not to join the session, <code>getSession</code> will
+ * return a different session on each request, and <code>isNew</code> will
+ * always return <code>true</code>.
+ * <p>
+ * Session information is scoped only to the current web application (
+ * <code>ServletContext</code>), so information stored in one context will not
+ * be directly visible in another.
+ *
+ * @see HttpSessionBindingListener
+ */
+public interface HttpSession {
+
+    /**
+     * Returns the time when this session was created, measured in milliseconds
+     * since midnight January 1, 1970 GMT.
+     *
+     * @return a <code>long</code> specifying when this session was created,
+     *         expressed in milliseconds since 1/1/1970 GMT
+     * @exception IllegalStateException
+     *                if this method is called on an invalidated session
+     */
+    public long getCreationTime();
+
+    /**
+     * Returns a string containing the unique identifier assigned to this
+     * session. The identifier is assigned by the servlet container and is
+     * implementation dependent.
+     *
+     * @return a string specifying the identifier assigned to this session
+     * @exception IllegalStateException
+     *                if this method is called on an invalidated session
+     */
+    public String getId();
+
+    /**
+     * Returns the last time the client sent a request associated with this
+     * session, as the number of milliseconds since midnight January 1, 1970
+     * GMT, and marked by the time the container received the request.
+     * <p>
+     * Actions that your application takes, such as getting or setting a value
+     * associated with the session, do not affect the access time.
+     *
+     * @return a <code>long</code> representing the last time the client sent a
+     *         request associated with this session, expressed in milliseconds
+     *         since 1/1/1970 GMT
+     * @exception IllegalStateException
+     *                if this method is called on an invalidated session
+     */
+    public long getLastAccessedTime();
+
+    /**
+     * Returns the ServletContext to which this session belongs.
+     *
+     * @return The ServletContext object for the web application
+     * @since 2.3
+     */
+    public ServletContext getServletContext();
+
+    /**
+     * Specifies the time, in seconds, between client requests before the
+     * servlet container will invalidate this session. A zero or negative time
+     * indicates that the session should never timeout.
+     *
+     * @param interval
+     *            An integer specifying the number of seconds
+     */
+    public void setMaxInactiveInterval(int interval);
+
+    /**
+     * Returns the maximum time interval, in seconds, that the servlet container
+     * will keep this session open between client accesses. After this interval,
+     * the servlet container will invalidate the session. The maximum time
+     * interval can be set with the <code>setMaxInactiveInterval</code> method.
+     * A zero or negative time indicates that the session should never timeout.
+     *
+     * @return an integer specifying the number of seconds this session remains
+     *         open between client requests
+     * @see #setMaxInactiveInterval
+     */
+    public int getMaxInactiveInterval();
+
+    /**
+     * @deprecated As of Version 2.1, this method is deprecated and has no
+     *             replacement. It will be removed in a future version of the
+     *             Java Servlet API.
+     */
+    @SuppressWarnings("dep-ann")
+    // Spec API does not use @Deprecated
+    public HttpSessionContext getSessionContext();
+
+    /**
+     * Returns the object bound with the specified name in this session, or
+     * <code>null</code> if no object is bound under the name.
+     *
+     * @param name
+     *            a string specifying the name of the object
+     * @return the object with the specified name
+     * @exception IllegalStateException
+     *                if this method is called on an invalidated session
+     */
+    public Object getAttribute(String name);
+
+    /**
+     * @param name
+     *            a string specifying the name of the object
+     * @return the object with the specified name
+     * @exception IllegalStateException
+     *                if this method is called on an invalidated session
+     * @deprecated As of Version 2.2, this method is replaced by
+     *             {@link #getAttribute}.
+     */
+    @SuppressWarnings("dep-ann")
+    // Spec API does not use @Deprecated
+    public Object getValue(String name);
+
+    /**
+     * Returns an <code>Enumeration</code> of <code>String</code> objects
+     * containing the names of all the objects bound to this session.
+     *
+     * @return an <code>Enumeration</code> of <code>String</code> objects
+     *         specifying the names of all the objects bound to this session
+     * @exception IllegalStateException
+     *                if this method is called on an invalidated session
+     */
+    public Enumeration<String> getAttributeNames();
+
+    /**
+     * @return an array of <code>String</code> objects specifying the names of
+     *         all the objects bound to this session
+     * @exception IllegalStateException
+     *                if this method is called on an invalidated session
+     * @deprecated As of Version 2.2, this method is replaced by
+     *             {@link #getAttributeNames}
+     */
+    @SuppressWarnings("dep-ann")
+    // Spec API does not use @Deprecated
+    public String[] getValueNames();
+
+    /**
+     * Binds an object to this session, using the name specified. If an object
+     * of the same name is already bound to the session, the object is replaced.
+     * <p>
+     * After this method executes, and if the new object implements
+     * <code>HttpSessionBindingListener</code>, the container calls
+     * <code>HttpSessionBindingListener.valueBound</code>. The container then
+     * notifies any <code>HttpSessionAttributeListener</code>s in the web
+     * application.
+     * <p>
+     * If an object was already bound to this session of this name that
+     * implements <code>HttpSessionBindingListener</code>, its
+     * <code>HttpSessionBindingListener.valueUnbound</code> method is called.
+     * <p>
+     * If the value passed in is null, this has the same effect as calling
+     * <code>removeAttribute()</code>.
+     *
+     * @param name
+     *            the name to which the object is bound; cannot be null
+     * @param value
+     *            the object to be bound
+     * @exception IllegalStateException
+     *                if this method is called on an invalidated session
+     */
+    public void setAttribute(String name, Object value);
+
+    /**
+     * @param name
+     *            the name to which the object is bound; cannot be null
+     * @param value
+     *            the object to be bound; cannot be null
+     * @exception IllegalStateException
+     *                if this method is called on an invalidated session
+     * @deprecated As of Version 2.2, this method is replaced by
+     *             {@link #setAttribute}
+     */
+    @SuppressWarnings("dep-ann")
+    // Spec API does not use @Deprecated
+    public void putValue(String name, Object value);
+
+    /**
+     * Removes the object bound with the specified name from this session. If
+     * the session does not have an object bound with the specified name, this
+     * method does nothing.
+     * <p>
+     * After this method executes, and if the object implements
+     * <code>HttpSessionBindingListener</code>, the container calls
+     * <code>HttpSessionBindingListener.valueUnbound</code>. The container then
+     * notifies any <code>HttpSessionAttributeListener</code>s in the web
+     * application.
+     *
+     * @param name
+     *            the name of the object to remove from this session
+     * @exception IllegalStateException
+     *                if this method is called on an invalidated session
+     */
+    public void removeAttribute(String name);
+
+    /**
+     * @param name
+     *            the name of the object to remove from this session
+     * @exception IllegalStateException
+     *                if this method is called on an invalidated session
+     * @deprecated As of Version 2.2, this method is replaced by
+     *             {@link #removeAttribute}
+     */
+    @SuppressWarnings("dep-ann")
+    // Spec API does not use @Deprecated
+    public void removeValue(String name);
+
+    /**
+     * Invalidates this session then unbinds any objects bound to it.
+     *
+     * @exception IllegalStateException
+     *                if this method is called on an already invalidated session
+     */
+    public void invalidate();
+
+    /**
+     * Returns <code>true</code> if the client does not yet know about the
+     * session or if the client chooses not to join the session. For example, if
+     * the server used only cookie-based sessions, and the client had disabled
+     * the use of cookies, then a session would be new on each request.
+     *
+     * @return <code>true</code> if the server has created a session, but the
+     *         client has not yet joined
+     * @exception IllegalStateException
+     *                if this method is called on an already invalidated session
+     */
+    public boolean isNew();
+}
diff --git a/lib/servlet-api/javax/servlet/http/HttpSessionActivationListener.java b/lib/servlet-api/javax/servlet/http/HttpSessionActivationListener.java
new file mode 100644 (file)
index 0000000..eace111
--- /dev/null
@@ -0,0 +1,37 @@
+/*
+* Licensed to the Apache Software Foundation (ASF) under one or more
+* contributor license agreements.  See the NOTICE file distributed with
+* this work for additional information regarding copyright ownership.
+* The ASF licenses this file to You under the Apache License, Version 2.0
+* (the "License"); you may not use this file except in compliance with
+* the License.  You may obtain a copy of the License at
+*
+*     http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+package javax.servlet.http;
+
+import java.util.EventListener;
+
+    /** Objects that are bound to a session may listen to container
+    ** events notifying them that sessions will be passivated and that
+    ** session will be activated. A container that migrates session between VMs
+    ** or persists sessions is required to notify all attributes bound to sessions
+    ** implementing HttpSessionActivationListener.
+    **
+    * @since 2.3
+    */
+
+public interface HttpSessionActivationListener extends EventListener {
+
+    /** Notification that the session is about to be passivated.*/
+    public void sessionWillPassivate(HttpSessionEvent se);
+    /** Notification that the session has just been activated.*/
+    public void sessionDidActivate(HttpSessionEvent se);
+}
+
diff --git a/lib/servlet-api/javax/servlet/http/HttpSessionAttributeListener.java b/lib/servlet-api/javax/servlet/http/HttpSessionAttributeListener.java
new file mode 100644 (file)
index 0000000..0c8f9ca
--- /dev/null
@@ -0,0 +1,46 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package javax.servlet.http;
+
+import java.util.EventListener;
+
+/**
+ * This listener interface can be implemented in order to get notifications of
+ * changes to the attribute lists of sessions within this web application.
+ *
+ * @since v 2.3
+ */
+public interface HttpSessionAttributeListener extends EventListener {
+
+    /**
+     * Notification that an attribute has been added to a session. Called after
+     * the attribute is added.
+     */
+    public void attributeAdded(HttpSessionBindingEvent se);
+
+    /**
+     * Notification that an attribute has been removed from a session. Called
+     * after the attribute is removed.
+     */
+    public void attributeRemoved(HttpSessionBindingEvent se);
+
+    /**
+     * Notification that an attribute has been replaced in a session. Called
+     * after the attribute is replaced.
+     */
+    public void attributeReplaced(HttpSessionBindingEvent se);
+}
diff --git a/lib/servlet-api/javax/servlet/http/HttpSessionBindingEvent.java b/lib/servlet-api/javax/servlet/http/HttpSessionBindingEvent.java
new file mode 100644 (file)
index 0000000..a921719
--- /dev/null
@@ -0,0 +1,111 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package javax.servlet.http;
+
+/**
+ * Events of this type are either sent to an object that implements
+ * {@link HttpSessionBindingListener} when it is bound or unbound from a
+ * session, or to a {@link HttpSessionAttributeListener} that has been
+ * configured in the deployment descriptor when any attribute is bound, unbound
+ * or replaced in a session.
+ * <p>
+ * The session binds the object by a call to
+ * <code>HttpSession.setAttribute</code> and unbinds the object by a call to
+ * <code>HttpSession.removeAttribute</code>.
+ *
+ * @see HttpSession
+ * @see HttpSessionBindingListener
+ * @see HttpSessionAttributeListener
+ */
+public class HttpSessionBindingEvent extends HttpSessionEvent {
+
+    private static final long serialVersionUID = 1L;
+
+    /* The name to which the object is being bound or unbound */
+    private final String name;
+
+    /* The object is being bound or unbound */
+    private final Object value;
+
+    /**
+     * Constructs an event that notifies an object that it has been bound to or
+     * unbound from a session. To receive the event, the object must implement
+     * {@link HttpSessionBindingListener}.
+     *
+     * @param session
+     *            the session to which the object is bound or unbound
+     * @param name
+     *            the name with which the object is bound or unbound
+     * @see #getName
+     * @see #getSession
+     */
+    public HttpSessionBindingEvent(HttpSession session, String name) {
+        super(session);
+        this.name = name;
+        this.value = null;
+    }
+
+    /**
+     * Constructs an event that notifies an object that it has been bound to or
+     * unbound from a session. To receive the event, the object must implement
+     * {@link HttpSessionBindingListener}.
+     *
+     * @param session
+     *            the session to which the object is bound or unbound
+     * @param name
+     *            the name with which the object is bound or unbound
+     * @see #getName
+     * @see #getSession
+     */
+    public HttpSessionBindingEvent(HttpSession session, String name,
+            Object value) {
+        super(session);
+        this.name = name;
+        this.value = value;
+    }
+
+    /** Return the session that changed. */
+    @Override
+    public HttpSession getSession() {
+        return super.getSession();
+    }
+
+    /**
+     * Returns the name with which the attribute is bound to or unbound from the
+     * session.
+     *
+     * @return a string specifying the name with which the object is bound to or
+     *         unbound from the session
+     */
+    public String getName() {
+        return name;
+    }
+
+    /**
+     * Returns the value of the attribute that has been added, removed or
+     * replaced. If the attribute was added (or bound), this is the value of the
+     * attribute. If the attribute was removed (or unbound), this is the value
+     * of the removed attribute. If the attribute was replaced, this is the old
+     * value of the attribute.
+     *
+     * @since 2.3
+     */
+    public Object getValue() {
+        return this.value;
+    }
+}
diff --git a/lib/servlet-api/javax/servlet/http/HttpSessionBindingListener.java b/lib/servlet-api/javax/servlet/http/HttpSessionBindingListener.java
new file mode 100644 (file)
index 0000000..a09845e
--- /dev/null
@@ -0,0 +1,53 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package javax.servlet.http;
+
+import java.util.EventListener;
+
+/**
+ * Causes an object to be notified when it is bound to or unbound from a
+ * session. The object is notified by an {@link HttpSessionBindingEvent} object.
+ * This may be as a result of a servlet programmer explicitly unbinding an
+ * attribute from a session, due to a session being invalidated, or due to a
+ * session timing out.
+ *
+ * @see HttpSession
+ * @see HttpSessionBindingEvent
+ */
+public interface HttpSessionBindingListener extends EventListener {
+
+    /**
+     * Notifies the object that it is being bound to a session and identifies
+     * the session.
+     *
+     * @param event
+     *            the event that identifies the session
+     * @see #valueUnbound
+     */
+    public void valueBound(HttpSessionBindingEvent event);
+
+    /**
+     * Notifies the object that it is being unbound from a session and
+     * identifies the session.
+     *
+     * @param event
+     *            the event that identifies the session
+     * @see #valueBound
+     */
+    public void valueUnbound(HttpSessionBindingEvent event);
+}
diff --git a/lib/servlet-api/javax/servlet/http/HttpSessionContext.java b/lib/servlet-api/javax/servlet/http/HttpSessionContext.java
new file mode 100644 (file)
index 0000000..2a7e18b
--- /dev/null
@@ -0,0 +1,49 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package javax.servlet.http;
+
+import java.util.Enumeration;
+
+/**
+ * @deprecated As of Java(tm) Servlet API 2.1 for security reasons, with no
+ *             replacement. This interface will be removed in a future version
+ *             of this API.
+ * @see HttpSession
+ * @see HttpSessionBindingEvent
+ * @see HttpSessionBindingListener
+ */
+@SuppressWarnings("dep-ann")
+// Spec API does not use @Deprecated
+public interface HttpSessionContext {
+
+    /**
+     * @deprecated As of Java Servlet API 2.1 with no replacement. This method
+     *             must return null and will be removed in a future version of
+     *             this API.
+     */
+    // Spec API does not use @Deprecated
+    public HttpSession getSession(String sessionId);
+
+    /**
+     * @deprecated As of Java Servlet API 2.1 with no replacement. This method
+     *             must return an empty <code>Enumeration</code> and will be
+     *             removed in a future version of this API.
+     */
+    // Spec API does not use @Deprecated
+    public Enumeration<String> getIds();
+}
diff --git a/lib/servlet-api/javax/servlet/http/HttpSessionEvent.java b/lib/servlet-api/javax/servlet/http/HttpSessionEvent.java
new file mode 100644 (file)
index 0000000..ef83877
--- /dev/null
@@ -0,0 +1,37 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package javax.servlet.http;
+
+/**
+ * This is the class representing event notifications for changes to sessions
+ * within a web application.
+ *
+ * @since v 2.3
+ */
+public class HttpSessionEvent extends java.util.EventObject {
+    private static final long serialVersionUID = 1L;
+
+    /** Construct a session event from the given source. */
+    public HttpSessionEvent(HttpSession source) {
+        super(source);
+    }
+
+    /** Return the session that changed. */
+    public HttpSession getSession() {
+        return (HttpSession) super.getSource();
+    }
+}
diff --git a/lib/servlet-api/javax/servlet/http/HttpSessionIdListener.java b/lib/servlet-api/javax/servlet/http/HttpSessionIdListener.java
new file mode 100644 (file)
index 0000000..0c3c0c2
--- /dev/null
@@ -0,0 +1,41 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package javax.servlet.http;
+
+import java.util.EventListener;
+
+/**
+ * Implementations of this interface are notified when an {@link HttpSession}'s
+ * ID changes. To receive notification events, the implementation class must be
+ * configured in the deployment descriptor for the web application, annotated
+ * with {@link javax.servlet.annotation.WebListener} or registered by calling an
+ * addListener method on the {@link javax.servlet.ServletContext}.
+ *
+ * @see HttpSessionEvent
+ * @see HttpServletRequest#changeSessionId()
+ * @since Servlet 3.1
+ */
+public interface HttpSessionIdListener extends EventListener {
+
+    /**
+     * Notification that a session ID has been changed.
+     *
+     * @param se the notification event
+     * @param oldSessionId the old session ID
+     */
+    public void sessionIdChanged(HttpSessionEvent se, String oldSessionId);
+}
diff --git a/lib/servlet-api/javax/servlet/http/HttpSessionListener.java b/lib/servlet-api/javax/servlet/http/HttpSessionListener.java
new file mode 100644 (file)
index 0000000..a0f1009
--- /dev/null
@@ -0,0 +1,48 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package javax.servlet.http;
+
+import java.util.EventListener;
+
+/**
+ * Implementations of this interface are notified of changes to the list of
+ * active sessions in a web application. To receive notification events, the
+ * implementation class must be configured in the deployment descriptor for the
+ * web application.
+ *
+ * @see HttpSessionEvent
+ * @since v 2.3
+ */
+public interface HttpSessionListener extends EventListener {
+
+    /**
+     * Notification that a session was created.
+     *
+     * @param se
+     *            the notification event
+     */
+    public void sessionCreated(HttpSessionEvent se);
+
+    /**
+     * Notification that a session is about to be invalidated.
+     *
+     * @param se
+     *            the notification event
+     */
+    public void sessionDestroyed(HttpSessionEvent se);
+
+}
diff --git a/lib/servlet-api/javax/servlet/http/HttpUpgradeHandler.java b/lib/servlet-api/javax/servlet/http/HttpUpgradeHandler.java
new file mode 100644 (file)
index 0000000..095200c
--- /dev/null
@@ -0,0 +1,42 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package javax.servlet.http;
+
+/**
+ * Interface between the HTTP upgrade process and the new protocol.
+ *
+ * @since Servlet 3.1
+ */
+public interface HttpUpgradeHandler {
+
+    /**
+     * This method is called once the request/response pair where
+     * {@link HttpServletRequest#upgrade(Class)} is called has completed
+     * processing and is the point where control of the connection passes from
+     * the container to the {@link HttpUpgradeHandler}.
+     *
+     * @param connection    The connection that has been upgraded
+     *
+     * @since Servlet 3.1
+     */
+    void init(WebConnection connection);
+
+    /**
+     * This method is called after the upgraded connection has been closed.
+     */
+    void destroy();
+}
diff --git a/lib/servlet-api/javax/servlet/http/HttpUtils.java b/lib/servlet-api/javax/servlet/http/HttpUtils.java
new file mode 100644 (file)
index 0000000..a0b4971
--- /dev/null
@@ -0,0 +1,282 @@
+/*
+* Licensed to the Apache Software Foundation (ASF) under one or more
+* contributor license agreements.  See the NOTICE file distributed with
+* this work for additional information regarding copyright ownership.
+* The ASF licenses this file to You under the Apache License, Version 2.0
+* (the "License"); you may not use this file except in compliance with
+* the License.  You may obtain a copy of the License at
+*
+*     http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+package javax.servlet.http;
+
+import java.io.IOException;
+import java.util.Hashtable;
+import java.util.ResourceBundle;
+import java.util.StringTokenizer;
+
+import javax.servlet.ServletInputStream;
+
+/**
+ * @deprecated            As of Java(tm) Servlet API 2.3.
+ *                        These methods were only useful
+ *                        with the default encoding and have been moved
+ *                        to the request interfaces.
+ */
+@SuppressWarnings("dep-ann") // Spec API does not use @Deprecated
+public class HttpUtils {
+
+    private static final String LSTRING_FILE =
+        "javax.servlet.http.LocalStrings";
+    private static final ResourceBundle lStrings =
+        ResourceBundle.getBundle(LSTRING_FILE);
+
+
+    /**
+     * Constructs an empty <code>HttpUtils</code> object.
+     *
+     */
+    public HttpUtils() {
+        // NOOP
+    }
+
+
+    /**
+     *
+     * Parses a query string passed from the client to the
+     * server and builds a <code>HashTable</code> object
+     * with key-value pairs.
+     * The query string should be in the form of a string
+     * packaged by the GET or POST method, that is, it
+     * should have key-value pairs in the form <i>key=value</i>,
+     * with each pair separated from the next by a &amp; character.
+     *
+     * <p>A key can appear more than once in the query string
+     * with different values. However, the key appears only once in
+     * the hashtable, with its value being
+     * an array of strings containing the multiple values sent
+     * by the query string.
+     *
+     * <p>The keys and values in the hashtable are stored in their
+     * decoded form, so
+     * any + characters are converted to spaces, and characters
+     * sent in hexadecimal notation (like <i>%xx</i>) are
+     * converted to ASCII characters.
+     *
+     * @param s                a string containing the query to be parsed
+     *
+     * @return                a <code>HashTable</code> object built
+     *                         from the parsed key-value pairs
+     *
+     * @exception IllegalArgumentException        if the query string
+     *                                                is invalid
+     *
+     */
+    public static Hashtable<String,String[]> parseQueryString(String s) {
+
+        String valArray[] = null;
+
+        if (s == null) {
+            throw new IllegalArgumentException();
+        }
+        Hashtable<String,String[]> ht = new Hashtable<>();
+        StringBuilder sb = new StringBuilder();
+        StringTokenizer st = new StringTokenizer(s, "&");
+        while (st.hasMoreTokens()) {
+            String pair = st.nextToken();
+            int pos = pair.indexOf('=');
+            if (pos == -1) {
+                // XXX
+                // should give more detail about the illegal argument
+                throw new IllegalArgumentException();
+            }
+            String key = parseName(pair.substring(0, pos), sb);
+            String val = parseName(pair.substring(pos+1, pair.length()), sb);
+            if (ht.containsKey(key)) {
+                String oldVals[] = ht.get(key);
+                valArray = new String[oldVals.length + 1];
+                for (int i = 0; i < oldVals.length; i++)
+                    valArray[i] = oldVals[i];
+                valArray[oldVals.length] = val;
+            } else {
+                valArray = new String[1];
+                valArray[0] = val;
+            }
+            ht.put(key, valArray);
+        }
+        return ht;
+    }
+
+
+    /**
+     *
+     * Parses data from an HTML form that the client sends to
+     * the server using the HTTP POST method and the
+     * <i>application/x-www-form-urlencoded</i> MIME type.
+     *
+     * <p>The data sent by the POST method contains key-value
+     * pairs. A key can appear more than once in the POST data
+     * with different values. However, the key appears only once in
+     * the hashtable, with its value being
+     * an array of strings containing the multiple values sent
+     * by the POST method.
+     *
+     * <p>The keys and values in the hashtable are stored in their
+     * decoded form, so
+     * any + characters are converted to spaces, and characters
+     * sent in hexadecimal notation (like <i>%xx</i>) are
+     * converted to ASCII characters.
+     *
+     *
+     *
+     * @param len        an integer specifying the length,
+     *                        in characters, of the
+     *                        <code>ServletInputStream</code>
+     *                        object that is also passed to this
+     *                        method
+     *
+     * @param in        the <code>ServletInputStream</code>
+     *                        object that contains the data sent
+     *                        from the client
+     *
+     * @return                a <code>HashTable</code> object built
+     *                        from the parsed key-value pairs
+     *
+     *
+     * @exception IllegalArgumentException        if the data
+     *                        sent by the POST method is invalid
+     *
+     */
+    public static Hashtable<String,String[]> parsePostData(int len,
+                                          ServletInputStream in) {
+        // XXX
+        // should a length of 0 be an IllegalArgumentException
+
+        // cheap hack to return an empty hash
+        if (len <=0)
+            return new Hashtable<>();
+
+        if (in == null) {
+            throw new IllegalArgumentException();
+        }
+
+        // Make sure we read the entire POSTed body.
+        byte[] postedBytes = new byte [len];
+        try {
+            int offset = 0;
+
+            do {
+                int inputLen = in.read (postedBytes, offset, len - offset);
+                if (inputLen <= 0) {
+                    String msg = lStrings.getString("err.io.short_read");
+                    throw new IllegalArgumentException (msg);
+                }
+                offset += inputLen;
+            } while ((len - offset) > 0);
+
+        } catch (IOException e) {
+            throw new IllegalArgumentException(e.getMessage(), e);
+        }
+
+        // XXX we shouldn't assume that the only kind of POST body
+        // is FORM data encoded using ASCII or ISO Latin/1 ... or
+        // that the body should always be treated as FORM data.
+        try {
+            String postedBody = new String(postedBytes, 0, len, "8859_1");
+            return parseQueryString(postedBody);
+        } catch (java.io.UnsupportedEncodingException e) {
+            // XXX function should accept an encoding parameter & throw this
+            // exception.  Otherwise throw something expected.
+            throw new IllegalArgumentException(e.getMessage(), e);
+        }
+    }
+
+
+    /*
+     * Parse a name in the query string.
+     */
+    private static String parseName(String s, StringBuilder sb) {
+        sb.setLength(0);
+        for (int i = 0; i < s.length(); i++) {
+            char c = s.charAt(i);
+            switch (c) {
+            case '+':
+                sb.append(' ');
+                break;
+            case '%':
+                try {
+                    sb.append((char) Integer.parseInt(s.substring(i+1, i+3),
+                                                      16));
+                    i += 2;
+                } catch (NumberFormatException e) {
+                    // XXX
+                    // need to be more specific about illegal arg
+                    throw new IllegalArgumentException();
+                } catch (StringIndexOutOfBoundsException e) {
+                    String rest  = s.substring(i);
+                    sb.append(rest);
+                    if (rest.length()==2)
+                        i++;
+                }
+
+                break;
+            default:
+                sb.append(c);
+                break;
+            }
+        }
+        return sb.toString();
+    }
+
+
+    /**
+     *
+     * Reconstructs the URL the client used to make the request,
+     * using information in the <code>HttpServletRequest</code> object.
+     * The returned URL contains a protocol, server name, port
+     * number, and server path, but it does not include query
+     * string parameters.
+     *
+     * <p>Because this method returns a <code>StringBuffer</code>,
+     * not a string, you can modify the URL easily, for example,
+     * to append query parameters.
+     *
+     * <p>This method is useful for creating redirect messages
+     * and for reporting errors.
+     *
+     * @param req        a <code>HttpServletRequest</code> object
+     *                        containing the client's request
+     *
+     * @return                a <code>StringBuffer</code> object containing
+     *                        the reconstructed URL
+     *
+     */
+    public static StringBuffer getRequestURL (HttpServletRequest req) {
+        StringBuffer url = new StringBuffer ();
+        String scheme = req.getScheme ();
+        int port = req.getServerPort ();
+        String urlPath = req.getRequestURI();
+
+        url.append (scheme);                // http, https
+        url.append ("://");
+        url.append (req.getServerName ());
+        if ((scheme.equals ("http") && port != 80)
+                || (scheme.equals ("https") && port != 443)) {
+            url.append (':');
+            url.append (req.getServerPort ());
+        }
+
+        url.append(urlPath);
+        return url;
+    }
+}
+
+
+
diff --git a/lib/servlet-api/javax/servlet/http/LocalStrings.properties b/lib/servlet-api/javax/servlet/http/LocalStrings.properties
new file mode 100644 (file)
index 0000000..a9def62
--- /dev/null
@@ -0,0 +1,30 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Default localized string information
+# Localized for Locale en_US
+
+err.cookie_name_is_token=Cookie name \"{0}\" is a reserved token
+err.cookie_name_blank=Cookie name may not be null or zero length
+err.io.nullArray=Null passed for byte array in write method
+err.io.indexOutOfBounds=Invalid offset [{0}] and / or length [{1}] specified for array of size [{2}]
+err.io.short_read=Short Read
+
+http.method_not_implemented=Method {0} is not is not implemented by this servlet for this URI
+
+http.method_get_not_supported=HTTP method GET is not supported by this URL
+http.method_post_not_supported=HTTP method POST is not supported by this URL
+http.method_put_not_supported=HTTP method PUT is not supported by this URL
+http.method_delete_not_supported=Http method DELETE is not supported by this URL
diff --git a/lib/servlet-api/javax/servlet/http/LocalStrings_es.properties b/lib/servlet-api/javax/servlet/http/LocalStrings_es.properties
new file mode 100644 (file)
index 0000000..31fc0d8
--- /dev/null
@@ -0,0 +1,23 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+err.cookie_name_is_token = El nombre de Cookie {0} es una palabra reservada
+err.cookie_name_blank = El nombre del Cookie no puede ser nulo o de longitud cero
+err.io.negativelength = Longitud Negativa en el metodo write
+err.io.short_read = Lectura Corta
+http.method_not_implemented = El Metodo {0} no esta implementado por este servlet para esta URI
+http.method_get_not_supported = El Metodo HTTP GET no est\u00E1 soportado por esta URL
+http.method_post_not_supported = El Metodo HTTP POST no est\u00E1 soportado por esta URL
+http.method_put_not_supported = El Metodo HTTP PUT no est\u00E1 soportado por esta URL
+http.method_delete_not_supported = El Metodo HTTP DELETE no es soportado por esta URL
diff --git a/lib/servlet-api/javax/servlet/http/LocalStrings_fr.properties b/lib/servlet-api/javax/servlet/http/LocalStrings_fr.properties
new file mode 100644 (file)
index 0000000..18fd1c0
--- /dev/null
@@ -0,0 +1,28 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Default localized string information
+# Localized for Locale fr_FR
+
+err.cookie_name_is_token=Le nom de cookie \"{0}\" est un \"token\" r\u00e9serv\u00e9
+err.io.negativelength=Taille n\u00e9gative donn\u00e9e dans la m\u00e9thode \"write\"
+err.io.short_read=Lecture partielle
+
+http.method_not_implemented=Le m\u00e9thode {0} n''est pas d\u00e9finie dans la RFC 2068 et n''est pas support\u00e9e par l''API Servlet
+
+http.method_get_not_supported=La m\u00e9thode HTTP GET n''est pas support\u00e9e par cette URL
+http.method_post_not_supported=La m\u00e9thode HTTP POST n''est pas support\u00e9e par cette URL
+http.method_put_not_supported=La m\u00e9thode HTTP PUT n''est pas support\u00e9e par cette URL
+http.method_delete_not_supported=La m\u00e9thode HTTP DELETE n''est pas support\u00e9e par cette URL
diff --git a/lib/servlet-api/javax/servlet/http/LocalStrings_ja.properties b/lib/servlet-api/javax/servlet/http/LocalStrings_ja.properties
new file mode 100644 (file)
index 0000000..70983d7
--- /dev/null
@@ -0,0 +1,28 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Default localized string information
+# Localized for Locale ja_JP
+
+err.cookie_name_is_token=\u30af\u30c3\u30ad\u30fc\u540d \"{0}\" \u306f\u4e88\u7d04\u6e08\u306e\u30c8\u30fc\u30af\u30f3\u3067\u3059\u3002
+err.io.negativelength=write\u30e1\u30bd\u30c3\u30c9\u306b\u8ca0\u306e\u9577\u3055\u304c\u6307\u5b9a\u3055\u308c\u307e\u3057\u305f\u3002
+err.io.short_read=\u8aad\u307f\u8fbc\u307f\u304c\u3059\u3050\u306b\u7d42\u308f\u308a\u307e\u3057\u305f\u3002
+
+http.method_not_implemented=\u30e1\u30bd\u30c3\u30c9 {0} \u306fRFC 2068\u306b\u306f\u5b9a\u7fa9\u3055\u308c\u3066\u304a\u3089\u305a\u3001\u30b5\u30fc\u30d6\u30ec\u30c3\u30c8API\u3067\u306f\u30b5\u30dd\u30fc\u30c8\u3055\u308c\u307e\u305b\u3093\u3002
+
+http.method_get_not_supported=HTTP\u306eGET\u30e1\u30bd\u30c3\u30c9\u306f\u3001\u3053\u306eURL\u3067\u306f\u30b5\u30dd\u30fc\u30c8\u3055\u308c\u3066\u3044\u307e\u305b\u3093\u3002
+http.method_post_not_supported=HTTP\u306ePOST\u30e1\u30bd\u30c3\u30c9\u306f\u3001\u3053\u306eURL\u3067\u306f\u30b5\u30dd\u30fc\u30c8\u3055\u308c\u3066\u3044\u307e\u305b\u3093\u3002
+http.method_put_not_supported=HTTP\u306ePUT\u30e1\u30bd\u30c3\u30c9\u306f\u3001\u3053\u306eURL\u3067\u306f\u30b5\u30dd\u30fc\u30c8\u3055\u308c\u3066\u3044\u307e\u305b\u3093\u3002
+http.method_delete_not_supported=HTTP\u306eDELETE\u30e1\u30bd\u30c3\u30c9\u306f\u3001\u3053\u306eURL\u3067\u306f\u30b5\u30dd\u30fc\u30c8\u3055\u308c\u3066\u3044\u307e\u305b\u3093\u3002
diff --git a/lib/servlet-api/javax/servlet/http/Part.java b/lib/servlet-api/javax/servlet/http/Part.java
new file mode 100644 (file)
index 0000000..6d08de4
--- /dev/null
@@ -0,0 +1,118 @@
+/*
+* Licensed to the Apache Software Foundation (ASF) under one or more
+* contributor license agreements.  See the NOTICE file distributed with
+* this work for additional information regarding copyright ownership.
+* The ASF licenses this file to You under the Apache License, Version 2.0
+* (the "License"); you may not use this file except in compliance with
+* the License.  You may obtain a copy of the License at
+*
+*     http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+package javax.servlet.http;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Collection;
+
+/**
+ * This class represents a part as uploaded to the server as part of a
+ * <code>multipart/form-data</code> request body. The part may represent either
+ * an uploaded file or form data.
+ *
+ * @since Servlet 3.0
+ */
+public interface Part {
+
+    /**
+     * Obtain an <code>InputStream</code> that can be used to retrieve the
+     * contents of the file.
+     */
+    public InputStream getInputStream() throws IOException;
+
+    /**
+     * Obtain the content type passed by the browser or <code>null</code> if not
+     * defined.
+     */
+    public String getContentType();
+
+    /**
+     * Obtain the name of the field in the multipart form corresponding to this
+     * part.
+     */
+    public String getName();
+
+    /**
+     * If this part represents an uploaded file, gets the file name submitted
+     * in the upload. Returns {@code null} if no file name is available or if
+     * this part is not a file upload.
+     *
+     * @return the submitted file name or {@code null}.
+     *
+     * @since Servlet 3.1
+     */
+    public String getSubmittedFileName();
+
+    /**
+     * Obtain the size of this part.
+     */
+    public long getSize();
+
+    /**
+     * A convenience method to write an uploaded part to disk. The client code
+     * is not concerned with whether or not the part is stored in memory, or on
+     * disk in a temporary location. They just want to write the uploaded part
+     * to a file.
+     *
+     *  This method is not guaranteed to succeed if called more than once for
+     *  the same part. This allows a particular implementation to use, for
+     *  example, file renaming, where possible, rather than copying all of the
+     *  underlying data, thus gaining a significant performance benefit.
+     *
+     * @param fileName  The location into which the uploaded part should be
+     *                  stored. Relative locations are relative to {@link
+     *                  javax.servlet.MultipartConfigElement#getLocation()}
+     */
+    public void write(String fileName) throws IOException;
+
+    /**
+     * Deletes the underlying storage for a part, including deleting any
+     * associated temporary disk file. Although the container will delete this
+     * storage automatically this method can be used to ensure that this is done
+     * at an earlier time, thus preserving system resources.
+     * <p>
+     * Containers are only required to delete the associated storage when the
+     * Part instance is garbage collected. Apache Tomcat will delete the
+     * associated storage when the associated request has finished processing.
+     * Behaviour of other containers may be different.
+     */
+    public void delete() throws IOException;
+
+    /**
+     * Obtains the value of the specified part header as a String. If there are
+     * multiple headers with the same name, this method returns the first header
+     * in the part. The header name is case insensitive.
+     *
+     * @param name  Header name
+     * @return      The header value or <code>null</code> if the header is not
+     *              present
+     */
+    public String getHeader(String name);
+
+    /**
+     * Obtain all the values of the specified part header. If the part did not
+     * include any headers of the specified name, this method returns an empty
+     * Collection. The header name is case insensitive.
+     */
+    public Collection<String> getHeaders(String name);
+
+    /**
+     * Returns a Collection of all the header names provided for this part.
+     */
+    public Collection<String> getHeaderNames();
+}
diff --git a/lib/servlet-api/javax/servlet/http/WebConnection.java b/lib/servlet-api/javax/servlet/http/WebConnection.java
new file mode 100644 (file)
index 0000000..0619360
--- /dev/null
@@ -0,0 +1,43 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package javax.servlet.http;
+
+import java.io.IOException;
+
+import javax.servlet.ServletInputStream;
+import javax.servlet.ServletOutputStream;
+
+/**
+ * The interface used by a {@link HttpUpgradeHandler} to interact with an upgraded
+ * HTTP connection.
+ *
+ * @since Servlet 3.1
+ */
+public interface WebConnection extends AutoCloseable {
+
+    /**
+     * Provides access to the {@link ServletInputStream} for reading data from
+     * the client.
+     */
+    ServletInputStream getInputStream() throws IOException;
+
+    /**
+     * Provides access to the {@link ServletOutputStream} for writing data to
+     * the client.
+     */
+    ServletOutputStream getOutputStream() throws IOException;
+}
\ No newline at end of file
diff --git a/lib/servlet-api/javax/servlet/http/package.html b/lib/servlet-api/javax/servlet/http/package.html
new file mode 100644 (file)
index 0000000..08e6692
--- /dev/null
@@ -0,0 +1,30 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<HTML>
+<HEAD>
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements.  See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You under the Apache License, Version 2.0
+  (the "License"); you may not use this file except in compliance with
+  the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+-->
+</HEAD>
+<BODY BGCOLOR="white">
+
+The javax.servlet.http package contains a number of classes and interfaces
+that describe and define the contracts between a servlet class
+running under the HTTP protocol and the runtime environment provided
+for an instance of such a class by a conforming servlet container.
+
+
+</BODY>
+</HTML>
diff --git a/lib/servlet-api/javax/servlet/package.html b/lib/servlet-api/javax/servlet/package.html
new file mode 100644 (file)
index 0000000..7336be6
--- /dev/null
@@ -0,0 +1,30 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<HTML>
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements.  See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You under the Apache License, Version 2.0
+  (the "License"); you may not use this file except in compliance with
+  the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+-->
+<HEAD>
+</HEAD>
+<BODY BGCOLOR="white">
+
+The javax.servlet package contains a number of classes and interfaces that
+describe and define the contracts between a servlet class and the
+runtime environment provided for an instance of such a class by a
+conforming servlet container.
+
+
+</BODY>
+</HTML>
diff --git a/locale/.gitignore b/locale/.gitignore
new file mode 100644 (file)
index 0000000..6fe7289
--- /dev/null
@@ -0,0 +1,3 @@
+*
+!.gitignore
+a
diff --git a/natives/.gitignore b/natives/.gitignore
new file mode 100644 (file)
index 0000000..0b5c1b3
--- /dev/null
@@ -0,0 +1,2 @@
+/libsetuid.so
+*.h
diff --git a/natives/Makefile b/natives/Makefile
new file mode 100644 (file)
index 0000000..3fbf922
--- /dev/null
@@ -0,0 +1,18 @@
+JAVA_HOME=/usr/lib/jvm/default-java
+SYSTEM= $(shell uname | awk '{print tolower($$0)}')
+JAVAH=javah
+CC=gcc
+CFLAGS=-O3 -g -flto -Wall -Werror -Wextra -pedantic -fPIC
+CFLAGS+=-I$(JAVA_HOME)/include -I$(JAVA_HOME)/include/$(SYSTEM)
+LDFLAGS=-shared
+
+
+all: libsetuid.so
+
+libsetuid.so: org_cacert_gigi_natives_SetUID.c
+       $(JAVAH) -classpath ../bin/ -jni org.cacert.gigi.natives.SetUID && \
+       $(CC) $(CFLAGS) $(LDFLAGS) -o libsetuid.so org_cacert_gigi_natives_SetUID.c
+
+clean:
+       rm -f *.so
+       rm -f *.h
diff --git a/natives/org_cacert_gigi_natives_SetUID.c b/natives/org_cacert_gigi_natives_SetUID.c
new file mode 100644 (file)
index 0000000..6c94d61
--- /dev/null
@@ -0,0 +1,40 @@
+#include <jni.h>
+#include <sys/types.h>
+
+#include <unistd.h>
+
+#ifndef _Included_org_cacert_natives_SetUID
+#define _Included_org_cacert_natives_SetUID
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+static jobject getStatus(JNIEnv *env, int successCode, const char * message) {
+    jstring message_str = (*env)->NewStringUTF(env, message);
+    jboolean success = successCode;
+    jclass cls = (*env)->FindClass(env, "Lorg/cacert/gigi/natives/SetUID$Status;");
+    jmethodID constructor = (*env)->GetMethodID(env, cls, "<init>", "(ZLjava/lang/String;)V");
+    return (*env)->NewObject(env, cls, constructor, success, message_str);
+}
+
+JNIEXPORT jobject JNICALL Java_org_cacert_gigi_natives_SetUID_setUid
+        (JNIEnv *env, jobject obj, jint uid, jint gid) {
+
+    /* We don't need the reference for the object/class we are working on */
+    (void)obj;
+
+    if(setgid((int)gid)) {
+        return (jobject)getStatus(env, 0, "Error while setting GID.");
+    }
+
+    if(setuid((int)uid)) {
+        return (jobject)getStatus(env, 0, "Error while setting UID.");
+    }
+
+    return (jobject)getStatus(env, 1, "Successfully set uid/gid.");
+}
+
+#ifdef __cplusplus
+}
+#endif
+#endif
diff --git a/src/org/cacert/gigi/DevelLauncher.java b/src/org/cacert/gigi/DevelLauncher.java
new file mode 100644 (file)
index 0000000..97b4b3d
--- /dev/null
@@ -0,0 +1,165 @@
+package org.cacert.gigi;
+
+import java.io.BufferedReader;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.DataOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.lang.reflect.Field;
+import java.net.URL;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Properties;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.cacert.gigi.dbObjects.ObjectCache;
+import org.cacert.gigi.pages.Page;
+import org.kamranzafar.jtar.TarEntry;
+import org.kamranzafar.jtar.TarHeader;
+import org.kamranzafar.jtar.TarOutputStream;
+
+public class DevelLauncher {
+
+    public static final boolean DEVEL = true;
+
+    public static void main(String[] args) throws Exception {
+        Properties mainProps = new Properties();
+        try (FileInputStream inStream = new FileInputStream("config/gigi.properties")) {
+            mainProps.load(inStream);
+        }
+        for (int i = 0; i < args.length; i++) {
+            if (args[i].equals("--port")) {
+                mainProps.setProperty("port", args[i + 1]);
+            }
+            i++;
+        }
+        killPreviousInstance(mainProps);
+
+        ByteArrayOutputStream chunkConfig = new ByteArrayOutputStream();
+        DataOutputStream dos = new DataOutputStream(chunkConfig);
+        byte[] cacerts = Files.readAllBytes(Paths.get("config/cacerts.jks"));
+        byte[] keystore = Files.readAllBytes(Paths.get("config/keystore.pkcs12"));
+
+        DevelLauncher.writeGigiConfig(dos, "changeit".getBytes("UTF-8"), "changeit".getBytes("UTF-8"), mainProps, cacerts, keystore);
+        dos.flush();
+        InputStream oldin = System.in;
+        System.setIn(new ByteArrayInputStream(chunkConfig.toByteArray()));
+        Launcher.boot();
+        addDevelPage();
+        System.setIn(oldin);
+        BufferedReader br = new BufferedReader(new InputStreamReader(System.in, "UTF-8"));
+        System.out.println("Cacert-gigi system sucessfully started.");
+        System.out.println("Press enter to shutdown.");
+        br.readLine();
+        System.exit(0);
+    }
+
+    private static void killPreviousInstance(Properties mainProps) {
+        try {
+            String targetPort = mainProps.getProperty("http.port");
+            String targetHost = mainProps.getProperty("name.www");
+            URL u = new URL("http://" + targetHost + ":" + targetPort + "/kill");
+            u.openStream();
+        } catch (IOException e) {
+        }
+    }
+
+    public static void addDevelPage() {
+        try {
+            Field instF = Gigi.class.getDeclaredField("instance");
+            Field pageF = Gigi.class.getDeclaredField("pages");
+            instF.setAccessible(true);
+            pageF.setAccessible(true);
+            Object gigi = instF.get(null);
+
+            // Check if we got a proper map (as much as we can tell)
+            Object pagesObj = pageF.get(gigi);
+            @SuppressWarnings("unchecked")
+            HashMap<String, Page> pages = pagesObj instanceof Map ? new HashMap<>((Map<String, Page>) pagesObj) : null;
+
+            pages.put("/manage", new Page("Page-manager") {
+
+                @Override
+                public void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
+                    ObjectCache.clearAllCaches();
+                    resp.getWriter().println("All caches cleared.");
+                    System.out.println("Caches cleared.");
+
+                }
+
+                @Override
+                public boolean needsLogin() {
+                    return false;
+                }
+
+            });
+
+            pages.put("/kill", new Page("Kill") {
+
+                /**
+                 * The contained call to {@link System#exit(int)} is mainly
+                 * needed to kill this instance immediately if another
+                 * {@link DevelLauncher} is booting up to free all ports This is
+                 * required for fast development cycles.
+                 * 
+                 * @see #killPreviousInstance(Properties)
+                 */
+                @Override
+                public void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
+                    System.exit(0);
+                }
+
+                @Override
+                public boolean needsLogin() {
+                    return false;
+                }
+            });
+
+            pageF.set(gigi, Collections.unmodifiableMap(pages));
+        } catch (ReflectiveOperationException e) {
+            e.printStackTrace();
+        }
+    }
+
+    public static void writeGigiConfig(OutputStream target, byte[] keystorepw, byte[] truststorepw, Properties mainprop, byte[] cacerts, byte[] keystore) throws IOException {
+        TarOutputStream tos = new TarOutputStream(target);
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        mainprop.store(baos, "");
+
+        putTarEntry(baos.toByteArray(), tos, "gigi.properties");
+        putTarEntry(keystorepw, tos, "keystorepw");
+        putTarEntry(truststorepw, tos, "truststorepw");
+        putTarEntry(keystore, tos, "keystore.pkcs12");
+        putTarEntry(cacerts, tos, "cacerts.jks");
+        tos.close();
+
+    }
+
+    private static void putTarEntry(byte[] data, TarOutputStream tos, String name) throws IOException {
+        TarHeader th = new TarHeader();
+        th.name = new StringBuffer(name);
+        th.size = data.length;
+        tos.putNextEntry(new TarEntry(th));
+        tos.write(data);
+    }
+
+    public static void writeChunk(DataOutputStream dos, byte[] chunk) throws IOException {
+        dos.writeInt(chunk.length);
+        dos.write(chunk);
+    }
+
+    public static void launch(Properties props, File cacerts, File keystore) throws IOException {
+        ByteArrayOutputStream config = new ByteArrayOutputStream();
+        props.store(config, "");
+    }
+}
diff --git a/src/org/cacert/gigi/Gigi.java b/src/org/cacert/gigi/Gigi.java
new file mode 100644 (file)
index 0000000..6af640e
--- /dev/null
@@ -0,0 +1,430 @@
+package org.cacert.gigi;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.UnsupportedEncodingException;
+import java.security.KeyStore;
+import java.security.cert.X509Certificate;
+import java.util.Calendar;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Properties;
+import java.util.regex.Pattern;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpSession;
+
+import org.cacert.gigi.database.DatabaseConnection;
+import org.cacert.gigi.dbObjects.DomainPingConfiguration;
+import org.cacert.gigi.dbObjects.User;
+import org.cacert.gigi.localisation.Language;
+import org.cacert.gigi.output.Menu;
+import org.cacert.gigi.output.PageMenuItem;
+import org.cacert.gigi.output.SimpleMenuItem;
+import org.cacert.gigi.output.template.Form.CSRFException;
+import org.cacert.gigi.output.template.Outputable;
+import org.cacert.gigi.output.template.Template;
+import org.cacert.gigi.pages.LoginPage;
+import org.cacert.gigi.pages.LogoutPage;
+import org.cacert.gigi.pages.MainPage;
+import org.cacert.gigi.pages.Page;
+import org.cacert.gigi.pages.PolicyIndex;
+import org.cacert.gigi.pages.RootCertPage;
+import org.cacert.gigi.pages.StaticPage;
+import org.cacert.gigi.pages.TestSecure;
+import org.cacert.gigi.pages.Verify;
+import org.cacert.gigi.pages.account.ChangePasswordPage;
+import org.cacert.gigi.pages.account.MyDetails;
+import org.cacert.gigi.pages.account.certs.CertificateAdd;
+import org.cacert.gigi.pages.account.certs.Certificates;
+import org.cacert.gigi.pages.account.domain.DomainOverview;
+import org.cacert.gigi.pages.account.mail.MailOverview;
+import org.cacert.gigi.pages.admin.TTPAdminPage;
+import org.cacert.gigi.pages.admin.support.FindDomainPage;
+import org.cacert.gigi.pages.admin.support.FindUserPage;
+import org.cacert.gigi.pages.admin.support.SupportUserDetailsPage;
+import org.cacert.gigi.pages.error.AccessDenied;
+import org.cacert.gigi.pages.error.PageNotFound;
+import org.cacert.gigi.pages.main.RegisterPage;
+import org.cacert.gigi.pages.orga.CreateOrgPage;
+import org.cacert.gigi.pages.orga.ViewOrgPage;
+import org.cacert.gigi.pages.wot.AssurePage;
+import org.cacert.gigi.pages.wot.MyPoints;
+import org.cacert.gigi.pages.wot.RequestTTPPage;
+import org.cacert.gigi.ping.PingerDaemon;
+import org.cacert.gigi.util.ServerConstants;
+
+public class Gigi extends HttpServlet {
+
+    private class MenuBuilder {
+
+        private LinkedList<Menu> categories = new LinkedList<Menu>();
+
+        private HashMap<String, Page> pages = new HashMap<String, Page>();
+
+        private Menu rootMenu;
+
+        public MenuBuilder() {}
+
+        private void putPage(String path, Page p, String category) {
+            pages.put(path, p);
+            if (category == null) {
+                return;
+            }
+            Menu m = getMenu(category);
+            m.addItem(new PageMenuItem(p, path.replaceFirst("/?\\*$", "")));
+
+        }
+
+        private Menu getMenu(String category) {
+            Menu m = null;
+            for (Menu menu : categories) {
+                if (menu.getMenuName().equals(category)) {
+                    m = menu;
+                    break;
+                }
+            }
+            if (m == null) {
+                m = new Menu(category);
+                categories.add(m);
+            }
+            return m;
+        }
+
+        public Menu generateMenu() throws ServletException {
+            putPage("/denied", new AccessDenied(), null);
+            putPage("/error", new PageNotFound(), null);
+            putPage("/login", new LoginPage("Password Login"), "CAcert.org");
+            getMenu("CAcert.org").addItem(new SimpleMenuItem("https://" + ServerConstants.getSecureHostNamePort() + "/login", "Certificate Login") {
+
+                @Override
+                public boolean isPermitted(User u) {
+                    return u == null;
+                }
+            });
+            putPage("/", new MainPage("CAcert - Home"), null);
+            putPage("/roots", new RootCertPage(truststore), "CAcert.org");
+            putPage(ChangePasswordPage.PATH, new ChangePasswordPage(), "My Account");
+            putPage(LogoutPage.PATH, new LogoutPage("Logout"), "My Account");
+            putPage("/secure", new TestSecure(), null);
+            putPage(Verify.PATH, new Verify(), null);
+            putPage(AssurePage.PATH + "/*", new AssurePage(), "Web of Trust");
+            putPage(Certificates.PATH + "/*", new Certificates(), "Certificates");
+            putPage(MyDetails.PATH, new MyDetails(), "My Account");
+            putPage(RegisterPage.PATH, new RegisterPage(), "CAcert.org");
+            putPage(CertificateAdd.PATH, new CertificateAdd(), "Certificates");
+            putPage(MailOverview.DEFAULT_PATH, new MailOverview("My email addresses"), "Certificates");
+            putPage(DomainOverview.PATH + "*", new DomainOverview("Domains"), "Certificates");
+            putPage(MyPoints.PATH, new MyPoints("My Points"), "Web of Trust");
+            putPage(RequestTTPPage.PATH, new RequestTTPPage(), "Web of Trust");
+            putPage(TTPAdminPage.PATH + "/*", new TTPAdminPage(), "Admin");
+            putPage(CreateOrgPage.DEFAULT_PATH, new CreateOrgPage(), "Organisation Admin");
+            putPage(ViewOrgPage.DEFAULT_PATH + "/*", new ViewOrgPage(), "Organisation Admin");
+            putPage(FindDomainPage.PATH, new FindDomainPage("Find Domain"), "System Admin");
+            putPage(FindUserPage.PATH, new FindUserPage("Find User"), "System Admin");
+            putPage(SupportUserDetailsPage.PATH + "*", new SupportUserDetailsPage("Support: User Details"), null);
+            if (testing) {
+                try {
+                    Class<?> manager = Class.forName("org.cacert.gigi.pages.Manager");
+                    Page p = (Page) manager.getMethod("getInstance").invoke(null);
+                    String pa = (String) manager.getField("PATH").get(null);
+                    putPage(pa + "/*", p, "Gigi test server");
+                } catch (ReflectiveOperationException e) {
+                    e.printStackTrace();
+                }
+            }
+
+            try {
+                putPage("/wot/rules", new StaticPage("Web of Trust Rules", AssurePage.class.getResourceAsStream("Rules.templ")), "Web of Trust");
+            } catch (UnsupportedEncodingException e) {
+                throw new ServletException(e);
+            }
+            baseTemplate = new Template(Gigi.class.getResource("Gigi.templ"));
+            rootMenu = new Menu("Main");
+            Menu about = new Menu("About CAcert.org");
+            categories.add(about);
+
+            about.addItem(new SimpleMenuItem("//blog.cacert.org/", "CAcert News"));
+            about.addItem(new SimpleMenuItem("//wiki.cacert.org/", "Wiki Documentation"));
+            putPage(PolicyIndex.DEFAULT_PATH, new PolicyIndex(), "About CAcert.org");
+            about.addItem(new SimpleMenuItem("//wiki.cacert.org/FAQ/Privileges", "Point System"));
+            about.addItem(new SimpleMenuItem("//bugs.cacert.org/", "Bug Database"));
+            about.addItem(new SimpleMenuItem("//wiki.cacert.org/Board", "CAcert Board"));
+            about.addItem(new SimpleMenuItem("//lists.cacert.org/wws", "Mailing Lists"));
+            about.addItem(new SimpleMenuItem("//blog.CAcert.org/feed", "RSS News Feed"));
+
+            Menu languages = new Menu("Translations");
+            for (Locale l : Language.getSupportedLocales()) {
+                languages.addItem(new SimpleMenuItem("?lang=" + l.toString(), l.getDisplayName(l)));
+            }
+            categories.add(languages);
+            for (Menu menu : categories) {
+                menu.prepare();
+                rootMenu.addItem(menu);
+            }
+
+            rootMenu.prepare();
+            return rootMenu;
+        }
+
+        public Map<String, Page> getPages() {
+            return Collections.unmodifiableMap(pages);
+        }
+    }
+
+    public static final String LOGGEDIN = "loggedin";
+
+    public static final String CERT_SERIAL = "org.cacert.gigi.serial";
+
+    public static final String CERT_ISSUER = "org.cacert.gigi.issuer";
+
+    public static final String USER = "user";
+
+    public static final String LOGIN_METHOD = "org.cacert.gigi.loginMethod";
+
+    private static final long serialVersionUID = -6386785421902852904L;
+
+    private static Gigi instance;
+
+    private Template baseTemplate;
+
+    private PingerDaemon pinger;
+
+    private KeyStore truststore;
+
+    private boolean testing;
+
+    private Menu rootMenu;
+
+    private Map<String, Page> pages;
+
+    private boolean firstInstanceInited = false;
+
+    public Gigi(Properties conf, KeyStore truststore) {
+        synchronized (Gigi.class) {
+            if (instance != null) {
+                throw new IllegalStateException("Multiple Gigi instances!");
+            }
+            testing = conf.getProperty("testing") != null;
+            instance = this;
+            DatabaseConnection.init(conf);
+            this.truststore = truststore;
+            pinger = new PingerDaemon(truststore);
+            pinger.start();
+        }
+    }
+
+    @Override
+    public synchronized void init() throws ServletException {
+        if (firstInstanceInited) {
+            super.init();
+            return;
+        }
+        MenuBuilder mb = new MenuBuilder();
+        rootMenu = mb.generateMenu();
+        pages = mb.getPages();
+
+        firstInstanceInited = true;
+        super.init();
+    }
+
+    private Page getPage(String pathInfo) {
+        if (pathInfo.endsWith("/") && !pathInfo.equals("/")) {
+            pathInfo = pathInfo.substring(0, pathInfo.length() - 1);
+        }
+        Page page = pages.get(pathInfo);
+        if (page != null) {
+            return page;
+        }
+        page = pages.get(pathInfo + "/*");
+        if (page != null) {
+            return page;
+        }
+        int idx = pathInfo.lastIndexOf('/');
+        pathInfo = pathInfo.substring(0, idx);
+
+        page = pages.get(pathInfo + "/*");
+        if (page != null) {
+            return page;
+        }
+        return null;
+
+    }
+
+    private static String staticTemplateVarHttp = "http://" + ServerConstants.getStaticHostNamePort();
+
+    private static String staticTemplateVarHttps = "https://" + ServerConstants.getStaticHostNamePortSecure();
+
+    private static String getStaticTemplateVar(boolean https) {
+        if (https) {
+            return staticTemplateVarHttps;
+        } else {
+            return staticTemplateVarHttp;
+        }
+    }
+
+    @Override
+    protected void service(final HttpServletRequest req, final HttpServletResponse resp) throws ServletException, IOException {
+        boolean isSecure = req.getServerPort() == ServerConstants.getSecurePort();
+        addXSSHeaders(resp, isSecure);
+        // Firefox only sends this, if it's a cross domain access; safari sends
+        // it always
+        String originHeader = req.getHeader("Origin");
+        if (originHeader != null //
+                &&
+                !(originHeader.matches("^" + Pattern.quote("https://" + ServerConstants.getWwwHostNamePortSecure()) + "(/.*|)") || //
+                        originHeader.matches("^" + Pattern.quote("http://" + ServerConstants.getWwwHostNamePort()) + "(/.*|)") || //
+                originHeader.matches("^" + Pattern.quote("https://" + ServerConstants.getSecureHostNamePort()) + "(/.*|)"))) {
+            resp.setContentType("text/html; charset=utf-8");
+            resp.getWriter().println("<html><head><title>Alert</title></head><body>No cross domain access allowed.<br/><b>If you don't know why you're seeing this you may have been fished! Please change your password immediately!</b></body></html>");
+            return;
+        }
+        HttpSession hs = req.getSession();
+        String clientSerial = (String) hs.getAttribute(CERT_SERIAL);
+        if (clientSerial != null) {
+            X509Certificate[] cert = (X509Certificate[]) req.getAttribute("javax.servlet.request.X509Certificate");
+            if (cert == null || cert[0] == null//
+                    || !cert[0].getSerialNumber().toString(16).toUpperCase().equals(clientSerial) //
+                    || !cert[0].getIssuerDN().equals(hs.getAttribute(CERT_ISSUER))) {
+                hs.invalidate();
+                resp.sendError(403, "Certificate mismatch.");
+                return;
+            }
+
+        }
+        if (req.getParameter("lang") != null) {
+            Locale l = Language.getLocaleFromString(req.getParameter("lang"));
+            Language lu = Language.getInstance(l);
+            req.getSession().setAttribute(Language.SESSION_ATTRIB_NAME, lu.getLocale());
+        }
+        final Page p = getPage(req.getPathInfo());
+
+        if (p != null) {
+            if ( !isSecure && (p.needsLogin() || p instanceof LoginPage || p instanceof RegisterPage)) {
+                resp.sendRedirect("https://" + ServerConstants.getWwwHostNamePortSecure() + req.getPathInfo());
+                return;
+            }
+            User currentPageUser = LoginPage.getUser(req);
+            if ( !p.isPermitted(currentPageUser)) {
+                if (hs.getAttribute("loggedin") == null) {
+                    String request = req.getPathInfo();
+                    request = request.split("\\?")[0];
+                    hs.setAttribute(LoginPage.LOGIN_RETURNPATH, request);
+                    resp.sendRedirect("/login");
+                    return;
+                }
+                resp.sendError(403);
+                return;
+            }
+            if (p.beforeTemplate(req, resp)) {
+                return;
+            }
+            HashMap<String, Object> vars = new HashMap<String, Object>();
+            Outputable content = new Outputable() {
+
+                @Override
+                public void output(PrintWriter out, Language l, Map<String, Object> vars) {
+                    try {
+                        if (req.getMethod().equals("POST")) {
+                            if (req.getQueryString() != null) {
+                                return;
+                            }
+                            p.doPost(req, resp);
+                        } else {
+                            p.doGet(req, resp);
+                        }
+                    } catch (CSRFException err) {
+                        try {
+                            resp.sendError(500, "CSRF invalid");
+                        } catch (IOException e) {
+                            e.printStackTrace();
+                        }
+                    } catch (IOException e) {
+                        e.printStackTrace();
+                    }
+
+                }
+            };
+            Language lang = Page.getLanguage(req);
+
+            vars.put(Menu.USER_VALUE, currentPageUser);
+            vars.put("menu", rootMenu);
+            vars.put("title", lang.getTranslation(p.getTitle()));
+            vars.put("static", getStaticTemplateVar(isSecure));
+            vars.put("year", Calendar.getInstance().get(Calendar.YEAR));
+            vars.put("content", content);
+            if (currentPageUser != null) {
+                vars.put("loggedInAs", currentPageUser.getName().toString());
+                vars.put("loginMethod", lang.getTranslation((String) req.getSession().getAttribute(LOGIN_METHOD)));
+            }
+            resp.setContentType("text/html; charset=utf-8");
+            baseTemplate.output(resp.getWriter(), lang, vars);
+        } else {
+            resp.sendError(404, "Page not found.");
+        }
+
+    }
+
+    public static void addXSSHeaders(HttpServletResponse hsr, boolean doHttps) {
+        hsr.addHeader("Access-Control-Allow-Origin", "https://" + ServerConstants.getWwwHostNamePortSecure() + " https://" + ServerConstants.getSecureHostNamePort());
+        hsr.addHeader("Access-Control-Max-Age", "60");
+        if (doHttps) {
+            hsr.addHeader("Content-Security-Policy", httpsCSP);
+        } else {
+            hsr.addHeader("Content-Security-Policy", httpCSP);
+        }
+        hsr.addHeader("Strict-Transport-Security", "max-age=31536000");
+
+    }
+
+    private static String httpsCSP = genHttpsCSP();
+
+    private static String httpCSP = genHttpCSP();
+
+    private static String genHttpsCSP() {
+        StringBuffer csp = new StringBuffer();
+        csp.append("default-src 'none'");
+        csp.append(";font-src https://" + ServerConstants.getStaticHostNamePortSecure());
+        csp.append(";img-src https://" + ServerConstants.getStaticHostNamePortSecure());
+        csp.append(";media-src 'none'; object-src 'none'");
+        csp.append(";script-src https://" + ServerConstants.getStaticHostNamePortSecure());
+        csp.append(";style-src https://" + ServerConstants.getStaticHostNamePortSecure());
+        csp.append(";form-action https://" + ServerConstants.getSecureHostNamePort() + " https://" + ServerConstants.getWwwHostNamePortSecure());
+        csp.append(";report-url https://api.cacert.org/security/csp/report");
+        return csp.toString();
+    }
+
+    private static String genHttpCSP() {
+        StringBuffer csp = new StringBuffer();
+        csp.append("default-src 'none'");
+        csp.append(";font-src http://" + ServerConstants.getStaticHostNamePort());
+        csp.append(";img-src http://" + ServerConstants.getStaticHostNamePort());
+        csp.append(";media-src 'none'; object-src 'none'");
+        csp.append(";script-src http://" + ServerConstants.getStaticHostNamePort());
+        csp.append(";style-src http://" + ServerConstants.getStaticHostNamePort());
+        csp.append(";form-action https://" + ServerConstants.getSecureHostNamePort() + " https://" + ServerConstants.getWwwHostNamePort());
+        csp.append(";report-url http://api.cacert.org/security/csp/report");
+        return csp.toString();
+    }
+
+    /**
+     * Requests Pinging of domains.
+     * 
+     * @param toReping
+     *            if not null, the {@link DomainPingConfiguration} to test, if
+     *            null, just re-check if there is something to do.
+     */
+    public static void notifyPinger(DomainPingConfiguration toReping) {
+        if (toReping != null) {
+            instance.pinger.queue(toReping);
+        }
+        instance.pinger.interrupt();
+    }
+
+}
diff --git a/src/org/cacert/gigi/Gigi.templ b/src/org/cacert/gigi/Gigi.templ
new file mode 100644 (file)
index 0000000..478c9e9
--- /dev/null
@@ -0,0 +1,61 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title><?=$title?></title>
+<link rel="stylesheet" href="<?=$static?>/default.css" type="text/css">
+<script src="<?=$static?>/menu.js"></script>
+<link rel="alternate" type="application/rss+xml" title="Newsfeed" href="//blog.CAcert.org/feed">
+<meta http-equiv="content-type" content="text/html; charset=UTF-8">
+</head>
+<body>
+       <div id="pagecell1">
+               <div id="pageName">
+                       <br>
+                       <div id="pageLogo">
+                               <a href="/"><img src="<?=$static?>/images/cacert4.png"
+                                       alt="CAcert.org logo"></a>
+                       </div>
+                       <div id="googlead">
+                               <h2><?=_Free digital certificates!?></h2>
+                       </div>
+               </div>
+               <div id="pageNav">
+                       <? if($loggedInAs) { ?><div><?=_Logged in as?>: <?=$loggedInAs?> <?=_with?> <?=$loginMethod?></div><? } ?>
+                       <?=$menu?>
+                       <div>
+                               <h3 class="pointer"><?=_Advertising?></h3>
+                               <ul class="menu hidden" id="recom"></ul>
+                       </div>
+               </div>
+               <div id="content">
+                       <h1><?=$title?></h1>
+                       <div class="content"><?=$content?></div>
+               </div>
+               <div class="sponsorinfo">
+                       <?=_CAcert operations are sponsored by?>
+                       <a href="http://www.bit.nl/" target="_blank">
+                               <img class="sponsorlogo" src="<?=$static?>/images/bit.png" alt="[BIT logo]">
+                       </a>
+                       <a href="http://www.tunix.nl/" target="_blank">
+                               <img class="sponsorlogo" src="<?=$static?>/images/tunix.png" alt="[TUNIX logo]">
+                       </a>
+                       <a href="http://www.nlnet.nl/" target="_blank">
+                               <img class="sponsorlogo" src="<?=$static?>/images/nlnet.png" alt="[NLnet logo]">
+                       </a>
+                       <a href="http://www.openarchitecturenetwork.org/" target="_blank">
+                               <img class="sponsorlogo" src="<?=$static?>/images/oan.png" alt="[OAN logo]">
+                       </a>
+               </div>
+
+               <div id="siteInfo">
+                       <a href="//wiki.cacert.org/FAQ/AboutUs"><?=_About Us?></a> |
+                       <a href="/index.php?id=13"><?=_Donations?></a> |
+                       <a href="//wiki.cacert.org/wiki/CAcertIncorporated"><?=_Association Membership?></a> |
+                       <a href="/policy/PrivacyPolicy.html"><?=_Privacy Policy?></a> |
+                       <a href="/index.php?id=51"><?=_Mission Statement?></a> |
+                       <a href="/index.php?id=11"><?=_Contact Us?></a> |
+                       ©2002-<?=$year?> <?=_by CAcert?>
+               </div>
+       </div>
+</body>
+</html>
diff --git a/src/org/cacert/gigi/GigiApiException.java b/src/org/cacert/gigi/GigiApiException.java
new file mode 100644 (file)
index 0000000..944fe64
--- /dev/null
@@ -0,0 +1,93 @@
+package org.cacert.gigi;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.sql.SQLException;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.Locale;
+
+import org.cacert.gigi.localisation.Language;
+import org.cacert.gigi.output.template.Outputable;
+import org.cacert.gigi.output.template.TranslateCommand;
+
+public class GigiApiException extends Exception {
+
+    private static final long serialVersionUID = -164928670180852588L;
+
+    private SQLException e;
+
+    private LinkedList<Outputable> messages = new LinkedList<>();
+
+    public GigiApiException(SQLException e) {
+        super(e);
+        this.e = e;
+    }
+
+    public GigiApiException(String message) {
+        super(message);
+        messages.add(new TranslateCommand(message));
+    }
+
+    public GigiApiException() {
+
+    }
+
+    public GigiApiException(Outputable out) {
+        messages.add(out);
+    }
+
+    public void mergeInto(GigiApiException e2) {
+        messages.addAll(e2.messages);
+        if (e == null) {
+            e = e2.e;
+        }
+    }
+
+    public boolean isInternalError() {
+        return e != null;
+    }
+
+    public void format(PrintWriter out, Language language) {
+        out.println("<div class='formError'>");
+        if (isInternalError()) {
+            e.printStackTrace();
+            out.print("<div>");
+            out.println(language.getTranslation("An internal error ouccured."));
+            out.println("</div>");
+        }
+        HashMap<String, Object> map = new HashMap<>();
+        for (Outputable message : messages) {
+            map.clear();
+
+            out.print("<div>");
+            message.output(out, language, map);
+            out.println("</div>");
+        }
+        out.println("</div>");
+
+    }
+
+    public boolean isEmpty() {
+        return e == null && messages.size() == 0;
+    }
+
+    @Override
+    public String getMessage() {
+        if (messages.size() != 0) {
+            StringWriter sw = new StringWriter();
+            PrintWriter pw = new PrintWriter(sw);
+
+            HashMap<String, Object> map = new HashMap<>();
+            for (Outputable message : messages) {
+                map.clear();
+                message.output(pw, Language.getInstance(Locale.ENGLISH), map);
+            }
+            pw.flush();
+
+            return sw.toString();
+        }
+        return "";
+    }
+
+}
diff --git a/src/org/cacert/gigi/GigiConfig.java b/src/org/cacert/gigi/GigiConfig.java
new file mode 100644 (file)
index 0000000..3a1b9ee
--- /dev/null
@@ -0,0 +1,100 @@
+package org.cacert.gigi;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.security.GeneralSecurityException;
+import java.security.KeyStore;
+import java.util.Properties;
+
+import org.kamranzafar.jtar.TarEntry;
+import org.kamranzafar.jtar.TarInputStream;
+
+public class GigiConfig {
+
+    public static final String GIGI_CONFIG_VERSION = "GigiConfigV1.0";
+
+    private byte[] cacerts;
+
+    private byte[] keystore;
+
+    private Properties mainProps = new Properties();
+
+    private char[] keystorpw;
+
+    private char[] truststorepw;
+
+    private GigiConfig() {}
+
+    public byte[] getCacerts() {
+        return cacerts;
+    }
+
+    public byte[] getKeystore() {
+        return keystore;
+    }
+
+    public Properties getMainProps() {
+        return mainProps;
+    }
+
+    public static GigiConfig parse(InputStream input) throws IOException {
+        TarInputStream tis = new TarInputStream(input);
+        TarEntry t;
+        GigiConfig gc = new GigiConfig();
+        while ((t = tis.getNextEntry()) != null) {
+            if (t.getName().equals("gigi.properties")) {
+                gc.mainProps.load(tis);
+            } else if (t.getName().equals("cacerts.jks")) {
+                gc.cacerts = readFully(tis);
+            } else if (t.getName().equals("keystore.pkcs12")) {
+                gc.keystore = readFully(tis);
+            } else if (t.getName().equals("keystorepw")) {
+                gc.keystorpw = transformSafe(readFully(tis));
+            } else if (t.getName().equals("truststorepw")) {
+                gc.truststorepw = transformSafe(readFully(tis));
+            } else {
+                System.out.println("Unknown config: " + t.getName());
+            }
+        }
+        tis.close();
+        return gc;
+    }
+
+    public static byte[] readFully(InputStream is) throws IOException {
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        byte[] buffer = new byte[1024];
+        int len = 0;
+        while ((len = is.read(buffer)) > 0) {
+            baos.write(buffer, 0, len);
+        }
+        baos.close();
+        return baos.toByteArray();
+    }
+
+    private static char[] transformSafe(byte[] readChunk) {
+        char[] res = new char[readChunk.length];
+        for (int i = 0; i < res.length; i++) {
+            res[i] = (char) readChunk[i];
+            readChunk[i] = 0;
+        }
+        return res;
+    }
+
+    public KeyStore getPrivateStore() throws GeneralSecurityException, IOException {
+        KeyStore ks1 = KeyStore.getInstance("pkcs12");
+        ks1.load(new ByteArrayInputStream(keystore), keystorpw);
+        return ks1;
+    }
+
+    public KeyStore getTrustStore() throws GeneralSecurityException, IOException {
+        KeyStore ks1 = KeyStore.getInstance("jks");
+        ks1.load(new ByteArrayInputStream(cacerts), truststorepw);
+        return ks1;
+    }
+
+    public String getPrivateStorePw() {
+        return new String(keystorpw);
+    }
+}
diff --git a/src/org/cacert/gigi/Launcher.java b/src/org/cacert/gigi/Launcher.java
new file mode 100644 (file)
index 0000000..a399dcd
--- /dev/null
@@ -0,0 +1,293 @@
+package org.cacert.gigi;
+
+import java.io.IOException;
+import java.security.GeneralSecurityException;
+import java.security.Key;
+import java.security.KeyStore;
+import java.security.KeyStoreException;
+import java.security.NoSuchAlgorithmException;
+import java.security.UnrecoverableKeyException;
+import java.security.cert.Certificate;
+import java.util.List;
+import java.util.Locale;
+import java.util.Properties;
+import java.util.TimeZone;
+
+import javax.net.ssl.ExtendedSSLSession;
+import javax.net.ssl.SNIHostName;
+import javax.net.ssl.SNIServerName;
+import javax.net.ssl.SSLEngine;
+import javax.net.ssl.SSLParameters;
+import javax.net.ssl.SSLSession;
+import javax.servlet.http.HttpServletResponse;
+
+import org.cacert.gigi.api.GigiAPI;
+import org.cacert.gigi.email.EmailProvider;
+import org.cacert.gigi.natives.SetUID;
+import org.cacert.gigi.util.CipherInfo;
+import org.cacert.gigi.util.ServerConstants;
+import org.eclipse.jetty.http.HttpHeader;
+import org.eclipse.jetty.http.HttpVersion;
+import org.eclipse.jetty.server.Connector;
+import org.eclipse.jetty.server.Handler;
+import org.eclipse.jetty.server.HttpConfiguration;
+import org.eclipse.jetty.server.HttpConnectionFactory;
+import org.eclipse.jetty.server.SecureRequestCustomizer;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.ServerConnector;
+import org.eclipse.jetty.server.SessionManager;
+import org.eclipse.jetty.server.SslConnectionFactory;
+import org.eclipse.jetty.server.handler.ContextHandler;
+import org.eclipse.jetty.server.handler.HandlerList;
+import org.eclipse.jetty.server.handler.HandlerWrapper;
+import org.eclipse.jetty.server.handler.ResourceHandler;
+import org.eclipse.jetty.servlet.ErrorPageErrorHandler;
+import org.eclipse.jetty.servlet.ServletContextHandler;
+import org.eclipse.jetty.servlet.ServletHolder;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.resource.Resource;
+import org.eclipse.jetty.util.ssl.SslContextFactory;
+
+public class Launcher {
+
+    public static void main(String[] args) throws Exception {
+        System.setProperty("jdk.tls.ephemeralDHKeySize", "4096");
+        boot();
+    }
+
+    public static void boot() throws Exception {
+        Locale.setDefault(Locale.ENGLISH);
+        TimeZone.setDefault(TimeZone.getTimeZone("UTC"));
+
+        GigiConfig conf = GigiConfig.parse(System.in);
+        ServerConstants.init(conf.getMainProps());
+        initEmails(conf);
+
+        Server s = new Server();
+        HttpConfiguration httpsConfig = createHttpConfiguration();
+
+        // for client-cert auth
+        httpsConfig.addCustomizer(new SecureRequestCustomizer());
+
+        HttpConfiguration httpConfig = createHttpConfiguration();
+
+        s.setConnectors(new Connector[] {
+                createConnector(conf, s, httpsConfig, true), createConnector(conf, s, httpConfig, false)
+        });
+
+        HandlerList hl = new HandlerList();
+        hl.setHandlers(new Handler[] {
+                generateStaticContext(), generateGigiContexts(conf.getMainProps(), conf.getTrustStore()), generateAPIContext()
+        });
+        s.setHandler(hl);
+        s.start();
+        if ((ServerConstants.getSecurePort() <= 1024 || ServerConstants.getPort() <= 1024) && !System.getProperty("os.name").toLowerCase().contains("win")) {
+            SetUID uid = new SetUID();
+            if ( !uid.setUid(65536 - 2, 65536 - 2).getSuccess()) {
+                Log.getLogger(Launcher.class).warn("Couldn't set uid!");
+            }
+        }
+        if (conf.getMainProps().containsKey("testrunner")) {
+            DevelLauncher.addDevelPage();
+        }
+    }
+
+    private static ServerConnector createConnector(GigiConfig conf, Server s, HttpConfiguration httpConfig, boolean doHttps) throws GeneralSecurityException, IOException {
+        ServerConnector connector;
+        if (doHttps) {
+            connector = new ServerConnector(s, createConnectionFactory(conf), new HttpConnectionFactory(httpConfig));
+        } else {
+            connector = new ServerConnector(s, new HttpConnectionFactory(httpConfig));
+        }
+        connector.setHost(conf.getMainProps().getProperty("host"));
+        if (doHttps) {
+            connector.setPort(ServerConstants.getSecurePort());
+        } else {
+            connector.setPort(ServerConstants.getPort());
+        }
+        connector.setAcceptQueueSize(100);
+        return connector;
+    }
+
+    private static HttpConfiguration createHttpConfiguration() {
+        // SSL HTTP Configuration
+        HttpConfiguration httpsConfig = new HttpConfiguration();
+        httpsConfig.setSendServerVersion(false);
+        httpsConfig.setSendXPoweredBy(false);
+        return httpsConfig;
+    }
+
+    private static void initEmails(GigiConfig conf) throws GeneralSecurityException, IOException, KeyStoreException, NoSuchAlgorithmException, UnrecoverableKeyException {
+        KeyStore privateStore = conf.getPrivateStore();
+        Certificate mail = privateStore.getCertificate("mail");
+        Key k = privateStore.getKey("mail", conf.getPrivateStorePw().toCharArray());
+        EmailProvider.initSystem(conf.getMainProps(), mail, k);
+    }
+
+    private static SslConnectionFactory createConnectionFactory(GigiConfig conf) throws GeneralSecurityException, IOException {
+        final SslContextFactory sslContextFactory = generateSSLContextFactory(conf, "www");
+        final SslContextFactory secureContextFactory = generateSSLContextFactory(conf, "secure");
+        secureContextFactory.setWantClientAuth(true);
+        secureContextFactory.setNeedClientAuth(false);
+        final SslContextFactory staticContextFactory = generateSSLContextFactory(conf, "static");
+        final SslContextFactory apiContextFactory = generateSSLContextFactory(conf, "api");
+        apiContextFactory.setWantClientAuth(true);
+        try {
+            secureContextFactory.start();
+            staticContextFactory.start();
+            apiContextFactory.start();
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+        return new SslConnectionFactory(sslContextFactory, HttpVersion.HTTP_1_1.asString()) {
+
+            @Override
+            public boolean shouldRestartSSL() {
+                return true;
+            }
+
+            @Override
+            public SSLEngine restartSSL(SSLSession sslSession) {
+                SSLEngine e2 = null;
+                if (sslSession instanceof ExtendedSSLSession) {
+                    ExtendedSSLSession es = (ExtendedSSLSession) sslSession;
+                    List<SNIServerName> names = es.getRequestedServerNames();
+                    for (SNIServerName sniServerName : names) {
+                        if (sniServerName instanceof SNIHostName) {
+                            SNIHostName host = (SNIHostName) sniServerName;
+                            String hostname = host.getAsciiName();
+                            if (hostname.equals(ServerConstants.getWwwHostName())) {
+                                e2 = sslContextFactory.newSSLEngine();
+                            } else if (hostname.equals(ServerConstants.getStaticHostName())) {
+                                e2 = staticContextFactory.newSSLEngine();
+                            } else if (hostname.equals(ServerConstants.getSecureHostName())) {
+                                e2 = secureContextFactory.newSSLEngine();
+                            } else if (hostname.equals(ServerConstants.getApiHostName())) {
+                                e2 = apiContextFactory.newSSLEngine();
+                            }
+                            break;
+                        }
+                    }
+                }
+                if (e2 == null) {
+                    e2 = sslContextFactory.newSSLEngine(sslSession.getPeerHost(), sslSession.getPeerPort());
+                }
+                e2.setUseClientMode(false);
+                return e2;
+            }
+        };
+    }
+
+    private static Handler generateGigiContexts(Properties conf, KeyStore trust) {
+        ServletHolder webAppServlet = new ServletHolder(new Gigi(conf, trust));
+
+        ContextHandler ch = generateGigiServletContext(webAppServlet);
+        ch.setVirtualHosts(new String[] {
+            ServerConstants.getWwwHostName()
+        });
+        ContextHandler chSecure = generateGigiServletContext(webAppServlet);
+        chSecure.setVirtualHosts(new String[] {
+            ServerConstants.getSecureHostName()
+        });
+
+        HandlerList hl = new HandlerList();
+        hl.setHandlers(new Handler[] {
+                ch, chSecure
+        });
+        return hl;
+    }
+
+    private static ContextHandler generateGigiServletContext(ServletHolder webAppServlet) {
+        final ResourceHandler rh = generateResourceHandler();
+        rh.setResourceBase("static/www");
+
+        HandlerWrapper hw = new PolicyRedirector();
+        hw.setHandler(rh);
+
+        ServletContextHandler servlet = new ServletContextHandler(ServletContextHandler.SESSIONS);
+        servlet.setInitParameter(SessionManager.__SessionCookieProperty, "CACert-Session");
+        servlet.addServlet(webAppServlet, "/*");
+        ErrorPageErrorHandler epeh = new ErrorPageErrorHandler();
+        epeh.addErrorPage(404, "/error");
+        epeh.addErrorPage(403, "/denied");
+        servlet.setErrorHandler(epeh);
+
+        HandlerList hl = new HandlerList();
+        hl.setHandlers(new Handler[] {
+                hw, servlet
+        });
+
+        ContextHandler ch = new ContextHandler();
+        ch.setHandler(hl);
+        return ch;
+    }
+
+    private static Handler generateStaticContext() {
+        final ResourceHandler rh = generateResourceHandler();
+        rh.setResourceBase("static/static");
+
+        ContextHandler ch = new ContextHandler();
+        ch.setHandler(rh);
+        ch.setVirtualHosts(new String[] {
+            ServerConstants.getStaticHostName()
+        });
+
+        return ch;
+    }
+
+    private static ResourceHandler generateResourceHandler() {
+        ResourceHandler rh = new ResourceHandler() {
+
+            @Override
+            protected void doResponseHeaders(HttpServletResponse response, Resource resource, String mimeType) {
+                super.doResponseHeaders(response, resource, mimeType);
+                response.setDateHeader(HttpHeader.EXPIRES.asString(), System.currentTimeMillis() + 1000L * 60 * 60 * 24 * 7);
+            }
+        };
+        rh.setEtags(true);
+        return rh;
+    }
+
+    private static Handler generateAPIContext() {
+        ServletContextHandler sch = new ServletContextHandler();
+
+        sch.addVirtualHosts(new String[] {
+            ServerConstants.getApiHostName()
+        });
+        sch.addServlet(new ServletHolder(new GigiAPI()), "/*");
+        return sch;
+    }
+
+    private static SslContextFactory generateSSLContextFactory(GigiConfig conf, String alias) throws GeneralSecurityException, IOException {
+        SslContextFactory scf = new SslContextFactory() {
+
+            String[] ciphers = null;
+
+            @Override
+            public void customize(SSLEngine sslEngine) {
+                super.customize(sslEngine);
+
+                SSLParameters ssl = sslEngine.getSSLParameters();
+                ssl.setUseCipherSuitesOrder(true);
+                if (ciphers == null) {
+                    ciphers = CipherInfo.filter(sslEngine.getSupportedCipherSuites());
+                }
+
+                ssl.setCipherSuites(ciphers);
+                sslEngine.setSSLParameters(ssl);
+
+            }
+
+        };
+        scf.setRenegotiationAllowed(false);
+
+        scf.setProtocol("TLS");
+        scf.setIncludeProtocols("TLSv1", "TLSv1.1", "TLSv1.2");
+        scf.setTrustStore(conf.getTrustStore());
+        KeyStore privateStore = conf.getPrivateStore();
+        scf.setKeyStorePassword(conf.getPrivateStorePw());
+        scf.setKeyStore(privateStore);
+        scf.setCertAlias(alias);
+        return scf;
+    }
+}
diff --git a/src/org/cacert/gigi/PermissionCheckable.java b/src/org/cacert/gigi/PermissionCheckable.java
new file mode 100644 (file)
index 0000000..7dee6fd
--- /dev/null
@@ -0,0 +1,10 @@
+package org.cacert.gigi;
+
+import org.cacert.gigi.dbObjects.User;
+
+
+public interface PermissionCheckable {
+
+    public boolean isPermitted(User u);
+
+}
diff --git a/src/org/cacert/gigi/PolicyRedirector.java b/src/org/cacert/gigi/PolicyRedirector.java
new file mode 100644 (file)
index 0000000..e907c37
--- /dev/null
@@ -0,0 +1,30 @@
+package org.cacert.gigi;
+
+import java.io.IOException;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.handler.HandlerWrapper;
+
+public class PolicyRedirector extends HandlerWrapper {
+
+    @Override
+    public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
+        if (target.equals("/")) {
+            return;
+        }
+        if (target.equals("/policy/")) {
+            return;
+        }
+        if (target.startsWith("/policy/") && target.endsWith(".php")) {
+            target = target.replace(".php", ".html");
+            response.sendRedirect(target);
+            baseRequest.setHandled(true);
+            return;
+        }
+        super.handle(target, baseRequest, request, response);
+    }
+}
diff --git a/src/org/cacert/gigi/api/GigiAPI.java b/src/org/cacert/gigi/api/GigiAPI.java
new file mode 100644 (file)
index 0000000..744432f
--- /dev/null
@@ -0,0 +1,87 @@
+package org.cacert.gigi.api;
+
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.security.GeneralSecurityException;
+import java.security.cert.X509Certificate;
+
+import javax.servlet.ServletException;
+import javax.servlet.ServletInputStream;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.cacert.gigi.GigiApiException;
+import org.cacert.gigi.dbObjects.Certificate;
+import org.cacert.gigi.dbObjects.Certificate.CertificateStatus;
+import org.cacert.gigi.dbObjects.User;
+import org.cacert.gigi.pages.LoginPage;
+import org.cacert.gigi.pages.account.certs.CertificateRequest;
+import org.cacert.gigi.util.Job;
+import org.cacert.gigi.util.PEM;
+
+public class GigiAPI extends HttpServlet {
+
+    private static final long serialVersionUID = 659963677032635817L;
+
+    @Override
+    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
+        String pi = req.getPathInfo();
+        if (pi == null) {
+            return;
+        }
+        if (pi.equals("/security/csp/report")) {
+            ServletInputStream sis = req.getInputStream();
+            InputStreamReader isr = new InputStreamReader(sis, "UTF-8");
+            StringBuffer strB = new StringBuffer();
+            char[] buffer = new char[4 * 1024];
+            int len;
+            while ((len = isr.read(buffer)) > 0) {
+                strB.append(buffer, 0, len);
+            }
+            System.out.println(strB);
+            return;
+        }
+        X509Certificate cert = LoginPage.getCertificateFromRequest(req);
+        if (cert == null) {
+            resp.sendError(403, "Error, cert authing required.");
+            return;
+        }
+        String serial = LoginPage.extractSerialFormCert(cert);
+        User u = LoginPage.fetchUserBySerial(serial);
+
+        if (pi.equals("/account/certs/new")) {
+
+            if ( !req.getMethod().equals("POST")) {
+                resp.sendError(500, "Error, POST required.");
+                return;
+            }
+            if (req.getQueryString() != null) {
+                resp.sendError(500, "Error, no query String allowed.");
+                return;
+            }
+            String csr = req.getParameter("csr");
+            if (csr == null) {
+                resp.sendError(500, "Error, no CSR found");
+                return;
+            }
+            try {
+                CertificateRequest cr = new CertificateRequest(u, csr);
+                Certificate result = cr.draft();
+                Job job = result.issue(null, "2y");
+                job.waitFor(60000);
+                if (result.getStatus() != CertificateStatus.ISSUED) {
+                    resp.sendError(510, "Error, issuing timed out");
+                    return;
+                }
+                resp.getWriter().println(PEM.encode("CERTIFICATE", result.cert().getEncoded()));
+            } catch (GeneralSecurityException e) {
+                e.printStackTrace();
+            } catch (GigiApiException e) {
+                e.printStackTrace();
+            } catch (InterruptedException e) {
+                e.printStackTrace();
+            }
+        }
+    }
+}
diff --git a/src/org/cacert/gigi/crypto/SMIME.java b/src/org/cacert/gigi/crypto/SMIME.java
new file mode 100644 (file)
index 0000000..f37fbe8
--- /dev/null
@@ -0,0 +1,106 @@
+package org.cacert.gigi.crypto;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.math.BigInteger;
+import java.security.GeneralSecurityException;
+import java.security.PrivateKey;
+import java.security.Signature;
+import java.security.cert.X509Certificate;
+import java.util.Random;
+
+import org.cacert.gigi.util.PEM;
+
+import sun.security.pkcs.ContentInfo;
+import sun.security.pkcs.PKCS7;
+import sun.security.pkcs.SignerInfo;
+import sun.security.util.DerOutputStream;
+import sun.security.x509.AlgorithmId;
+import sun.security.x509.X500Name;
+
+public class SMIME {
+
+    public static String doAlternatives(String plain, String html) {
+
+        plain = "Content-type: text/plain\r\n\r\n" + plain;
+        html = "Content-type: text/html\r\n\r\n" + html;
+        String boundary = generateBoundary(plain, html);
+        StringBuffer content = new StringBuffer("Content-Type: multipart/alternative; boundary=\"");
+        content.append(boundary);
+        content.append("\"\r\n\r\n");
+        content.append("--");
+        content.append(boundary);
+        content.append("\r\n");
+        content.append(plain);
+        content.append("\r\n--");
+        content.append(boundary);
+        content.append("\r\n");
+        content.append(html);
+        content.append("\r\n--");
+        content.append(boundary);
+        content.append("--\r\n");
+        return content.toString();
+
+    }
+
+    public static void smime(String contents, PrivateKey pKey, X509Certificate c, PrintWriter to) throws IOException, GeneralSecurityException {
+
+        Signature signature = Signature.getInstance("SHA1WithRSA");
+        signature.initSign(pKey);
+        signature.update(contents.getBytes("UTF-8"));
+        byte[] signedData = signature.sign();
+
+        // "IssuerAndSerialNumber"
+        X500Name xName = X500Name.asX500Name(c.getIssuerX500Principal());
+        BigInteger serial = c.getSerialNumber();
+
+        SignerInfo sInfo = new SignerInfo(xName, serial, new AlgorithmId(AlgorithmId.SHA_oid), null, new AlgorithmId(AlgorithmId.RSAEncryption_oid), signedData, null);
+
+        // Content is outside so content here is null.
+        ContentInfo cInfo = new ContentInfo(ContentInfo.DATA_OID, null);
+
+        // Create PKCS7 Signed data
+        PKCS7 p7 = new PKCS7(new AlgorithmId[] {
+            new AlgorithmId(AlgorithmId.SHA_oid)
+        }, cInfo, new java.security.cert.X509Certificate[] {
+            c
+        }, new SignerInfo[] {
+            sInfo
+        });
+
+        ByteArrayOutputStream bOut = new DerOutputStream();
+        p7.encodeSignedData(bOut);
+
+        mimeEncode(contents, PEM.formatBase64(bOut.toByteArray()), to);
+    }
+
+    private static Random r = new Random();
+
+    private static void mimeEncode(String contents, String signature, PrintWriter to) {
+        String boundary = generateBoundary(contents, null);
+        to.println("MIME-Version: 1.0");
+        to.println("Content-Type: multipart/signed; protocol=\"application/x-pkcs7-signature\"; micalg=\"sha1\"; boundary=\"" + boundary + "\"");
+        to.println("");
+        to.println("This is an S/MIME signed message");
+        to.println("");
+        to.println("--" + boundary);
+        to.println(contents);
+        to.println("--" + boundary);
+        to.println("Content-Type: application/x-pkcs7-signature; name=\"smime.p7s\"");
+        to.println("Content-Transfer-Encoding: base64");
+        to.println("Content-Disposition: attachment; filename=\"smime.p7s\"");
+        to.println("");
+        to.println(signature);
+        to.println();
+        to.println("--" + boundary + "--");
+    }
+
+    private static String generateBoundary(String contents, String contents2) {
+        String boundary = "";
+        while (contents.contains(boundary) || (contents2 != null && contents2.contains(boundary))) {
+            boundary = "--" + new BigInteger(16 * 8, r).toString(16).toUpperCase();
+        }
+        return boundary;
+    }
+}
diff --git a/src/org/cacert/gigi/crypto/SPKAC.java b/src/org/cacert/gigi/crypto/SPKAC.java
new file mode 100644 (file)
index 0000000..c97f5ac
--- /dev/null
@@ -0,0 +1,104 @@
+package org.cacert.gigi.crypto;
+
+import java.io.IOException;
+import java.security.GeneralSecurityException;
+import java.security.Signature;
+import java.security.SignatureException;
+
+import sun.security.util.DerInputStream;
+import sun.security.util.DerOutputStream;
+import sun.security.util.DerValue;
+import sun.security.x509.AlgorithmId;
+import sun.security.x509.X509Key;
+
+/**
+ * This class handles a SPKAC. A SPKAC has the following structure;
+ * 
+ * <pre>
+ * PublicKeyAndChallenge ::= SEQUENCE {
+ *     spki SubjectPublicKeyInfo,
+ *     challenge IA5STRING
+ * }
+ * 
+ * SignedPublicKeyAndChallenge ::= SEQUENCE {
+ *     publicKeyAndChallenge PublicKeyAndChallenge,
+ *     signatureAlgorithm AlgorithmIdentifier,
+ *     signature BIT STRING
+ * }
+ * </pre>
+ */
+public class SPKAC {
+
+    private X509Key pubkey;
+
+    private String challenge;
+
+    public SPKAC(byte[] data) throws IOException, GeneralSecurityException {
+        DerInputStream derIn = new DerInputStream(data);
+
+        DerValue derSPKACContent[] = derIn.getSequence(3);
+        if (derIn.available() != 0) {
+            throw new IllegalArgumentException("Additional data after SPKAC.");
+        }
+
+        AlgorithmId id = AlgorithmId.parse(derSPKACContent[1]);
+
+        derIn = derSPKACContent[0].toDerInputStream();
+
+        pubkey = (X509Key) X509Key.parse(derIn.getDerValue());
+
+        DerValue derChallenge = derIn.getDerValue();
+        if (derIn.available() != 0) {
+            throw new IllegalArgumentException("Additional data after SPKAC.");
+        }
+        if (derChallenge.length() != 0) {
+            challenge = derChallenge.getIA5String();
+        }
+
+        Signature s = Signature.getInstance(id.getName());
+        s.initVerify(pubkey);
+        s.update(derSPKACContent[0].toByteArray());
+        byte[] signature = derSPKACContent[2].getBitString();
+        if ( !s.verify(signature)) {
+            throw new SignatureException();
+        }
+
+    }
+
+    public String getChallenge() {
+        return challenge;
+    }
+
+    public X509Key getPubkey() {
+        return pubkey;
+    }
+
+    public SPKAC(X509Key pubkey, String challange) {
+        this.pubkey = pubkey;
+        challenge = challange;
+    }
+
+    public byte[] getEncoded(Signature sign) throws GeneralSecurityException, IOException {
+        DerOutputStream SPKAC = new DerOutputStream();
+        DerOutputStream SPKI = new DerOutputStream();
+
+        pubkey.encode(SPKI);
+        SPKI.putIA5String(challenge);
+
+        SPKAC.write(DerValue.tag_Sequence, SPKI);
+        byte[] toSign = SPKAC.toByteArray();
+        SPKI.close();
+
+        AlgorithmId aid = AlgorithmId.get(sign.getAlgorithm());
+        aid.encode(SPKAC);
+        sign.update(toSign);
+        SPKAC.putBitString(sign.sign());
+
+        DerOutputStream res = new DerOutputStream();
+        res.write(DerValue.tag_Sequence, SPKAC);
+        SPKAC.close();
+        byte[] result = res.toByteArray();
+        res.close();
+        return result;
+    }
+}
diff --git a/src/org/cacert/gigi/database/DatabaseConnection.java b/src/org/cacert/gigi/database/DatabaseConnection.java
new file mode 100644 (file)
index 0000000..eac822b
--- /dev/null
@@ -0,0 +1,168 @@
+package org.cacert.gigi.database;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.util.HashMap;
+import java.util.Properties;
+
+import org.cacert.gigi.database.SQLFileManager.ImportType;
+
+public class DatabaseConnection {
+
+    public static final int CURRENT_SCHEMA_VERSION = 1;
+
+    public static final int CONNECTION_TIMEOUT = 24 * 60 * 60;
+
+    private Connection c;
+
+    private HashMap<String, GigiPreparedStatement> statements = new HashMap<String, GigiPreparedStatement>();
+
+    private static Properties credentials;
+
+    private Statement adHoc;
+
+    public DatabaseConnection() {
+        try {
+            Class.forName(credentials.getProperty("sql.driver"));
+        } catch (ClassNotFoundException e) {
+            e.printStackTrace();
+        }
+        tryConnect();
+
+    }
+
+    private void tryConnect() {
+        try {
+            c = DriverManager.getConnection(credentials.getProperty("sql.url") + "?zeroDateTimeBehavior=convertToNull", credentials.getProperty("sql.user"), credentials.getProperty("sql.password"));
+            PreparedStatement ps = c.prepareStatement("SET SESSION wait_timeout=?, time_zone='+0:00';");
+            try {
+                ps.setInt(1, CONNECTION_TIMEOUT);
+                ps.execute();
+                adHoc = c.createStatement();
+            } finally {
+                ps.close();
+            }
+        } catch (SQLException e) {
+            e.printStackTrace();
+        }
+    }
+
+    public GigiPreparedStatement prepare(String query) {
+        ensureOpen();
+        GigiPreparedStatement statement = statements.get(query);
+        if (statement == null) {
+            try {
+                statement = new GigiPreparedStatement(c.prepareStatement(query, Statement.RETURN_GENERATED_KEYS));
+            } catch (SQLException e) {
+                throw new Error(e);
+            }
+            statements.put(query, statement);
+        }
+        return statement;
+    }
+
+    private long lastAction = System.currentTimeMillis();
+
+    private void ensureOpen() {
+        if (System.currentTimeMillis() - lastAction > CONNECTION_TIMEOUT * 1000L) {
+            try {
+                ResultSet rs = adHoc.executeQuery("SELECT 1");
+                rs.close();
+                lastAction = System.currentTimeMillis();
+                return;
+            } catch (SQLException e) {
+            }
+            statements.clear();
+            tryConnect();
+        }
+        lastAction = System.currentTimeMillis();
+    }
+
+    private static ThreadLocal<DatabaseConnection> instances = new ThreadLocal<DatabaseConnection>() {
+
+        @Override
+        protected DatabaseConnection initialValue() {
+            return new DatabaseConnection();
+        }
+    };
+
+    public static DatabaseConnection getInstance() {
+        return instances.get();
+    }
+
+    public static boolean isInited() {
+        return credentials != null;
+    }
+
+    public static void init(Properties conf) {
+        if (credentials != null) {
+            throw new Error("Re-initiaizing is forbidden.");
+        }
+        credentials = conf;
+        GigiResultSet rs = getInstance().prepare("SELECT version FROM schemeVersion ORDER BY version DESC LIMIT 1").executeQuery();
+        int version = 0;
+        if (rs.next()) {
+            version = rs.getInt(1);
+        }
+        if (version == CURRENT_SCHEMA_VERSION) {
+            return; // Good to go
+        }
+        if (version > CURRENT_SCHEMA_VERSION) {
+            throw new Error("Invalid database version. Please fix this.");
+        }
+        upgrade(version);
+    }
+
+    private static void upgrade(int version) {
+        try {
+            Statement s = getInstance().c.createStatement();
+            try {
+                while (version < CURRENT_SCHEMA_VERSION) {
+                    try (InputStream resourceAsStream = DatabaseConnection.class.getResourceAsStream("upgrade/from_" + version + ".sql")) {
+                        if (resourceAsStream == null) {
+                            throw new Error("Upgrade script from version " + version + " was not found.");
+                        }
+                        SQLFileManager.addFile(s, resourceAsStream, ImportType.PRODUCTION);
+                    }
+                    version++;
+                }
+                s.addBatch("INSERT INTO schemeVersion SET version='" + version + "'");
+                System.out.println("UPGRADING Database to version " + version);
+                s.executeBatch();
+                System.out.println("done.");
+            } finally {
+                s.close();
+            }
+        } catch (SQLException e) {
+            e.printStackTrace();
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+    }
+
+    public void beginTransaction() throws SQLException {
+        c.setAutoCommit(false);
+    }
+
+    public void commitTransaction() throws SQLException {
+        c.commit();
+        c.setAutoCommit(true);
+    }
+
+    public void quitTransaction() {
+        try {
+            if ( !c.getAutoCommit()) {
+                c.rollback();
+                c.setAutoCommit(true);
+            }
+        } catch (SQLException e) {
+            e.printStackTrace();
+        }
+    }
+}
diff --git a/src/org/cacert/gigi/database/GigiPreparedStatement.java b/src/org/cacert/gigi/database/GigiPreparedStatement.java
new file mode 100644 (file)
index 0000000..55ed6ad
--- /dev/null
@@ -0,0 +1,109 @@
+package org.cacert.gigi.database;
+
+import java.sql.Date;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Timestamp;
+
+public class GigiPreparedStatement {
+
+    PreparedStatement target;
+
+    public GigiPreparedStatement(PreparedStatement preparedStatement) {
+        target = preparedStatement;
+    }
+
+    public GigiResultSet executeQuery() {
+        try {
+            return new GigiResultSet(target.executeQuery());
+        } catch (SQLException e) {
+            handleSQL(e);
+            throw new Error(e);
+        }
+    }
+
+    public void executeUpdate() {
+        try {
+            int updated = target.executeUpdate();
+            if (updated != 1) {
+                throw new Error("FATAL: multiple or no data updated: " + updated);
+            }
+        } catch (SQLException e) {
+            handleSQL(e);
+            throw new Error(e);
+        }
+    }
+
+    public boolean execute() {
+        try {
+            return target.execute();
+        } catch (SQLException e) {
+            handleSQL(e);
+            throw new Error(e);
+        }
+    }
+
+    public void setInt(int parameterIndex, int x) {
+        try {
+            target.setInt(parameterIndex, x);
+        } catch (SQLException e) {
+            handleSQL(e);
+            throw new Error(e);
+        }
+    }
+
+    public void setString(int parameterIndex, String x) {
+        try {
+            target.setString(parameterIndex, x);
+        } catch (SQLException e) {
+            handleSQL(e);
+            throw new Error(e);
+        }
+    }
+
+    public void setDate(int parameterIndex, Date x) {
+        try {
+            target.setDate(parameterIndex, x);
+        } catch (SQLException e) {
+            handleSQL(e);
+            throw new Error(e);
+        }
+    }
+
+    public void setTimestamp(int parameterIndex, Timestamp x) {
+        try {
+            target.setTimestamp(parameterIndex, x);
+        } catch (SQLException e) {
+            handleSQL(e);
+            throw new Error(e);
+        }
+    }
+
+    public int lastInsertId() {
+        try {
+            ResultSet rs = target.getGeneratedKeys();
+            rs.next();
+            int id = rs.getInt(1);
+            rs.close();
+            return id;
+        } catch (SQLException e) {
+            handleSQL(e);
+            throw new Error(e);
+        }
+    }
+
+    public void setBoolean(int parameterIndex, boolean x) {
+        try {
+            target.setBoolean(parameterIndex, x);
+        } catch (SQLException e) {
+            handleSQL(e);
+            throw new Error(e);
+        }
+    }
+
+    private void handleSQL(SQLException e) {
+        // TODO Auto-generated method stub
+
+    }
+}
diff --git a/src/org/cacert/gigi/database/GigiResultSet.java b/src/org/cacert/gigi/database/GigiResultSet.java
new file mode 100644 (file)
index 0000000..28fda28
--- /dev/null
@@ -0,0 +1,158 @@
+package org.cacert.gigi.database;
+
+import java.io.Closeable;
+import java.sql.Date;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Timestamp;
+
+public class GigiResultSet implements Closeable {
+
+    ResultSet target;
+
+    public GigiResultSet(ResultSet target) {
+        this.target = target;
+    }
+
+    public String getString(int columnIndex) {
+        try {
+            return target.getString(columnIndex);
+        } catch (SQLException e) {
+            handleSQL(e);
+            throw new Error(e);
+        }
+    }
+
+    public boolean getBoolean(int columnIndex) {
+        try {
+            return target.getBoolean(columnIndex);
+        } catch (SQLException e) {
+            handleSQL(e);
+            throw new Error(e);
+        }
+    }
+
+    public int getInt(int columnIndex) {
+        try {
+            return target.getInt(columnIndex);
+        } catch (SQLException e) {
+            handleSQL(e);
+            throw new Error(e);
+        }
+    }
+
+    public Date getDate(int columnIndex) {
+        try {
+            return target.getDate(columnIndex);
+        } catch (SQLException e) {
+            handleSQL(e);
+            throw new Error(e);
+        }
+    }
+
+    public Timestamp getTimestamp(int columnIndex) {
+        try {
+            return target.getTimestamp(columnIndex);
+        } catch (SQLException e) {
+            handleSQL(e);
+            throw new Error(e);
+        }
+    }
+
+    public String getString(String columnLabel) {
+        try {
+            return target.getString(columnLabel);
+        } catch (SQLException e) {
+            handleSQL(e);
+            throw new Error(e);
+        }
+    }
+
+    public boolean getBoolean(String columnLabel) {
+        try {
+            return target.getBoolean(columnLabel);
+        } catch (SQLException e) {
+            handleSQL(e);
+            throw new Error(e);
+        }
+    }
+
+    public int getInt(String columnLabel) {
+        try {
+            return target.getInt(columnLabel);
+        } catch (SQLException e) {
+            handleSQL(e);
+            throw new Error(e);
+        }
+    }
+
+    public Date getDate(String columnLabel) {
+        try {
+            return target.getDate(columnLabel);
+        } catch (SQLException e) {
+            handleSQL(e);
+            throw new Error(e);
+        }
+    }
+
+    public Timestamp getTimestamp(String columnLabel) {
+        try {
+            return target.getTimestamp(columnLabel);
+        } catch (SQLException e) {
+            handleSQL(e);
+            throw new Error(e);
+        }
+    }
+
+    public boolean next() {
+        try {
+            return target.next();
+        } catch (SQLException e) {
+            handleSQL(e);
+            throw new Error(e);
+        }
+    }
+
+    public int getRow() {
+        try {
+            return target.getRow();
+        } catch (SQLException e) {
+            handleSQL(e);
+            throw new Error(e);
+        }
+    }
+
+    public void beforeFirst() {
+        try {
+            target.beforeFirst();
+        } catch (SQLException e) {
+            handleSQL(e);
+            throw new Error(e);
+        }
+    }
+
+    public void last() {
+        try {
+            target.last();
+        } catch (SQLException e) {
+            handleSQL(e);
+            throw new Error(e);
+        }
+    }
+
+    public void close() {
+        try {
+            target.close();
+        } catch (SQLException e) {
+            handleSQL(e);
+            throw new Error(e);
+        }
+
+    }
+
+    private void handleSQL(SQLException e) {
+        // TODO Auto-generated method stub
+
+    }
+
+}
diff --git a/src/org/cacert/gigi/database/SQLFileManager.java b/src/org/cacert/gigi/database/SQLFileManager.java
new file mode 100644 (file)
index 0000000..62083c7
--- /dev/null
@@ -0,0 +1,61 @@
+package org.cacert.gigi.database;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class SQLFileManager {
+
+    public static enum ImportType {
+        /**
+         * Execute Script as-as
+         */
+        PRODUCTION,
+        /**
+         * Execute Script, but changing Engine=InnoDB to Engine=Memory
+         */
+        TEST,
+        /**
+         * Execute INSERT statements as-is, and TRUNCATE instead of DROPPING
+         */
+        TRUNCATE
+    }
+
+    public static void addFile(Statement stmt, InputStream f, ImportType type) throws IOException, SQLException {
+        String sql = readFile(f);
+        sql = sql.replaceAll("--[^\n]+\n", "\n");
+        String[] stmts = sql.split(";");
+        Pattern p = Pattern.compile("\\s*DROP TABLE IF EXISTS `([^`]+)`");
+        for (String string : stmts) {
+            Matcher m = p.matcher(string);
+            string = string.trim();
+            if (string.equals("")) {
+                continue;
+            }
+            if (m.matches() && type == ImportType.TRUNCATE) {
+                String sql2 = "TRUNCATE `" + m.group(1) + "`";
+                stmt.addBatch(sql2);
+                continue;
+            }
+            if (type == ImportType.PRODUCTION || string.startsWith("INSERT")) {
+                stmt.addBatch(string);
+            } else if (type == ImportType.TEST) {
+                stmt.addBatch(string.replace("ENGINE=InnoDB", "ENGINE=Memory"));
+            }
+        }
+    }
+
+    private static String readFile(InputStream f) throws IOException {
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        int len;
+        byte[] buf = new byte[4096];
+        while ((len = f.read(buf)) > 0) {
+            baos.write(buf, 0, len);
+        }
+        return new String(baos.toByteArray(), "UTF-8");
+    }
+}
diff --git a/src/org/cacert/gigi/database/tableStructure.sql b/src/org/cacert/gigi/database/tableStructure.sql
new file mode 100644 (file)
index 0000000..e72c270
--- /dev/null
@@ -0,0 +1,350 @@
+DROP TABLE IF EXISTS `certOwners`;
+CREATE TABLE `certOwners` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `created` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
+  `modified` timestamp NULL DEFAULT NULL,
+  `deleted` timestamp NULL DEFAULT NULL,
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=0 DEFAULT CHARSET=utf8;
+
+DROP TABLE IF EXISTS `users`;
+CREATE TABLE `users` (
+  `id` int(11) NOT NULL,
+  `email` varchar(255) NOT NULL DEFAULT '',
+  `password` varchar(255) NOT NULL DEFAULT '',
+  `fname` varchar(255) NOT NULL DEFAULT '',
+  `mname` varchar(255) NOT NULL DEFAULT '',
+  `lname` varchar(255) NOT NULL DEFAULT '',
+  `suffix` varchar(50) NOT NULL DEFAULT '',
+  `dob` date NOT NULL DEFAULT '0000-00-00',
+  `verified` int(1) NOT NULL DEFAULT '0',
+  `ccid` int(3) NOT NULL DEFAULT '0',
+  `regid` int(5) NOT NULL DEFAULT '0',
+  `locid` int(7) NOT NULL DEFAULT '0',
+  `listme` int(1) NOT NULL DEFAULT '0',
+  `contactinfo` varchar(255) NOT NULL DEFAULT '',
+  `language` varchar(5) NOT NULL DEFAULT '',
+  PRIMARY KEY (`id`),
+  KEY `ccid` (`ccid`),
+  KEY `regid` (`regid`),
+  KEY `locid` (`locid`),
+  KEY `email` (`email`),
+  KEY `stats_users_verified` (`verified`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+
+DROP TABLE IF EXISTS `organisations`;
+CREATE TABLE IF NOT EXISTS `organisations` (
+  `id` int(11) NOT NULL,
+  `name` varchar(100) NOT NULL,
+  `state` varchar(2) NOT NULL,
+  `province` varchar(100) NOT NULL,
+  `city` varchar(100) NOT NULL,
+  `contactEmail` varchar(100) NOT NULL,
+  `creator` int(11) NOT NULL,
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+DROP TABLE IF EXISTS `domains`;
+CREATE TABLE `domains` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `memid` int(11) NOT NULL,
+  `domain` varchar(255) NOT NULL,
+  `created` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
+  `modified` datetime NULL DEFAULT NULL,
+  `deleted` datetime NULL DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  KEY `memid` (`memid`),
+  KEY `domain` (`domain`),
+  KEY `stats_domains_deleted` (`deleted`)
+) ENGINE=InnoDB AUTO_INCREMENT=0 DEFAULT CHARSET=utf8;
+
+DROP TABLE IF EXISTS `emails`;
+CREATE TABLE `emails` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `memid` int(11) NOT NULL DEFAULT '0',
+  `email` varchar(255) NOT NULL DEFAULT '',
+  `created` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
+  `modified` datetime NULL DEFAULT NULL,
+  `deleted` datetime NULL DEFAULT NULL,
+  `hash` varchar(50) NOT NULL DEFAULT '',
+  `attempts` int(1) NOT NULL DEFAULT '0',
+  PRIMARY KEY (`id`),
+  KEY `memid` (`memid`),
+  KEY `stats_email_hash` (`hash`),
+  KEY `stats_email_deleted` (`deleted`),
+  KEY `email` (`email`)
+) ENGINE=InnoDB AUTO_INCREMENT=0 DEFAULT CHARSET=utf8;
+
+DROP TABLE IF EXISTS `emailPinglog`;
+CREATE TABLE `emailPinglog` (
+  `when` datetime NOT NULL,
+  `uid` int(11) NOT NULL,
+  `email` varchar(255) NOT NULL,
+  `type` enum('fast', 'active') NOT NULL,
+  `status` enum('open', 'success', 'failed') NOT NULL,
+  `result` varchar(255) NOT NULL
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+DROP TABLE IF EXISTS `pingconfig`;
+CREATE TABLE `pingconfig` (
+  `id` int(13) NOT NULL AUTO_INCREMENT,
+  `domainid` int(11) NOT NULL,
+  `type` enum('email', 'ssl', 'http', 'dns') NOT NULL,
+  `info` varchar(255) NOT NULL,
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+
+DROP TABLE IF EXISTS `domainPinglog`;
+CREATE TABLE `domainPinglog` (
+  `when` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
+  `configId` int(13) NOT NULL,
+  `state` enum('open', 'success', 'failed') NOT NULL,
+  `challenge` varchar(16),
+  `result` varchar(255)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+DROP TABLE IF EXISTS `baddomains`;
+CREATE TABLE `baddomains` (
+  `domain` varchar(255) NOT NULL DEFAULT ''
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+
+DROP TABLE IF EXISTS `alerts`;
+CREATE TABLE `alerts` (
+  `memid` int(11) NOT NULL DEFAULT '0',
+  `general` tinyint(1) NOT NULL DEFAULT '0',
+  `country` tinyint(1) NOT NULL DEFAULT '0',
+  `regional` tinyint(1) NOT NULL DEFAULT '0',
+  `radius` tinyint(1) NOT NULL DEFAULT '0',
+  PRIMARY KEY (`memid`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+DROP TABLE IF EXISTS `user_agreements`;
+CREATE TABLE `user_agreements` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `memid` int(11) NOT NULL,
+  `secmemid` int(11) DEFAULT NULL,
+  `document` varchar(50) DEFAULT NULL,
+  `date` datetime DEFAULT NULL,
+  `active` int(1) NOT NULL,
+  `method` varchar(100) NOT NULL,
+  `comment` varchar(100) DEFAULT NULL,
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=0 DEFAULT CHARSET=utf8;
+
+DROP TABLE IF EXISTS `certs`;
+CREATE TABLE `certs` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `memid` int(11) NOT NULL DEFAULT '0',
+  `serial` varchar(50) NOT NULL DEFAULT '',
+  `keytype` char(2) NOT NULL DEFAULT 'NS',
+  `codesign` tinyint(1) NOT NULL DEFAULT '0',
+  `md` enum('md5','sha1','sha256','sha512') NOT NULL DEFAULT 'sha512',
+  `profile` int(3) NOT NULL,
+  `caid` int(3) NULL DEFAULT NULL,
+
+  `csr_name` varchar(255) NOT NULL DEFAULT '',
+  `csr_type` enum('CSR', 'SPKAC') NOT NULL,
+  `crt_name` varchar(255) NOT NULL DEFAULT '',
+  `created` timestamp NULL DEFAULT NULL,
+  `modified` datetime NULL DEFAULT NULL,
+  `revoked` datetime NULL DEFAULT NULL,
+  `expire` datetime NULL DEFAULT NULL,
+  `renewed` tinyint(1) NOT NULL DEFAULT '0',
+  `disablelogin` int(1) NOT NULL DEFAULT '0',
+  `pkhash` char(40) DEFAULT NULL,
+  `certhash` char(40) DEFAULT NULL,
+  `description` varchar(100) NOT NULL DEFAULT '',
+  PRIMARY KEY (`id`),
+  KEY `emailcerts_pkhash` (`pkhash`),
+  KEY `revoked` (`revoked`),
+  KEY `created` (`created`),
+  KEY `memid` (`memid`),
+  KEY `serial` (`serial`),
+  KEY `stats_emailcerts_expire` (`expire`),
+  KEY `emailcrt` (`crt_name`)
+) ENGINE=InnoDB AUTO_INCREMENT=0 DEFAULT CHARSET=utf8;
+
+
+DROP TABLE IF EXISTS `certAvas`;
+CREATE TABLE `certAvas` (
+  `certid` int(11) NOT NULL,
+  `name` varchar(20) NOT NULL,
+  `value` varchar(255) NOT NULL,
+
+  PRIMARY KEY (`certid`, `name`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+DROP TABLE IF EXISTS `clientcerts`;
+CREATE TABLE `clientcerts` (
+  `id` int(11) NOT NULL,
+  `disablelogin` int(1) NOT NULL DEFAULT '0',
+
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+DROP TABLE IF EXISTS `profiles`;
+CREATE TABLE `profiles` (
+  `id` int(3) NOT NULL AUTO_INCREMENT,
+  `keyname` varchar(60) NOT NULL,
+  `keyUsage` varchar(100) NOT NULL,
+  `extendedKeyUsage` varchar(100) NOT NULL,
+  `rootcert` int(2) NOT NULL DEFAULT '1',
+  `name` varchar(100) NOT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE (`keyname`)
+) ENGINE=InnoDB AUTO_INCREMENT=0 DEFAULT CHARSET=utf8;
+
+INSERT INTO `profiles` SET rootcert=0, keyname='client', name='ssl-client (unassured)', keyUsage='digitalSignature, keyEncipherment, keyAgreement', extendedKeyUsage='clientAuth';
+INSERT INTO `profiles` SET rootcert=0, keyname='mail',  name='mail (unassured)', keyUsage='digitalSignature, keyEncipherment, keyAgreement', extendedKeyUsage='emailProtection';
+INSERT INTO `profiles` SET rootcert=0, keyname='client-mail', name='ssl-client + mail (unassured)', keyUsage='digitalSignature, keyEncipherment, keyAgreement', extendedKeyUsage='clientAuth, emailProtection';
+INSERT INTO `profiles` SET rootcert=0, keyname='server', name='ssl-server (unassured)', keyUsage='digitalSignature, keyEncipherment, keyAgreement', extendedKeyUsage='serverAuth';
+
+INSERT INTO `profiles` SET rootcert=1, keyname='client-a', name='ssl-client (assured)', keyUsage='digitalSignature, keyEncipherment, keyAgreement', extendedKeyUsage='clientAuth';
+INSERT INTO `profiles` SET rootcert=1, keyname='mail-a',  name='mail (assured)', keyUsage='digitalSignature, keyEncipherment, keyAgreement', extendedKeyUsage='emailProtection';
+INSERT INTO `profiles` SET rootcert=1, keyname='client-mail-a', name='ssl-client + mail(assured)', keyUsage='digitalSignature, keyEncipherment, keyAgreement', extendedKeyUsage='clientAuth, emailProtection';
+INSERT INTO `profiles` SET rootcert=1, keyname='server-a', name='ssl-server (assured)', keyUsage='digitalSignature, keyEncipherment, keyAgreement', extendedKeyUsage='serverAuth';
+INSERT INTO `profiles` SET rootcert=2, keyname='code-a', name='codesign (assured)', keyUsage='digitalSignature, keyEncipherment, keyAgreement', extendedKeyUsage='codeSigning, msCodeInd, msCodeCom';
+
+INSERT INTO `profiles` SET rootcert=3, keyname='client-orga', name='ssl-client (orga)', keyUsage='digitalSignature, keyEncipherment, keyAgreement', extendedKeyUsage='clientAuth';
+INSERT INTO `profiles` SET rootcert=3, keyname='mail-orga',  name='mail (orga)', keyUsage='digitalSignature, keyEncipherment, keyAgreement', extendedKeyUsage='emailProtection';
+INSERT INTO `profiles` SET rootcert=3, keyname='client-mail-orga', name='ssl-client + mail(orga)', keyUsage='digitalSignature, keyEncipherment, keyAgreement', extendedKeyUsage='clientAuth, emailProtection';
+INSERT INTO `profiles` SET rootcert=3, keyname='server-orga', name='ssl-server (orga)', keyUsage='digitalSignature, keyEncipherment, keyAgreement', extendedKeyUsage='serverAuth';
+INSERT INTO `profiles` SET rootcert=4, keyname='code-orga', name='codesign (orga)', keyUsage='digitalSignature, keyEncipherment, keyAgreement', extendedKeyUsage='codeSigning, msCodeInd, msCodeCom';
+
+-- 0=unassured, 1=assured, 2=codesign, 3=orga, 4=orga-sign
+DROP TABLE IF EXISTS `subjectAlternativeNames`;
+CREATE TABLE `subjectAlternativeNames` (
+  `certId` int(11) NOT NULL,
+  `contents` varchar(50) NOT NULL,
+  `type` enum('email','DNS') NOT NULL
+) ENGINE=InnoDB AUTO_INCREMENT=0 DEFAULT CHARSET=utf8;
+
+DROP TABLE IF EXISTS `cacerts`;
+CREATE TABLE `cacerts` (
+  `id` int(3) NOT NULL AUTO_INCREMENT,
+  `keyname` varchar(60) NOT NULL,
+  `subroot` int(2) NOT NULL,
+  `validFrom` datetime NULL DEFAULT NULL,
+  `validTo` datetime NULL DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE (`keyname`)
+) ENGINE=InnoDB AUTO_INCREMENT=0 DEFAULT CHARSET=utf8;
+
+
+DROP TABLE IF EXISTS `jobs`;
+CREATE TABLE `jobs` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `targetId` int(11) NOT NULL,
+  `task` enum('sign','revoke') NOT NULL,
+  `state` enum('open', 'done', 'error') NOT NULL DEFAULT 'open',
+  `warning` int(2) NOT NULL DEFAULT '0',
+  `executeFrom` DATE,
+  `executeTo` VARCHAR(11),
+  PRIMARY KEY (`id`),
+  KEY `state` (`state`)
+) ENGINE=InnoDB AUTO_INCREMENT=0 DEFAULT CHARSET=utf8;
+
+
+DROP TABLE IF EXISTS `notary`;
+CREATE TABLE `notary` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `from` int(11) NOT NULL DEFAULT '0',
+  `to` int(11) NOT NULL DEFAULT '0',
+# total points that have been entered
+  `points` int(3) NOT NULL DEFAULT '0',
+# awarded and the "experience points" are calculated virtually
+# Face to Face is default, TOPUP is for the remaining 30Points after two TTP
+# TTP is default ttp assurance
+  `method` enum('Face to Face Meeting', 'TOPUP', 'TTP-Assisted') NOT NULL DEFAULT 'Face to Face Meeting',
+  `location` varchar(255) NOT NULL DEFAULT '',
+  `date` varchar(255) NOT NULL DEFAULT '',
+# date when assurance was entered
+  `when` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
+#?
+  `expire` datetime NULL DEFAULT NULL,
+#?????????????????
+  `sponsor` int(11) NOT NULL DEFAULT '0',
+# date when assurance was deleted (or 0)
+  `deleted` datetime NULL DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  KEY `from` (`from`),
+  KEY `to` (`to`),
+  KEY `stats_notary_when` (`when`),
+  KEY `stats_notary_method` (`method`)
+) ENGINE=InnoDB AUTO_INCREMENT=0 DEFAULT CHARSET=utf8;
+
+
+DROP TABLE IF EXISTS `cats_passed`;
+CREATE TABLE `cats_passed` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `user_id` int(11) NOT NULL,
+  `variant_id` int(11) NOT NULL,
+  `pass_date` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `test_passed` (`user_id`,`variant_id`,`pass_date`)
+) ENGINE=InnoDB AUTO_INCREMENT=0 DEFAULT CHARSET=utf8;
+
+# --------------------------------------------------------
+
+#
+# Table structure for table `cats_type`
+#
+
+DROP TABLE IF EXISTS `cats_type`;
+CREATE TABLE `cats_type` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `type_text` varchar(255) NOT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `type_text` (`type_text`)
+) ENGINE=InnoDB AUTO_INCREMENT=0 DEFAULT CHARSET=utf8;
+
+DROP TABLE IF EXISTS `arbitrations`;
+CREATE TABLE IF NOT EXISTS `arbitrations` (
+  `user` int(11) NOT NULL,
+  `arbitration` varchar(20) NOT NULL,
+  PRIMARY KEY (`user`,`arbitration`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+DROP TABLE IF EXISTS `user_groups`;
+CREATE TABLE IF NOT EXISTS `user_groups` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `user` int(11) NOT NULL,
+  `permission` enum('supporter','arbitrator','blockedassuree','blockedassurer','blockedlogin','ttp-assurer','ttp-applicant', 'codesigning', 'orgassurer') NOT NULL,
+  `granted` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
+  `deleted` timestamp NULL DEFAULT NULL,
+  `grantedby` int(11) NOT NULL,
+  `revokedby` int(11) DEFAULT NULL,
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+DROP TABLE IF EXISTS `org_admin`;
+CREATE TABLE IF NOT EXISTS `org_admin` (
+  `orgid` int(11) NOT NULL,
+  `memid` int(11) NOT NULL,
+  `master` enum('y', 'n') NOT NULL,
+  `creator` int(11) NOT NULL,
+  `created` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
+  `deleter` int(11) NULL DEFAULT NULL,
+  `deleted` timestamp NULL DEFAULT NULL,
+  KEY (`orgid`, `memid`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+DROP TABLE IF EXISTS `adminLog`;
+CREATE TABLE `adminLog` (
+  `when` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
+  `uid` int(11) unsigned NOT NULL,
+  `admin` int(11) unsigned NOT NULL,
+  `type` varchar(100) NOT NULL DEFAULT '',
+  `information` varchar(50) NOT NULL DEFAULT '',
+  PRIMARY KEY (`when`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+DROP TABLE IF EXISTS `schemeVersion`;
+CREATE TABLE `schemeVersion` (
+  `version` int(5) NOT NULL,
+  PRIMARY KEY (`version`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+INSERT INTO schemeVersion(version)  VALUES(1);
diff --git a/src/org/cacert/gigi/dbObjects/Assurance.java b/src/org/cacert/gigi/dbObjects/Assurance.java
new file mode 100644 (file)
index 0000000..2d7c593
--- /dev/null
@@ -0,0 +1,74 @@
+package org.cacert.gigi.dbObjects;
+
+import org.cacert.gigi.database.GigiResultSet;
+
+public class Assurance {
+
+    public enum AssuranceType {
+        FACE_TO_FACE("Face to Face Meeting"), TOPUP("TOPUP"), TTP_ASSISTED("TTP-Assisted");
+
+        private final String description;
+
+        private AssuranceType(String description) {
+            this.description = description;
+        }
+
+        public String getDescription() {
+            return description;
+        }
+    }
+
+    private int id;
+
+    private User from;
+
+    private User to;
+
+    private String location;
+
+    private String method;
+
+    private int points;
+
+    private String date;
+
+    public Assurance(GigiResultSet res) {
+        super();
+        this.id = res.getInt("id");
+        this.from = User.getById(res.getInt("from"));
+        this.to = User.getById(res.getInt("to"));
+        this.location = res.getString("location");
+        this.method = res.getString("method");
+        this.points = res.getInt("points");
+        this.date = res.getString("date");
+    }
+
+    public User getFrom() {
+        return from;
+    }
+
+    public int getId() {
+        return id;
+    }
+
+    public String getLocation() {
+        return location;
+    }
+
+    public int getPoints() {
+        return points;
+    }
+
+    public User getTo() {
+        return to;
+    }
+
+    public String getMethod() {
+        return method;
+    }
+
+    public String getDate() {
+        return date;
+    }
+
+}
diff --git a/src/org/cacert/gigi/dbObjects/CPS.properties b/src/org/cacert/gigi/dbObjects/CPS.properties
new file mode 100644 (file)
index 0000000..87acaed
--- /dev/null
@@ -0,0 +1,3 @@
+# from http://www.cacert.org/policy/CertificationPracticeStatement.php#p3.1.7 on 07.11.2014
+IDN-enabled=ac,ar,at,biz,br,cat,ch,cl,cn,de,dk,es,fi,gr,hu,info,io,ir,is,jp,kr,li,lt,museum,no,org,pl,pr,se,sh,th,tm,tw,vn
+# from https://data.iana.org/TLD/tlds-alpha-by-domain.txt
diff --git a/src/org/cacert/gigi/dbObjects/Certificate.java b/src/org/cacert/gigi/dbObjects/Certificate.java
new file mode 100644 (file)
index 0000000..c0fb628
--- /dev/null
@@ -0,0 +1,380 @@
+package org.cacert.gigi.dbObjects;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.security.GeneralSecurityException;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+import java.sql.Date;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map.Entry;
+
+import org.cacert.gigi.GigiApiException;
+import org.cacert.gigi.database.DatabaseConnection;
+import org.cacert.gigi.database.GigiPreparedStatement;
+import org.cacert.gigi.database.GigiResultSet;
+import org.cacert.gigi.util.Job;
+import org.cacert.gigi.util.KeyStorage;
+import org.cacert.gigi.util.Notary;
+
+public class Certificate {
+
+    public enum SANType {
+        EMAIL("email"), DNS("DNS");
+
+        private final String opensslName;
+
+        private SANType(String opensslName) {
+            this.opensslName = opensslName;
+        }
+
+        public String getOpensslName() {
+            return opensslName;
+        }
+    }
+
+    public static class SubjectAlternateName implements Comparable<SubjectAlternateName> {
+
+        private SANType type;
+
+        private String name;
+
+        public SubjectAlternateName(SANType type, String name) {
+            this.type = type;
+            this.name = name;
+        }
+
+        public String getName() {
+            return name;
+        }
+
+        public SANType getType() {
+            return type;
+        }
+
+        @Override
+        public int compareTo(SubjectAlternateName o) {
+            int i = type.compareTo(o.type);
+            if (i != 0) {
+                return i;
+            }
+            return name.compareTo(o.name);
+        }
+
+        @Override
+        public int hashCode() {
+            final int prime = 31;
+            int result = 1;
+            result = prime * result + ((name == null) ? 0 : name.hashCode());
+            result = prime * result + ((type == null) ? 0 : type.hashCode());
+            return result;
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            if (this == obj) {
+                return true;
+            }
+            if (obj == null) {
+                return false;
+            }
+            if (getClass() != obj.getClass()) {
+                return false;
+            }
+            SubjectAlternateName other = (SubjectAlternateName) obj;
+            if (name == null) {
+                if (other.name != null) {
+                    return false;
+                }
+            } else if ( !name.equals(other.name)) {
+                return false;
+            }
+            if (type != other.type) {
+                return false;
+            }
+            return true;
+        }
+
+    }
+
+    public enum CSRType {
+        CSR, SPKAC;
+    }
+
+    private int id;
+
+    private User owner;
+
+    private String serial;
+
+    private String md;
+
+    private String csrName;
+
+    private String crtName;
+
+    private String csr = null;
+
+    private CSRType csrType;
+
+    private List<SubjectAlternateName> sans;
+
+    private CertificateProfile profile;
+
+    private HashMap<String, String> dn;
+
+    private String dnString;
+
+    public Certificate(User owner, HashMap<String, String> dn, String md, String csr, CSRType csrType, CertificateProfile profile, SubjectAlternateName... sans) throws GigiApiException {
+        if ( !owner.canIssue(profile)) {
+            throw new GigiApiException("You are not allowed to issue these certificates.");
+        }
+        this.owner = owner;
+        this.dn = dn;
+        if (dn.size() == 0) {
+            throw new GigiApiException("DN must not be empty");
+        }
+        dnString = stringifyDN(dn);
+        this.md = md;
+        this.csr = csr;
+        this.csrType = csrType;
+        this.profile = profile;
+        this.sans = Arrays.asList(sans);
+    }
+
+    private Certificate(String serial) {
+        //
+        String concat = "group_concat(concat('/', `name`, '=', REPLACE(REPLACE(value, '\\\\', '\\\\\\\\'), '/', '\\\\/')))";
+        GigiPreparedStatement ps = DatabaseConnection.getInstance().prepare("SELECT certs.id, " + concat + " as subject, md, csr_name, crt_name,memid, profile FROM `certs` LEFT JOIN certAvas ON certAvas.certid=certs.id WHERE serial=? GROUP BY certs.id");
+        ps.setString(1, serial);
+        GigiResultSet rs = ps.executeQuery();
+        if ( !rs.next()) {
+            throw new IllegalArgumentException("Invalid mid " + serial);
+        }
+        this.id = rs.getInt(1);
+        dnString = rs.getString(2);
+        md = rs.getString(3);
+        csrName = rs.getString(4);
+        crtName = rs.getString(5);
+        owner = User.getById(rs.getInt(6));
+        profile = CertificateProfile.getById(rs.getInt(7));
+        this.serial = serial;
+
+        GigiPreparedStatement ps2 = DatabaseConnection.getInstance().prepare("SELECT contents, type FROM `subjectAlternativeNames` WHERE certId=?");
+        ps2.setInt(1, id);
+        GigiResultSet rs2 = ps2.executeQuery();
+        sans = new LinkedList<>();
+        while (rs2.next()) {
+            sans.add(new SubjectAlternateName(SANType.valueOf(rs2.getString("type").toUpperCase()), rs2.getString("contents")));
+        }
+        rs2.close();
+
+        rs.close();
+    }
+
+    public enum CertificateStatus {
+        /**
+         * This certificate is not in the database, has no id and only exists as
+         * this java object.
+         */
+        DRAFT(),
+        /**
+         * The certificate has been signed. It is stored in the database.
+         * {@link Certificate#cert()} is valid.
+         */
+        ISSUED(),
+
+        /**
+         * The certificate has been revoked.
+         */
+        REVOKED(),
+
+        /**
+         * If this certificate cannot be updated because an error happened in
+         * the signer.
+         */
+        ERROR();
+
+        private CertificateStatus() {}
+
+    }
+
+    public synchronized CertificateStatus getStatus() {
+        if (id == 0) {
+            return CertificateStatus.DRAFT;
+        }
+        GigiPreparedStatement searcher = DatabaseConnection.getInstance().prepare("SELECT crt_name, created, revoked, serial FROM certs WHERE id=?");
+        searcher.setInt(1, id);
+        GigiResultSet rs = searcher.executeQuery();
+        if ( !rs.next()) {
+            throw new IllegalStateException("Certificate not in Database");
+        }
+
+        crtName = rs.getString(1);
+        serial = rs.getString(4);
+        if (rs.getTimestamp(2) == null) {
+            return CertificateStatus.DRAFT;
+        }
+        if (rs.getTimestamp(2) != null && rs.getTimestamp(3) == null) {
+            return CertificateStatus.ISSUED;
+        }
+        return CertificateStatus.REVOKED;
+    }
+
+    /**
+     * @param start
+     *            the date from which on the certificate should be valid. (or
+     *            null if it should be valid instantly)
+     * @param period
+     *            the period for which the date should be valid. (a
+     *            <code>yyyy-mm-dd</code> or a "2y" (2 calendar years), "6m" (6
+     *            months)
+     * @return A job which can be used to monitor the progress of this task.
+     * @throws IOException
+     *             for problems with writing the CSR/SPKAC
+     * @throws GigiApiException
+     *             if the period is bogus
+     */
+    public Job issue(Date start, String period) throws IOException, GigiApiException {
+        if (getStatus() != CertificateStatus.DRAFT) {
+            throw new IllegalStateException();
+        }
+        Notary.writeUserAgreement(owner, "CCA", "issue certificate", "", true, 0);
+
+        GigiPreparedStatement inserter = DatabaseConnection.getInstance().prepare("INSERT INTO certs SET md=?, csr_type=?, crt_name='', memid=?, profile=?");
+        inserter.setString(1, md);
+        inserter.setString(2, csrType.toString());
+        inserter.setInt(3, owner.getId());
+        inserter.setInt(4, profile.getId());
+        inserter.execute();
+        id = inserter.lastInsertId();
+
+        GigiPreparedStatement san = DatabaseConnection.getInstance().prepare("INSERT INTO subjectAlternativeNames SET certId=?, contents=?, type=?");
+        for (SubjectAlternateName subjectAlternateName : sans) {
+            san.setInt(1, id);
+            san.setString(2, subjectAlternateName.getName());
+            san.setString(3, subjectAlternateName.getType().getOpensslName());
+            san.execute();
+        }
+
+        GigiPreparedStatement insertAVA = DatabaseConnection.getInstance().prepare("INSERT certAvas SET certid=?, name=?, value=?");
+        insertAVA.setInt(1, id);
+        for (Entry<String, String> e : dn.entrySet()) {
+            insertAVA.setString(2, e.getKey());
+            insertAVA.setString(3, e.getValue());
+            insertAVA.execute();
+        }
+        File csrFile = KeyStorage.locateCsr(id);
+        csrName = csrFile.getPath();
+        try (FileOutputStream fos = new FileOutputStream(csrFile)) {
+            fos.write(csr.getBytes("UTF-8"));
+        }
+
+        GigiPreparedStatement updater = DatabaseConnection.getInstance().prepare("UPDATE certs SET csr_name=? WHERE id=?");
+        updater.setString(1, csrName);
+        updater.setInt(2, id);
+        updater.execute();
+        return Job.sign(this, start, period);
+
+    }
+
+    public Job revoke() {
+        if (getStatus() != CertificateStatus.ISSUED) {
+            throw new IllegalStateException();
+        }
+        return Job.revoke(this);
+
+    }
+
+    public X509Certificate cert() throws IOException, GeneralSecurityException {
+        CertificateStatus status = getStatus();
+        if (status != CertificateStatus.REVOKED && status != CertificateStatus.ISSUED) {
+            throw new IllegalStateException(status + " is not wanted here.");
+        }
+        InputStream is = null;
+        X509Certificate crt = null;
+        try {
+            is = new FileInputStream(crtName);
+            CertificateFactory cf = CertificateFactory.getInstance("X.509");
+            crt = (X509Certificate) cf.generateCertificate(is);
+        } finally {
+            if (is != null) {
+                is.close();
+            }
+        }
+        return crt;
+    }
+
+    public Certificate renew() {
+        return null;
+    }
+
+    public int getId() {
+        return id;
+    }
+
+    public String getSerial() {
+        getStatus();
+        // poll changes
+        return serial;
+    }
+
+    public String getDistinguishedName() {
+        return dnString;
+    }
+
+    public String getMessageDigest() {
+        return md;
+    }
+
+    public User getOwner() {
+        return owner;
+    }
+
+    public List<SubjectAlternateName> getSANs() {
+        return Collections.unmodifiableList(sans);
+    }
+
+    public CertificateProfile getProfile() {
+        return profile;
+    }
+
+    public static Certificate getBySerial(String serial) {
+        // TODO caching?
+        try {
+            return new Certificate(serial);
+        } catch (IllegalArgumentException e) {
+
+        }
+        return null;
+    }
+
+    public static String escapeAVA(String value) {
+
+        return value.replace("\\", "\\\\").replace("/", "\\/");
+    }
+
+    public static String stringifyDN(HashMap<String, String> contents) {
+        StringBuffer res = new StringBuffer();
+        for (Entry<String, String> i : contents.entrySet()) {
+            res.append("/" + i.getKey() + "=");
+            res.append(escapeAVA(i.getValue()));
+        }
+        return res.toString();
+    }
+
+    public static HashMap<String, String> buildDN(String... contents) {
+        HashMap<String, String> res = new HashMap<>();
+        for (int i = 0; i + 1 < contents.length; i += 2) {
+            res.put(contents[i], contents[i + 1]);
+        }
+        return res;
+    }
+}
diff --git a/src/org/cacert/gigi/dbObjects/CertificateOwner.java b/src/org/cacert/gigi/dbObjects/CertificateOwner.java
new file mode 100644 (file)
index 0000000..e9fb53f
--- /dev/null
@@ -0,0 +1,138 @@
+package org.cacert.gigi.dbObjects;
+
+import java.util.LinkedList;
+
+import org.cacert.gigi.database.DatabaseConnection;
+import org.cacert.gigi.database.GigiPreparedStatement;
+import org.cacert.gigi.database.GigiResultSet;
+
+public abstract class CertificateOwner implements IdCachable {
+
+    private static ObjectCache<CertificateOwner> myCache = new ObjectCache<>();
+
+    private int id;
+
+    public CertificateOwner(int id) {
+        this.id = id;
+    }
+
+    public CertificateOwner() {}
+
+    public int getId() {
+        return id;
+    }
+
+    public static synchronized CertificateOwner getById(int id) {
+        CertificateOwner u = myCache.get(id);
+        if (u == null) {
+            GigiPreparedStatement ps = DatabaseConnection.getInstance().prepare("SELECT *, users.id AS uid, organisations.id AS oid FROM certOwners LEFT JOIN users ON users.id=certOwners.id LEFT JOIN organisations ON organisations.id = certOwners.id WHERE certOwners.id=? AND deleted is null");
+            ps.setInt(1, id);
+            try (GigiResultSet rs = ps.executeQuery()) {
+                if ( !rs.next()) {
+                    return null;
+                }
+                if (rs.getString("uid") != null) {
+                    myCache.put(u = new User(rs));
+                } else if (rs.getString("oid") != null) {
+                    myCache.put(u = new Organisation(rs));
+                } else {
+                    System.err.print("Malformed cert owner: " + id);
+                }
+            }
+        }
+        return u;
+    }
+
+    protected int insert() {
+        synchronized (User.class) {
+            if (id != 0) {
+                throw new Error("refusing to insert");
+            }
+            GigiPreparedStatement ps = DatabaseConnection.getInstance().prepare("INSERT INTO certOwners() VALUES()");
+            ps.execute();
+            id = ps.lastInsertId();
+            myCache.put(this);
+        }
+
+        return id;
+    }
+
+    public EmailAddress[] getEmails() {
+        GigiPreparedStatement ps = DatabaseConnection.getInstance().prepare("SELECT id FROM emails WHERE memid=? AND deleted is NULL");
+        ps.setInt(1, getId());
+
+        try (GigiResultSet rs = ps.executeQuery()) {
+            LinkedList<EmailAddress> data = new LinkedList<EmailAddress>();
+
+            while (rs.next()) {
+                data.add(EmailAddress.getById(rs.getInt(1)));
+            }
+
+            return data.toArray(new EmailAddress[0]);
+        }
+    }
+
+    public Domain[] getDomains() {
+        GigiPreparedStatement ps = DatabaseConnection.getInstance().prepare("SELECT id FROM domains WHERE memid=? AND deleted IS NULL");
+        ps.setInt(1, getId());
+
+        try (GigiResultSet rs = ps.executeQuery()) {
+            LinkedList<Domain> data = new LinkedList<Domain>();
+
+            while (rs.next()) {
+                data.add(Domain.getById(rs.getInt(1)));
+            }
+
+            return data.toArray(new Domain[0]);
+        }
+    }
+
+    public Certificate[] getCertificates(boolean includeRevoked) {
+        GigiPreparedStatement ps;
+        if (includeRevoked) {
+            ps = DatabaseConnection.getInstance().prepare("SELECT serial FROM certs WHERE memid=?");
+        } else {
+            ps = DatabaseConnection.getInstance().prepare("SELECT serial FROM certs WHERE memid=? AND revoked IS NULL");
+        }
+        ps.setInt(1, getId());
+
+        try (GigiResultSet rs = ps.executeQuery()) {
+            LinkedList<Certificate> data = new LinkedList<Certificate>();
+
+            while (rs.next()) {
+                data.add(Certificate.getBySerial(rs.getString(1)));
+            }
+
+            return data.toArray(new Certificate[0]);
+        }
+    }
+
+    public boolean isValidDomain(String domainname) {
+        for (Domain d : getDomains()) {
+            String sfx = d.getSuffix();
+            if (domainname.equals(sfx) || domainname.endsWith("." + sfx)) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    public boolean isValidEmail(String email) {
+        for (EmailAddress em : getEmails()) {
+            if (em.getAddress().equals(email)) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    public void delete() {
+        GigiPreparedStatement ps = DatabaseConnection.getInstance().prepare("UPDATE certOwners SET deleted=NOW() WHERE id=?");
+        ps.setInt(1, getId());
+        ps.execute();
+        myCache.remove(this);
+    }
+
+}
diff --git a/src/org/cacert/gigi/dbObjects/CertificateProfile.java b/src/org/cacert/gigi/dbObjects/CertificateProfile.java
new file mode 100644 (file)
index 0000000..0aa4551
--- /dev/null
@@ -0,0 +1,69 @@
+package org.cacert.gigi.dbObjects;
+
+import java.util.HashMap;
+
+import org.cacert.gigi.database.DatabaseConnection;
+import org.cacert.gigi.database.GigiPreparedStatement;
+import org.cacert.gigi.database.GigiResultSet;
+
+public class CertificateProfile {
+
+    private final int id;
+
+    private final String keyName;
+
+    private final String visibleName;
+
+    private final int caId;
+
+    private static HashMap<String, CertificateProfile> byName = new HashMap<>();
+
+    private static HashMap<Integer, CertificateProfile> byId = new HashMap<>();
+
+    private CertificateProfile(int id, String keyName, String visibleName, int caId) {
+        this.id = id;
+        this.keyName = keyName;
+        this.visibleName = visibleName;
+        this.caId = caId;
+    }
+
+    public int getId() {
+        return id;
+    }
+
+    public String getKeyName() {
+        return keyName;
+    }
+
+    public String getVisibleName() {
+        return visibleName;
+    }
+
+    public int getCAId() {
+        return caId;
+    }
+
+    static {
+        GigiPreparedStatement ps = DatabaseConnection.getInstance().prepare("SELECT id, keyname, name, rootcert FROM `profiles`");
+        GigiResultSet rs = ps.executeQuery();
+        while (rs.next()) {
+            CertificateProfile cp = new CertificateProfile(rs.getInt("id"), rs.getString("keyName"), rs.getString("name"), rs.getInt("rootcert"));
+            byId.put(cp.getId(), cp);
+            byName.put(cp.getKeyName(), cp);
+        }
+
+    }
+
+    public static CertificateProfile getById(int id) {
+        return byId.get(id);
+    }
+
+    public static CertificateProfile getByName(String name) {
+        return byName.get(name);
+    }
+
+    public static CertificateProfile[] getAll() {
+        return byId.values().toArray(new CertificateProfile[byId.size()]);
+    }
+
+}
diff --git a/src/org/cacert/gigi/dbObjects/Digest.java b/src/org/cacert/gigi/dbObjects/Digest.java
new file mode 100644 (file)
index 0000000..edd8dca
--- /dev/null
@@ -0,0 +1,19 @@
+package org.cacert.gigi.dbObjects;
+
+public enum Digest {
+    SHA256("Currently recommended, because the other algorithms" + " might break on some older versions of the GnuTLS library" + " (older than 3.x) still shipped in Debian for example."), SHA384(null), SHA512("Highest protection against hash collision attacks of the algorithms offered here.");
+
+    private final String exp;
+
+    private Digest(String explanation) {
+        exp = explanation;
+    }
+
+    public String getExp() {
+        return exp;
+    }
+
+    public static Digest getDefault() {
+        return SHA256;
+    }
+}
diff --git a/src/org/cacert/gigi/dbObjects/Domain.java b/src/org/cacert/gigi/dbObjects/Domain.java
new file mode 100644 (file)
index 0000000..9df2e48
--- /dev/null
@@ -0,0 +1,286 @@
+package org.cacert.gigi.dbObjects;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.IDN;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Properties;
+import java.util.Set;
+
+import org.cacert.gigi.GigiApiException;
+import org.cacert.gigi.database.DatabaseConnection;
+import org.cacert.gigi.database.GigiPreparedStatement;
+import org.cacert.gigi.database.GigiResultSet;
+import org.cacert.gigi.dbObjects.DomainPingConfiguration.PingType;
+import org.cacert.gigi.util.PublicSuffixes;
+
+public class Domain implements IdCachable {
+
+    public class DomainPingExecution {
+
+        private String state;
+
+        private String type;
+
+        private String info;
+
+        private String result;
+
+        private DomainPingConfiguration config;
+
+        public DomainPingExecution(GigiResultSet rs) {
+            state = rs.getString(1);
+            type = rs.getString(2);
+            info = rs.getString(3);
+            result = rs.getString(4);
+            config = DomainPingConfiguration.getById(rs.getInt(5));
+        }
+
+        public String getState() {
+            return state;
+        }
+
+        public String getType() {
+            return type;
+        }
+
+        public String getInfo() {
+            return info;
+        }
+
+        public String getResult() {
+            return result;
+        }
+
+        public DomainPingConfiguration getConfig() {
+            return config;
+        }
+
+    }
+
+    private User owner;
+
+    private String suffix;
+
+    private int id;
+
+    private static final Set<String> IDNEnabledTLDs;
+    static {
+        Properties CPS = new Properties();
+        try (InputStream resourceAsStream = Domain.class.getResourceAsStream("CPS.properties")) {
+            CPS.load(resourceAsStream);
+            IDNEnabledTLDs = Collections.unmodifiableSet(new HashSet<>(Arrays.asList(CPS.getProperty("IDN-enabled").split(","))));
+        } catch (IOException e) {
+            throw new Error(e);
+        }
+    }
+
+    private Domain(int id) {
+        GigiPreparedStatement ps = DatabaseConnection.getInstance().prepare("SELECT memid, domain FROM `domains` WHERE id=? AND deleted IS NULL");
+        ps.setInt(1, id);
+
+        GigiResultSet rs = ps.executeQuery();
+        if ( !rs.next()) {
+            throw new IllegalArgumentException("Invalid domain id " + id);
+        }
+        this.id = id;
+        owner = User.getById(rs.getInt(1));
+        suffix = rs.getString(2);
+        rs.close();
+    }
+
+    public Domain(User owner, String suffix) throws GigiApiException {
+        checkCertifyableDomain(suffix, owner.isInGroup(Group.CODESIGNING));
+        this.owner = owner;
+        this.suffix = suffix;
+
+    }
+
+    public static void checkCertifyableDomain(String s, boolean hasPunycodeRight) throws GigiApiException {
+        String[] parts = s.split("\\.", -1);
+        if (parts.length < 2) {
+            throw new GigiApiException("Domain does not contain '.'.");
+        }
+        for (int i = parts.length - 1; i >= 0; i--) {
+            if ( !isVaildDomainPart(parts[i], hasPunycodeRight)) {
+                throw new GigiApiException("Syntax error in Domain");
+            }
+        }
+        String publicSuffix = PublicSuffixes.getInstance().getRegistrablePart(s);
+        if ( !s.equals(publicSuffix)) {
+            throw new GigiApiException("You may only register a domain with exactly one lable before the public suffix.");
+        }
+        checkPunycode(parts[0], s.substring(parts[0].length() + 1));
+    }
+
+    private static void checkPunycode(String label, String domainContext) throws GigiApiException {
+        if (label.charAt(2) != '-' || label.charAt(3) != '-') {
+            return; // is no punycode
+        }
+        if ( !IDNEnabledTLDs.contains(domainContext)) {
+            throw new GigiApiException("Punycode label could not be positively verified.");
+        }
+        if ( !label.startsWith("xn--")) {
+            throw new GigiApiException("Unknown ACE prefix.");
+        }
+        try {
+            String unicode = IDN.toUnicode(label);
+            if (unicode.startsWith("xn--")) {
+                throw new GigiApiException("Punycode label could not be positively verified.");
+            }
+        } catch (IllegalArgumentException e) {
+            throw new GigiApiException("Punycode label could not be positively verified.");
+        }
+    }
+
+    public static boolean isVaildDomainPart(String s, boolean allowPunycode) {
+        if ( !s.matches("[a-z0-9-]+")) {
+            return false;
+        }
+        if (s.charAt(0) == '-' || s.charAt(s.length() - 1) == '-') {
+            return false;
+        }
+        if (s.length() > 63) {
+            return false;
+        }
+        boolean canBePunycode = s.length() >= 4 && s.charAt(2) == '-' && s.charAt(3) == '-';
+        if (canBePunycode && !allowPunycode) {
+            return false;
+        }
+        return true;
+    }
+
+    private static void checkInsert(String suffix) throws GigiApiException {
+        GigiPreparedStatement ps = DatabaseConnection.getInstance().prepare("SELECT 1 FROM `domains` WHERE (domain=? OR (CONCAT('.', domain)=RIGHT(?,LENGTH(domain)+1)  OR RIGHT(domain,LENGTH(?)+1)=CONCAT('.',?))) AND deleted IS NULL");
+        ps.setString(1, suffix);
+        ps.setString(2, suffix);
+        ps.setString(3, suffix);
+        ps.setString(4, suffix);
+        GigiResultSet rs = ps.executeQuery();
+        boolean existed = rs.next();
+        rs.close();
+        if (existed) {
+            throw new GigiApiException("Domain could not be inserted. Domain is already valid.");
+        }
+    }
+
+    public void insert() throws GigiApiException {
+        synchronized (Domain.class) {
+            if (id != 0) {
+                throw new GigiApiException("already inserted.");
+            }
+            checkInsert(suffix);
+            GigiPreparedStatement ps = DatabaseConnection.getInstance().prepare("INSERT INTO `domains` SET memid=?, domain=?");
+            ps.setInt(1, owner.getId());
+            ps.setString(2, suffix);
+            ps.execute();
+            id = ps.lastInsertId();
+            myCache.put(this);
+        }
+    }
+
+    public void delete() throws GigiApiException {
+        if (id == 0) {
+            throw new GigiApiException("not inserted.");
+        }
+        GigiPreparedStatement ps = DatabaseConnection.getInstance().prepare("UPDATE `domains` SET deleted=CURRENT_TIMESTAMP WHERE id=?");
+        ps.setInt(1, id);
+        ps.execute();
+    }
+
+    public User getOwner() {
+        return owner;
+    }
+
+    @Override
+    public int getId() {
+        return id;
+    }
+
+    public String getSuffix() {
+        return suffix;
+    }
+
+    private LinkedList<DomainPingConfiguration> configs = null;
+
+    public List<DomainPingConfiguration> getConfiguredPings() throws GigiApiException {
+        LinkedList<DomainPingConfiguration> configs = this.configs;
+        if (configs == null) {
+            configs = new LinkedList<>();
+            GigiPreparedStatement ps = DatabaseConnection.getInstance().prepare("SELECT id FROM pingconfig WHERE domainid=?");
+            ps.setInt(1, id);
+            GigiResultSet rs = ps.executeQuery();
+            while (rs.next()) {
+                configs.add(DomainPingConfiguration.getById(rs.getInt(1)));
+            }
+            rs.close();
+            this.configs = configs;
+
+        }
+        return Collections.unmodifiableList(configs);
+    }
+
+    public void addPing(PingType type, String config) throws GigiApiException {
+        GigiPreparedStatement ps = DatabaseConnection.getInstance().prepare("INSERT INTO pingconfig SET domainid=?, type=?, info=?");
+        ps.setInt(1, id);
+        ps.setString(2, type.toString().toLowerCase());
+        ps.setString(3, config);
+        ps.execute();
+        configs = null;
+    }
+
+    public void verify(String hash) throws GigiApiException {
+        GigiPreparedStatement ps = DatabaseConnection.getInstance().prepare("UPDATE domainPinglog SET state='success' WHERE challenge=? AND configId IN (SELECT id FROM pingconfig WHERE domainId=?)");
+        ps.setString(1, hash);
+        ps.setInt(2, id);
+        ps.executeUpdate();
+    }
+
+    public boolean isVerified() {
+        GigiPreparedStatement ps = DatabaseConnection.getInstance().prepare("SELECT 1 FROM domainPinglog INNER JOIN pingconfig ON pingconfig.id=domainPinglog.configId WHERE domainid=? AND state='success'");
+        ps.setInt(1, id);
+        GigiResultSet rs = ps.executeQuery();
+        return rs.next();
+    }
+
+    public DomainPingExecution[] getPings() throws GigiApiException {
+        GigiPreparedStatement ps = DatabaseConnection.getInstance().prepare("SELECT state, type, info, result, configId FROM domainPinglog INNER JOIN pingconfig ON pingconfig.id=domainPinglog.configid WHERE pingconfig.domainid=? ORDER BY `when` DESC;");
+        ps.setInt(1, id);
+        GigiResultSet rs = ps.executeQuery();
+        rs.last();
+        DomainPingExecution[] contents = new DomainPingExecution[rs.getRow()];
+        rs.beforeFirst();
+        for (int i = 0; i < contents.length && rs.next(); i++) {
+            contents[i] = new DomainPingExecution(rs);
+        }
+        return contents;
+
+    }
+
+    private static ObjectCache<Domain> myCache = new ObjectCache<>();
+
+    public static synchronized Domain getById(int id) throws IllegalArgumentException {
+        Domain em = myCache.get(id);
+        if (em == null) {
+            myCache.put(em = new Domain(id));
+        }
+        return em;
+    }
+
+    public static int searchUserIdByDomain(String domain) {
+        GigiPreparedStatement ps = DatabaseConnection.getInstance().prepare("SELECT memid FROM domains WHERE domain = ?");
+        ps.setString(1, domain);
+        GigiResultSet res = ps.executeQuery();
+        res.beforeFirst();
+        if (res.next()) {
+            return res.getInt(1);
+        } else {
+            return -1;
+        }
+    }
+
+}
diff --git a/src/org/cacert/gigi/dbObjects/DomainPingConfiguration.java b/src/org/cacert/gigi/dbObjects/DomainPingConfiguration.java
new file mode 100644 (file)
index 0000000..e27b696
--- /dev/null
@@ -0,0 +1,101 @@
+package org.cacert.gigi.dbObjects;
+
+import java.util.Arrays;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.cacert.gigi.Gigi;
+import org.cacert.gigi.GigiApiException;
+import org.cacert.gigi.database.DatabaseConnection;
+import org.cacert.gigi.database.GigiPreparedStatement;
+import org.cacert.gigi.database.GigiResultSet;
+import org.cacert.gigi.output.template.Scope;
+import org.cacert.gigi.output.template.SprintfCommand;
+
+public class DomainPingConfiguration implements IdCachable {
+
+    public static enum PingType {
+        EMAIL, DNS, HTTP, SSL;
+    }
+
+    private int id;
+
+    private Domain target;
+
+    private PingType type;
+
+    private String info;
+
+    private DomainPingConfiguration(int id) {
+        GigiPreparedStatement ps = DatabaseConnection.getInstance().prepare("SELECT id, domainid, type, info FROM pingconfig WHERE id=?");
+        ps.setInt(1, id);
+
+        GigiResultSet rs = ps.executeQuery();
+        if ( !rs.next()) {
+            throw new IllegalArgumentException("Invalid pingconfig id " + id);
+        }
+        this.id = rs.getInt("id");
+        target = Domain.getById(rs.getInt("domainid"));
+        type = PingType.valueOf(rs.getString("type").toUpperCase());
+        info = rs.getString("info");
+    }
+
+    @Override
+    public int getId() {
+        return id;
+    }
+
+    public Domain getTarget() {
+        return target;
+    }
+
+    public PingType getType() {
+        return type;
+    }
+
+    public String getInfo() {
+        return info;
+    }
+
+    private static ObjectCache<DomainPingConfiguration> cache = new ObjectCache<>();
+
+    public static synchronized DomainPingConfiguration getById(int id) {
+        DomainPingConfiguration res = cache.get(id);
+        if (res == null) {
+            cache.put(res = new DomainPingConfiguration(id));
+        }
+        return res;
+    }
+
+    public Date getLastExecution() {
+        GigiPreparedStatement ps = DatabaseConnection.getInstance().prepare("SELECT `when` AS stamp from domainPinglog WHERE configId=? ORDER BY `when` DESC LIMIT 1");
+        ps.setInt(1, id);
+        GigiResultSet rs = ps.executeQuery();
+        if (rs.next()) {
+            return new Date(rs.getTimestamp("stamp").getTime());
+        }
+        return new Date(0);
+    }
+
+    public Date getLastSuccess() {
+        GigiPreparedStatement ps = DatabaseConnection.getInstance().prepare("SELECT `when` AS stamp from domainPinglog WHERE configId=? AND state='success' ORDER BY `when` DESC LIMIT 1");
+        ps.setInt(1, id);
+        GigiResultSet rs = ps.executeQuery();
+        if (rs.next()) {
+            return new Date(rs.getTimestamp("stamp").getTime());
+        }
+        return new Date(0);
+    }
+
+    public synchronized void requestReping() throws GigiApiException {
+        Date lastExecution = getLastExecution();
+        if (lastExecution.getTime() + 5 * 60 * 1000 < System.currentTimeMillis()) {
+            Gigi.notifyPinger(this);
+            return;
+        }
+        Map<String, Object> data = new HashMap<String, Object>();
+        data.put("data", new Date(lastExecution.getTime() + 5 * 60 * 1000));
+        throw new GigiApiException(new Scope(new SprintfCommand("Reping is only allowed after 5 minutes, yours end at %s.", Arrays.asList("$data")), data));
+    }
+}
diff --git a/src/org/cacert/gigi/dbObjects/EmailAddress.java b/src/org/cacert/gigi/dbObjects/EmailAddress.java
new file mode 100644 (file)
index 0000000..13e2e45
--- /dev/null
@@ -0,0 +1,114 @@
+package org.cacert.gigi.dbObjects;
+
+import java.io.IOException;
+
+import org.cacert.gigi.GigiApiException;
+import org.cacert.gigi.database.DatabaseConnection;
+import org.cacert.gigi.database.GigiPreparedStatement;
+import org.cacert.gigi.database.GigiResultSet;
+import org.cacert.gigi.email.EmailProvider;
+import org.cacert.gigi.email.MailProbe;
+import org.cacert.gigi.localisation.Language;
+import org.cacert.gigi.util.RandomToken;
+
+public class EmailAddress implements IdCachable {
+
+    private String address;
+
+    private int id;
+
+    private User owner;
+
+    private String hash = null;
+
+    private EmailAddress(int id) {
+        GigiPreparedStatement ps = DatabaseConnection.getInstance().prepare("SELECT memid, email, hash FROM `emails` WHERE id=? AND deleted is NULL");
+        ps.setInt(1, id);
+
+        GigiResultSet rs = ps.executeQuery();
+        if ( !rs.next()) {
+            throw new IllegalArgumentException("Invalid email id " + id);
+        }
+        this.id = id;
+        owner = User.getById(rs.getInt(1));
+        address = rs.getString(2);
+        hash = rs.getString(3);
+        rs.close();
+    }
+
+    public EmailAddress(User owner, String address) {
+        if ( !EmailProvider.MAIL.matcher(address).matches()) {
+            throw new IllegalArgumentException("Invalid email.");
+        }
+        this.address = address;
+        this.owner = owner;
+        this.hash = RandomToken.generateToken(16);
+    }
+
+    public void insert(Language l) throws GigiApiException {
+        try {
+            synchronized (EmailAddress.class) {
+                if (id != 0) {
+                    throw new IllegalStateException("already inserted.");
+                }
+                GigiPreparedStatement psCheck = DatabaseConnection.getInstance().prepare("SELECT 1 FROM `emails` WHERE email=? AND deleted is NULL");
+                GigiPreparedStatement ps = DatabaseConnection.getInstance().prepare("INSERT INTO `emails` SET memid=?, hash=?, email=?");
+                ps.setInt(1, owner.getId());
+                ps.setString(2, hash);
+                ps.setString(3, address);
+                psCheck.setString(1, address);
+                GigiResultSet res = psCheck.executeQuery();
+                if (res.next()) {
+                    throw new GigiApiException("The email is currently valid");
+                }
+                ps.execute();
+                id = ps.lastInsertId();
+                myCache.put(this);
+            }
+            MailProbe.sendMailProbe(l, "email", id, hash, address);
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+    }
+
+    public int getId() {
+        return id;
+    }
+
+    public String getAddress() {
+        return address;
+    }
+
+    public synchronized void verify(String hash) throws GigiApiException {
+        if (this.hash.equals(hash)) {
+            GigiPreparedStatement ps = DatabaseConnection.getInstance().prepare("UPDATE `emails` SET hash='' WHERE id=?");
+            ps.setInt(1, id);
+            ps.execute();
+            hash = "";
+
+            // Verify user with that primary email
+            GigiPreparedStatement ps2 = DatabaseConnection.getInstance().prepare("update `users` set `verified`='1' where `id`=? and `email`=? and `verified`='0'");
+            ps2.setInt(1, owner.getId());
+            ps2.setString(2, address);
+            ps2.execute();
+            this.hash = "";
+
+        } else {
+            throw new GigiApiException("Email verification hash is invalid.");
+        }
+    }
+
+    public boolean isVerified() {
+        return hash.isEmpty();
+    }
+
+    private static ObjectCache<EmailAddress> myCache = new ObjectCache<>();
+
+    public static synchronized EmailAddress getById(int id) throws IllegalArgumentException {
+        EmailAddress em = myCache.get(id);
+        if (em == null) {
+            myCache.put(em = new EmailAddress(id));
+        }
+        return em;
+    }
+}
diff --git a/src/org/cacert/gigi/dbObjects/Group.java b/src/org/cacert/gigi/dbObjects/Group.java
new file mode 100644 (file)
index 0000000..a40403e
--- /dev/null
@@ -0,0 +1,39 @@
+package org.cacert.gigi.dbObjects;
+
+import org.cacert.gigi.database.DatabaseConnection;
+import org.cacert.gigi.database.GigiPreparedStatement;
+import org.cacert.gigi.database.GigiResultSet;
+
+public enum Group {
+    SUPPORTER("supporter"), ARBITRATOR("arbitrator"), BLOCKEDASSURER("blockedassurer"), BLOCKEDASSUREE("blockedassuree"), BLOCKEDLOGIN("blockedlogin"), TTP_ASSURER("ttp-assurer"), TTP_APPLICANT("ttp-applicant"), CODESIGNING("codesigning"), ORGASSURER("orgassurer");
+
+    private final String dbName;
+
+    private Group(String name) {
+        dbName = name;
+    }
+
+    public static Group getByString(String name) {
+        return valueOf(name.toUpperCase().replace('-', '_'));
+    }
+
+    public String getDatabaseName() {
+        return dbName;
+    }
+
+    public User[] getMembers(int offset, int count) {
+        GigiPreparedStatement gps = DatabaseConnection.getInstance().prepare("SELECT user FROM user_groups WHERE permission=? AND deleted is NULL LIMIT ?,?");
+        gps.setString(1, dbName);
+        gps.setInt(2, offset);
+        gps.setInt(3, count);
+        GigiResultSet grs = gps.executeQuery();
+        grs.last();
+        User[] users = new User[grs.getRow()];
+        int i = 0;
+        grs.beforeFirst();
+        while (grs.next()) {
+            users[i++] = User.getById(grs.getInt(1));
+        }
+        return users;
+    }
+}
diff --git a/src/org/cacert/gigi/dbObjects/IdCachable.java b/src/org/cacert/gigi/dbObjects/IdCachable.java
new file mode 100644 (file)
index 0000000..af439bc
--- /dev/null
@@ -0,0 +1,7 @@
+package org.cacert.gigi.dbObjects;
+
+public interface IdCachable {
+
+    public int getId();
+
+}
diff --git a/src/org/cacert/gigi/dbObjects/Name.java b/src/org/cacert/gigi/dbObjects/Name.java
new file mode 100644 (file)
index 0000000..7c803b8
--- /dev/null
@@ -0,0 +1,105 @@
+package org.cacert.gigi.dbObjects;
+
+import java.io.PrintWriter;
+import java.util.Map;
+
+import org.cacert.gigi.localisation.Language;
+import org.cacert.gigi.output.template.Outputable;
+import org.cacert.gigi.util.HTMLEncoder;
+
+public class Name implements Outputable {
+
+    String fname;
+
+    String mname;
+
+    String lname;
+
+    String suffix;
+
+    public Name(String fname, String lname, String mname, String suffix) {
+        this.fname = fname;
+        this.lname = lname;
+        this.mname = mname;
+        this.suffix = suffix;
+    }
+
+    @Override
+    public void output(PrintWriter out, Language l, Map<String, Object> vars) {
+        out.println("<span class=\"accountdetail\">");
+        out.print("<span class=\"fname\">");
+        out.print(HTMLEncoder.encodeHTML(fname));
+        out.print("</span> ");
+        out.print("<span class=\"lname\">");
+        out.print(HTMLEncoder.encodeHTML(lname));
+        out.print("</span>");
+        out.println("</span>");
+    }
+
+    @Override
+    public String toString() {
+        return fname + " " + lname;
+    }
+
+    @Override
+    public int hashCode() {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result + ((fname == null) ? 0 : fname.hashCode());
+        result = prime * result + ((lname == null) ? 0 : lname.hashCode());
+        result = prime * result + ((mname == null) ? 0 : mname.hashCode());
+        result = prime * result + ((suffix == null) ? 0 : suffix.hashCode());
+        return result;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj == null) {
+            return false;
+        }
+        if (getClass() != obj.getClass()) {
+            return false;
+        }
+        Name other = (Name) obj;
+        if (fname == null) {
+            if (other.fname != null) {
+                return false;
+            }
+        } else if ( !fname.equals(other.fname)) {
+            return false;
+        }
+        if (lname == null) {
+            if (other.lname != null) {
+                return false;
+            }
+        } else if ( !lname.equals(other.lname)) {
+            return false;
+        }
+        if (mname == null) {
+            if (other.mname != null) {
+                return false;
+            }
+        } else if ( !mname.equals(other.mname)) {
+            return false;
+        }
+        if (suffix == null) {
+            if (other.suffix != null) {
+                return false;
+            }
+        } else if ( !suffix.equals(other.suffix)) {
+            return false;
+        }
+        return true;
+    }
+
+    public boolean matches(String text) {
+        return text.equals(fname + " " + lname) || //
+                (mname != null && text.equals(fname + " " + mname + " " + lname)) || //
+                (suffix != null && text.equals(fname + " " + lname + " " + suffix)) || //
+                (mname != null && suffix != null && text.equals(fname + " " + mname + " " + lname + " " + suffix));
+    }
+
+}
diff --git a/src/org/cacert/gigi/dbObjects/ObjectCache.java b/src/org/cacert/gigi/dbObjects/ObjectCache.java
new file mode 100644 (file)
index 0000000..d1e41f8
--- /dev/null
@@ -0,0 +1,38 @@
+package org.cacert.gigi.dbObjects;
+
+import java.lang.ref.WeakReference;
+import java.util.HashMap;
+import java.util.HashSet;
+
+public class ObjectCache<T extends IdCachable> {
+
+    private HashMap<Integer, WeakReference<T>> hashmap = new HashMap<>();
+
+    private static HashSet<ObjectCache<?>> caches = new HashSet<>();
+
+    protected ObjectCache() {
+        caches.add(this);
+    }
+
+    public void put(T c) {
+        hashmap.put(c.getId(), new WeakReference<T>(c));
+    }
+
+    public T get(int id) {
+        WeakReference<T> res = hashmap.get(id);
+        if (res != null) {
+            return res.get();
+        }
+        return null;
+    }
+
+    public static void clearAllCaches() {
+        for (ObjectCache<?> objectCache : caches) {
+            objectCache.hashmap.clear();
+        }
+    }
+
+    public void remove(T toRm) {
+        hashmap.remove(toRm);
+    }
+}
diff --git a/src/org/cacert/gigi/dbObjects/Organisation.java b/src/org/cacert/gigi/dbObjects/Organisation.java
new file mode 100644 (file)
index 0000000..d96f95a
--- /dev/null
@@ -0,0 +1,183 @@
+package org.cacert.gigi.dbObjects;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.cacert.gigi.database.DatabaseConnection;
+import org.cacert.gigi.database.GigiPreparedStatement;
+import org.cacert.gigi.database.GigiResultSet;
+import org.cacert.gigi.dbObjects.Certificate.CertificateStatus;
+
+public class Organisation extends CertificateOwner {
+
+    public class Affiliation {
+
+        private final User target;
+
+        private final boolean master;
+
+        private final String fixedOU;
+
+        public Affiliation(User target, boolean master, String fixedOU) {
+            this.target = target;
+            this.master = master;
+            this.fixedOU = fixedOU;
+        }
+
+        public User getTarget() {
+            return target;
+        }
+
+        public boolean isMaster() {
+            return master;
+        }
+
+        public String getFixedOU() {
+            return fixedOU;
+        }
+
+        public Organisation getOrganisation() {
+            return Organisation.this;
+        }
+    }
+
+    private String name;
+
+    private String state;
+
+    private String province;
+
+    private String city;
+
+    private String email;
+
+    public Organisation(String name, String state, String province, String city, String email, User creator) {
+        this.name = name;
+        this.state = state;
+        this.province = province;
+        this.city = city;
+        this.email = email;
+        int id = super.insert();
+        GigiPreparedStatement ps = DatabaseConnection.getInstance().prepare("INSERT INTO organisations SET id=?, name=?, state=?, province=?, city=?, contactEmail=?, creator=?");
+        ps.setInt(1, id);
+        ps.setString(2, name);
+        ps.setString(3, state);
+        ps.setString(4, province);
+        ps.setString(5, city);
+        ps.setString(6, email);
+        ps.setInt(7, creator.getId());
+        synchronized (Organisation.class) {
+            ps.execute();
+        }
+
+    }
+
+    protected Organisation(GigiResultSet rs) {
+        super(rs.getInt("id"));
+        name = rs.getString("name");
+        state = rs.getString("state");
+        province = rs.getString("province");
+        city = rs.getString("city");
+        email = rs.getString("contactEmail");
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public String getState() {
+        return state;
+    }
+
+    public String getProvince() {
+        return province;
+    }
+
+    public String getCity() {
+        return city;
+    }
+
+    public String getContactEmail() {
+        return email;
+    }
+
+    public static synchronized Organisation getById(int id) {
+        CertificateOwner co = CertificateOwner.getById(id);
+        if (co instanceof Organisation) {
+            return (Organisation) co;
+        }
+        return null;
+    }
+
+    public synchronized void addAdmin(User admin, User actor, boolean master) {
+        GigiPreparedStatement ps1 = DatabaseConnection.getInstance().prepare("SELECT 1 FROM org_admin WHERE orgid=? AND memid=? AND deleted is null");
+        ps1.setInt(1, getId());
+        ps1.setInt(2, admin.getId());
+        GigiResultSet result = ps1.executeQuery();
+        if (result.next()) {
+            return;
+        }
+        GigiPreparedStatement ps2 = DatabaseConnection.getInstance().prepare("INSERT INTO org_admin SET orgid=?, memid=?, creator=?, master=?");
+        ps2.setInt(1, getId());
+        ps2.setInt(2, admin.getId());
+        ps2.setInt(3, actor.getId());
+        ps2.setString(4, master ? "y" : "n");
+        ps2.execute();
+    }
+
+    public void removeAdmin(User admin, User actor) {
+        GigiPreparedStatement ps = DatabaseConnection.getInstance().prepare("UPDATE org_admin SET deleter=?, deleted=NOW() WHERE orgid=? AND memid=?");
+        ps.setInt(1, actor.getId());
+        ps.setInt(2, getId());
+        ps.setInt(3, admin.getId());
+        ps.execute();
+    }
+
+    public List<Affiliation> getAllAdmins() {
+        GigiPreparedStatement ps = DatabaseConnection.getInstance().prepare("SELECT memid, master FROM org_admin WHERE orgid=? AND deleted is null");
+        ps.setInt(1, getId());
+        GigiResultSet rs = ps.executeQuery();
+        rs.last();
+        ArrayList<Affiliation> al = new ArrayList<>(rs.getRow());
+        rs.beforeFirst();
+        while (rs.next()) {
+            al.add(new Affiliation(User.getById(rs.getInt(1)), rs.getString(2).equals("y"), null));
+        }
+        return al;
+    }
+
+    public static Organisation[] getOrganisations(int offset, int count) {
+        GigiPreparedStatement ps = DatabaseConnection.getInstance().prepare("SELECT certOwners.id FROM organisations inner join certOwners on certOwners.id=organisations.id where certOwners.deleted is null LIMIT ?,?");
+        ps.setInt(1, offset);
+        ps.setInt(2, count);
+        GigiResultSet res = ps.executeQuery();
+        res.last();
+        Organisation[] resu = new Organisation[res.getRow()];
+        res.beforeFirst();
+        int i = 0;
+        while (res.next()) {
+            resu[i++] = getById(res.getInt(1));
+        }
+        return resu;
+    }
+
+    public void update(String o, String c, String st, String l, String mail) {
+        for (Certificate cert : getCertificates(false)) {
+            if (cert.getStatus() == CertificateStatus.ISSUED) {
+                cert.revoke();
+            }
+        }
+        GigiPreparedStatement ps = DatabaseConnection.getInstance().prepare("UPDATE organisations SET name=?, state=?, province=?, city=?, contactEmail=?");
+        ps.setString(1, o);
+        ps.setString(2, c);
+        ps.setString(3, st);
+        ps.setString(4, l);
+        ps.setString(5, mail);
+        ps.execute();
+        email = mail;
+        name = o;
+        state = c;
+        province = st;
+        city = l;
+    }
+}
diff --git a/src/org/cacert/gigi/dbObjects/SupportedUser.java b/src/org/cacert/gigi/dbObjects/SupportedUser.java
new file mode 100644 (file)
index 0000000..bdad013
--- /dev/null
@@ -0,0 +1,59 @@
+package org.cacert.gigi.dbObjects;
+
+import java.sql.Date;
+
+import org.cacert.gigi.database.DatabaseConnection;
+import org.cacert.gigi.database.GigiPreparedStatement;
+
+public class SupportedUser {
+
+    private User target, supporter;
+
+    private String ticket;
+
+    public SupportedUser(User target, User supporter, String ticket) {
+        this.supporter = supporter;
+        this.target = target;
+        this.ticket = ticket;
+    }
+
+    public void setName(String fname, String mname, String lname, String suffix) {
+        writeSELog("SE Name change");
+        target.setName(new Name(fname, lname, mname, suffix));
+    }
+
+    public void setDob(Date dob) {
+        writeSELog("SE dob change");
+        target.setDoB(dob);
+    }
+
+    public void revokeAllCertificates() {
+        writeSELog("SE Revoke certificates");
+        Certificate[] certs = target.getCertificates(false);
+        for (int i = 0; i < certs.length; i++) {
+            certs[i].revoke();
+        }
+    }
+
+    public void writeSELog(String type) {
+        GigiPreparedStatement prep = DatabaseConnection.getInstance().prepare("INSERT INTO adminLog SET uid=?, admin=?, type=?, information=?");
+        prep.setInt(1, target.getId());
+        prep.setInt(2, supporter.getId());
+        prep.setString(3, type);
+        prep.setString(4, ticket);
+        prep.executeUpdate();
+    }
+
+    public int getId() {
+        return target.getId();
+    }
+
+    public Certificate[] getCertificates(boolean includeRevoked) {
+        return target.getCertificates(includeRevoked);
+    }
+
+    public String getTicket() {
+        return ticket;
+    }
+
+}
diff --git a/src/org/cacert/gigi/dbObjects/User.java b/src/org/cacert/gigi/dbObjects/User.java
new file mode 100644 (file)
index 0000000..ccbe53f
--- /dev/null
@@ -0,0 +1,490 @@
+package org.cacert.gigi.dbObjects;
+
+import java.sql.Date;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Locale;
+import java.util.Set;
+
+import org.cacert.gigi.GigiApiException;
+import org.cacert.gigi.database.DatabaseConnection;
+import org.cacert.gigi.database.GigiPreparedStatement;
+import org.cacert.gigi.database.GigiResultSet;
+import org.cacert.gigi.localisation.Language;
+import org.cacert.gigi.util.Notary;
+import org.cacert.gigi.util.PasswordHash;
+import org.cacert.gigi.util.PasswordStrengthChecker;
+
+public class User extends CertificateOwner {
+
+    private Name name = new Name(null, null, null, null);
+
+    private Date dob;
+
+    private String email;
+
+    private Assurance[] receivedAssurances;
+
+    private Assurance[] madeAssurances;
+
+    private Locale locale;
+
+    private Set<Group> groups = new HashSet<>();
+
+    protected User(GigiResultSet rs) {
+        super(rs.getInt("id"));
+        updateName(rs);
+    }
+
+    private void updateName(GigiResultSet rs) {
+        name = new Name(rs.getString("fname"), rs.getString("lname"), rs.getString("mname"), rs.getString("suffix"));
+        dob = rs.getDate("dob");
+        email = rs.getString("email");
+
+        String localeStr = rs.getString("language");
+        if (localeStr == null || localeStr.equals("")) {
+            locale = Locale.getDefault();
+        } else {
+            locale = Language.getLocaleFromString(localeStr);
+        }
+
+        GigiPreparedStatement psg = DatabaseConnection.getInstance().prepare("SELECT permission FROM user_groups WHERE user=? AND deleted is NULL");
+        psg.setInt(1, rs.getInt("id"));
+
+        try (GigiResultSet rs2 = psg.executeQuery()) {
+            while (rs2.next()) {
+                groups.add(Group.getByString(rs2.getString(1)));
+            }
+        }
+    }
+
+    public User() {}
+
+    public String getFName() {
+        return name.fname;
+    }
+
+    public String getLName() {
+        return name.lname;
+    }
+
+    public String getMName() {
+        return name.mname;
+    }
+
+    public Name getName() {
+        return name;
+    }
+
+    public void setMName(String mname) {
+        this.name.mname = mname;
+    }
+
+    public String getSuffix() {
+        return name.suffix;
+    }
+
+    public void setSuffix(String suffix) {
+        this.name.suffix = suffix;
+    }
+
+    public Date getDoB() {
+        return dob;
+    }
+
+    public void setDoB(Date dob) {
+        this.dob = dob;
+    }
+
+    public String getEmail() {
+        return email;
+    }
+
+    public void setEmail(String email) {
+        this.email = email;
+    }
+
+    public void setFName(String fname) {
+        this.name.fname = fname;
+    }
+
+    public void setLName(String lname) {
+        this.name.lname = lname;
+    }
+
+    public void insert(String password) {
+        int id = super.insert();
+        GigiPreparedStatement query = DatabaseConnection.getInstance().prepare("insert into `users` set `email`=?, `password`=?, " + "`fname`=?, `mname`=?, `lname`=?, " + "`suffix`=?, `dob`=?, `language`=?, id=?");
+        query.setString(1, email);
+        query.setString(2, PasswordHash.hash(password));
+        query.setString(3, name.fname);
+        query.setString(4, name.mname);
+        query.setString(5, name.lname);
+        query.setString(6, name.suffix);
+        query.setDate(7, new java.sql.Date(dob.getTime()));
+        query.setString(8, locale.toString());
+        query.setInt(9, id);
+        query.execute();
+    }
+
+    public void changePassword(String oldPass, String newPass) throws GigiApiException {
+        GigiPreparedStatement ps = DatabaseConnection.getInstance().prepare("SELECT `password` FROM users WHERE id=?");
+        ps.setInt(1, getId());
+        try (GigiResultSet rs = ps.executeQuery()) {
+            if ( !rs.next()) {
+                throw new GigiApiException("User not found... very bad.");
+            }
+            if (PasswordHash.verifyHash(oldPass, rs.getString(1)) == null) {
+                throw new GigiApiException("Old password does not match.");
+            }
+        }
+
+        PasswordStrengthChecker.assertStrongPassword(newPass, this);
+        ps = DatabaseConnection.getInstance().prepare("UPDATE users SET `password`=? WHERE id=?");
+        ps.setString(1, PasswordHash.hash(newPass));
+        ps.setInt(2, getId());
+        ps.executeUpdate();
+    }
+
+    public void setName(Name name) {
+        this.name = name;
+    }
+
+    public boolean canAssure() {
+        if ( !isOfAge(14)) { // PoJAM
+            return false;
+        }
+        if (getAssurancePoints() < 100) {
+            return false;
+        }
+
+        return hasPassedCATS();
+
+    }
+
+    public boolean hasPassedCATS() {
+        GigiPreparedStatement query = DatabaseConnection.getInstance().prepare("SELECT 1 FROM `cats_passed` where `user_id`=?");
+        query.setInt(1, getId());
+        try (GigiResultSet rs = query.executeQuery()) {
+            if (rs.next()) {
+                return true;
+            } else {
+                return false;
+            }
+        }
+    }
+
+    public int getAssurancePoints() {
+        GigiPreparedStatement query = DatabaseConnection.getInstance().prepare("SELECT sum(points) FROM `notary` where `to`=? AND `deleted` is NULL");
+        query.setInt(1, getId());
+
+        try (GigiResultSet rs = query.executeQuery()) {
+            int points = 0;
+
+            if (rs.next()) {
+                points = rs.getInt(1);
+            }
+
+            return points;
+        }
+    }
+
+    public int getExperiencePoints() {
+        GigiPreparedStatement query = DatabaseConnection.getInstance().prepare("SELECT count(*) FROM `notary` where `from`=? AND `deleted` is NULL");
+        query.setInt(1, getId());
+
+        try (GigiResultSet rs = query.executeQuery()) {
+            int points = 0;
+
+            if (rs.next()) {
+                points = rs.getInt(1) * 2;
+            }
+
+            return points;
+        }
+    }
+
+    /**
+     * Gets the maximum allowed points NOW. Note that an assurance needs to
+     * re-check PoJam as it has taken place in the past.
+     * 
+     * @return the maximal points @
+     */
+    public int getMaxAssurePoints() {
+        if ( !isOfAge(18)) {
+            return 10; // PoJAM
+        }
+
+        int exp = getExperiencePoints();
+        int points = 10;
+
+        if (exp >= 10) {
+            points += 5;
+        }
+        if (exp >= 20) {
+            points += 5;
+        }
+        if (exp >= 30) {
+            points += 5;
+        }
+        if (exp >= 40) {
+            points += 5;
+        }
+        if (exp >= 50) {
+            points += 5;
+        }
+
+        return points;
+    }
+
+    public boolean isOfAge(int desiredAge) {
+        Calendar c = Calendar.getInstance();
+        c.setTime(dob);
+        int year = c.get(Calendar.YEAR);
+        int month = c.get(Calendar.MONTH);
+        int day = c.get(Calendar.DAY_OF_MONTH);
+        c.set(year, month, day);
+        c.add(Calendar.YEAR, desiredAge);
+        return System.currentTimeMillis() >= c.getTime().getTime();
+    }
+
+    public boolean isValidName(String name) {
+        return getName().matches(name);
+    }
+
+    public void updateDefaultEmail(EmailAddress newMail) throws GigiApiException {
+        for (EmailAddress email : getEmails()) {
+            if (email.getAddress().equals(newMail.getAddress())) {
+                if ( !email.isVerified()) {
+                    throw new GigiApiException("Email not verified.");
+                }
+
+                GigiPreparedStatement ps = DatabaseConnection.getInstance().prepare("UPDATE users SET email=? WHERE id=?");
+                ps.setString(1, newMail.getAddress());
+                ps.setInt(2, getId());
+                ps.execute();
+
+                this.email = newMail.getAddress();
+                return;
+            }
+        }
+
+        throw new GigiApiException("Given address not an address of the user.");
+    }
+
+    public void deleteEmail(EmailAddress delMail) throws GigiApiException {
+        if (getEmail().equals(delMail.getAddress())) {
+            throw new GigiApiException("Can't delete user's default e-mail.");
+        }
+
+        for (EmailAddress email : getEmails()) {
+            if (email.getId() == delMail.getId()) {
+                GigiPreparedStatement ps = DatabaseConnection.getInstance().prepare("UPDATE emails SET deleted=? WHERE id=?");
+                ps.setDate(1, new Date(System.currentTimeMillis()));
+                ps.setInt(2, delMail.getId());
+                ps.execute();
+                return;
+            }
+        }
+        throw new GigiApiException("Email not one of user's email addresses.");
+    }
+
+    public synchronized Assurance[] getReceivedAssurances() {
+        if (receivedAssurances == null) {
+            GigiPreparedStatement query = DatabaseConnection.getInstance().prepare("SELECT * FROM notary WHERE `to`=? AND deleted IS NULL");
+            query.setInt(1, getId());
+
+            try (GigiResultSet res = query.executeQuery()) {
+                List<Assurance> assurances = new LinkedList<Assurance>();
+
+                while (res.next()) {
+                    assurances.add(new Assurance(res));
+                }
+
+                this.receivedAssurances = assurances.toArray(new Assurance[0]);
+            }
+        }
+
+        return receivedAssurances;
+    }
+
+    public synchronized Assurance[] getMadeAssurances() {
+        if (madeAssurances == null) {
+            GigiPreparedStatement query = DatabaseConnection.getInstance().prepare("SELECT * FROM notary WHERE `from`=? AND deleted is NULL");
+            query.setInt(1, getId());
+
+            try (GigiResultSet res = query.executeQuery()) {
+                List<Assurance> assurances = new LinkedList<Assurance>();
+
+                while (res.next()) {
+                    assurances.add(new Assurance(res));
+                }
+
+                this.madeAssurances = assurances.toArray(new Assurance[0]);
+            }
+        }
+
+        return madeAssurances;
+    }
+
+    public synchronized void invalidateMadeAssurances() {
+        madeAssurances = null;
+    }
+
+    public synchronized void invalidateReceivedAssurances() {
+        receivedAssurances = null;
+    }
+
+    public void updateUserData() throws GigiApiException {
+        synchronized (Notary.class) {
+            // FIXME: No assurance, not no points.
+            if (getAssurancePoints() != 0) {
+                throw new GigiApiException("No change after assurance allowed.");
+            }
+
+            GigiPreparedStatement update = DatabaseConnection.getInstance().prepare("UPDATE users SET fname=?, lname=?, mname=?, suffix=?, dob=? WHERE id=?");
+            update.setString(1, getFName());
+            update.setString(2, getLName());
+            update.setString(3, getMName());
+            update.setString(4, getSuffix());
+            update.setDate(5, getDoB());
+            update.setInt(6, getId());
+            update.executeUpdate();
+        }
+    }
+
+    public Locale getPreferredLocale() {
+        return locale;
+    }
+
+    public void setPreferredLocale(Locale locale) {
+        this.locale = locale;
+
+    }
+
+    public boolean wantsDirectoryListing() {
+        GigiPreparedStatement get = DatabaseConnection.getInstance().prepare("SELECT listme FROM users WHERE id=?");
+        get.setInt(1, getId());
+        try (GigiResultSet exec = get.executeQuery()) {
+            return exec.next() && exec.getBoolean("listme");
+        }
+    }
+
+    public String getContactInformation() {
+        GigiPreparedStatement get = DatabaseConnection.getInstance().prepare("SELECT contactinfo FROM users WHERE id=?");
+        get.setInt(1, getId());
+
+        try (GigiResultSet exec = get.executeQuery()) {
+            exec.next();
+            return exec.getString("contactinfo");
+        }
+    }
+
+    public void setDirectoryListing(boolean on) {
+        GigiPreparedStatement update = DatabaseConnection.getInstance().prepare("UPDATE users SET listme = ? WHERE id = ?");
+        update.setBoolean(1, on);
+        update.setInt(2, getId());
+        update.executeUpdate();
+    }
+
+    public void setContactInformation(String contactInfo) {
+        GigiPreparedStatement update = DatabaseConnection.getInstance().prepare("UPDATE users SET contactinfo = ? WHERE id = ?");
+        update.setString(1, contactInfo);
+        update.setInt(2, getId());
+        update.executeUpdate();
+    }
+
+    public boolean isInGroup(Group g) {
+        return groups.contains(g);
+    }
+
+    public Set<Group> getGroups() {
+        return Collections.unmodifiableSet(groups);
+    }
+
+    public void grantGroup(User granter, Group toGrant) {
+        groups.add(toGrant);
+        GigiPreparedStatement ps = DatabaseConnection.getInstance().prepare("INSERT INTO user_groups SET user=?, permission=?, grantedby=?");
+        ps.setInt(1, getId());
+        ps.setString(2, toGrant.getDatabaseName());
+        ps.setInt(3, granter.getId());
+        ps.execute();
+    }
+
+    public void revokeGroup(User revoker, Group toRevoke) {
+        groups.remove(toRevoke);
+        GigiPreparedStatement ps = DatabaseConnection.getInstance().prepare("UPDATE user_groups SET deleted=CURRENT_TIMESTAMP, revokedby=? WHERE deleted is NULL AND permission=? AND user=?");
+        ps.setInt(1, revoker.getId());
+        ps.setString(2, toRevoke.getDatabaseName());
+        ps.setInt(3, getId());
+        ps.execute();
+    }
+
+    public List<Organisation> getOrganisations() {
+        List<Organisation> orgas = new ArrayList<>();
+        GigiPreparedStatement query = DatabaseConnection.getInstance().prepare("SELECT orgid FROM org_admin WHERE `memid`=? AND deleted is NULL");
+        query.setInt(1, getId());
+        try (GigiResultSet res = query.executeQuery()) {
+            while (res.next()) {
+                orgas.add(Organisation.getById(res.getInt(1)));
+            }
+
+            return orgas;
+        }
+    }
+
+    public static synchronized User getById(int id) {
+        CertificateOwner co = CertificateOwner.getById(id);
+        if (co instanceof User) {
+            return (User) co;
+        }
+
+        return null;
+    }
+
+    public static User getByEmail(String mail) {
+        GigiPreparedStatement ps = DatabaseConnection.getInstance().prepare("SELECT users.id FROM users INNER JOIN certOwners ON certOwners.id = users.id WHERE email=? AND deleted IS NULL");
+        ps.setString(1, mail);
+        try (GigiResultSet rs = ps.executeQuery()) {
+            if ( !rs.next()) {
+                return null;
+            }
+
+            return User.getById(rs.getInt(1));
+        }
+    }
+
+    public static User[] findByEmail(String mail) {
+        LinkedList<User> results = new LinkedList<User>();
+        GigiPreparedStatement ps = DatabaseConnection.getInstance().prepare("SELECT users.id FROM users INNER JOIN certOwners ON certOwners.id = users.id WHERE users.email LIKE ? AND deleted IS NULL GROUP BY users.id ASC LIMIT 100");
+        ps.setString(1, mail);
+        try (GigiResultSet rs = ps.executeQuery()) {
+            while (rs.next()) {
+                results.add(User.getById(rs.getInt(1)));
+            }
+            return results.toArray(new User[results.size()]);
+        }
+    }
+
+    public boolean canIssue(CertificateProfile p) {
+        // FIXME: Use descriptive constants
+        switch (p.getCAId()) {
+        case 0:
+            return true;
+        case 1:
+            return getAssurancePoints() > 50;
+        case 2:
+            return getAssurancePoints() > 50 && isInGroup(Group.getByString("codesigning"));
+        case 3:
+        case 4:
+            return getOrganisations().size() > 0;
+        default:
+            return false;
+        }
+    }
+
+}
diff --git a/src/org/cacert/gigi/email/EmailProvider.java b/src/org/cacert/gigi/email/EmailProvider.java
new file mode 100644 (file)
index 0000000..6494bf8
--- /dev/null
@@ -0,0 +1,183 @@
+package org.cacert.gigi.email;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.OutputStreamWriter;
+import java.io.PrintWriter;
+import java.net.Socket;
+import java.security.GeneralSecurityException;
+import java.security.Key;
+import java.security.PrivateKey;
+import java.security.cert.Certificate;
+import java.security.cert.X509Certificate;
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.Properties;
+import java.util.regex.Pattern;
+
+import javax.naming.NamingException;
+import javax.net.ssl.SSLSocketFactory;
+
+import org.cacert.gigi.crypto.SMIME;
+import org.cacert.gigi.database.DatabaseConnection;
+import org.cacert.gigi.database.GigiPreparedStatement;
+import org.cacert.gigi.util.DNSUtil;
+
+public abstract class EmailProvider {
+
+    public abstract void sendmail(String to, String subject, String message, String from, String replyto, String toname, String fromname, String errorsto, boolean extra) throws IOException;
+
+    private static EmailProvider instance;
+
+    private X509Certificate c;
+
+    private PrivateKey k;
+
+    protected final void init(Certificate c, Key k) {
+        this.c = (X509Certificate) c;
+        this.k = (PrivateKey) k;
+    }
+
+    protected final void sendSigned(String contents, PrintWriter output) throws IOException, GeneralSecurityException {
+        SMIME.smime(contents, k, c, output);
+    }
+
+    public static EmailProvider getInstance() {
+        return instance;
+    }
+
+    protected static void setInstance(EmailProvider instance) {
+        EmailProvider.instance = instance;
+    }
+
+    public static void initSystem(Properties conf, Certificate cert, Key pk) {
+        try {
+            Class<?> c = Class.forName(conf.getProperty("emailProvider"));
+            EmailProvider ep = (EmailProvider) c.getDeclaredConstructor(Properties.class).newInstance(conf);
+            ep.init(cert, pk);
+            instance = ep;
+        } catch (ReflectiveOperationException e) {
+            e.printStackTrace();
+        }
+    }
+
+    public static final String OK = "OK";
+
+    public static final String FAIL = "FAIL";
+
+    public static final Pattern MAIL = Pattern.compile("^([a-zA-Z0-9])+([a-zA-Z0-9\\+\\._-])*@([a-zA-Z0-9_-])+([a-zA-Z0-9\\._-]+)+$");
+
+    public String checkEmailServer(int forUid, String address) throws IOException {
+        if (MAIL.matcher(address).matches()) {
+            String[] parts = address.split("@", 2);
+            String domain = parts[1];
+
+            String[] mxhosts;
+            try {
+                mxhosts = DNSUtil.getMXEntries(domain);
+            } catch (NamingException e1) {
+                return "MX lookup for your hostname failed.";
+            }
+            sortMX(mxhosts);
+
+            for (String host : mxhosts) {
+                host = host.split(" ", 2)[1];
+                if (host.endsWith(".")) {
+                    host = host.substring(0, host.length() - 1);
+                } else {
+                    return "Strange MX records.";
+                }
+                try (Socket s = new Socket(host, 25); BufferedReader br0 = new BufferedReader(new InputStreamReader(s.getInputStream(), "UTF-8"));//
+                        PrintWriter pw0 = new PrintWriter(new OutputStreamWriter(s.getOutputStream(), "UTF-8"))) {
+                    BufferedReader br = br0;
+                    PrintWriter pw = pw0;
+                    String line;
+                    if ( !Sendmail.readSMTPResponse(br, 220)) {
+                        continue;
+                    }
+
+                    pw.print("EHLO www.cacert.org\r\n");
+                    pw.flush();
+                    boolean starttls = false;
+                    do {
+                        line = br.readLine();
+                        if (line == null) {
+                            break;
+                        }
+                        starttls |= line.substring(4).equals("STARTTLS");
+                    } while (line.startsWith("250-"));
+                    if (line == null || !line.startsWith("250 ")) {
+                        continue;
+                    }
+
+                    if (starttls) {
+                        pw.print("STARTTLS\r\n");
+                        pw.flush();
+                        if ( !Sendmail.readSMTPResponse(br, 220)) {
+                            continue;
+                        }
+                        Socket s1 = ((SSLSocketFactory) SSLSocketFactory.getDefault()).createSocket(s, host, 25, true);
+                        br = new BufferedReader(new InputStreamReader(s1.getInputStream(), "UTF-8"));
+                        pw = new PrintWriter(new OutputStreamWriter(s1.getOutputStream(), "UTF-8"));
+                        pw.print("EHLO www.cacert.org\r\n");
+                        pw.flush();
+                        if ( !Sendmail.readSMTPResponse(br, 250)) {
+                            continue;
+                        }
+                    }
+
+                    pw.print("MAIL FROM: <returns@cacert.org>\r\n");
+                    pw.flush();
+
+                    if ( !Sendmail.readSMTPResponse(br, 250)) {
+                        continue;
+                    }
+                    pw.print("RCPT TO: <" + address + ">\r\n");
+                    pw.flush();
+
+                    if ( !Sendmail.readSMTPResponse(br, 250)) {
+                        continue;
+                    }
+                    pw.print("QUIT\r\n");
+                    pw.flush();
+                    if ( !Sendmail.readSMTPResponse(br, 221)) {
+                        continue;
+                    }
+
+                    GigiPreparedStatement statmt = DatabaseConnection.getInstance().prepare("insert into `emailPinglog` set `when`=NOW(), `email`=?, `result`=?, `uid`=?");
+                    statmt.setString(1, address);
+                    statmt.setString(2, line);
+                    statmt.setInt(3, forUid);
+                    statmt.execute();
+
+                    if (line == null || !line.startsWith("250")) {
+                        return line;
+                    } else {
+                        return OK;
+                    }
+                }
+
+            }
+        }
+        GigiPreparedStatement statmt = DatabaseConnection.getInstance().prepare("insert into `emailPinglog` set `when`=NOW(), `email`=?, `result`=?, `uid`=?");
+        statmt.setString(1, address);
+        statmt.setString(2, "Failed to make a connection to the mail server");
+        statmt.setInt(3, forUid);
+        statmt.execute();
+        return FAIL;
+    }
+
+    private static void sortMX(String[] mxhosts) {
+        Arrays.sort(mxhosts, new Comparator<String>() {
+
+            @Override
+            public int compare(String o1, String o2) {
+                int i1 = Integer.parseInt(o1.split(" ")[0]);
+                int i2 = Integer.parseInt(o2.split(" ")[0]);
+                return Integer.compare(i1, i2);
+            }
+        });
+    }
+
+}
diff --git a/src/org/cacert/gigi/email/MailProbe.java b/src/org/cacert/gigi/email/MailProbe.java
new file mode 100644 (file)
index 0000000..8a0eaae
--- /dev/null
@@ -0,0 +1,28 @@
+package org.cacert.gigi.email;
+
+import java.io.IOException;
+
+import org.cacert.gigi.localisation.Language;
+import org.cacert.gigi.util.ServerConstants;
+
+public class MailProbe {
+
+    public static void sendMailProbe(Language l, String type, int id, String hash, String address) throws IOException {
+        StringBuffer body = new StringBuffer();
+        body.append(l.getTranslation("Thanks for signing up with CAcert.org, below is the link you need to open to verify your account. Once your account is verified you will be able to start issuing certificates till your hearts' content!"));
+        body.append("\n\nhttps://");
+        body.append(ServerConstants.getWwwHostNamePortSecure());
+        body.append("/verify?type=");
+        body.append(type);
+        body.append("&id=");
+        body.append(id);
+        body.append("&hash=");
+        body.append(hash);
+        body.append("\n\n");
+        body.append(l.getTranslation("Best regards"));
+        body.append("\n");
+        body.append(l.getTranslation("CAcert.org Support!"));
+        EmailProvider.getInstance().sendmail(address, "[CAcert.org] " + l.getTranslation("Mail Probe"), body.toString(), "support@cacert.org", null, null, null, null, false);
+    }
+
+}
diff --git a/src/org/cacert/gigi/email/Sendmail.java b/src/org/cacert/gigi/email/Sendmail.java
new file mode 100644 (file)
index 0000000..285eee6
--- /dev/null
@@ -0,0 +1,109 @@
+package org.cacert.gigi.email;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.OutputStreamWriter;
+import java.io.PrintWriter;
+import java.net.Socket;
+import java.security.GeneralSecurityException;
+import java.text.SimpleDateFormat;
+import java.util.Base64;
+import java.util.Date;
+import java.util.Locale;
+import java.util.Properties;
+import java.util.regex.Pattern;
+
+import org.cacert.gigi.util.PEM;
+import org.cacert.gigi.util.ServerConstants;
+
+public class Sendmail extends EmailProvider {
+
+    protected Sendmail(Properties props) {}
+
+    private static final Pattern NON_ASCII = Pattern.compile("[^a-zA-Z0-9 .-\\[\\]!_@]");
+
+    @Override
+    public void sendmail(String to, String subject, String message, String from, String replyto, String toname, String fromname, String errorsto, boolean extra) throws IOException {
+
+        String[] bits = from.split(",");
+
+        try (Socket smtp = new Socket("localhost", 25); PrintWriter out = new PrintWriter(new OutputStreamWriter(smtp.getOutputStream(), "UTF-8")); BufferedReader in = new BufferedReader(new InputStreamReader(smtp.getInputStream(), "UTF-8"));) {
+            readSMTPResponse(in, 220);
+            out.print("HELO www.cacert.org\r\n");
+            out.flush();
+            readSMTPResponse(in, 250);
+            out.print("MAIL FROM:<returns@cacert.org>\r\n");
+            out.flush();
+            readSMTPResponse(in, 250);
+            bits = to.split(",");
+            for (String user : bits) {
+                out.print("RCPT TO:<" + user.trim() + ">\r\n");
+                out.flush();
+                readSMTPResponse(in, 250);
+            }
+            out.print("DATA\r\n");
+            out.flush();
+            readSMTPResponse(in, 250);
+            out.print("X-Mailer: CAcert.org Website\r\n");
+            // if (array_key_exists("REMOTE_ADDR", $_SERVER)) {
+            // out.print("X-OriginatingIP: ".$_SERVER["REMOTE_ADDR"]."\r\n");
+            // }
+            // TODO
+            SimpleDateFormat emailDate = new SimpleDateFormat("E, d MMM yyyy HH:mm:ss ZZZZ (z)", Locale.ENGLISH);
+            out.print("Date: " + emailDate.format(new Date(System.currentTimeMillis())) + "\r\n");
+            if (errorsto != null) {
+                out.print("Sender: " + errorsto + "\r\n");
+                out.print("Errors-To: " + errorsto + "\r\n");
+            }
+            if (replyto != null) {
+                out.print("Reply-To: " + replyto + "\r\n");
+            } else {
+                out.print("Reply-To: " + from + "\r\n");
+            }
+            out.print("From: support@" + ServerConstants.getWwwHostName().replaceAll("^www.", "") + "\r\n");
+            out.print("To: " + to + "\r\n");
+            if (NON_ASCII.matcher(subject).matches()) {
+
+                out.print("Subject: =?utf-8?B?" + Base64.getEncoder().encodeToString(subject.getBytes("UTF-8")) + "?=\r\n");
+            } else {
+                out.print("Subject: " + subject + "\r\n");
+            }
+            StringBuffer headers = new StringBuffer();
+            headers.append("Content-Type: text/plain; charset=\"utf-8\"\r\n");
+            headers.append("Content-Transfer-Encoding: base64\r\n");
+            // out.print(chunk_split(base64_encode(recode("html..utf-8",
+            // $message)))."\r\n.\r\n");
+            headers.append("\r\n");
+            headers.append(PEM.formatBase64(message.getBytes("UTF-8")));
+            headers.append("\r\n");
+
+            try {
+                sendSigned(headers.toString(), out);
+                out.print("\r\n.\r\n");
+                out.flush();
+            } catch (GeneralSecurityException e) {
+                e.printStackTrace();
+                return;
+            }
+            readSMTPResponse(in, 250);
+            out.print("QUIT\n");
+            out.flush();
+            readSMTPResponse(in, 221);
+        }
+    }
+
+    public static boolean readSMTPResponse(BufferedReader in, int code) throws IOException {
+        String line;
+        while ((line = in.readLine()) != null) {
+            if (line.startsWith(code + " ")) {
+                return true;
+            } else if ( !line.startsWith(code + "-")) {
+                return false;
+            }
+        }
+        return false;
+
+    }
+
+}
diff --git a/src/org/cacert/gigi/localisation/Language.java b/src/org/cacert/gigi/localisation/Language.java
new file mode 100644 (file)
index 0000000..9597112
--- /dev/null
@@ -0,0 +1,138 @@
+package org.cacert.gigi.localisation;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.Locale;
+
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.NodeList;
+import org.xml.sax.SAXException;
+
+public class Language {
+
+    public static final String SESSION_ATTRIB_NAME = "lang";
+
+    private static Locale[] supportedLocales;
+
+    static {
+        LinkedList<Locale> supported = new LinkedList<>();
+        File locales = new File("locale");
+        File[] listFiles = locales.listFiles();
+        if (listFiles != null) {
+            for (File f : listFiles) {
+                if ( !f.getName().endsWith(".xml")) {
+                    continue;
+                }
+                String language = f.getName().split("\\.", 2)[0];
+                supported.add(getLocaleFromString(language));
+            }
+        }
+        Collections.sort(supported, new Comparator<Locale>() {
+
+            @Override
+            public int compare(Locale o1, Locale o2) {
+                return o1.toString().compareTo(o2.toString());
+            }
+
+        });
+        supportedLocales = supported.toArray(new Locale[supported.size()]);
+    }
+
+    public static Locale getLocaleFromString(String language) {
+        if (language.contains("_")) {
+            String[] parts = language.split("_", 2);
+            return new Locale(parts[0], parts[1]);
+
+        } else {
+            return new Locale(language);
+        }
+    }
+
+    public static Locale[] getSupportedLocales() {
+        return supportedLocales;
+    }
+
+    private static HashMap<String, Language> langs = new HashMap<String, Language>();
+
+    private HashMap<String, String> translations = new HashMap<String, String>();
+
+    private Locale locale;
+
+    private static Locale project(Locale locale) {
+        if (locale == null) {
+            return Locale.getDefault();
+        }
+        File file = new File("locale", locale.toString() + ".xml");
+        if ( !file.exists()) {
+            return new Locale(locale.getLanguage());
+        }
+        return locale;
+    }
+
+    protected Language(Locale locale) throws ParserConfigurationException, IOException, SAXException {
+        File file = new File("locale", locale.toString() + ".xml");
+        this.locale = locale;
+        DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
+        DocumentBuilder db = dbf.newDocumentBuilder();
+        Document d = db.parse(new FileInputStream(file));
+        NodeList nl = d.getDocumentElement().getChildNodes();
+        for (int i = 0; i < nl.getLength(); i++) {
+            if ( !(nl.item(i) instanceof Element)) {
+                continue;
+            }
+            Element e = (Element) nl.item(i);
+            Element id = (Element) e.getElementsByTagName("id").item(0);
+            Element msg = (Element) e.getElementsByTagName("msg").item(0);
+            translations.put(id.getTextContent(), msg.getTextContent());
+        }
+        System.out.println(translations.size() + " strings loaded.");
+    }
+
+    public String getTranslation(String text) {
+        String string = translations.get(text);
+        if (string == null || string.equals("")) {
+            return text;
+        }
+        return string;
+    }
+
+    public static Language getInstance(Locale locale) {
+        locale = project(locale);
+        File file = new File("locale", locale.toString() + ".xml");
+        if ( !file.exists()) {
+            return null;
+        }
+        synchronized (Language.class) {
+            Language lang = langs.get(locale.toString());
+            if (lang != null) {
+                return lang;
+            }
+            try {
+                lang = new Language(locale);
+                langs.put(locale.toString(), lang);
+            } catch (ParserConfigurationException e) {
+                e.printStackTrace();
+            } catch (IOException e) {
+                e.printStackTrace();
+            } catch (SAXException e) {
+                e.printStackTrace();
+            }
+            return lang;
+        }
+    }
+
+    public Locale getLocale() {
+        return locale;
+    }
+
+}
diff --git a/src/org/cacert/gigi/natives/SetUID.java b/src/org/cacert/gigi/natives/SetUID.java
new file mode 100644 (file)
index 0000000..a4a5d85
--- /dev/null
@@ -0,0 +1,37 @@
+package org.cacert.gigi.natives;
+
+import java.io.File;
+
+/**
+ * Native to use privileged ports on unix-like hosts.
+ * 
+ * @author janis
+ */
+public class SetUID {
+
+    static {
+        System.load(new File("natives/libsetuid.so").getAbsolutePath());
+    }
+
+    public native Status setUid(int uid, int gid);
+
+    public static class Status {
+
+        private boolean success;
+
+        private String message;
+
+        public Status(boolean success, String message) {
+            this.success = success;
+            this.message = message;
+        }
+
+        public boolean getSuccess() {
+            return success;
+        }
+
+        public String getMessage() {
+            return message;
+        }
+    }
+}
diff --git a/src/org/cacert/gigi/output/AssurancesDisplay.java b/src/org/cacert/gigi/output/AssurancesDisplay.java
new file mode 100644 (file)
index 0000000..639197c
--- /dev/null
@@ -0,0 +1,67 @@
+package org.cacert.gigi.output;
+
+import java.io.PrintWriter;
+import java.util.Map;
+
+import org.cacert.gigi.dbObjects.Assurance;
+import org.cacert.gigi.localisation.Language;
+import org.cacert.gigi.output.template.IterableDataset;
+import org.cacert.gigi.output.template.Outputable;
+import org.cacert.gigi.output.template.Template;
+
+public class AssurancesDisplay implements Outputable {
+
+    private static Template template;
+
+    private boolean assurer;
+
+    public String assuranceArray;
+
+    static {
+        template = new Template(AssurancesDisplay.class.getResource("AssurancesDisplay.templ"));
+    }
+
+    public AssurancesDisplay(String assuranceArray, boolean assurer) {
+        this.assuranceArray = assuranceArray;
+        this.assurer = assurer;
+    }
+
+    @Override
+    public void output(PrintWriter out, Language l, Map<String, Object> vars) {
+        final Assurance[] assurances = (Assurance[]) vars.get(assuranceArray);
+        if (assurer) {
+            vars.put("verb", l.getTranslation("To"));
+        } else {
+            vars.put("verb", l.getTranslation("From"));
+        }
+
+        IterableDataset assuranceGroup = new IterableDataset() {
+
+            private int i = 0;
+
+            @Override
+            public boolean next(Language l, Map<String, Object> vars) {
+                if (i >= assurances.length) {
+                    return false;
+                } else {
+                    Assurance assurance = assurances[i];
+                    vars.put("id", assurance.getId());
+                    vars.put("method", assurance.getMethod());
+                    if (assurer) {
+                        vars.put("verbVal", assurance.getTo().getName());
+                    } else {
+                        vars.put("verbVal", assurance.getFrom().getName());
+                    }
+                    vars.put("date", assurance.getDate());
+                    vars.put("location", assurance.getLocation());
+                    vars.put("points", assurance.getPoints());
+                    i++;
+                    return true;
+                }
+            }
+        };
+        vars.put("assurances", assuranceGroup);
+        template.output(out, l, vars);
+    }
+
+}
diff --git a/src/org/cacert/gigi/output/AssurancesDisplay.templ b/src/org/cacert/gigi/output/AssurancesDisplay.templ
new file mode 100644 (file)
index 0000000..18d9bc2
--- /dev/null
@@ -0,0 +1,23 @@
+<table class="wrapper dataTable">
+<tr>
+<th colspan="7"><?=_Assurances?></th>
+</tr>
+<tr>
+<td><?=_Id?></td>
+<td><?=_Date?></td>
+<td><?=$verb?></td>
+<td><?=_Points?></td>
+<td><?=_Location?></td>
+<td><?=_Method?></td>
+</tr>
+<? foreach($assurances) {?>
+<tr>
+<td><?=$id?></td>
+<td><?=$date?></td>
+<td><?=$verbVal?></td>
+<td><?=$points?></td>
+<td><?=$location?></td>
+<td><?=$method?></td>
+</tr>
+<? } ?>
+</table>
\ No newline at end of file
diff --git a/src/org/cacert/gigi/output/CertificateIterable.java b/src/org/cacert/gigi/output/CertificateIterable.java
new file mode 100644 (file)
index 0000000..e4ba804
--- /dev/null
@@ -0,0 +1,52 @@
+package org.cacert.gigi.output;
+
+import java.io.IOException;
+import java.security.GeneralSecurityException;
+import java.security.cert.X509Certificate;
+import java.util.Map;
+
+import org.cacert.gigi.dbObjects.Certificate;
+import org.cacert.gigi.dbObjects.Certificate.CertificateStatus;
+import org.cacert.gigi.localisation.Language;
+import org.cacert.gigi.output.template.IterableDataset;
+
+public class CertificateIterable implements IterableDataset {
+
+    private Certificate[] certificates;
+
+    public CertificateIterable(Certificate[] certificates) {
+        this.certificates = certificates;
+    }
+
+    private int i = 0;
+
+    @Override
+    public boolean next(Language l, Map<String, Object> vars) {
+        if (i >= certificates.length) {
+            return false;
+        }
+        Certificate c = certificates[i++];
+        vars.put("state", l.getTranslation(c.getStatus().toString().toLowerCase()));
+        vars.put("CN", c.getDistinguishedName());
+        vars.put("serial", c.getSerial());
+        vars.put("digest", c.getMessageDigest());
+        vars.put("profile", c.getProfile().getVisibleName());
+        try {
+            CertificateStatus st = c.getStatus();
+            if (st == CertificateStatus.ISSUED || st == CertificateStatus.REVOKED) {
+                X509Certificate cert = c.cert();
+                vars.put("issued", DateSelector.getDateFormat().format(cert.getNotBefore()));
+                vars.put("expire", DateSelector.getDateFormat().format(cert.getNotAfter()));
+            } else {
+                vars.put("issued", l.getTranslation("N/A"));
+                vars.put("expire", l.getTranslation("N/A"));
+            }
+        } catch (IOException e) {
+            e.printStackTrace();
+        } catch (GeneralSecurityException e) {
+            e.printStackTrace();
+        }
+        vars.put("revoked", "TODO");// TODO output date
+        return true;
+    }
+}
diff --git a/src/org/cacert/gigi/output/CertificateTable.templ b/src/org/cacert/gigi/output/CertificateTable.templ
new file mode 100644 (file)
index 0000000..1999897
--- /dev/null
@@ -0,0 +1,32 @@
+<form method="post">
+<table class="wrapper dataTable">
+<thead><tr>
+<th><?=_Renew/Revoke/Delete?></th>
+<th><?=_Status?></th>
+<th><?=_Email Address?></th>
+<th><?=_SerialNumber?></th>
+<th><?=_Digest?></th>
+<th><?=_Profile?></th>
+<th><?=_Issued?></th>
+<th><?=_Revoked?></th>
+<th><?=_Expires?></th>
+<th><?=_Login?></th>
+</tr></thead>
+<tbody>
+<? foreach($certs) {?>
+<tr>
+       <td><input type='checkbox' name='certs[]' value='<?=$serial?>'></td>
+       <td><?=$state?></td>
+       <td><?=$CN?></td>
+       <td><a href='/account/certs/<?=$serial?>'><?=$serial?></a></td>
+       <td><?=$digest?></td>
+       <td><?=$profile?></td>
+       <td><?=$issued?></td>
+       <td><?=$revoked?></td>
+       <td><?=$expire?></td>
+       <td>a</td>
+</tr>
+<? } ?>
+</tbody>
+</table>
+</form>
diff --git a/src/org/cacert/gigi/output/CertificateValiditySelector.java b/src/org/cacert/gigi/output/CertificateValiditySelector.java
new file mode 100644 (file)
index 0000000..b8601de
--- /dev/null
@@ -0,0 +1,136 @@
+package org.cacert.gigi.output;
+
+import java.io.PrintWriter;
+import java.sql.Date;
+import java.text.ParseException;
+import java.util.Map;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.cacert.gigi.GigiApiException;
+import org.cacert.gigi.localisation.Language;
+import org.cacert.gigi.output.template.Outputable;
+import org.cacert.gigi.util.HTMLEncoder;
+
+public class CertificateValiditySelector implements Outputable {
+
+    private static final long DAY = 1000 * 60 * 60 * 24;
+
+    private Date from;
+
+    private String val = "2y";
+
+    public CertificateValiditySelector() {
+
+    }
+
+    @Override
+    public void output(PrintWriter out, Language l, Map<String, Object> vars) {
+        out.print("<select name='validFrom'><option value='now'");
+        if (from == null) {
+            out.print(" selected='selected'");
+        }
+        out.print(">");
+        out.print(l.getTranslation("now"));
+        out.print("</option>");
+        long base = getCurrentDayBase();
+        for (int i = 0; i < 14; i++) {
+            long date = base + DAY * i;
+            String d = DateSelector.getDateFormat().format(new Date(date));
+            out.print("<option value='");
+            out.print(d);
+            out.print("'");
+            if (from != null && from.getTime() == date) {
+                out.print(" selected='selected'");
+            }
+            out.print(">");
+            out.print(d);
+            out.println("</option>");
+        }
+        out.println("</select>");
+
+        out.print("<input type='text' name='validity' value='");
+        out.print(HTMLEncoder.encodeHTML(val));
+        out.println("'>");
+
+        if (from == null) {
+            return;
+        }
+
+    }
+
+    private long getCurrentDayBase() {
+        long base = System.currentTimeMillis();
+        base -= base % DAY;
+        base += DAY;
+        return base;
+    }
+
+    public void update(HttpServletRequest r) throws GigiApiException {
+        String from = r.getParameter("validFrom");
+
+        GigiApiException gae = new GigiApiException();
+        try {
+            saveStartDate(from);
+        } catch (GigiApiException e) {
+            gae.mergeInto(e);
+        }
+        try {
+            String validity = r.getParameter("validity");
+            if (validity != null) {
+                checkValidityLength(validity);
+                val = validity;
+            }
+        } catch (GigiApiException e) {
+            gae.mergeInto(e);
+        }
+        if ( !gae.isEmpty()) {
+            throw gae;
+        }
+
+    }
+
+    public static void checkValidityLength(String newval) throws GigiApiException {
+        if (newval.endsWith("y") || newval.endsWith("m")) {
+            if (newval.length() > 10) { // for database
+                throw new GigiApiException("The validity interval entered is invalid.");
+            }
+            String num = newval.substring(0, newval.length() - 1);
+            try {
+                int len = Integer.parseInt(num);
+                if (len <= 0) {
+                    throw new GigiApiException("The validity interval entered is invalid.");
+                }
+            } catch (NumberFormatException e) {
+                throw new GigiApiException("The validity interval entered is invalid.");
+            }
+        } else {
+            try {
+                DateSelector.getDateFormat().parse(newval);
+            } catch (ParseException e) {
+                throw new GigiApiException("The validity interval entered is invalid.");
+            }
+        }
+    }
+
+    private void saveStartDate(String from) throws GigiApiException {
+        if (from == null || "now".equals(from)) {
+            this.from = null;
+        } else {
+            try {
+                this.from = new Date(DateSelector.getDateFormat().parse(from).getTime());
+            } catch (ParseException e) {
+                throw new GigiApiException("The validity start date entered is invalid.");
+            }
+        }
+    }
+
+    public Date getFrom() {
+        return from;
+    }
+
+    public String getTo() {
+        return val;
+    }
+
+}
diff --git a/src/org/cacert/gigi/output/ClientCSRGenerate.java b/src/org/cacert/gigi/output/ClientCSRGenerate.java
new file mode 100644 (file)
index 0000000..6146066
--- /dev/null
@@ -0,0 +1,35 @@
+package org.cacert.gigi.output;
+
+import java.io.IOException;
+import java.util.HashMap;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.cacert.gigi.output.template.Template;
+import org.cacert.gigi.pages.Page;
+import org.cacert.gigi.util.ServerConstants;
+
+public class ClientCSRGenerate {
+
+    private static Template normal;
+
+    private static Template IE;
+    static {
+        normal = new Template(ClientCSRGenerate.class.getResource("ClientCSRGenerate.templ"));
+        IE = new Template(ClientCSRGenerate.class.getResource("ClientCSRGenerateIE.templ"));
+    }
+
+    public static void output(HttpServletRequest req, HttpServletResponse resp) {
+        HashMap<String, Object> vars = new HashMap<String, Object>();
+        vars.put("minsize", "2048");
+        vars.put("normalhost", "https://" + ServerConstants.getWwwHostNamePortSecure());
+        vars.put("securehost", "https://" + ServerConstants.getSecureHostNamePort());
+        vars.put("statichost", "https://" + ServerConstants.getStaticHostNamePortSecure());
+        try {
+            normal.output(resp.getWriter(), Page.getLanguage(req), vars);
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+    }
+}
diff --git a/src/org/cacert/gigi/output/ClientCSRGenerate.templ b/src/org/cacert/gigi/output/ClientCSRGenerate.templ
new file mode 100644 (file)
index 0000000..a35e3f8
--- /dev/null
@@ -0,0 +1,9 @@
+
+       <p>
+               <form method="post" action="account.php">
+                       <input type="hidden" name="keytype" value="NS">
+                       <?=_Keysize:?> <keygen name="SPKAC" challenge="<?=$spkac_hash?>">
+
+                       <input type="submit" name="submit" value="<?=_Create Certificate Request?>">
+               </form>
+       </p>
diff --git a/src/org/cacert/gigi/output/ClientCSRGenerateIE.templ b/src/org/cacert/gigi/output/ClientCSRGenerateIE.templ
new file mode 100644 (file)
index 0000000..75765e1
--- /dev/null
@@ -0,0 +1,70 @@
+       <noscript>
+               <p><?=_You have to enable JavaScript to generate certificates in the browser.?></p>
+               <p><?=_If you don't want to do that for any reason, you can use manually created certificate requests instead.?></p>
+       </noscript>
+
+       <div id="noActiveX" style="color:red">
+               <p><?=_Could not initialize ActiveX object required for certificate generation.?></p>
+               <p><?=_You have to enable ActiveX for this to work. On Windows Vista, Windows 7 and later versions you have to add this website to the list of trusted sites in the internet settings.?></p>
+               <p><?=_Go to "Extras -> Internet Options -> Security -> Trusted Websites",
+ click on "Custom Level", set "ActiveX control elements that are not marked as safe initialized on start
+ in scripts" to "Confirm" and click "OK". Now click "Sites", add "$normalhost" and "$securehost" to your list of trusted
+ sites and make the changes come into effect by clicking "Close" and "OK".?></p>
+       </div>
+
+       <form method="post" style="display:none" action="account.php"
+                       id="CertReqForm">
+               <input type="hidden" id="CSR" name="CSR" />
+               <input type="hidden" name="keytype" value="MS" />
+
+               <p><?=_Security level?>:
+                       <select id="SecurityLevel">
+                               <option value="high" selected="selected"><?=_High?></option>
+                               <option value="medium"><?=_Medium?></option>
+                               <option value="custom"><?=_Custom?>&hellip;</option>
+                       </select>
+               </p>
+
+               <fieldset id="customSettings" style="display:none">
+                       <legend><?=_Custom Parameters?></legend>
+
+                       <p><?=_Cryptography Provider?>:
+                               <select id="CspProvider"></select>
+                       </p>
+                       <p><?=_Algorithm?>: <select id="algorithm"></select></p>
+                       <p><?=_Keysize?>:
+                               <input id="keySize" type="number" />
+                               <?=_Minimum Size?>: <span id="keySizeMin"></span>,
+                               <?=_Maximum Size?>: <span id="keySizeMax"></span>,
+                               <?=_Step?>: <span id="keySizeStep"></span></p>
+                       <p style="color:red"><?=_Please note that RSA key sizes smaller than $minsize bit will not be accepted by CAcert?>
+                       </p>
+               </fieldset>
+
+               <p><input type="submit" id="GenReq" name="GenReq" value="<?=_Create Certificate?>" /></p>
+               <p id="generatingKeyNotice" style="display:none">
+                       <?=_Generating your key. Please wait?>&hellip;</p>
+       </form>
+
+       <!-- Error messages used in the JavaScript. Defined here so they can be
+       translated without passing the JavaScript code through PHP -->
+       <p id="createRequestErrorChooseAlgorithm" style="display:none">
+               <?=_Could not generate certificate request. Probably you need to choose a different algorithm.?>
+       </p>
+       <p id="createRequestErrorConfirmDialogue" style="display:none">
+               <?=_Could not generate certificate request. Please confirm the dialogue if you are asked if you want to generate the key.?>
+       </p>
+       <p id="createRequestErrorConnectDevice" style="display:none">
+               <?=_Could not generate certificate request. Please make sure the cryptography device (e.g. the smartcard) is connected.?>
+       </p>
+       <p id="createRequestError" style="display:none">
+               <?=_Could not generate certificate request.?>
+       </p>
+       <p id="invalidKeySizeError" style="display:none">
+               <?=_You have specified an invalid key size?>
+       </p>
+       <p id="unsupportedPlatformError" style="display:none">
+               <?=_Could not initialize the cryptographic module for your platform. Currently we support Microsoft Windows XP, Vista and 7. If you're using one of these platforms and see this error message anyway you might have to enable ActiveX as described in the red explanation text and accept loading of the module.?>
+       </p>
+
+       <script type="text/javascript" src="<?=$statichost?>/keygenIE.js"></script>
diff --git a/src/org/cacert/gigi/output/DateSelector.java b/src/org/cacert/gigi/output/DateSelector.java
new file mode 100644 (file)
index 0000000..aadac04
--- /dev/null
@@ -0,0 +1,145 @@
+package org.cacert.gigi.output;
+
+import java.io.PrintWriter;
+import java.text.SimpleDateFormat;
+import java.util.Arrays;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.GregorianCalendar;
+import java.util.Map;
+import java.util.TimeZone;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.cacert.gigi.GigiApiException;
+import org.cacert.gigi.localisation.Language;
+import org.cacert.gigi.output.template.Outputable;
+
+public class DateSelector implements Outputable {
+
+    private String[] names;
+
+    public DateSelector(String day, String month, String year, Date date) {
+        this(day, month, year);
+        Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("UTF"));
+        cal.setTime(date);
+        this.day = cal.get(Calendar.DAY_OF_MONTH);
+        this.month = cal.get(Calendar.MONTH);
+        this.year = cal.get(Calendar.YEAR);
+    }
+
+    public DateSelector(String day, String month, String year) {
+        this.names = new String[] {
+                day, month, year
+        };
+    }
+
+    private int day;
+
+    private int month;
+
+    private int year;
+
+    private static ThreadLocal<SimpleDateFormat> fmt = new ThreadLocal<>();
+
+    @Override
+    public void output(PrintWriter out, Language l, Map<String, Object> vars) {
+        out.print("<nobr>");
+        outputYear(out);
+        outputMonth(out, l);
+        outputDay(out);
+        out.print("</nobr>");
+    }
+
+    private void outputDay(PrintWriter out) {
+        out.print("<select name=\"");
+        out.print(names[0]);
+        out.println("\">");
+        for (int i = 1; i <= 31; i++) {
+            out.print("<option");
+            if (i == day) {
+                out.print(" selected=\"selected\"");
+            }
+            out.println(">" + i + "</option>");
+        }
+        out.println("</select>");
+    }
+
+    private void outputMonth(PrintWriter out, Language l) {
+        SimpleDateFormat sdf = new SimpleDateFormat("MMMM", l.getLocale());
+        out.print("<select name=\"");
+        out.print(names[1]);
+        out.println("\">");
+        Calendar c = sdf.getCalendar();
+        for (int i = 1; i <= 12; i++) {
+            c.set(Calendar.MONTH, i - 1);
+            out.print("<option value='" + i + "'");
+            if (i == month) {
+                out.print(" selected=\"selected\"");
+            }
+            out.println(">" + sdf.format(c.getTime()) + " (" + i + ")</option>");
+        }
+        out.println("</select>");
+    }
+
+    private void outputYear(PrintWriter out) {
+        out.print("<input type=\"text\" name=\"");
+        out.print(names[2]);
+        out.print("\" value=\"");
+        if (year != 0) {
+            out.print(year);
+        }
+        out.print("\" size=\"4\" autocomplete=\"off\">");
+    }
+
+    public void update(HttpServletRequest r) throws GigiApiException {
+        try {
+            String dayS = r.getParameter(names[0]);
+            if (dayS != null) {
+                day = Integer.parseInt(dayS);
+            }
+
+            String monthS = r.getParameter(names[1]);
+            if (monthS != null) {
+                month = Integer.parseInt(monthS);
+            }
+
+            String yearS = r.getParameter(names[2]);
+            if (yearS != null) {
+                year = Integer.parseInt(yearS);
+            }
+        } catch (NumberFormatException e) {
+            throw new GigiApiException("Unparsable date.");
+        }
+    }
+
+    public boolean isValid() {
+        if ( !(1900 < year && 1 <= month && month <= 12 && 1 <= day && day <= 32)) {
+            return false;
+        }
+        return true; // TODO checkdate
+    }
+
+    @Override
+    public String toString() {
+        return "DateSelector [names=" + Arrays.toString(names) + ", day=" + day + ", month=" + month + ", year=" + year + "]";
+    }
+
+    public java.sql.Date getDate() {
+        Calendar gc = GregorianCalendar.getInstance();
+        gc.set(year, month - 1, day);
+        return new java.sql.Date(gc.getTime().getTime());
+    }
+
+    public static SimpleDateFormat getDateFormat() {
+        SimpleDateFormat local = fmt.get();
+        if (local == null) {
+            local = new SimpleDateFormat("yyyy-MM-dd");
+            local.setLenient(false);
+            local.setTimeZone(TimeZone.getTimeZone("UTC"));
+            fmt.set(local);
+        }
+        return local;
+    }
+
+}
diff --git a/src/org/cacert/gigi/output/HashAlgorithms.java b/src/org/cacert/gigi/output/HashAlgorithms.java
new file mode 100644 (file)
index 0000000..8e13513
--- /dev/null
@@ -0,0 +1,32 @@
+package org.cacert.gigi.output;
+
+import java.util.Map;
+
+import org.cacert.gigi.dbObjects.Digest;
+import org.cacert.gigi.localisation.Language;
+import org.cacert.gigi.output.template.IterableDataset;
+
+public class HashAlgorithms implements IterableDataset {
+
+    private int i = 0;
+
+    private Digest selected;
+
+    public HashAlgorithms(Digest selected) {
+        this.selected = selected;
+    }
+
+    @Override
+    public boolean next(Language l, Map<String, Object> vars) {
+        Digest[] length = Digest.values();
+        if (i >= length.length) {
+            return false;
+        }
+        Digest d = length[i++];
+        vars.put("algorithm", d.toString());
+        vars.put("name", d.toString());
+        vars.put("info", l.getTranslation(d.getExp()));
+        vars.put("checked", selected == d ? " checked='checked'" : "");
+        return true;
+    }
+}
diff --git a/src/org/cacert/gigi/output/IMenuItem.java b/src/org/cacert/gigi/output/IMenuItem.java
new file mode 100644 (file)
index 0000000..5e85f62
--- /dev/null
@@ -0,0 +1,12 @@
+package org.cacert.gigi.output;
+
+import org.cacert.gigi.PermissionCheckable;
+import org.cacert.gigi.output.template.Outputable;
+
+/**
+ * Markerinterface for an {@link Outputable} speicially used in a {@link Menu}.
+ * 
+ * @author janis
+ */
+public interface IMenuItem extends Outputable, PermissionCheckable {
+}
diff --git a/src/org/cacert/gigi/output/Menu.java b/src/org/cacert/gigi/output/Menu.java
new file mode 100644 (file)
index 0000000..9d275ff
--- /dev/null
@@ -0,0 +1,77 @@
+package org.cacert.gigi.output;
+
+import java.io.PrintWriter;
+import java.util.LinkedList;
+import java.util.Map;
+
+import org.cacert.gigi.dbObjects.User;
+import org.cacert.gigi.localisation.Language;
+
+public class Menu implements IMenuItem {
+
+    public static final String USER_VALUE = "user";
+
+    private String menuName;
+
+    private IMenuItem[] content;
+
+    private LinkedList<IMenuItem> prepare = new LinkedList<IMenuItem>();
+
+    public Menu(String menuName) {
+        this.menuName = menuName;
+    }
+
+    @Override
+    public void output(PrintWriter out, Language l, Map<String, Object> vars) {
+        boolean visible = false;
+        User u = (User) vars.get(USER_VALUE);
+        for (IMenuItem mi : content) {
+            if (mi.isPermitted(u)) {
+                if ( !visible) {
+                    visible = true;
+                    out.println("<div>");
+                    out.print("<h3 class='pointer'>+ ");
+                    out.print(l.getTranslation(menuName));
+                    out.println("</h3>");
+                    out.print("<ul class=\"menu\">");
+                }
+                mi.output(out, l, vars);
+            }
+        }
+        if (visible) {
+            out.println("</ul></div>");
+        }
+    }
+
+    public void addItem(IMenuItem item) {
+        prepare.add(item);
+    }
+
+    public void prepare() {
+        content = new IMenuItem[prepare.size()];
+        content = prepare.toArray(content);
+        prepare = null;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (obj instanceof Menu) {
+            return menuName.equals(((Menu) obj).getMenuName());
+        }
+        return false;
+    }
+
+    @Override
+    public int hashCode() {
+        return menuName.hashCode();
+    }
+
+    public String getMenuName() {
+        return menuName;
+    }
+
+    @Override
+    public boolean isPermitted(User u) {
+        return true;
+    }
+}
diff --git a/src/org/cacert/gigi/output/PageMenuItem.java b/src/org/cacert/gigi/output/PageMenuItem.java
new file mode 100644 (file)
index 0000000..8b7d41e
--- /dev/null
@@ -0,0 +1,20 @@
+package org.cacert.gigi.output;
+
+import org.cacert.gigi.dbObjects.User;
+import org.cacert.gigi.pages.Page;
+import org.cacert.gigi.util.ServerConstants;
+
+public class PageMenuItem extends SimpleMenuItem {
+
+    private Page p;
+
+    public PageMenuItem(Page p, String path) {
+        super("https://" + ServerConstants.getWwwHostNamePortSecure() + path, p.getTitle());
+        this.p = p;
+    }
+
+    @Override
+    public boolean isPermitted(User u) {
+        return p.isPermitted(u);
+    }
+}
diff --git a/src/org/cacert/gigi/output/SimpleMenuItem.java b/src/org/cacert/gigi/output/SimpleMenuItem.java
new file mode 100644 (file)
index 0000000..cccd3b1
--- /dev/null
@@ -0,0 +1,34 @@
+package org.cacert.gigi.output;
+
+import java.io.PrintWriter;
+import java.util.Map;
+
+import org.cacert.gigi.dbObjects.User;
+import org.cacert.gigi.localisation.Language;
+
+public class SimpleMenuItem implements IMenuItem {
+
+    private final String href;
+
+    private final String name;
+
+    public SimpleMenuItem(String href, String name) {
+        this.href = href;
+        this.name = name;
+    }
+
+    @Override
+    public void output(PrintWriter out, Language l, Map<String, Object> vars) {
+        out.print("<li><a href=\"");
+        out.print(href);
+        out.print("\">");
+        out.print(l.getTranslation(name));
+        out.print("</a></li>");
+    }
+
+    @Override
+    public boolean isPermitted(User u) {
+        return true;
+    }
+
+}
diff --git a/src/org/cacert/gigi/output/template/ForeachStatement.java b/src/org/cacert/gigi/output/template/ForeachStatement.java
new file mode 100644 (file)
index 0000000..edabab7
--- /dev/null
@@ -0,0 +1,31 @@
+package org.cacert.gigi.output.template;
+
+import java.io.PrintWriter;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.cacert.gigi.localisation.Language;
+
+public final class ForeachStatement implements Outputable {
+
+    private final String variable;
+
+    private final TemplateBlock body;
+
+    public ForeachStatement(String variable, TemplateBlock body) {
+        this.variable = variable;
+        this.body = body;
+    }
+
+    @Override
+    public void output(PrintWriter out, Language l, Map<String, Object> vars) {
+        Object o = vars.get(variable);
+        if (o instanceof IterableDataset) {
+            IterableDataset id = (IterableDataset) o;
+            Map<String, Object> subcontext = new HashMap<String, Object>(vars);
+            while (id.next(l, subcontext)) {
+                body.output(out, l, subcontext);
+            }
+        }
+    }
+}
diff --git a/src/org/cacert/gigi/output/template/Form.java b/src/org/cacert/gigi/output/template/Form.java
new file mode 100644 (file)
index 0000000..ed1a866
--- /dev/null
@@ -0,0 +1,115 @@
+package org.cacert.gigi.output.template;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.Map;
+
+import javax.servlet.ServletRequest;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpSession;
+
+import org.cacert.gigi.GigiApiException;
+import org.cacert.gigi.localisation.Language;
+import org.cacert.gigi.pages.Page;
+import org.cacert.gigi.util.RandomToken;
+
+public abstract class Form implements Outputable {
+
+    public static final String CSRF_FIELD = "csrf";
+
+    private final String csrf;
+
+    private final String action;
+
+    public Form(HttpServletRequest hsr) {
+        this(hsr, null);
+    }
+
+    public Form(HttpServletRequest hsr, String action) {
+        csrf = RandomToken.generateToken(32);
+        this.action = action;
+        HttpSession hs = hsr.getSession();
+        hs.setAttribute("form/" + getClass().getName() + "/" + csrf, this);
+    }
+
+    public abstract boolean submit(PrintWriter out, HttpServletRequest req) throws GigiApiException;
+
+    protected String getCsrfFieldName() {
+        return CSRF_FIELD;
+    }
+
+    @Override
+    public void output(PrintWriter out, Language l, Map<String, Object> vars) {
+        if (action == null) {
+            out.println("<form method='POST'>");
+        } else {
+            out.println("<form method='POST' action='" + action + "'>");
+        }
+        failed = false;
+        outputContent(out, l, vars);
+        out.print("<input type='hidden' name='" + CSRF_FIELD + "' value='");
+        out.print(getCSRFToken());
+        out.println("'></form>");
+    }
+
+    protected abstract void outputContent(PrintWriter out, Language l, Map<String, Object> vars);
+
+    boolean failed;
+
+    protected void outputError(PrintWriter out, ServletRequest req, String text, Object... contents) {
+        if ( !failed) {
+            failed = true;
+            out.println("<div class='formError'>");
+        }
+        out.print("<div>");
+        if (contents.length == 0) {
+            out.print(Page.translate(req, text));
+        } else {
+            out.print(String.format(Page.translate(req, text), contents));
+        }
+        out.println("</div>");
+    }
+
+    protected void outputErrorPlain(PrintWriter out, String text) {
+        if ( !failed) {
+            failed = true;
+            out.println("<div class='formError'>");
+        }
+        out.print("<div>");
+        out.print(text);
+        out.println("</div>");
+    }
+
+    public boolean isFailed(PrintWriter out) {
+        if (failed) {
+            out.println("</div>");
+        }
+        return failed;
+    }
+
+    protected String getCSRFToken() {
+        return csrf;
+    }
+
+    public static <T extends Form> T getForm(HttpServletRequest req, Class<T> target) throws CSRFException {
+        String csrf = req.getParameter(CSRF_FIELD);
+        if (csrf == null) {
+            throw new CSRFException();
+        }
+        HttpSession hs = req.getSession();
+        if (hs == null) {
+            throw new CSRFException();
+        }
+        Form f = (Form) hs.getAttribute("form/" + target.getName() + "/" + csrf);
+        if (f == null) {
+            throw new CSRFException();
+        }
+        return (T) f;
+    }
+
+    public static class CSRFException extends IOException {
+
+        private static final long serialVersionUID = 59708247477988362L;
+
+    }
+}
diff --git a/src/org/cacert/gigi/output/template/IfStatement.java b/src/org/cacert/gigi/output/template/IfStatement.java
new file mode 100644 (file)
index 0000000..0347a36
--- /dev/null
@@ -0,0 +1,39 @@
+package org.cacert.gigi.output.template;
+
+import java.io.PrintWriter;
+import java.util.Map;
+
+import org.cacert.gigi.localisation.Language;
+
+public final class IfStatement implements Outputable {
+
+    private final String variable;
+
+    private final TemplateBlock iftrue;
+
+    private final TemplateBlock iffalse;
+
+    public IfStatement(String variable, TemplateBlock body) {
+        this.variable = variable;
+        this.iftrue = body;
+        this.iffalse = null;
+    }
+
+    public IfStatement(String variable, TemplateBlock iftrue, TemplateBlock iffalse) {
+        this.variable = variable;
+        this.iftrue = iftrue;
+        this.iffalse = iffalse;
+    }
+
+    @Override
+    public void output(PrintWriter out, Language l, Map<String, Object> vars) {
+        Object o = vars.get(variable);
+
+        if ( !(o == null || Boolean.FALSE.equals(o))) {
+            iftrue.output(out, l, vars);
+        } else if (iffalse != null) {
+            iffalse.output(out, l, vars);
+        }
+    }
+
+}
diff --git a/src/org/cacert/gigi/output/template/IterableDataset.java b/src/org/cacert/gigi/output/template/IterableDataset.java
new file mode 100644 (file)
index 0000000..e95dce8
--- /dev/null
@@ -0,0 +1,24 @@
+package org.cacert.gigi.output.template;
+
+import java.util.Map;
+
+import org.cacert.gigi.localisation.Language;
+
+/**
+ * Represents some kind of data, that may be iterated over in a template.
+ */
+public interface IterableDataset {
+
+    /**
+     * Moves to the next Dataset.
+     * 
+     * @param l
+     *            the language for l10n-ed strings
+     * @param vars
+     *            the variables used in this template. They need to be updated
+     *            for each line.
+     * @return true, iff there was a data-line "installed". False of this set is
+     *         already empty.
+     */
+    public boolean next(Language l, Map<String, Object> vars);
+}
diff --git a/src/org/cacert/gigi/output/template/OutputVariableCommand.java b/src/org/cacert/gigi/output/template/OutputVariableCommand.java
new file mode 100644 (file)
index 0000000..b3534ed
--- /dev/null
@@ -0,0 +1,28 @@
+package org.cacert.gigi.output.template;
+
+import java.io.PrintWriter;
+import java.util.Map;
+
+import org.cacert.gigi.localisation.Language;
+
+public final class OutputVariableCommand implements Outputable {
+
+    private final String raw;
+
+    private final boolean unescaped;
+
+    public OutputVariableCommand(String raw) {
+        if (raw.charAt(0) == '!') {
+            unescaped = true;
+            this.raw = raw.substring(1);
+        } else {
+            unescaped = false;
+            this.raw = raw;
+        }
+    }
+
+    @Override
+    public void output(PrintWriter out, Language l, Map<String, Object> vars) {
+        Template.outputVar(out, l, vars, raw, unescaped);
+    }
+}
diff --git a/src/org/cacert/gigi/output/template/Outputable.java b/src/org/cacert/gigi/output/template/Outputable.java
new file mode 100644 (file)
index 0000000..5716162
--- /dev/null
@@ -0,0 +1,11 @@
+package org.cacert.gigi.output.template;
+
+import java.io.PrintWriter;
+import java.util.Map;
+
+import org.cacert.gigi.localisation.Language;
+
+public interface Outputable {
+
+    public void output(PrintWriter out, Language l, Map<String, Object> vars);
+}
diff --git a/src/org/cacert/gigi/output/template/OutputableArrayIterable.java b/src/org/cacert/gigi/output/template/OutputableArrayIterable.java
new file mode 100644 (file)
index 0000000..5a78e73
--- /dev/null
@@ -0,0 +1,31 @@
+package org.cacert.gigi.output.template;
+
+import java.util.Map;
+
+import org.cacert.gigi.localisation.Language;
+
+public class OutputableArrayIterable implements IterableDataset {
+
+    Object[] content;
+
+    String targetName;
+
+    int index = 0;
+
+    public OutputableArrayIterable(Object[] content, String targetName) {
+        this.content = content;
+        this.targetName = targetName;
+    }
+
+    @Override
+    public boolean next(Language l, Map<String, Object> vars) {
+        if (index >= content.length) {
+            return false;
+        }
+        vars.put(targetName, content[index]);
+        vars.put("i", index);
+        index++;
+        return true;
+    }
+
+}
diff --git a/src/org/cacert/gigi/output/template/Scope.java b/src/org/cacert/gigi/output/template/Scope.java
new file mode 100644 (file)
index 0000000..9a15b3a
--- /dev/null
@@ -0,0 +1,28 @@
+package org.cacert.gigi.output.template;
+
+import java.io.PrintWriter;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.cacert.gigi.localisation.Language;
+
+public class Scope implements Outputable {
+
+    private Map<String, Object> vars;
+
+    private Outputable out;
+
+    public Scope(Outputable out, Map<String, Object> vars) {
+        this.out = out;
+        this.vars = vars;
+    }
+
+    @Override
+    public void output(PrintWriter out, Language l, Map<String, Object> vars) {
+        HashMap<String, Object> map = new HashMap<>();
+        map.putAll(vars);
+        map.putAll(this.vars);
+        this.out.output(out, l, map);
+    }
+
+}
diff --git a/src/org/cacert/gigi/output/template/SprintfCommand.java b/src/org/cacert/gigi/output/template/SprintfCommand.java
new file mode 100644 (file)
index 0000000..4738964
--- /dev/null
@@ -0,0 +1,76 @@
+package org.cacert.gigi.output.template;
+
+import java.io.PrintWriter;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.cacert.gigi.localisation.Language;
+import org.cacert.gigi.util.HTMLEncoder;
+
+public final class SprintfCommand implements Outputable {
+
+    private final String text;
+
+    private final String[] store;
+
+    public SprintfCommand(String text, List<String> store) {
+        this.text = text;
+        this.store = store.toArray(new String[store.size()]);
+    }
+
+    private static final String VARIABLE = "\\$!?\\{[a-zA-Z0-9_-]+\\}";
+
+    private static final Pattern processingInstruction = Pattern.compile("(" + VARIABLE + ")|(!'[^{}'\\$]*)'");
+
+    public SprintfCommand(String content) {
+        StringBuffer raw = new StringBuffer();
+        List<String> var = new LinkedList<String>();
+        int counter = 0;
+        Matcher m = processingInstruction.matcher(content);
+        int last = 0;
+        while (m.find()) {
+            raw.append(content.substring(last, m.start()));
+            String group = null;
+            if ((group = m.group(1)) != null) {
+                var.add(group);
+            } else if ((group = m.group(2)) != null) {
+                var.add(group);
+            } else {
+                throw new Error("Regex is broken??");
+            }
+            last = m.end();
+            raw.append("{" + (counter++) + "}");
+        }
+        raw.append(content.substring(last));
+        text = raw.toString();
+        store = var.toArray(new String[var.size()]);
+    }
+
+    private final Pattern replacant = Pattern.compile("\\{([0-9]+)\\}");
+
+    @Override
+    public void output(PrintWriter out, Language l, Map<String, Object> vars) {
+        String parts = l.getTranslation(text);
+        Matcher m = replacant.matcher(parts);
+        int pos = 0;
+        while (m.find()) {
+            out.print(HTMLEncoder.encodeHTML(parts.substring(pos, m.start())));
+            String var = store[Integer.parseInt(m.group(1))];
+            if (var.startsWith("$!")) {
+                Template.outputVar(out, l, vars, var.substring(3, var.length() - 1), true);
+            } else if (var.startsWith("!'")) {
+                out.print(var.substring(2));
+            } else if (var.startsWith("$")) {
+                Template.outputVar(out, l, vars, var.substring(2, var.length() - 1), false);
+            } else {
+                throw new Error("Processing error in template.");
+            }
+            pos = m.end();
+
+        }
+        out.print(HTMLEncoder.encodeHTML(parts.substring(pos)));
+    }
+}
diff --git a/src/org/cacert/gigi/output/template/Template.java b/src/org/cacert/gigi/output/template/Template.java
new file mode 100644 (file)
index 0000000..4789a36
--- /dev/null
@@ -0,0 +1,197 @@
+package org.cacert.gigi.output.template;
+
+import java.io.EOFException;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.PrintWriter;
+import java.io.Reader;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.util.LinkedList;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.cacert.gigi.DevelLauncher;
+import org.cacert.gigi.localisation.Language;
+import org.cacert.gigi.util.HTMLEncoder;
+
+public class Template implements Outputable {
+
+    class ParseResult {
+
+        TemplateBlock block;
+
+        String endType;
+
+        public ParseResult(TemplateBlock block, String endType) {
+            this.block = block;
+            this.endType = endType;
+        }
+
+        public String getEndType() {
+            return endType;
+        }
+
+        public TemplateBlock getBlock(String reqType) {
+            if (endType == null && reqType == null) {
+                return block;
+            }
+            if (endType == null || reqType == null) {
+                throw new Error("Invalid block type: " + endType);
+            }
+            if (endType.equals(reqType)) {
+                return block;
+            }
+            throw new Error("Invalid block type: " + endType);
+        }
+    }
+
+    private TemplateBlock data;
+
+    private long lastLoaded;
+
+    private File source;
+
+    private static final Pattern CONTROL_PATTERN = Pattern.compile(" ?([a-z]+)\\(\\$([^)]+)\\) ?\\{ ?");
+
+    private static final Pattern ELSE_PATTERN = Pattern.compile(" ?\\} ?else ?\\{ ?");
+
+    public Template(URL u) {
+        try {
+            Reader r = new InputStreamReader(u.openStream(), "UTF-8");
+            try {
+                if (u.getProtocol().equals("file") && DevelLauncher.DEVEL) {
+                    source = new File(u.toURI());
+                    lastLoaded = source.lastModified() + 1000;
+                }
+            } catch (URISyntaxException e) {
+                e.printStackTrace();
+            }
+            data = parse(r).getBlock(null);
+            r.close();
+        } catch (IOException e) {
+            throw new Error(e);
+        }
+    }
+
+    public Template(Reader r) {
+        try {
+            data = parse(r).getBlock(null);
+            r.close();
+        } catch (IOException e) {
+            throw new Error(e);
+        }
+    }
+
+    private ParseResult parse(Reader r) throws IOException {
+        LinkedList<String> splitted = new LinkedList<String>();
+        LinkedList<Outputable> commands = new LinkedList<Outputable>();
+        StringBuffer buf = new StringBuffer();
+        String blockType = null;
+        outer:
+        while (true) {
+            while ( !endsWith(buf, "<?")) {
+                int ch = r.read();
+                if (ch == -1) {
+                    break outer;
+                }
+                buf.append((char) ch);
+            }
+            buf.delete(buf.length() - 2, buf.length());
+            splitted.add(buf.toString());
+            buf.delete(0, buf.length());
+            while ( !endsWith(buf, "?>")) {
+                int ch = r.read();
+                if (ch == -1) {
+                    throw new EOFException();
+                }
+                buf.append((char) ch);
+            }
+            buf.delete(buf.length() - 2, buf.length());
+            String com = buf.toString().replace("\n", "");
+            buf.delete(0, buf.length());
+            Matcher m = CONTROL_PATTERN.matcher(com);
+            if (m.matches()) {
+                String type = m.group(1);
+                String variable = m.group(2);
+                ParseResult body = parse(r);
+                if (type.equals("if")) {
+                    if ("else".equals(body.getEndType())) {
+                        commands.add(new IfStatement(variable, body.getBlock("else"), parse(r).getBlock("}")));
+                    } else {
+                        commands.add(new IfStatement(variable, body.getBlock("}")));
+                    }
+                } else if (type.equals("foreach")) {
+                    commands.add(new ForeachStatement(variable, body.getBlock("}")));
+                } else {
+                    throw new IOException("Syntax error: unknown control structure: " + type);
+                }
+                continue;
+            } else if ((m = ELSE_PATTERN.matcher(com)).matches()) {
+                blockType = "else";
+                break;
+            } else if (com.matches(" ?\\} ?")) {
+                blockType = "}";
+                break;
+            } else {
+                commands.add(parseCommand(com));
+            }
+        }
+        splitted.add(buf.toString());
+        return new ParseResult(new TemplateBlock(splitted.toArray(new String[splitted.size()]), commands.toArray(new Outputable[commands.size()])), blockType);
+    }
+
+    private boolean endsWith(StringBuffer buf, String string) {
+        return buf.length() >= string.length() && buf.substring(buf.length() - string.length(), buf.length()).equals(string);
+    }
+
+    private Outputable parseCommand(String s2) {
+        if (s2.startsWith("=_")) {
+            final String raw = s2.substring(2);
+            if ( !s2.contains("$") && !s2.contains("!'")) {
+                return new TranslateCommand(raw);
+            } else {
+                return new SprintfCommand(raw);
+            }
+        } else if (s2.startsWith("=$")) {
+            final String raw = s2.substring(2);
+            return new OutputVariableCommand(raw);
+        } else {
+            throw new Error("Unknown processing instruction: " + s2);
+        }
+    }
+
+    @Override
+    public void output(PrintWriter out, Language l, Map<String, Object> vars) {
+        if (source != null && DevelLauncher.DEVEL) {
+            if (lastLoaded < source.lastModified()) {
+                try {
+                    System.out.println("Reloading template.... " + source);
+                    InputStreamReader r = new InputStreamReader(new FileInputStream(source), "UTF-8");
+                    data = parse(r).getBlock(null);
+                    r.close();
+                    lastLoaded = source.lastModified() + 1000;
+                } catch (IOException e) {
+                    e.printStackTrace();
+                }
+            }
+        }
+        data.output(out, l, vars);
+    }
+
+    protected static void outputVar(PrintWriter out, Language l, Map<String, Object> vars, String varname, boolean unescaped) {
+        Object s = vars.get(varname);
+
+        if (s == null) {
+            System.out.println("Empty variable: " + varname);
+        }
+        if (s instanceof Outputable) {
+            ((Outputable) s).output(out, l, vars);
+        } else {
+            out.print(s == null ? "null" : (unescaped ? s.toString() : HTMLEncoder.encodeHTML(s.toString())));
+        }
+    }
+}
diff --git a/src/org/cacert/gigi/output/template/TemplateBlock.java b/src/org/cacert/gigi/output/template/TemplateBlock.java
new file mode 100644 (file)
index 0000000..15ce4e5
--- /dev/null
@@ -0,0 +1,29 @@
+package org.cacert.gigi.output.template;
+
+import java.io.PrintWriter;
+import java.util.Map;
+
+import org.cacert.gigi.localisation.Language;
+
+class TemplateBlock implements Outputable {
+
+    private String[] contents;
+
+    private Outputable[] vars;
+
+    public TemplateBlock(String[] contents, Outputable[] vars) {
+        this.contents = contents;
+        this.vars = vars;
+    }
+
+    @Override
+    public void output(PrintWriter out, Language l, Map<String, Object> vars) {
+        for (int i = 0; i < contents.length; i++) {
+            out.print(contents[i]);
+            if (i < this.vars.length) {
+                this.vars[i].output(out, l, vars);
+            }
+        }
+    }
+
+}
diff --git a/src/org/cacert/gigi/output/template/TranslateCommand.java b/src/org/cacert/gigi/output/template/TranslateCommand.java
new file mode 100644 (file)
index 0000000..cae48a6
--- /dev/null
@@ -0,0 +1,21 @@
+package org.cacert.gigi.output.template;
+
+import java.io.PrintWriter;
+import java.util.Map;
+
+import org.cacert.gigi.localisation.Language;
+import org.cacert.gigi.util.HTMLEncoder;
+
+public final class TranslateCommand implements Outputable {
+
+    private final String raw;
+
+    public TranslateCommand(String raw) {
+        this.raw = raw;
+    }
+
+    @Override
+    public void output(PrintWriter out, Language l, Map<String, Object> vars) {
+        out.print(HTMLEncoder.encodeHTML(l.getTranslation(raw)));
+    }
+}
diff --git a/src/org/cacert/gigi/pages/LoginPage.java b/src/org/cacert/gigi/pages/LoginPage.java
new file mode 100644 (file)
index 0000000..91b6b1b
--- /dev/null
@@ -0,0 +1,174 @@
+package org.cacert.gigi.pages;
+
+import static org.cacert.gigi.Gigi.*;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.security.cert.X509Certificate;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpSession;
+
+import org.cacert.gigi.GigiApiException;
+import org.cacert.gigi.database.DatabaseConnection;
+import org.cacert.gigi.database.GigiPreparedStatement;
+import org.cacert.gigi.database.GigiResultSet;
+import org.cacert.gigi.dbObjects.Group;
+import org.cacert.gigi.dbObjects.User;
+import org.cacert.gigi.localisation.Language;
+import org.cacert.gigi.output.template.Form;
+import org.cacert.gigi.util.PasswordHash;
+
+public class LoginPage extends Page {
+
+    public class LoginForm extends Form {
+
+        public LoginForm(HttpServletRequest hsr) {
+            super(hsr);
+        }
+
+        @Override
+        public boolean submit(PrintWriter out, HttpServletRequest req) throws GigiApiException {
+            tryAuthWithUnpw(req);
+            return false;
+        }
+
+        @Override
+        protected void outputContent(PrintWriter out, Language l, Map<String, Object> vars) {
+            getDefaultTemplate().output(out, l, vars);
+        }
+
+    }
+
+    public static final String LOGIN_RETURNPATH = "login-returnpath";
+
+    public LoginPage(String title) {
+        super(title);
+    }
+
+    @Override
+    public void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
+        new LoginForm(req).output(resp.getWriter(), getLanguage(req), new HashMap<String, Object>());
+    }
+
+    @Override
+    public boolean beforeTemplate(HttpServletRequest req, HttpServletResponse resp) throws IOException {
+        String redir = (String) req.getSession().getAttribute(LOGIN_RETURNPATH);
+        if (req.getSession().getAttribute("loggedin") == null) {
+            X509Certificate cert = getCertificateFromRequest(req);
+            if (cert != null) {
+                tryAuthWithCertificate(req, cert);
+            }
+            if (req.getMethod().equals("POST")) {
+                try {
+                    Form.getForm(req, LoginForm.class).submit(resp.getWriter(), req);
+                } catch (GigiApiException e) {
+                }
+            }
+        }
+
+        if (req.getSession().getAttribute("loggedin") != null) {
+            String s = redir;
+            if (s != null) {
+                if ( !s.startsWith("/")) {
+                    s = "/" + s;
+                }
+                resp.sendRedirect(s);
+            } else {
+                resp.sendRedirect("/");
+            }
+            return true;
+        }
+        return false;
+    }
+
+    @Override
+    public boolean needsLogin() {
+        return false;
+    }
+
+    private void tryAuthWithUnpw(HttpServletRequest req) {
+        String un = req.getParameter("username");
+        String pw = req.getParameter("password");
+        GigiPreparedStatement ps = DatabaseConnection.getInstance().prepare("SELECT `password`, `id` FROM `users` WHERE `email`=? AND verified='1'");
+        ps.setString(1, un);
+        GigiResultSet rs = ps.executeQuery();
+        if (rs.next()) {
+            String dbHash = rs.getString(1);
+            String hash = PasswordHash.verifyHash(pw, dbHash);
+            if (hash != null) {
+                if ( !hash.equals(dbHash)) {
+                    GigiPreparedStatement gps = DatabaseConnection.getInstance().prepare("UPDATE `users` SET `password`=? WHERE `email`=?");
+                    gps.setString(1, hash);
+                    gps.setString(2, un);
+                    gps.executeUpdate();
+                }
+                loginSession(req, User.getById(rs.getInt(2)));
+                req.getSession().setAttribute(LOGIN_METHOD, "Password");
+            }
+        }
+        rs.close();
+    }
+
+    public static User getUser(HttpServletRequest req) {
+        return (User) req.getSession().getAttribute(USER);
+    }
+
+    private void tryAuthWithCertificate(HttpServletRequest req, X509Certificate x509Certificate) {
+        String serial = extractSerialFormCert(x509Certificate);
+        User user = fetchUserBySerial(serial);
+        if (user == null) {
+            return;
+        }
+        loginSession(req, user);
+        req.getSession().setAttribute(CERT_SERIAL, serial);
+        req.getSession().setAttribute(CERT_ISSUER, x509Certificate.getIssuerDN());
+        req.getSession().setAttribute(LOGIN_METHOD, "Certificate");
+    }
+
+    public static String extractSerialFormCert(X509Certificate x509Certificate) {
+        return x509Certificate.getSerialNumber().toString(16).toUpperCase();
+    }
+
+    public static User fetchUserBySerial(String serial) {
+        GigiPreparedStatement ps = DatabaseConnection.getInstance().prepare("SELECT `memid` FROM `certs` WHERE `serial`=? AND `disablelogin`='0' AND `revoked` is NULL");
+        ps.setString(1, serial);
+        GigiResultSet rs = ps.executeQuery();
+        User user = null;
+        if (rs.next()) {
+            user = User.getById(rs.getInt(1));
+        }
+        rs.close();
+        return user;
+    }
+
+    public static X509Certificate getCertificateFromRequest(HttpServletRequest req) {
+        X509Certificate[] cert = (X509Certificate[]) req.getAttribute("javax.servlet.request.X509Certificate");
+        X509Certificate uc = null;
+        if (cert != null && cert[0] != null) {
+            uc = cert[0];
+        }
+        return uc;
+    }
+
+    private static final Group LOGIN_BLOCKED = Group.getByString("blockedlogin");
+
+    private void loginSession(HttpServletRequest req, User user) {
+        if (user.isInGroup(LOGIN_BLOCKED)) {
+            return;
+        }
+        req.getSession().invalidate();
+        HttpSession hs = req.getSession();
+        hs.setAttribute(LOGGEDIN, true);
+        hs.setAttribute(Language.SESSION_ATTRIB_NAME, user.getPreferredLocale());
+        hs.setAttribute(USER, user);
+    }
+
+    @Override
+    public boolean isPermitted(User u) {
+        return u == null;
+    }
+}
diff --git a/src/org/cacert/gigi/pages/LoginPage.templ b/src/org/cacert/gigi/pages/LoginPage.templ
new file mode 100644 (file)
index 0000000..7d49607
--- /dev/null
@@ -0,0 +1,11 @@
+<div class='loginbox'>
+<h1><?=_Login?></h1>
+<p class='smalltext'><?=_Warning! This site requires cookies to be enabled to ensure your privacy and security. This site uses session cookies to store temporary values to prevent people from copying and pasting the session ID to someone else exposing their account, personal details and identity theft as a result.?></p>
+<label for="username"><?=_Email Address?>:</label><input type='text' name="username"/><br />
+<label for="password"><?=_Pass Phrase?>:</label><input type='password' name='password'/><br />
+<input type='submit' name="process" value="<?=_Login?>" /><br /><br />
+<a href='https://blah/index.php?id=4'><?=_Password Login?></a> -<!-- TODO -->
+<a href='https://blah/index.php?id=5'><?=_Lost Password?></a> -
+<a href='https://blah/index.php?id=4&amp;noauto=1'><?=_Net Cafe Login?></a><br />
+<p class='smalltext'><?=_If you are having trouble with your username or password, please visit our !'<a href="http://wiki.cacert.org/wiki/FAQ/LostPasswordOrAccount" target="_new">'wiki page!'</a>' for more information?></p>
+</div>
diff --git a/src/org/cacert/gigi/pages/LogoutPage.java b/src/org/cacert/gigi/pages/LogoutPage.java
new file mode 100644 (file)
index 0000000..3d82be7
--- /dev/null
@@ -0,0 +1,38 @@
+package org.cacert.gigi.pages;
+
+import java.io.IOException;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpSession;
+
+import org.cacert.gigi.Gigi;
+import org.cacert.gigi.dbObjects.User;
+
+public class LogoutPage extends Page {
+
+    public static final String PATH = "/logout";
+
+    public LogoutPage(String title) {
+        super(title);
+    }
+
+    @Override
+    public void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
+        HttpSession hs = req.getSession();
+        if (req.getPathInfo() != null && req.getPathInfo().equals("/logout")) {
+            if (hs != null) {
+                hs.setAttribute(Gigi.LOGGEDIN, null);
+                hs.invalidate();
+            }
+            resp.sendRedirect("/");
+            return;
+        }
+    }
+
+    @Override
+    public boolean isPermitted(User u) {
+        return u != null;
+    }
+
+}
diff --git a/src/org/cacert/gigi/pages/MainPage.java b/src/org/cacert/gigi/pages/MainPage.java
new file mode 100644 (file)
index 0000000..6e4d8ea
--- /dev/null
@@ -0,0 +1,32 @@
+package org.cacert.gigi.pages;
+
+import java.io.IOException;
+import java.util.HashMap;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.cacert.gigi.output.template.Template;
+
+public class MainPage extends Page {
+
+    Template notLog = new Template(MainPage.class.getResource("MainPageNotLogin.templ"));
+
+    public MainPage(String title) {
+        super(title);
+    }
+
+    @Override
+    public void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
+        if (LoginPage.getUser(req) != null) {
+            getDefaultTemplate().output(resp.getWriter(), getLanguage(req), new HashMap<String, Object>());
+        } else {
+            notLog.output(resp.getWriter(), getLanguage(req), new HashMap<String, Object>());
+        }
+    }
+
+    @Override
+    public boolean needsLogin() {
+        return false;
+    }
+}
diff --git a/src/org/cacert/gigi/pages/MainPage.templ b/src/org/cacert/gigi/pages/MainPage.templ
new file mode 100644 (file)
index 0000000..41c8522
--- /dev/null
@@ -0,0 +1,14 @@
+<p><?=_Welcome to your account section of the website. Below is a description of the different sections and what they're for.?></p>
+<H4><?=_CAcert.org?></H4>
+<p><?=_If you would like to view news items or change languages you can click the logout or go home links. Go home doesn't log you out of the system, just returns you to the front of the website. Logout logs you out of the system.?></p>
+<H4><?=_My Details?></H4>
+<p><?=_In this section you will be able to edit your personal information (if you haven't been assured), update your pass phrase, and lost pass phrase questions. You will also be able to set your location for the Web of Trust, it also effects the email announcement settings which among other things can be set to notify you if you're within 200km of a planned assurance event. You'll also be able to set additional contact information when you become fully trusted, so others can contact you to meet up outside official events.?></p>
+<h4><?=_Email Accounts and Client Certificates?></h4>
+<p><?=_The email account section is for adding/updating/removing email accounts which can be used to issue client certificates against. The client certificate section steps you through generating a certificate signing request for one or more emails you've registered in the email account section.?></p>
+<h4><?=_Domains and Server Certificates.?></h4>
+<p><?=_Before you can start issuing certificates for your website, irc server, smtp server, pop3, imap etc you will need to add domains to your account under the domain menu. You can also remove domains from here as well. Once you've added a domain you are free then to go into the Server Certificate section and start pasting CSR into the website and have the website return you a valid certificate for up to 2 years if you have 50 trust points, or 6 months for no trust points.?></p>
+<h4><?=_Org Client and Server Certificates?></h4>
+<p><?=_Once you have verified your company you will see these menu options. They allow you to issue as many certificates as you like without proving individual email accounts as you like, further more you are able to get your company details on the certificate.?></p>
+<h4><?=_CAcert Web of Trust?></h4>
+<p><?=_The Web of Trust system CAcert uses is similar to that many involved with GPG/PGP use, they hold face to face meetings to verify each others photo identities match their GPG/PGP key information. CAcert differs however in that we have modified things to work within the PKI framework, for you to gain trust in the system you must first locate someone already trusted. The trust person depending how many people they've trusted or meet before will determine how many points they can issue to you (the number of points they can issue is listed in the locate assurer section). Once you've met up you can show your ID and you will need to fill out a CAP form which the person assuring your details must retain for verification reasons.?></p>
+<p><b><?=_For information about the TTP-assisted-assurance program please read !'<a href="//wiki.cacert.org/TTP/TTPuser">https://wiki.cacert.org/TTP/TTPuser</a>' and !'<a href="//wiki.cacert.org/TTP/TTPAL"> https://wiki.cacert.org/TTP/TTPAL</a>'.?></b></p>
diff --git a/src/org/cacert/gigi/pages/MainPageNotLogin.templ b/src/org/cacert/gigi/pages/MainPageNotLogin.templ
new file mode 100644 (file)
index 0000000..c2359c5
--- /dev/null
@@ -0,0 +1,21 @@
+<h3><?=_Are you new to CAcert??></h3>
+
+<p><?=_CAcert.org is a community-driven Certificate Authority that issues certificates to the public at large for free.?></p>
+
+<p><?=_CAcert's goal is to promote awareness and education on computer security through the use of encryption, specifically by providing cryptographic certificates. These certificates can be used to digitally sign and encrypt email, authenticate and authorize users connecting to websites and secure data transmission over the internet. Any application that supports the Secure Socket Layer Protocol (SSL or TLS) can make use of certificates signed by CAcert, as can any application that uses X.509 certificates, e.g. for encryption or code signing and document signatures.?></p>
+
+<p><?=_If you want to have free certificates issued to you, !'<a href="/register">'join the CAcert Community!'</a>'.?></p>
+
+<p><?=_If you want to use certificates issued by CAcert, read the CAcert !'<a href="/policy/RootDistributionLicense.html">'Root Distribution License!'</a>'.?>
+<?=_This license applies to using the CAcert !'<a href="/roots">'root keys!'</a>'.?></p>
+
+<hr/>
+
+<h3><?=_For CAcert Community Members?></h3>
+
+<p><?=_Have you passed the CAcert !'<a href="http://wiki.cacert.org/wiki/AssurerChallenge">'Assurer Challenge!'</a>' yet??></p>
+
+<p><?=_Have you read the CAcert !'<a href="/policy/CAcertCommunityAgreement.html">'Community Agreement!'</a>' yet??></p>
+
+<p><?=_For general documentation and help, please visit the CAcert !'<a href="http://wiki.CAcert.org">'Wiki Documentation site!'</a>'.?>
+<?=_For specific policies, see the CAcert !'<a href="/policy/">'Approved Policies page!'</a>'.?></p>
diff --git a/src/org/cacert/gigi/pages/OneFormPage.java b/src/org/cacert/gigi/pages/OneFormPage.java
new file mode 100644 (file)
index 0000000..e61511d
--- /dev/null
@@ -0,0 +1,45 @@
+package org.cacert.gigi.pages;
+
+import java.io.IOException;
+import java.util.HashMap;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.cacert.gigi.GigiApiException;
+import org.cacert.gigi.output.template.Form;
+
+public abstract class OneFormPage extends Page {
+
+    Class<? extends Form> c;
+
+    public OneFormPage(String title, Class<? extends Form> t) {
+        super(title);
+        c = t;
+    }
+
+    @Override
+    public void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException {
+        try {
+            Form form = Form.getForm(req, c);
+            if (form.submit(resp.getWriter(), req)) {
+                resp.sendRedirect(getSuccessPath(form));
+            }
+        } catch (GigiApiException e) {
+            e.format(resp.getWriter(), getLanguage(req));
+            doGet(req, resp);
+        }
+    }
+
+    @Override
+    public void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
+        try {
+            c.getConstructor(HttpServletRequest.class).newInstance(req).output(resp.getWriter(), getLanguage(req), new HashMap<String, Object>());
+        } catch (ReflectiveOperationException e) {
+            new GigiApiException(e.getMessage()).format(resp.getWriter(), getLanguage(req));
+        }
+    }
+
+    public abstract String getSuccessPath(Form f);
+
+}
diff --git a/src/org/cacert/gigi/pages/Page.java b/src/org/cacert/gigi/pages/Page.java
new file mode 100644 (file)
index 0000000..f2bdbd7
--- /dev/null
@@ -0,0 +1,144 @@
+package org.cacert.gigi.pages;
+
+import java.io.IOException;
+import java.net.URL;
+import java.util.Enumeration;
+import java.util.Locale;
+
+import javax.servlet.ServletRequest;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpSession;
+
+import org.cacert.gigi.PermissionCheckable;
+import org.cacert.gigi.dbObjects.User;
+import org.cacert.gigi.localisation.Language;
+import org.cacert.gigi.output.template.Template;
+
+/**
+ * This class encapsulates a sub page of Gigi. A template residing nearby this
+ * class with name &lt;className&gt;.templ will be loaded automatically.
+ */
+public abstract class Page implements PermissionCheckable {
+
+    private String title;
+
+    private Template defaultTemplate;
+
+    public Page(String title) {
+        this.title = title;
+        URL resource = getClass().getResource(getClass().getSimpleName() + ".templ");
+        if (resource != null) {
+            defaultTemplate = new Template(resource);
+        }
+    }
+
+    /**
+     * Retrieves the default template (&lt;className&gt;.templ) which has
+     * already been loaded.
+     * 
+     * @return the default template.
+     */
+    public Template getDefaultTemplate() {
+        return defaultTemplate;
+    }
+
+    /**
+     * This method can be overridden to execute code and do stuff before the
+     * default template is applied.
+     * 
+     * @param req
+     *            the request to handle.
+     * @param resp
+     *            the response to write to
+     * @return true, if the request is consumed and the default template should
+     *         not be applied.
+     * @throws IOException
+     *             if output goes wrong.
+     */
+    public boolean beforeTemplate(HttpServletRequest req, HttpServletResponse resp) throws IOException {
+        return false;
+    }
+
+    /**
+     * This method is called to generate the content inside the default
+     * template.
+     * 
+     * @param req
+     *            the request to handle.
+     * @param resp
+     *            the response to write to
+     * @throws IOException
+     *             if output goes wrong.
+     */
+    public abstract void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException;
+
+    /**
+     * Same as {@link #doGet(HttpServletRequest, HttpServletResponse)} but for
+     * POST requests. By default they are redirected to
+     * {@link #doGet(HttpServletRequest, HttpServletResponse)};
+     * 
+     * @param req
+     *            the request to handle.
+     * @param resp
+     *            the response to write to
+     * @throws IOException
+     *             if output goes wrong.
+     */
+    public void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException {
+        doGet(req, resp);
+    }
+
+    /**
+     * Returns true, if this page requires login. Default is <code>true</code>
+     * 
+     * @return if the page needs login.
+     */
+    public boolean needsLogin() {
+        return true;
+    }
+
+    public String getTitle() {
+        return title;
+    }
+
+    public static Language getLanguage(ServletRequest req) {
+        HttpSession session = ((HttpServletRequest) req).getSession();
+        synchronized (session) {
+
+            Locale sessval = (Locale) session.getAttribute(Language.SESSION_ATTRIB_NAME);
+            if (sessval != null) {
+                Language l = Language.getInstance(sessval);
+                if (l != null) {
+                    return l;
+                }
+            }
+            Enumeration<Locale> langs = req.getLocales();
+            while (langs.hasMoreElements()) {
+                Locale c = langs.nextElement();
+                Language l = Language.getInstance(c);
+                if (l != null) {
+                    session.setAttribute(Language.SESSION_ATTRIB_NAME, l.getLocale());
+                    return l;
+                }
+            }
+            session.setAttribute(Language.SESSION_ATTRIB_NAME, Locale.ENGLISH);
+            return Language.getInstance(Locale.ENGLISH);
+        }
+    }
+
+    public static String translate(ServletRequest req, String string) {
+        Language l = getLanguage(req);
+        return l.getTranslation(string);
+    }
+
+    public static User getUser(HttpServletRequest req) {
+        return LoginPage.getUser(req);
+    }
+
+    @Override
+    public boolean isPermitted(User u) {
+        return !needsLogin() || u != null;
+    }
+
+}
diff --git a/src/org/cacert/gigi/pages/PolicyIndex.java b/src/org/cacert/gigi/pages/PolicyIndex.java
new file mode 100644 (file)
index 0000000..25e6944
--- /dev/null
@@ -0,0 +1,48 @@
+package org.cacert.gigi.pages;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.PrintWriter;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+public class PolicyIndex extends Page {
+
+    public PolicyIndex() {
+        super("CAcert.org Policies");
+    }
+
+    File root = new File("static/www/policy");
+
+    public static final String DEFAULT_PATH = "/policy";
+
+    @Override
+    public void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
+        PrintWriter out = resp.getWriter();
+        out.println("<ul>");
+        File[] files = root.listFiles();
+        if (files != null) {
+            for (File f : files) {
+                String name = f.getName();
+                if ( !name.endsWith(".html")) {
+                    continue;
+                }
+                String display = name.replaceFirst("\\.html$", "");
+
+                out.print("<li><a href='");
+                out.print(name);
+                out.print("'>");
+                out.print(display);
+                out.println("</a></li>");
+            }
+        }
+        out.println("</ul>");
+    }
+
+    @Override
+    public boolean needsLogin() {
+        return false;
+    }
+
+}
diff --git a/src/org/cacert/gigi/pages/RootCertPage.java b/src/org/cacert/gigi/pages/RootCertPage.java
new file mode 100644 (file)
index 0000000..d714af9
--- /dev/null
@@ -0,0 +1,64 @@
+package org.cacert.gigi.pages;
+
+import java.io.IOException;
+import java.security.KeyStore;
+import java.security.KeyStoreException;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateEncodingException;
+import java.util.HashMap;
+
+import javax.servlet.ServletOutputStream;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.cacert.gigi.util.PEM;
+
+public class RootCertPage extends Page {
+
+    private Certificate root;
+
+    public RootCertPage(KeyStore ks) {
+        super("Root Certificates");
+        try {
+            root = ks.getCertificate("root");
+        } catch (KeyStoreException e) {
+            e.printStackTrace();
+        }
+    }
+
+    @Override
+    public boolean beforeTemplate(HttpServletRequest req, HttpServletResponse resp) throws IOException {
+        if (req.getParameter("pem") != null && root != null) {
+            resp.setContentType("application/x-x509-ca-cert");
+            ServletOutputStream out = resp.getOutputStream();
+            try {
+                out.println(PEM.encode("CERTIFICATE", root.getEncoded()));
+            } catch (CertificateEncodingException e) {
+                e.printStackTrace();
+            }
+            return true;
+        } else if (req.getParameter("cer") != null && root != null) {
+            resp.setContentType("application/x-x509-ca-cert");
+            ServletOutputStream out = resp.getOutputStream();
+            try {
+                out.write(root.getEncoded());
+            } catch (CertificateEncodingException e) {
+                e.printStackTrace();
+            }
+            return true;
+        }
+        return false;
+    }
+
+    @Override
+    public void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
+        getDefaultTemplate().output(resp.getWriter(), getLanguage(req), new HashMap<String, Object>());
+
+    }
+
+    @Override
+    public boolean needsLogin() {
+        return false;
+    }
+
+}
diff --git a/src/org/cacert/gigi/pages/RootCertPage.templ b/src/org/cacert/gigi/pages/RootCertPage.templ
new file mode 100644 (file)
index 0000000..cd65445
--- /dev/null
@@ -0,0 +1,2 @@
+<?=_The Root certificates are available for download here. Choose your preferred format:?><br/>
+<a href="?pem">PEM</a> <a href="?cer">CER</a>
diff --git a/src/org/cacert/gigi/pages/StaticPage.java b/src/org/cacert/gigi/pages/StaticPage.java
new file mode 100644 (file)
index 0000000..be2e75b
--- /dev/null
@@ -0,0 +1,29 @@
+package org.cacert.gigi.pages;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.UnsupportedEncodingException;
+import java.util.HashMap;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.cacert.gigi.output.template.Template;
+
+public class StaticPage extends Page {
+
+    private Template content;
+
+    public StaticPage(String title, InputStream content) throws UnsupportedEncodingException {
+        super(title);
+        this.content = new Template(new InputStreamReader(content, "UTF-8"));
+    }
+
+    @Override
+    public void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
+        HashMap<String, Object> vars = new HashMap<String, Object>();
+        content.output(resp.getWriter(), getLanguage(req), vars);
+    }
+
+}
diff --git a/src/org/cacert/gigi/pages/TestSecure.java b/src/org/cacert/gigi/pages/TestSecure.java
new file mode 100644 (file)
index 0000000..5f8d38c
--- /dev/null
@@ -0,0 +1,19 @@
+package org.cacert.gigi.pages;
+
+import java.io.IOException;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+public class TestSecure extends Page {
+
+    public TestSecure() {
+        super("Secure testpage");
+    }
+
+    @Override
+    public void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
+        resp.getWriter().println("This page is secure.");
+    }
+
+}
diff --git a/src/org/cacert/gigi/pages/Verify.java b/src/org/cacert/gigi/pages/Verify.java
new file mode 100644 (file)
index 0000000..a1d613a
--- /dev/null
@@ -0,0 +1,96 @@
+package org.cacert.gigi.pages;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.cacert.gigi.GigiApiException;
+import org.cacert.gigi.dbObjects.Domain;
+import org.cacert.gigi.dbObjects.EmailAddress;
+import org.cacert.gigi.localisation.Language;
+import org.cacert.gigi.output.template.Form;
+
+public class Verify extends Page {
+
+    private class VerificationForm extends Form {
+
+        private String hash;
+
+        private String type;
+
+        private String id;
+
+        public VerificationForm(HttpServletRequest hsr) {
+            super(hsr, PATH);
+            hash = hsr.getParameter("hash");
+            type = hsr.getParameter("type");
+            id = hsr.getParameter("id");
+        }
+
+        @Override
+        public boolean submit(PrintWriter out, HttpServletRequest req) throws GigiApiException {
+            if ("email".equals(type)) {
+                try {
+                    EmailAddress ea = EmailAddress.getById(Integer.parseInt(id));
+                    ea.verify(hash);
+                    out.println("Email verification completed.");
+                } catch (IllegalArgumentException e) {
+                    out.println(translate(req, "The email address is invalid."));
+                } catch (GigiApiException e) {
+                    e.format(out, getLanguage(req));
+                }
+            } else if ("domain".equals(type)) {
+                try {
+                    Domain ea = Domain.getById(Integer.parseInt(id));
+                    ea.verify(hash);
+                    out.println("Domain verification completed.");
+                } catch (IllegalArgumentException e) {
+                    out.println(translate(req, "The domain address is invalid."));
+                } catch (GigiApiException e) {
+                    e.format(out, getLanguage(req));
+                }
+            }
+            return true;
+        }
+
+        @Override
+        protected void outputContent(PrintWriter out, Language l, Map<String, Object> vars) {
+            vars.put("hash", hash);
+            vars.put("id", id);
+            vars.put("type", type);
+            getDefaultTemplate().output(out, l, vars);
+        }
+
+    }
+
+    public static final String PATH = "/verify";
+
+    public Verify() {
+        super("Verify email");
+    }
+
+    @Override
+    public boolean needsLogin() {
+        return false;
+    }
+
+    @Override
+    public void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException {
+        try {
+            if (Form.getForm(req, VerificationForm.class).submit(resp.getWriter(), req)) {
+            }
+        } catch (GigiApiException e) {
+            e.format(resp.getWriter(), getLanguage(req));
+        }
+    }
+
+    @Override
+    public void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
+        new VerificationForm(req).output(resp.getWriter(), getLanguage(req), new HashMap<String, Object>());
+    }
+
+}
diff --git a/src/org/cacert/gigi/pages/Verify.templ b/src/org/cacert/gigi/pages/Verify.templ
new file mode 100644 (file)
index 0000000..68cfb77
--- /dev/null
@@ -0,0 +1,5 @@
+<?=_Verify this element:?>
+<input type="hidden" name="hash" value="<?=$hash?>"/>
+<input type="hidden" name="type" value="<?=$type?>"/>
+<input type="hidden" name="id" value="<?=$id?>"/>
+<input type="submit" value="OK"/>
diff --git a/src/org/cacert/gigi/pages/account/ChangeForm.java b/src/org/cacert/gigi/pages/account/ChangeForm.java
new file mode 100644 (file)
index 0000000..8e73d3f
--- /dev/null
@@ -0,0 +1,60 @@
+package org.cacert.gigi.pages.account;
+
+import java.io.PrintWriter;
+import java.util.Map;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.cacert.gigi.GigiApiException;
+import org.cacert.gigi.dbObjects.User;
+import org.cacert.gigi.localisation.Language;
+import org.cacert.gigi.output.template.Form;
+import org.cacert.gigi.output.template.Template;
+import org.cacert.gigi.pages.Page;
+
+public class ChangeForm extends Form {
+
+    private User target;
+
+    public ChangeForm(HttpServletRequest hsr, User target) {
+        super(hsr);
+        this.target = target;
+    }
+
+    private static Template t;
+    static {
+        t = new Template(ChangePasswordPage.class.getResource("ChangePasswordForm.templ"));
+    }
+
+    @Override
+    public void outputContent(PrintWriter out, Language l, Map<String, Object> vars) {
+        t.output(out, l, vars);
+    }
+
+    @Override
+    public boolean submit(PrintWriter out, HttpServletRequest req) {
+        String oldpassword = req.getParameter("oldpassword");
+        String p1 = req.getParameter("pword1");
+        String p2 = req.getParameter("pword2");
+        GigiApiException error = new GigiApiException();
+        if (oldpassword == null || p1 == null || p2 == null) {
+            new GigiApiException("All fields are required.").format(out, Page.getLanguage(req));
+            return false;
+        }
+        if ( !p1.equals(p2)) {
+            new GigiApiException("New passwords do not match.").format(out, Page.getLanguage(req));
+            return false;
+        }
+        try {
+            target.changePassword(oldpassword, p1);
+        } catch (GigiApiException e) {
+            error.mergeInto(e);
+        }
+        if ( !error.isEmpty()) {
+            error.format(out, Page.getLanguage(req));
+            return false;
+        }
+        return true;
+    }
+
+}
diff --git a/src/org/cacert/gigi/pages/account/ChangePasswordForm.templ b/src/org/cacert/gigi/pages/account/ChangePasswordForm.templ
new file mode 100644 (file)
index 0000000..9a2a23e
--- /dev/null
@@ -0,0 +1,27 @@
+<table class="wrapper dataTable">
+  <thead>
+  <tr>
+    <th colspan="2" class="title"><?=_Change Pass Phrase?></th>
+  </tr>
+  </thead>
+  <tbody>
+  <tr>
+    <td><?=_Old Pass Phrase?>: </td>
+    <td><input type="password" name="oldpassword"></td>
+  </tr>
+  <tr>
+    <td><?=_New Pass Phrase?><span class="formMandatory">*</span>: </td>
+    <td><input type="password" name="pword1"></td>
+  </tr>
+  <tr>
+    <td><?=_Pass Phrase Again?><span class="formMandatory">*</span>: </td>
+    <td><input type="password" name="pword2"></td>
+  </tr>
+  <tr>
+    <td colspan="2"><span class="formMandatory">*</span><?=_Please note, in the interests of good security, the pass phrase must be made up of an upper case letter, lower case letter, number and symbol (all white spaces at the beginning and end are removed).?></td>
+  </tr>
+  <tr>
+    <td colspan="2"><input type="submit" name="process" value="<?=_Update Pass Phrase?>"></td>
+  </tr>
+  </tbody>
+</table>
diff --git a/src/org/cacert/gigi/pages/account/ChangePasswordPage.java b/src/org/cacert/gigi/pages/account/ChangePasswordPage.java
new file mode 100644 (file)
index 0000000..47a5f91
--- /dev/null
@@ -0,0 +1,31 @@
+package org.cacert.gigi.pages.account;
+
+import java.io.IOException;
+import java.util.HashMap;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.cacert.gigi.output.template.Form;
+import org.cacert.gigi.pages.Page;
+
+public class ChangePasswordPage extends Page {
+
+    public static final String PATH = "/account/password";
+
+    public ChangePasswordPage() {
+        super("Change Password");
+    }
+
+    @Override
+    public void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
+        new ChangeForm(req, getUser(req)).output(resp.getWriter(), getLanguage(req), new HashMap<String, Object>());
+    }
+
+    @Override
+    public void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException {
+        ChangeForm f = Form.getForm(req, ChangeForm.class);
+        f.submit(resp.getWriter(), req);
+    }
+
+}
diff --git a/src/org/cacert/gigi/pages/account/MyDetails.java b/src/org/cacert/gigi/pages/account/MyDetails.java
new file mode 100644 (file)
index 0000000..9de78a2
--- /dev/null
@@ -0,0 +1,43 @@
+package org.cacert.gigi.pages.account;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.HashMap;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.cacert.gigi.output.template.Form;
+import org.cacert.gigi.pages.Page;
+
+public class MyDetails extends Page {
+
+    public MyDetails() {
+        super("My Details");
+    }
+
+    public static final String PATH = "/account/details";
+
+    @Override
+    public void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
+        PrintWriter out = resp.getWriter();
+        HashMap<String, Object> map = new HashMap<String, Object>();
+        MyDetailsForm form = new MyDetailsForm(req, getUser(req));
+        MyListingForm listingForm = new MyListingForm(req, getUser(req));
+        map.put("detailsForm", form);
+        map.put("contactMeForm", listingForm);
+        getDefaultTemplate().output(out, getLanguage(req), map);
+    }
+
+    @Override
+    public void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException {
+        if(req.getParameter("processDetails") != null) {
+            MyDetailsForm form = Form.getForm(req, MyDetailsForm.class);
+            form.submit(resp.getWriter(), req);
+        } else if (req.getParameter("processContact") != null) {
+            MyListingForm form = Form.getForm(req, MyListingForm.class);
+            form.submit(resp.getWriter(), req);
+        }
+        super.doPost(req, resp);
+    }
+}
diff --git a/src/org/cacert/gigi/pages/account/MyDetails.templ b/src/org/cacert/gigi/pages/account/MyDetails.templ
new file mode 100644 (file)
index 0000000..eb86e51
--- /dev/null
@@ -0,0 +1,3 @@
+<?=$detailsForm?>
+<h2><?=_My Listing?></h2>
+<?=$contactMeForm?>
\ No newline at end of file
diff --git a/src/org/cacert/gigi/pages/account/MyDetailsForm.java b/src/org/cacert/gigi/pages/account/MyDetailsForm.java
new file mode 100644 (file)
index 0000000..803bdea
--- /dev/null
@@ -0,0 +1,83 @@
+package org.cacert.gigi.pages.account;
+
+import java.io.PrintWriter;
+import java.util.Map;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.cacert.gigi.GigiApiException;
+import org.cacert.gigi.dbObjects.User;
+import org.cacert.gigi.localisation.Language;
+import org.cacert.gigi.output.DateSelector;
+import org.cacert.gigi.output.template.Form;
+import org.cacert.gigi.output.template.Template;
+import org.cacert.gigi.pages.Page;
+import org.cacert.gigi.util.HTMLEncoder;
+
+public class MyDetailsForm extends Form {
+
+    private static Template assured = new Template(MyDetails.class.getResource("MyDetailsFormAssured.templ"));
+
+    private static Template templ;
+    static {
+        templ = new Template(MyDetailsForm.class.getResource("MyDetailsForm.templ"));
+    }
+
+    private User target;
+
+    private DateSelector ds;
+
+    public MyDetailsForm(HttpServletRequest hsr, User target) {
+        super(hsr);
+        this.target = target;
+        this.ds = new DateSelector("day", "month", "year", target.getDoB());
+    }
+
+    @Override
+    public boolean submit(PrintWriter out, HttpServletRequest req) {
+        try {
+            if (target.getAssurancePoints() == 0) {
+                String newFname = req.getParameter("fname").trim();
+                String newLname = req.getParameter("lname").trim();
+                String newMname = req.getParameter("mname").trim();
+                String newSuffix = req.getParameter("suffix").trim();
+                if (newLname.isEmpty()) {
+                    throw new GigiApiException("Last name cannot be empty.");
+                }
+                target.setFName(newFname);
+                target.setLName(newLname);
+                target.setMName(newMname);
+                target.setSuffix(newSuffix);
+                ds.update(req);
+                target.setDoB(ds.getDate());
+                target.updateUserData();
+            } else {
+                throw new GigiApiException("No change after assurance allowed.");
+            }
+        } catch (GigiApiException e) {
+            e.format(out, Page.getLanguage(req));
+            return false;
+        } catch (NumberFormatException e) {
+            new GigiApiException("Invalid value.").format(out, Page.getLanguage(req));
+            return false;
+        }
+        return false;
+    }
+
+    @Override
+    protected void outputContent(PrintWriter out, Language l, Map<String, Object> vars) {
+        vars.put("fname", HTMLEncoder.encodeHTML(target.getFName()));
+        vars.put("mname", target.getMName() == null ? "" : HTMLEncoder.encodeHTML(target.getMName()));
+        vars.put("lname", HTMLEncoder.encodeHTML(target.getLName()));
+        vars.put("suffix", target.getSuffix() == null ? "" : HTMLEncoder.encodeHTML(target.getSuffix()));
+        vars.put("details", "");
+        if (target.getAssurancePoints() == 0) {
+            vars.put("DoB", ds);
+            templ.output(out, l, vars);
+        } else {
+            vars.put("DoB", DateSelector.getDateFormat().format(target.getDoB()));
+            assured.output(out, l, vars);
+        }
+    }
+
+}
diff --git a/src/org/cacert/gigi/pages/account/MyDetailsForm.templ b/src/org/cacert/gigi/pages/account/MyDetailsForm.templ
new file mode 100644 (file)
index 0000000..ec290a8
--- /dev/null
@@ -0,0 +1,42 @@
+<table class="wrapper dataTable" width="400">
+<thead>
+  <tr>
+    <th colspan="2"><?=_My Details?></th>
+  </tr>
+  </thead>
+  <tbody>
+  <tr>
+    <td width="125"><?=_First Name?>: </td>
+    <td width="125"><input type="text" name="fname" value="<?=$fname?>"></td>
+  </tr>
+  <tr>
+    <td valign="top"><?=_Middle Name(s)?><br>
+      (<?=_optional?>)
+    </td>
+    <td><input type="text" name="mname" value="<?=$mname?>"></td>
+  </tr>
+  <tr>
+    <td><?=_Last Name?>: </td>
+    <td><input type="text" name="lname" value="<?=$lname?>"></td>
+  </tr>
+  <tr>
+    <td><?=_Suffix?><br>
+      (<?=_optional?>)</td>
+    <td><input type="text" name="suffix" value="<?=$suffix?>"></td>
+  </tr>
+  <tr>
+    <td><?=_Date of Birth?><br>
+           (<?=_yyyy-mm-dd?>)</td>
+    <td><?=$DoB?></td>
+  </tr>
+  <tr>
+    <td colspan="2" class="title"><?=_Show account history?></td>
+  </tr>
+  <tr>
+    <td colspan="2" class="title"><?=_View secret question & answers and OTP phrases?></td>
+  </tr>
+  <?=$details?>
+  <tr><td colspan="2"><input type="submit" name="processDetails" value="<?=_Update?>"></td>
+  </tr>
+  </tbody>
+</table>
diff --git a/src/org/cacert/gigi/pages/account/MyDetailsFormAssured.templ b/src/org/cacert/gigi/pages/account/MyDetailsFormAssured.templ
new file mode 100644 (file)
index 0000000..6d73107
--- /dev/null
@@ -0,0 +1,42 @@
+<table class="wrapper dataTable" width="400">
+<thead>
+  <tr>
+    <th colspan="2"><?=_My Details?></th>
+  </tr>
+  </thead>
+  <tbody>
+  <tr>
+    <td width="125"><?=_First Name?>: </td>
+    <td width="125"><?=$fname?></td>
+  </tr>
+  <tr>
+    <td valign="top"><?=_Middle Name(s)?><br>
+      (<?=_optional?>)
+    </td>
+    <td><?=$mname?></td>
+  </tr>
+  <tr>
+    <td><?=_Last Name?>: </td>
+    <td><?=$lname?></td>
+  </tr>
+  <tr>
+    <td><?=_Suffix?><br>
+      (<?=_optional?>)</td>
+    <td><?=$suffix?></td>
+  </tr>
+  <tr>
+    <td><?=_Date of Birth?><br>
+           (<?=_yyyy-mm-dd?>)</td>
+    <td><?=$DoB?></td>
+  </tr>
+  <tr>
+    <td colspan="2" class="title"><?=_Show account history?></td>
+  </tr>
+  <tr>
+    <td colspan="2" class="title"><?=_View secret question & answers and OTP phrases?></td>
+  </tr>
+  <?=$details?>
+  <tr><td colspan="2"><input type="submit" name="processDetails" value="<?=_Update?>"></td>
+  </tr>
+  </tbody>
+</table>
diff --git a/src/org/cacert/gigi/pages/account/MyListingForm.java b/src/org/cacert/gigi/pages/account/MyListingForm.java
new file mode 100644 (file)
index 0000000..b3e8318
--- /dev/null
@@ -0,0 +1,52 @@
+package org.cacert.gigi.pages.account;
+
+import java.io.PrintWriter;
+import java.util.Map;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.cacert.gigi.dbObjects.User;
+import org.cacert.gigi.localisation.Language;
+import org.cacert.gigi.output.template.Form;
+import org.cacert.gigi.output.template.Template;
+
+public class MyListingForm extends Form {
+
+    private static Template template;
+
+    static {
+        template = new Template(MyListingForm.class.getResource("MyListingForm.templ"));
+    }
+
+    private User target;
+
+    public MyListingForm(HttpServletRequest hsr, User target) {
+        super(hsr);
+        this.target = target;
+    }
+
+    @Override
+    public boolean submit(PrintWriter out, HttpServletRequest req) {
+        if (req.getParameter("listme") != null && req.getParameter("contactinfo") != null) {
+            target.setDirectoryListing( !req.getParameter("listme").equals("0"));
+            target.setContactInformation(req.getParameter("contactinfo"));
+            return true;
+        }
+        return false;
+    }
+
+    @Override
+    protected void outputContent(PrintWriter out, Language l, Map<String, Object> vars) {
+        if (target.wantsDirectoryListing()) {
+            vars.put("selected", "selected");
+            vars.put("notSelected", "");
+            vars.put("activeInfo", target.getContactInformation());
+        } else {
+            vars.put("selected", "");
+            vars.put("notSelected", "selected");
+            vars.put("activeInfo", "");
+        }
+        template.output(out, l, vars);
+    }
+
+}
diff --git a/src/org/cacert/gigi/pages/account/MyListingForm.templ b/src/org/cacert/gigi/pages/account/MyListingForm.templ
new file mode 100644 (file)
index 0000000..1eed71e
--- /dev/null
@@ -0,0 +1,21 @@
+<table class="wrapper dataTable">
+  <tr>
+    <th colspan="2"><?=_My Listing?></td>
+  </tr>
+  <tr>
+    <td><?=_Directory Listing?>:</td>
+    <td>
+       <select name="listme">
+               <option value="0" <?=$notSelected?>><?=_I don't want to be listed?></option>
+               <option value="1" <?=$selected?>><?=_I want to be listed?></option>
+       </select>
+    </td>
+  </tr>
+  <tr>
+    <td><?=_Contact information?>:</td>
+    <td><textarea name="contactinfo" cols="40" rows="5" wrap="virtual"><?=$activeInfo?></textarea></td>
+  </tr>
+  <tr>
+    <td colspan="2"><input type="submit" name="processContact" value="<?=_Update?>"></td>
+  </tr>
+</table>
\ No newline at end of file
diff --git a/src/org/cacert/gigi/pages/account/certs/CertificateAdd.java b/src/org/cacert/gigi/pages/account/certs/CertificateAdd.java
new file mode 100644 (file)
index 0000000..4e7da1d
--- /dev/null
@@ -0,0 +1,38 @@
+package org.cacert.gigi.pages.account.certs;
+
+import java.io.IOException;
+import java.util.Collections;
+import java.util.HashMap;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.cacert.gigi.dbObjects.Certificate;
+import org.cacert.gigi.output.template.Form;
+import org.cacert.gigi.pages.Page;
+
+public class CertificateAdd extends Page {
+
+    public static final String PATH = "/account/certs/new";
+
+    public CertificateAdd() {
+        super("Create certificate");
+    }
+
+    @Override
+    public void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
+        new CertificateIssueForm(req).output(resp.getWriter(), getLanguage(req), new HashMap<String, Object>());
+    }
+
+    @Override
+    public void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException {
+        CertificateIssueForm f = Form.getForm(req, CertificateIssueForm.class);
+        if (f.submit(resp.getWriter(), req)) {
+            Certificate c = f.getResult();
+            String ser = c.getSerial();
+            resp.sendRedirect(Certificates.PATH + "/" + ser);
+        }
+        f.output(resp.getWriter(), getLanguage(req), Collections.<String,Object>emptyMap());
+
+    }
+}
diff --git a/src/org/cacert/gigi/pages/account/certs/CertificateDisplay.templ b/src/org/cacert/gigi/pages/account/certs/CertificateDisplay.templ
new file mode 100644 (file)
index 0000000..ace4be5
--- /dev/null
@@ -0,0 +1,6 @@
+<a href='<?=$serial?>.crt'><?=_PEM encoded Certificate?></a><br/>
+<a href='<?=$serial?>.cer'><?=_DER encoded Certificate?></a><br/>
+<a href='<?=$serial?>.cer?install'><?=_Install into browser.?></a><br/>
+<pre>
+<?=$cert?>
+</pre>
diff --git a/src/org/cacert/gigi/pages/account/certs/CertificateIssueForm.java b/src/org/cacert/gigi/pages/account/certs/CertificateIssueForm.java
new file mode 100644 (file)
index 0000000..8a3107f
--- /dev/null
@@ -0,0 +1,199 @@
+package org.cacert.gigi.pages.account.certs;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.security.GeneralSecurityException;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.cacert.gigi.GigiApiException;
+import org.cacert.gigi.dbObjects.Certificate;
+import org.cacert.gigi.dbObjects.Certificate.SubjectAlternateName;
+import org.cacert.gigi.dbObjects.CertificateProfile;
+import org.cacert.gigi.dbObjects.Organisation;
+import org.cacert.gigi.dbObjects.User;
+import org.cacert.gigi.localisation.Language;
+import org.cacert.gigi.output.CertificateValiditySelector;
+import org.cacert.gigi.output.HashAlgorithms;
+import org.cacert.gigi.output.template.Form;
+import org.cacert.gigi.output.template.IterableDataset;
+import org.cacert.gigi.output.template.Template;
+import org.cacert.gigi.pages.Page;
+import org.cacert.gigi.util.RandomToken;
+
+/**
+ * This class represents a form that is used for issuing certificates. This
+ * class uses "sun.security" and therefore needs "-XDignore.symbol.file"
+ */
+public class CertificateIssueForm extends Form {
+
+    private final static Template t = new Template(CertificateIssueForm.class.getResource("CertificateIssueForm.templ"));
+
+    private final static Template tIni = new Template(CertificateAdd.class.getResource("RequestCertificate.templ"));
+
+    private User u;
+
+    private String spkacChallenge;
+
+    private boolean login;
+
+    public CertificateIssueForm(HttpServletRequest hsr) {
+        super(hsr);
+        u = Page.getUser(hsr);
+        spkacChallenge = RandomToken.generateToken(16);
+    }
+
+    private Certificate result;
+
+    public Certificate getResult() {
+        return result;
+    }
+
+    private CertificateRequest cr;
+
+    CertificateValiditySelector issueDate = new CertificateValiditySelector();
+
+    @Override
+    public boolean submit(PrintWriter out, HttpServletRequest req) {
+        String csr = req.getParameter("CSR");
+        String spkac = req.getParameter("SPKAC");
+        try {
+            try {
+                if (csr != null) {
+                    cr = new CertificateRequest(u, csr);
+                    cr.checkKeyStrength(out);
+                } else if (spkac != null) {
+                    cr = new CertificateRequest(u, spkac, spkacChallenge);
+                    cr.checkKeyStrength(out);
+                } else if (cr != null) {
+                    login = "1".equals(req.getParameter("login"));
+                    issueDate.update(req);
+                    GigiApiException error = new GigiApiException();
+
+                    try {
+                        cr.update(req.getParameter("CN"), req.getParameter("hash_alg"), req.getParameter("profile"), //
+                                req.getParameter("org"), req.getParameter("OU"), req.getParameter("SANs"), out, req);
+                    } catch (GigiApiException e) {
+                        error.mergeInto(e);
+                    }
+                    if (req.getParameter("CCA") == null) {
+                        error.mergeInto(new GigiApiException("You need to accept the CCA."));
+                    }
+                    Certificate result = null;
+                    try {
+                        result = cr.draft();
+                    } catch (GigiApiException e) {
+                        error.mergeInto(e);
+                    }
+                    if ( !error.isEmpty() || result == null) {
+                        error.format(out, Page.getLanguage(req));
+                        return false;
+                    }
+                    result.issue(issueDate.getFrom(), issueDate.getTo()).waitFor(60000);
+                    this.result = result;
+                    return true;
+                } else {
+                    throw new GigiApiException("Error no action.");
+                }
+            } catch (IOException e) {
+                e.printStackTrace();
+            } catch (IllegalArgumentException e) {
+                e.printStackTrace();
+                throw new GigiApiException("Certificate Request format is invalid.");
+            } catch (GeneralSecurityException e) {
+                e.printStackTrace();
+                throw new GigiApiException("Certificate Request format is invalid.");
+            } catch (InterruptedException e) {
+                e.printStackTrace();
+            }
+        } catch (GigiApiException e) {
+            e.format(out, Page.getLanguage(req));
+        }
+        return false;
+    }
+
+    @Override
+    public void output(PrintWriter out, Language l, Map<String, Object> vars) {
+        if (cr == null) {
+            HashMap<String, Object> vars2 = new HashMap<String, Object>(vars);
+            vars2.put("csrf", getCSRFToken());
+            vars2.put("csrf_name", getCsrfFieldName());
+            vars2.put("spkacChallenge", spkacChallenge);
+            tIni.output(out, l, vars2);
+            return;
+        } else {
+            super.output(out, l, vars);
+        }
+    }
+
+    @Override
+    protected void outputContent(PrintWriter out, Language l, Map<String, Object> vars) {
+        HashMap<String, Object> vars2 = new HashMap<String, Object>(vars);
+        vars2.put("CCA", "<a href='/policy/CAcertCommunityAgreement.html'>CCA</a>");
+
+        StringBuffer content = new StringBuffer();
+        for (SubjectAlternateName SAN : cr.getSANs()) {
+            content.append(SAN.getType().toString().toLowerCase());
+            content.append(':');
+            content.append(SAN.getName());
+            content.append('\n');
+        }
+
+        vars2.put("CN", cr.getCN());
+        vars2.put("department", cr.getOu());
+        vars2.put("validity", issueDate);
+        vars2.put("emails", content.toString());
+        vars2.put("hashs", new HashAlgorithms(cr.getSelectedDigest()));
+        vars2.put("profiles", new IterableDataset() {
+
+            int i = 1;
+
+            @Override
+            public boolean next(Language l, Map<String, Object> vars) {
+                CertificateProfile cp;
+                do {
+                    cp = CertificateProfile.getById(i++);
+                    if (cp == null) {
+                        return false;
+                    }
+                } while ( !u.canIssue(cp));
+
+                if (cp.getId() == cr.getProfile().getId()) {
+                    vars.put("selected", " selected");
+                } else {
+                    vars.put("selected", "");
+                }
+                vars.put("key", cp.getKeyName());
+                vars.put("name", cp.getVisibleName());
+                return true;
+            }
+        });
+        final List<Organisation> orgs = u.getOrganisations();
+        vars2.put("orga", orgs.size() == 0 ? null : new IterableDataset() {
+
+            Iterator<Organisation> iter = orgs.iterator();
+
+            @Override
+            public boolean next(Language l, Map<String, Object> vars) {
+                if ( !iter.hasNext()) {
+                    return false;
+                }
+                Organisation orga = iter.next();
+                vars.put("key", orga.getId());
+                vars.put("name", orga.getName());
+                if (orga == cr.getOrg()) {
+                    vars.put("selected", " selected");
+                } else {
+                    vars.put("selected", "");
+                }
+                return true;
+            }
+        });
+
+        t.output(out, l, vars2);
+    }
+}
diff --git a/src/org/cacert/gigi/pages/account/certs/CertificateIssueForm.templ b/src/org/cacert/gigi/pages/account/certs/CertificateIssueForm.templ
new file mode 100644 (file)
index 0000000..ee749ab
--- /dev/null
@@ -0,0 +1,111 @@
+<h3><?=_CAcert Certificate Acceptable Use Policy?></h3>
+<p><?=_I hereby represent that I am fully authorized by the owner of the information contained in the CSR sent to CAcert Inc. to apply for an Digital Certificate for secure and authenticated electronic transactions. I understand that a digital certificate serves to identify the Subscriber for the purposes of electronic communication and that the management of the private keys associated with such certificates is the responsibility of the subscriber's technical staff and/or contractors.?></p>
+
+<p><?=_CAcert Inc.'s public certification services are governed by a CPS as amended from time to time which is incorporated into this Agreement by reference. The Subscriber will use the SSL Server Certificate in accordance with CAcert Inc.'s CPS and supporting documentation published at?> <a href="http://www.cacert.org/cps.php">http://www.cacert.org/cps.php</a></p>
+
+<p><?=_If the Subscriber's name and/or domain name registration change the subscriber will immediately inform CAcert Inc. who shall revoke the digital certificate. When the Digital Certificate expires or is revoked the company will permanently remove the certificate from the server on which it is installed and will not use it for any purpose thereafter. The person responsible for key management and security is fully authorized to install and utilize the certificate to represent this organization's electronic presence.?></p>
+
+<table align="center" valign="middle" border="0" cellspacing="0" cellpadding="0" class="wrapper dataTable">
+  <thead>
+  <tr>
+    <th colspan="2" class="title"><?=_New Certificate?></th>
+  </tr>
+  </thead>
+  <tbody>
+  <tr>
+    <td>
+    <label for='profile'><?=_Key type?></label>
+    </td>
+    <td>
+    <select name="profile" id='profile'>
+    <? foreach($profiles) { ?>
+      <option value="<?=$key?>"<?=$!selected?>><?=$name?></option>
+    <? } ?>
+    </select>
+    </td>
+  </tr>
+  <? if($orga) { ?>
+  <tr> 
+    <td>
+      <label for='org'><?=_Organisation?></label>
+    </td>  
+    <td><select name="org" id='org'>
+      <option value="-1"><?=_(none)?></option>
+    <? foreach($orga) { ?>
+      <option value="<?=$key?>"<?=$!selected?>><?=$name?></option>
+    <? } ?>
+    </select></td>
+  </tr>
+  <? } ?>
+  <tr>
+    <td>
+    <label for='CN'><?=_Your name?></label>
+    </td>
+    <td><input type='text' id='CN' name='CN' value='<?=$CN?>'/></td>
+  </tr>
+  <tr>
+    <td>SANs</td>
+    <td align="left"><textarea rows='5' name='SANs' placeholder="dns:my.domain.example.com, dns:*.example.com, email:my.email@example.com (or newline separated)"><?=$emails?></textarea></td>
+  </tr>
+  <? if($orga) { ?>
+  <tr>
+    <td><?=_Departement?></td>
+    <td align="left"><input type='text' name='OU' value='<?=$department?>'/></td>
+  </tr>
+  <? } ?>
+  <tr class="expertoff">
+    <td class='check'>
+      <input type="checkbox" id="expertbox" name="expertbox"/>
+    </td>
+    <td align="left">
+      <label for="expertbox"><?=_Show advanced options?></label>
+    </td>
+  </tr>
+
+  <tr class="expert">
+       <td><?=_Hash algorithm for signing?></td>
+    <td class='radio'>
+      <? foreach($hashs) { ?>
+        <input type="radio" id="hash_alg_<?=$algorithm?>" name="hash_alg" value="<?=$algorithm?>"<?=$!checked?>/>
+        <label for="hash_alg_<?=$algorithm?>"><span class='name'><?=$name?></span><? if($info) { ?> <span class='addinfo'> <?=$info?></span><? } ?></label><div class='elements'></div>
+      <? } ?>
+    </td>
+  </tr>
+  <tr class="expert">
+    <td><?=_Valid period?></td>
+    <td>
+        <?=$validity?>
+    </td>
+  </tr>
+    <tr>
+    <td class='check'>
+      <input type="checkbox" id="CCA" name="CCA" />
+    </td>
+    <td align="left">
+      <label for="CCA"><strong><?=_I accept the CAcert Community Agreement ($!CCA).?> </strong><br />
+      <?=_Please note: You need to accept the CCA to proceed.?></label>
+    </td>
+  </tr>
+  <tr><td colspan='2'>&nbsp;</td></tr>
+
+  <tr>
+    <td class='check'>
+      <input type="checkbox" id="login" name="login" value="1" checked="checked" />
+    </td>
+    <td align="left">
+      <label for="login"><?=_Enable certificate login with this certificate?><br />
+      <?=_By allowing certificate login, this certificate can be used to login into this account at https://secure.cacert.org/ .?></label>
+    </td>
+  </tr>
+  <tr>
+    <td colspan="2" align="left">
+      <label for="description"><?=_Optional comment, only used in the certificate overview?></label><br />
+      <input type="text" id="description" name="description" maxlength="100" size="100" />
+    </td>
+  </tr>
+
+  <tr>
+    <td colspan="2"><input type="submit" name="process" value="<?=_Issue Certificate?>" /></td>
+  </tr>
+  </tbody>
+</table>
diff --git a/src/org/cacert/gigi/pages/account/certs/CertificateRequest.java b/src/org/cacert/gigi/pages/account/certs/CertificateRequest.java
new file mode 100644 (file)
index 0000000..0f1620f
--- /dev/null
@@ -0,0 +1,394 @@
+package org.cacert.gigi.pages.account.certs;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.security.GeneralSecurityException;
+import java.security.PublicKey;
+import java.security.interfaces.DSAPublicKey;
+import java.security.interfaces.ECPublicKey;
+import java.security.interfaces.RSAPublicKey;
+import java.util.Arrays;
+import java.util.Base64;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashSet;
+import java.util.Set;
+import java.util.TreeSet;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.cacert.gigi.GigiApiException;
+import org.cacert.gigi.crypto.SPKAC;
+import org.cacert.gigi.dbObjects.Certificate;
+import org.cacert.gigi.dbObjects.Certificate.CSRType;
+import org.cacert.gigi.dbObjects.Certificate.SANType;
+import org.cacert.gigi.dbObjects.Certificate.SubjectAlternateName;
+import org.cacert.gigi.dbObjects.CertificateProfile;
+import org.cacert.gigi.dbObjects.Digest;
+import org.cacert.gigi.dbObjects.Organisation;
+import org.cacert.gigi.dbObjects.User;
+import org.cacert.gigi.output.template.Scope;
+import org.cacert.gigi.output.template.SprintfCommand;
+import org.cacert.gigi.util.PEM;
+
+import sun.security.pkcs.PKCS9Attribute;
+import sun.security.pkcs10.PKCS10;
+import sun.security.pkcs10.PKCS10Attribute;
+import sun.security.pkcs10.PKCS10Attributes;
+import sun.security.util.DerInputStream;
+import sun.security.util.DerValue;
+import sun.security.util.ObjectIdentifier;
+import sun.security.x509.AVA;
+import sun.security.x509.AlgorithmId;
+import sun.security.x509.CertificateExtensions;
+import sun.security.x509.DNSName;
+import sun.security.x509.ExtendedKeyUsageExtension;
+import sun.security.x509.Extension;
+import sun.security.x509.GeneralName;
+import sun.security.x509.GeneralNameInterface;
+import sun.security.x509.GeneralNames;
+import sun.security.x509.PKIXExtensions;
+import sun.security.x509.RDN;
+import sun.security.x509.RFC822Name;
+import sun.security.x509.SubjectAlternativeNameExtension;
+import sun.security.x509.X500Name;
+
+public class CertificateRequest {
+
+    public static final String DEFAULT_CN = "CAcert WoT User";
+
+    public static final ObjectIdentifier OID_KEY_USAGE_SSL_SERVER = ObjectIdentifier.newInternal(new int[] {
+            1, 3, 6, 1, 5, 5, 7, 3, 1
+    });
+
+    public static final ObjectIdentifier OID_KEY_USAGE_SSL_CLIENT = ObjectIdentifier.newInternal(new int[] {
+            1, 3, 6, 1, 5, 5, 7, 3, 2
+    });
+
+    public static final ObjectIdentifier OID_KEY_USAGE_CODESIGN = ObjectIdentifier.newInternal(new int[] {
+            1, 3, 6, 1, 5, 5, 7, 3, 3
+    });
+
+    public static final ObjectIdentifier OID_KEY_USAGE_EMAIL_PROTECTION = ObjectIdentifier.newInternal(new int[] {
+            1, 3, 6, 1, 5, 5, 7, 3, 4
+    });
+
+    public static final ObjectIdentifier OID_KEY_USAGE_TIMESTAMP = ObjectIdentifier.newInternal(new int[] {
+            1, 3, 6, 1, 5, 5, 7, 3, 8
+    });
+
+    public static final ObjectIdentifier OID_KEY_USAGE_OCSP = ObjectIdentifier.newInternal(new int[] {
+            1, 3, 6, 1, 5, 5, 7, 3, 9
+    });
+
+    private CSRType csrType;
+
+    private final PublicKey pk;
+
+    private String csr;
+
+    public String CN = DEFAULT_CN;
+
+    private Set<SubjectAlternateName> SANs;
+
+    private Digest selectedDigest = Digest.getDefault();
+
+    private CertificateProfile profile = CertificateProfile.getById(1);
+
+    private String ou = "";
+
+    private Organisation org = null;
+
+    private User u;
+
+    private String pDNS, pMail;
+
+    public CertificateRequest(User issuer, String csr) throws IOException, GeneralSecurityException, GigiApiException {
+        u = issuer;
+        byte[] data = PEM.decode("(NEW )?CERTIFICATE REQUEST", csr);
+        PKCS10 parsed = new PKCS10(data);
+        PKCS10Attributes atts = parsed.getAttributes();
+
+        TreeSet<SubjectAlternateName> SANs = new TreeSet<>();
+        for (RDN r : parsed.getSubjectName().rdns()) {
+            for (AVA a : r.avas()) {
+                if (a.getObjectIdentifier().equals((Object) PKCS9Attribute.EMAIL_ADDRESS_OID)) {
+                    SANs.add(new SubjectAlternateName(SANType.EMAIL, a.getValueString()));
+                } else if (a.getObjectIdentifier().equals((Object) X500Name.commonName_oid)) {
+                    String value = a.getValueString();
+                    if (value.contains(".") && !value.contains(" ")) {
+                        SANs.add(new SubjectAlternateName(SANType.DNS, value));
+                    } else {
+                        CN = value;
+                    }
+                } else if (a.getObjectIdentifier().equals((Object) PKIXExtensions.SubjectAlternativeName_Id)) {
+                    // TODO? parse invalid SANs
+                }
+            }
+        }
+
+        for (PKCS10Attribute b : atts.getAttributes()) {
+
+            if ( !b.getAttributeId().equals((Object) PKCS9Attribute.EXTENSION_REQUEST_OID)) {
+                // unknown attrib
+                continue;
+            }
+
+            for (Extension c : ((CertificateExtensions) b.getAttributeValue()).getAllExtensions()) {
+                if (c instanceof SubjectAlternativeNameExtension) {
+
+                    SubjectAlternativeNameExtension san = (SubjectAlternativeNameExtension) c;
+                    GeneralNames obj = san.get(SubjectAlternativeNameExtension.SUBJECT_NAME);
+                    for (int i = 0; i < obj.size(); i++) {
+                        GeneralName generalName = obj.get(i);
+                        GeneralNameInterface peeled = generalName.getName();
+                        if (peeled instanceof DNSName) {
+                            SANs.add(new SubjectAlternateName(SANType.DNS, ((DNSName) peeled).getName()));
+                        } else if (peeled instanceof RFC822Name) {
+                            SANs.add(new SubjectAlternateName(SANType.EMAIL, ((RFC822Name) peeled).getName()));
+                        }
+                    }
+                } else if (c instanceof ExtendedKeyUsageExtension) {
+                    ExtendedKeyUsageExtension ekue = (ExtendedKeyUsageExtension) c;
+                    for (String s : ekue.getExtendedKeyUsage()) {
+                        if (s.equals(OID_KEY_USAGE_SSL_SERVER.toString())) {
+                            // server
+                            profile = CertificateProfile.getByName("server");
+                        } else if (s.equals(OID_KEY_USAGE_SSL_CLIENT.toString())) {
+                            // client
+                            profile = CertificateProfile.getByName("client");
+                        } else if (s.equals(OID_KEY_USAGE_CODESIGN.toString())) {
+                            // code sign
+                        } else if (s.equals(OID_KEY_USAGE_EMAIL_PROTECTION.toString())) {
+                            // emailProtection
+                            profile = CertificateProfile.getByName("mail");
+                        } else if (s.equals(OID_KEY_USAGE_TIMESTAMP.toString())) {
+                            // timestamp
+                        } else if (s.equals(OID_KEY_USAGE_OCSP.toString())) {
+                            // OCSP
+                        }
+                    }
+                } else {
+                    // Unknown requested extension
+                }
+            }
+
+        }
+        this.SANs = SANs;
+        pk = parsed.getSubjectPublicKeyInfo();
+        String sign = getSignatureAlgorithm(data);
+        guessDigest(sign);
+
+        this.csr = csr;
+        this.csrType = CSRType.CSR;
+    }
+
+    public CertificateRequest(User issuer, String spkac, String spkacChallenge) throws IOException, GigiApiException, GeneralSecurityException {
+        u = issuer;
+        String cleanedSPKAC = spkac.replaceAll("[\r\n]", "");
+        byte[] data = Base64.getDecoder().decode(cleanedSPKAC);
+        SPKAC parsed = new SPKAC(data);
+        if ( !parsed.getChallenge().equals(spkacChallenge)) {
+            throw new GigiApiException("Challenge mismatch");
+        }
+        pk = parsed.getPubkey();
+        String sign = getSignatureAlgorithm(data);
+        guessDigest(sign);
+        this.SANs = new HashSet<>();
+        this.csr = "SPKAC=" + cleanedSPKAC;
+        this.csrType = CSRType.SPKAC;
+
+    }
+
+    private static String getSignatureAlgorithm(byte[] data) throws IOException {
+        DerInputStream in = new DerInputStream(data);
+        DerValue[] seq = in.getSequence(3);
+        return AlgorithmId.parse(seq[1]).getName();
+    }
+
+    private void guessDigest(String sign) {
+        if (sign.toLowerCase().startsWith("sha512")) {
+            selectedDigest = Digest.SHA512;
+        } else if (sign.toLowerCase().startsWith("sha384")) {
+            selectedDigest = Digest.SHA384;
+        }
+    }
+
+    public void checkKeyStrength(PrintWriter out) {
+        out.println("Type: " + pk.getAlgorithm() + "<br/>");
+        if (pk instanceof RSAPublicKey) {
+            out.println("Exponent: " + ((RSAPublicKey) pk).getPublicExponent() + "<br/>");
+            out.println("Length: " + ((RSAPublicKey) pk).getModulus().bitLength());
+        } else if (pk instanceof DSAPublicKey) {
+            DSAPublicKey dpk = (DSAPublicKey) pk;
+            out.println("Length: " + dpk.getY().bitLength() + "<br/>");
+            out.println(dpk.getParams());
+        } else if (pk instanceof ECPublicKey) {
+            ECPublicKey epk = (ECPublicKey) pk;
+            out.println("Length-x: " + epk.getW().getAffineX().bitLength() + "<br/>");
+            out.println("Length-y: " + epk.getW().getAffineY().bitLength() + "<br/>");
+            out.println(epk.getParams().getCurve());
+        }
+    }
+
+    private TreeSet<SubjectAlternateName> parseSANBox(String SANs) {
+        String[] SANparts = SANs.split("[\r\n]+|, *");
+        TreeSet<SubjectAlternateName> parsedNames = new TreeSet<>();
+        for (String SANline : SANparts) {
+            String[] parts = SANline.split(":", 2);
+            if (parts.length == 1) {
+                if (parts[0].trim().equals("")) {
+                    continue;
+                }
+                if (parts[0].contains("@")) {
+                    parsedNames.add(new SubjectAlternateName(SANType.EMAIL, parts[0]));
+                } else {
+                    parsedNames.add(new SubjectAlternateName(SANType.DNS, parts[0]));
+                }
+                continue;
+            }
+            try {
+                SANType t = Certificate.SANType.valueOf(parts[0].toUpperCase());
+                if (t == null) {
+                    continue;
+                }
+                parsedNames.add(new SubjectAlternateName(t, parts[1]));
+            } catch (IllegalArgumentException e) {
+                // invalid enum type
+                continue;
+            }
+        }
+        return parsedNames;
+    }
+
+    public Set<SubjectAlternateName> getSANs() {
+        return SANs;
+    }
+
+    public String getCN() {
+        return CN;
+    }
+
+    public Organisation getOrg() {
+        return org;
+    }
+
+    public String getOu() {
+        return ou;
+    }
+
+    public Digest getSelectedDigest() {
+        return selectedDigest;
+    }
+
+    public CertificateProfile getProfile() {
+        return profile;
+    }
+
+    public boolean update(String CNin, String hashAlg, String profileStr, String newOrgStr, String ou, String SANsStr, PrintWriter out, HttpServletRequest req) throws GigiApiException {
+        GigiApiException error = new GigiApiException();
+        this.CN = CNin;
+        if (hashAlg != null) {
+            selectedDigest = Digest.valueOf(hashAlg);
+        }
+        this.profile = CertificateProfile.getByName(profileStr);
+        if (newOrgStr != null) {
+            Organisation neworg = Organisation.getById(Integer.parseInt(newOrgStr));
+            if (neworg == null || u.getOrganisations().contains(neworg)) {
+                org = neworg;
+            } else {
+                error.mergeInto(new GigiApiException("Selected Organisation is not part of your account."));
+            }
+        }
+        this.ou = ou;
+
+        boolean server = profile.getKeyName().equals("server");
+        SANs = verifySANs(error, server, parseSANBox(SANsStr));
+
+        if ( !error.isEmpty()) {
+            throw error;
+        }
+        return true;
+    }
+
+    private Set<SubjectAlternateName> verifySANs(GigiApiException error, boolean server, Set<SubjectAlternateName> sANs2) {
+        Set<SubjectAlternateName> filteredSANs = new LinkedHashSet<>();
+        for (SubjectAlternateName san : sANs2) {
+            if (san.getType() == SANType.DNS) {
+                if (u.isValidDomain(san.getName()) && server) {
+                    if (pDNS == null) {
+                        pDNS = san.getName();
+                    }
+                    filteredSANs.add(san);
+                    continue;
+                }
+            } else if (san.getType() == SANType.EMAIL) {
+                if (u.isValidEmail(san.getName()) && !server) {
+                    if (pMail == null) {
+                        pMail = san.getName();
+                    }
+                    filteredSANs.add(san);
+                    continue;
+                }
+            }
+            HashMap<String, Object> vars = new HashMap<>();
+            vars.put("SAN", san.getType().toString().toLowerCase() + ":" + san.getName());
+            error.mergeInto(new GigiApiException(new Scope(new SprintfCommand(//
+                    "The requested Subject alternate name \"%s\" has been removed.", Arrays.asList("$SAN")), vars)));
+        }
+        return filteredSANs;
+    }
+
+    public Certificate draft() throws GigiApiException {
+
+        GigiApiException error = new GigiApiException();
+        if ( !u.canIssue(this.profile)) {
+            this.profile = CertificateProfile.getById(1);
+            error.mergeInto(new GigiApiException("Certificate Profile is invalid."));
+            throw error;
+        }
+
+        boolean server = profile.getKeyName().equals("server");
+
+        HashMap<String, String> subject = new HashMap<>();
+        if (server) {
+            if (pDNS != null) {
+                subject.put("CN", pDNS);
+            } else {
+                error.mergeInto(new GigiApiException("Server Certificates require a DNS name."));
+            }
+            if (pMail != null) {
+                error.mergeInto(new GigiApiException("No email is included in this certificate."));
+            }
+            if ( !CN.equals("")) {
+                CN = "";
+                this.CN = "";
+                error.mergeInto(new GigiApiException("No real name is included in this certificate. The real name, you entered will be ignored."));
+            }
+        } else {
+            if ( !u.isValidName(CN) && !CN.equals(DEFAULT_CN)) {
+                this.CN = DEFAULT_CN;
+                error.mergeInto(new GigiApiException("The name entered, does not match the details in your account. You cannot issue certificates with this name. Enter a name that matches the one that has been assured in your account."));
+            }
+
+            subject.put("CN", this.CN);
+            if (pMail != null) {
+                subject.put("EMAIL", pMail);
+            }
+        }
+        this.SANs = verifySANs(error, server, SANs);
+        if (org != null) {
+            subject.put("O", org.getName());
+            subject.put("C", org.getState());
+            subject.put("ST", org.getProvince());
+            subject.put("L", org.getCity());
+            subject.put("OU", ou);
+        }
+
+        if ( !error.isEmpty()) {
+            throw error;
+        }
+        return new Certificate(u, subject, selectedDigest.toString(), //
+                this.csr, this.csrType, profile, SANs.toArray(new SubjectAlternateName[SANs.size()]));
+    }
+}
diff --git a/src/org/cacert/gigi/pages/account/certs/Certificates.java b/src/org/cacert/gigi/pages/account/certs/Certificates.java
new file mode 100644 (file)
index 0000000..e41a1b2
--- /dev/null
@@ -0,0 +1,117 @@
+package org.cacert.gigi.pages.account.certs;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.net.URLEncoder;
+import java.security.GeneralSecurityException;
+import java.security.cert.X509Certificate;
+import java.util.HashMap;
+
+import javax.servlet.ServletOutputStream;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.cacert.gigi.dbObjects.Certificate;
+import org.cacert.gigi.dbObjects.User;
+import org.cacert.gigi.output.CertificateIterable;
+import org.cacert.gigi.output.template.Template;
+import org.cacert.gigi.pages.LoginPage;
+import org.cacert.gigi.pages.Page;
+import org.cacert.gigi.util.PEM;
+
+public class Certificates extends Page {
+
+    private Template certDisplay = new Template(Certificates.class.getResource("CertificateDisplay.templ"));
+
+    public static final String PATH = "/account/certs";
+
+    public Certificates() {
+        super("Certificates");
+    }
+
+    @Override
+    public boolean beforeTemplate(HttpServletRequest req, HttpServletResponse resp) throws IOException {
+
+        String pi = req.getPathInfo().substring(PATH.length());
+        if (pi.length() == 0) {
+            return false;
+        }
+        pi = pi.substring(1);
+        boolean crt = false;
+        boolean cer = false;
+        resp.setContentType("application/pkix-cert");
+        if (pi.endsWith(".crt")) {
+            crt = true;
+            pi = pi.substring(0, pi.length() - 4);
+        } else if (pi.endsWith(".cer")) {
+            if (req.getParameter("install") != null) {
+                resp.setContentType("application/x-x509-user-cert");
+            }
+            cer = true;
+            pi = pi.substring(0, pi.length() - 4);
+        } else if (pi.endsWith(".cer")) {
+            cer = true;
+            pi = pi.substring(0, pi.length() - 4);
+        }
+        String serial = pi;
+        try {
+            Certificate c = Certificate.getBySerial(serial);
+            if (c == null || getUser(req).getId() != c.getOwner().getId()) {
+                resp.sendError(404);
+                return true;
+            }
+            X509Certificate cert = c.cert();
+            if ( !crt && !cer) {
+                return false;
+            }
+            ServletOutputStream out = resp.getOutputStream();
+            if (crt) {
+                out.println(PEM.encode("CERTIFICATE", cert.getEncoded()));
+            } else if (cer) {
+                out.write(cert.getEncoded());
+            }
+        } catch (IllegalArgumentException e) {
+            resp.sendError(404);
+            return true;
+        } catch (GeneralSecurityException e) {
+            resp.sendError(404);
+            return true;
+        }
+
+        return true;
+    }
+
+    private Template certTable = new Template(CertificateIterable.class.getResource("CertificateTable.templ"));
+
+    @Override
+    public void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
+        PrintWriter out = resp.getWriter();
+        String pi = req.getPathInfo().substring(PATH.length());
+        if (pi.length() != 0) {
+            pi = pi.substring(1);
+
+            String serial = pi;
+            Certificate c = Certificate.getBySerial(serial);
+            if (c == null || LoginPage.getUser(req).getId() != c.getOwner().getId()) {
+                resp.sendError(404);
+                return;
+            }
+            HashMap<String, Object> vars = new HashMap<>();
+            vars.put("serial", URLEncoder.encode(serial, "UTF-8"));
+            try {
+                vars.put("cert", c.cert());
+            } catch (GeneralSecurityException e) {
+                e.printStackTrace();
+            }
+            certDisplay.output(out, getLanguage(req), vars);
+
+            return;
+        }
+
+        HashMap<String, Object> vars = new HashMap<String, Object>();
+        User us = LoginPage.getUser(req);
+        vars.put("certs", new CertificateIterable(us.getCertificates(false)));
+        certTable.output(out, getLanguage(req), vars);
+    }
+
+}
diff --git a/src/org/cacert/gigi/pages/account/certs/RequestCertificate.templ b/src/org/cacert/gigi/pages/account/certs/RequestCertificate.templ
new file mode 100644 (file)
index 0000000..9a448ba
--- /dev/null
@@ -0,0 +1,47 @@
+<form method="post">
+<table class="wrapper dataTable">
+  <thead>
+  <tr>
+    <th colspan="2" class="title"><?=_New Certificate from CSR?></th>
+  </tr>
+  </thead>
+  <tbody>
+  <tr>
+    <td><?=_I have a CSR! Paste it here:?></td>
+    <td>
+      <textarea name="CSR"></textarea>
+    </td>
+  </tr>
+  <tr>
+    <td colspan="2">
+      <input type="submit" name="process" value="<?=_Next?>" />
+      <input type='hidden' name='<?=$csrf_name?>' value='<?=$csrf?>'>
+    </td>
+  </tr>
+  </tbody>
+</table>
+</form>
+<br>
+<form method="post">
+<table class="wrapper dataTable">
+  <thead>
+  <tr>
+    <th colspan="2" class="title"><?=_New Certificate from newly generatey Key (SPKAC)?></th>
+  </tr>
+  </thead>
+  <tbody>
+  <tr>
+    <td><?=_I do not have a CSR.?></td>
+    <td align="left">
+     <keygen name="SPKAC" challenge="<?=$spkacChallenge?>"/>
+    </td>
+  </tr>
+  <tr>
+    <td colspan="2">
+     <input type="submit" name="process" value="<?=_Next?>" />
+     <input type='hidden' name='<?=$csrf_name?>' value='<?=$csrf?>'>
+    </td>
+  </tr>
+  </tbody>
+</table>
+</form>
diff --git a/src/org/cacert/gigi/pages/account/domain/DomainAddForm.java b/src/org/cacert/gigi/pages/account/domain/DomainAddForm.java
new file mode 100644 (file)
index 0000000..6244e9d
--- /dev/null
@@ -0,0 +1,63 @@
+package org.cacert.gigi.pages.account.domain;
+
+import java.io.PrintWriter;
+import java.util.Map;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.cacert.gigi.GigiApiException;
+import org.cacert.gigi.dbObjects.Domain;
+import org.cacert.gigi.dbObjects.User;
+import org.cacert.gigi.localisation.Language;
+import org.cacert.gigi.output.template.Form;
+import org.cacert.gigi.output.template.Outputable;
+import org.cacert.gigi.output.template.Template;
+import org.cacert.gigi.pages.Page;
+
+public class DomainAddForm extends Form {
+
+    private static final Template t = new Template(DomainManagementForm.class.getResource("DomainAddForm.templ"));
+
+    private User target;
+
+    PingConfigForm pcf;
+
+    public DomainAddForm(HttpServletRequest hsr, User target) throws GigiApiException {
+        super(hsr);
+        this.target = target;
+        pcf = new PingConfigForm(hsr, null);
+    }
+
+    @Override
+    public boolean submit(PrintWriter out, HttpServletRequest req) {
+        try {
+            String parameter = req.getParameter("newdomain");
+            if (parameter.trim().isEmpty()) {
+                throw new GigiApiException("No domain inserted.");
+            }
+            Domain d = new Domain(target, parameter);
+            d.insert();
+            pcf.setTarget(d);
+            pcf.submit(out, req);
+            return true;
+        } catch (NumberFormatException e) {
+            new GigiApiException("A number could not be parsed").format(out, Page.getLanguage(req));
+            return false;
+        } catch (GigiApiException e) {
+            e.format(out, Page.getLanguage(req));
+            return false;
+        }
+    }
+
+    @Override
+    protected void outputContent(PrintWriter out, Language l, Map<String, Object> vars) {
+        vars.put("pingconfig", new Outputable() {
+
+            @Override
+            public void output(PrintWriter out, Language l, Map<String, Object> vars) {
+                pcf.outputEmbeddableContent(out, l, vars);
+            }
+        });
+        t.output(out, l, vars);
+    }
+}
diff --git a/src/org/cacert/gigi/pages/account/domain/DomainAddForm.templ b/src/org/cacert/gigi/pages/account/domain/DomainAddForm.templ
new file mode 100644 (file)
index 0000000..9c6de6b
--- /dev/null
@@ -0,0 +1,17 @@
+<table align="center" valign="middle" border="0" cellspacing="0" cellpadding="0" class="wrapper dataTable">
+  <thead>
+  <tr>
+    <th colspan="2" class="title"><?=_Add Domain?></th>
+  </tr>
+  </thead>
+<tbody>
+  <tr>
+    <td><?=_Domain?> </td>
+    <td><input type="text" name="newdomain" value=""> (<?=_In the following:?> <span class='exampleDomainPlace'>example.org</span>)</td>
+  </tr>
+  <?=$pingconfig?>
+  <tr>
+    <td colspan="2"><input type="submit" name="adddomain" value="<?=_I own or am authorised to control this domain?>"></td>
+  </tr>
+</tbody>
+</table>
diff --git a/src/org/cacert/gigi/pages/account/domain/DomainManagementForm.java b/src/org/cacert/gigi/pages/account/domain/DomainManagementForm.java
new file mode 100644 (file)
index 0000000..617d3d6
--- /dev/null
@@ -0,0 +1,74 @@
+package org.cacert.gigi.pages.account.domain;
+
+import java.io.PrintWriter;
+import java.util.Map;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.cacert.gigi.GigiApiException;
+import org.cacert.gigi.dbObjects.Domain;
+import org.cacert.gigi.dbObjects.User;
+import org.cacert.gigi.localisation.Language;
+import org.cacert.gigi.output.template.Form;
+import org.cacert.gigi.output.template.IterableDataset;
+import org.cacert.gigi.output.template.Template;
+import org.cacert.gigi.pages.Page;
+import org.cacert.gigi.util.ServerConstants;
+
+public class DomainManagementForm extends Form {
+
+    private static final Template t = new Template(DomainManagementForm.class.getResource("DomainManagementForm.templ"));
+
+    private User target;
+
+    public DomainManagementForm(HttpServletRequest hsr, User target) {
+        super(hsr);
+        this.target = target;
+    }
+
+    @Override
+    public boolean submit(PrintWriter out, HttpServletRequest req) {
+        try {
+            String[] dels = req.getParameterValues("delid[]");
+            Domain[] usDomains = target.getDomains();
+            for (int i = 0; i < dels.length; i++) {
+                int delId = Integer.parseInt(dels[i]);
+                for (int j = 0; j < usDomains.length; j++) {
+                    if (usDomains[j].getId() == delId) {
+                        usDomains[j].delete();
+                        break;
+                    }
+                }
+            }
+        } catch (GigiApiException e) {
+            e.format(out, Page.getLanguage(req));
+            return false;
+        }
+        return true;
+    }
+
+    @Override
+    protected void outputContent(PrintWriter out, Language l, Map<String, Object> vars) {
+        final Domain[] doms = (Domain[]) vars.get("doms");
+        IterableDataset dts = new IterableDataset() {
+
+            private int point = 0;
+
+            @Override
+            public boolean next(Language l, Map<String, Object> vars) {
+                if (point >= doms.length) {
+                    return false;
+                }
+                Domain domain = doms[point];
+                vars.put("id", domain.getId());
+                vars.put("domainhref", "https://" + ServerConstants.getWwwHostNamePortSecure() + DomainOverview.PATH + domain.getId());
+                vars.put("domain", domain.getSuffix());
+                vars.put("status", l.getTranslation(domain.isVerified() ? "verified" : "not verified"));
+                point++;
+                return true;
+            }
+        };
+        vars.put("domains", dts);
+        t.output(out, l, vars);
+    }
+}
diff --git a/src/org/cacert/gigi/pages/account/domain/DomainManagementForm.templ b/src/org/cacert/gigi/pages/account/domain/DomainManagementForm.templ
new file mode 100644 (file)
index 0000000..0bdc9d3
--- /dev/null
@@ -0,0 +1,21 @@
+<table align="center" valign="middle" border="0" cellspacing="0" cellpadding="0" class="wrapper dataTable">
+  <tbody><tr>
+    <th colspan="3">Domains</th>
+  </tr>
+  <tr>
+    <td><?=_Delete?></td>
+    <td><?=_Status?></td>
+    <td><?=_Address?></td>
+
+  </tr>
+  <? foreach($domains) { ?>
+  <tr>
+       <td><input type="checkbox" name="delid[]" value="<?=$id?>" /></td>
+       <td><?=$status?></td>
+       <td><a href='<?=$domainhref?>'><?=$domain?></a></td>
+  </tr>
+  <? } ?>
+  <tr>
+    <td colspan="3"><input type="submit" name="domdel" value="<?=_Delete?>"></td>
+  </tr>
+</tbody></table>
\ No newline at end of file
diff --git a/src/org/cacert/gigi/pages/account/domain/DomainOverview.java b/src/org/cacert/gigi/pages/account/domain/DomainOverview.java
new file mode 100644 (file)
index 0000000..906e2a1
--- /dev/null
@@ -0,0 +1,94 @@
+package org.cacert.gigi.pages.account.domain;
+
+import java.io.IOException;
+import java.util.HashMap;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.cacert.gigi.GigiApiException;
+import org.cacert.gigi.dbObjects.Domain;
+import org.cacert.gigi.dbObjects.DomainPingConfiguration;
+import org.cacert.gigi.dbObjects.User;
+import org.cacert.gigi.output.template.Form;
+import org.cacert.gigi.pages.Page;
+
+public class DomainOverview extends Page {
+
+    public static final String PATH = "/account/domains/";
+
+    public DomainOverview(String title) {
+        super(title);
+    }
+
+    @Override
+    public void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
+        User u = getUser(req);
+        String pi = req.getPathInfo();
+        if (pi.length() - PATH.length() > 0) {
+            int i = Integer.parseInt(pi.substring(PATH.length()));
+            Domain d = Domain.getById(i);
+            if (u.getId() != d.getOwner().getId()) {
+                System.out.println(u.getId());
+                System.out.println(d.getOwner().getId());
+                return;
+            }
+            new DomainPinglogForm(req, d).output(resp.getWriter(), getLanguage(req), new HashMap<String, Object>());
+            try {
+                new PingConfigForm(req, d).output(resp.getWriter(), getLanguage(req), new HashMap<String, Object>());
+            } catch (GigiApiException e) {
+                e.format(resp.getWriter(), getLanguage(req));
+            }
+            return;
+
+        }
+        try {
+            DomainManagementForm domMan = new DomainManagementForm(req, u);
+            DomainAddForm domAdd = new DomainAddForm(req, u);
+            HashMap<String, Object> vars = new HashMap<>();
+            vars.put("doms", u.getDomains());
+            vars.put("domainman", domMan);
+            vars.put("domainadd", domAdd);
+            getDefaultTemplate().output(resp.getWriter(), getLanguage(req), vars);
+        } catch (GigiApiException e) {
+            e.format(resp.getWriter(), getLanguage(req));
+        }
+    }
+
+    @Override
+    public void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException {
+        User u = getUser(req);
+        String pi = req.getPathInfo();
+        if (pi.length() - PATH.length() > 0) {
+            int i = Integer.parseInt(pi.substring(PATH.length()));
+            Domain d = Domain.getById(i);
+            if (u.getId() != d.getOwner().getId()) {
+                return;
+            }
+            int reping = Integer.parseInt(req.getParameter("configId"));
+            DomainPingConfiguration dpc = DomainPingConfiguration.getById(reping);
+            if (dpc.getTarget() != d) {
+                return;
+            }
+            try {
+                dpc.requestReping();
+            } catch (GigiApiException e) {
+                e.format(resp.getWriter(), getLanguage(req));
+                return;
+            }
+            resp.sendRedirect(PATH + i);
+        }
+        if (req.getParameter("adddomain") != null) {
+            DomainAddForm f = Form.getForm(req, DomainAddForm.class);
+            if (f.submit(resp.getWriter(), req)) {
+                resp.sendRedirect(PATH);
+            }
+        } else if (req.getParameter("domdel") != null) {
+            DomainManagementForm f = Form.getForm(req, DomainManagementForm.class);
+            if (f.submit(resp.getWriter(), req)) {
+                resp.sendRedirect(PATH);
+            }
+        }
+        super.doPost(req, resp);
+    }
+}
diff --git a/src/org/cacert/gigi/pages/account/domain/DomainOverview.templ b/src/org/cacert/gigi/pages/account/domain/DomainOverview.templ
new file mode 100644 (file)
index 0000000..11364a5
--- /dev/null
@@ -0,0 +1,9 @@
+<?=$domainman?>
+<h2><?=_Add Domain?></h2>
+<p>
+<?=_Please Note: You only need to enter the main part of your domain, eg. mydomain.com rather then www.mydomain.com. Once you have verified your domain you are able to enter any sub-domain, such as www.mydomain.com or www.this.is.mydomain.com as the system checks from right to left, rather then specific hostnames when you upload a CSR to the system.?>
+</p>
+<?=$domainadd?>
+<p>
+<?=_Currently we only issue certificates for Punycode domains if the person requesting them has code signing attributes attached to their account, as these have potentially slightly higher security risk.?>
+</p>
diff --git a/src/org/cacert/gigi/pages/account/domain/DomainPinglogForm.java b/src/org/cacert/gigi/pages/account/domain/DomainPinglogForm.java
new file mode 100644 (file)
index 0000000..4d4a36f
--- /dev/null
@@ -0,0 +1,71 @@
+package org.cacert.gigi.pages.account.domain;
+
+import java.io.PrintWriter;
+import java.util.Map;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.cacert.gigi.GigiApiException;
+import org.cacert.gigi.dbObjects.Domain;
+import org.cacert.gigi.dbObjects.Domain.DomainPingExecution;
+import org.cacert.gigi.dbObjects.DomainPingConfiguration;
+import org.cacert.gigi.localisation.Language;
+import org.cacert.gigi.output.template.Form;
+import org.cacert.gigi.output.template.IterableDataset;
+import org.cacert.gigi.output.template.Template;
+
+public class DomainPinglogForm extends Form {
+
+    static Template t = new Template(DomainPinglogForm.class.getResource("DomainPinglogForm.templ"));
+
+    Domain target;
+
+    public DomainPinglogForm(HttpServletRequest hsr, Domain target) {
+        super(hsr);
+        this.target = target;
+    }
+
+    @Override
+    public boolean submit(PrintWriter out, HttpServletRequest req) throws GigiApiException {
+        return false;
+    }
+
+    @Override
+    protected void outputContent(PrintWriter out, Language l, Map<String, Object> vars) {
+        final DomainPingExecution[] pings;
+        try {
+            pings = target.getPings();
+        } catch (GigiApiException e) {
+            e.format(out, l);
+            return;
+        }
+        vars.put("domainname", target.getSuffix());
+        vars.put("pings", new IterableDataset() {
+
+            int counter = 0;
+
+            @Override
+            public boolean next(Language l, Map<String, Object> vars) {
+                if (counter >= pings.length) {
+                    return false;
+                }
+                vars.put("state", pings[counter].getState());
+                vars.put("type", pings[counter].getType());
+                vars.put("config", pings[counter].getInfo());
+                String ping3 = pings[counter].getResult();
+                if (ping3 == null) {
+                    vars.put("result", "");
+                } else {
+                    vars.put("result", ping3);
+                }
+                DomainPingConfiguration dpc = pings[counter].getConfig();
+                if (dpc != null) {
+                    vars.put("configId", Integer.toString(dpc.getId()));
+                }
+                counter++;
+                return true;
+            }
+        });
+        t.output(out, l, vars);
+    }
+}
diff --git a/src/org/cacert/gigi/pages/account/domain/DomainPinglogForm.templ b/src/org/cacert/gigi/pages/account/domain/DomainPinglogForm.templ
new file mode 100644 (file)
index 0000000..854f0d1
--- /dev/null
@@ -0,0 +1,18 @@
+<h2><?=_Ping log for?> '<?=$domainname?>'</h2>
+<table class="wrapper dataTable">
+<tr><th><?=_Type?></th>
+<th><?=_State?></th>
+<th><?=_Config?></th>
+<th><?=_Result?></th>
+<th><?=_Reping?></th></tr>
+<? foreach($pings) { ?>
+<tr>
+<td><?=$type?></td>
+<td><?=$state?></td>
+<td><?=$config?></td>
+<td><?=$result?></td>
+<td><button name='configId' value="<?=$configId?>"><?=_Re-execute?></button></td>
+</tr>
+<?}?>
+</table>
+<br/>
diff --git a/src/org/cacert/gigi/pages/account/domain/PingConfigForm.java b/src/org/cacert/gigi/pages/account/domain/PingConfigForm.java
new file mode 100644 (file)
index 0000000..5b5da85
--- /dev/null
@@ -0,0 +1,214 @@
+package org.cacert.gigi.pages.account.domain;
+
+import java.io.PrintWriter;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.cacert.gigi.Gigi;
+import org.cacert.gigi.GigiApiException;
+import org.cacert.gigi.dbObjects.Domain;
+import org.cacert.gigi.dbObjects.DomainPingConfiguration;
+import org.cacert.gigi.dbObjects.DomainPingConfiguration.PingType;
+import org.cacert.gigi.localisation.Language;
+import org.cacert.gigi.output.template.Form;
+import org.cacert.gigi.output.template.IterableDataset;
+import org.cacert.gigi.output.template.Template;
+import org.cacert.gigi.ping.SSLPinger;
+import org.cacert.gigi.util.RandomToken;
+
+public class PingConfigForm extends Form {
+
+    public enum SSLType {
+        DIRECT, XMPP, XMPP_SERVER, SMTP, IMAP;
+
+        @Override
+        public String toString() {
+            return super.toString().toLowerCase();
+        }
+    }
+
+    private Domain target;
+
+    private String tokenName = RandomToken.generateToken(8);
+
+    private String tokenValue = RandomToken.generateToken(16);
+
+    private static final int MAX_SSL_TESTS = 4;
+
+    public static final String[] AUTHORATIVE_EMAILS = new String[] {
+            "root", "hostmaster", "postmaster", "admin", "webmaster"
+    };
+
+    private int selectedMail = -1;
+
+    private boolean doMail, doDNS, doHTTP, doSSL;
+
+    private int[] ports = new int[MAX_SSL_TESTS];
+
+    private SSLType[] sslTypes = new SSLType[MAX_SSL_TESTS];
+
+    private final Template t = new Template(PingConfigForm.class.getResource("PingConfigForm.templ"));
+
+    public PingConfigForm(HttpServletRequest hsr, Domain target) throws GigiApiException {
+        super(hsr);
+        this.target = target;
+        if (target == null) {
+            return;
+        }
+        List<DomainPingConfiguration> configs = target.getConfiguredPings();
+        int portpos = 0;
+        for (DomainPingConfiguration dpc : configs) {
+            switch (dpc.getType()) {
+            case EMAIL:
+                doMail = true;
+                for (int i = 0; i < AUTHORATIVE_EMAILS.length; i++) {
+                    if (AUTHORATIVE_EMAILS[i].equals(dpc.getInfo())) {
+                        selectedMail = i;
+                    }
+                }
+                break;
+            case DNS: {
+                doDNS = true;
+                String[] parts = dpc.getInfo().split(":");
+                tokenName = parts[0];
+                tokenValue = parts[1];
+                break;
+            }
+            case HTTP: {
+                doHTTP = true;
+                String[] parts = dpc.getInfo().split(":");
+                tokenName = parts[0];
+                tokenValue = parts[1];
+                break;
+            }
+            case SSL: {
+                doSSL = true;
+                String[] parts = dpc.getInfo().split(":");
+                ports[portpos] = Integer.parseInt(parts[0]);
+                if (parts.length == 2) {
+                    sslTypes[portpos] = SSLType.valueOf(parts[1].toUpperCase());
+                } else {
+                    sslTypes[portpos] = SSLType.DIRECT;
+                }
+                portpos++;
+                break;
+            }
+            }
+        }
+    }
+
+    public void setTarget(Domain target) {
+        this.target = target;
+    }
+
+    @Override
+    public boolean submit(PrintWriter out, HttpServletRequest req) throws GigiApiException {
+        if (req.getParameter("emailType") != null) {
+            String mail = AUTHORATIVE_EMAILS[Integer.parseInt(req.getParameter("email"))];
+            target.addPing(PingType.EMAIL, mail);
+        }
+        if (req.getParameter("DNSType") != null) {
+            target.addPing(PingType.DNS, tokenName + ":" + tokenValue);
+        }
+        if (req.getParameter("HTTPType") != null) {
+            target.addPing(PingType.HTTP, tokenName + ":" + tokenValue);
+        }
+        if (req.getParameter("SSLType") != null) {
+            List<String> types = Arrays.asList(SSLPinger.TYPES);
+            for (int i = 0; i < MAX_SSL_TESTS; i++) {
+                String type = req.getParameter("ssl-type-" + i);
+                String port = req.getParameter("ssl-port-" + i);
+                if (type == null || port == null || port.equals("")) {
+                    continue;
+                }
+                int portInt = Integer.parseInt(port);
+                if ("direct".equals(type)) {
+                    target.addPing(PingType.SSL, port);
+                } else if (types.contains(type)) {
+                    target.addPing(PingType.SSL, portInt + ":" + type);
+                }
+
+            }
+        }
+        Gigi.notifyPinger(null);
+        return false;
+    }
+
+    @Override
+    protected void outputContent(PrintWriter out, Language l, Map<String, Object> vars) {
+        out.print("<table class=\"wrapper dataTable\"><tbody>");
+        outputEmbeddableContent(out, l, vars);
+        out.print("<tr><td></td><td><input type=\"submit\" value=\"Update\"/></td></tbody></table>");
+    }
+
+    protected void outputEmbeddableContent(PrintWriter out, Language l, Map<String, Object> vars) {
+        vars.put("tokenName", tokenName);
+        vars.put("tokenValue", tokenValue);
+        vars.put("authEmails", new IterableDataset() {
+
+            int i = 0;
+
+            @Override
+            public boolean next(Language l, Map<String, Object> vars) {
+                if (i >= AUTHORATIVE_EMAILS.length) {
+                    return false;
+                }
+                vars.put("i", i);
+                vars.put("email", AUTHORATIVE_EMAILS[i]);
+                if (i == selectedMail) {
+                    vars.put("checked", " checked=\"checked\"");
+                } else {
+                    vars.put("checked", "");
+                }
+
+                i++;
+                return true;
+            }
+        });
+        vars.put("mail", doMail ? " checked=\"checked\"" : "");
+        vars.put("dns", doDNS ? " checked=\"checked\"" : "");
+        vars.put("http", doHTTP ? " checked=\"checked\"" : "");
+        vars.put("ssl", doSSL ? " checked=\"checked\"" : "");
+        vars.put("ssl-services", new IterableDataset() {
+
+            int counter = 0;
+
+            @Override
+            public boolean next(Language l, Map<String, Object> vars) {
+                if (counter >= MAX_SSL_TESTS) {
+                    return false;
+                }
+                vars.put("i", counter);
+                vars.put("port", ports[counter] == 0 ? "" : Integer.toString(ports[counter]));
+                final SSLType selectedType = sslTypes[counter];
+                vars.put("ssl-types", new IterableDataset() {
+
+                    int i = 0;
+
+                    SSLType[] type = SSLType.values();
+
+                    @Override
+                    public boolean next(Language l, Map<String, Object> vars) {
+                        if (i >= type.length) {
+                            return false;
+                        }
+                        vars.put("name", type[i].toString());
+                        if (selectedType == type[i]) {
+                            vars.put("selected", " selected=\"selected\"");
+                        } else {
+                            vars.put("selected", "");
+                        }
+                        i++;
+                        return true;
+                    }
+                });
+                counter++;
+                return true;
+            }
+        });
+        t.output(out, l, vars);
+    }
+}
diff --git a/src/org/cacert/gigi/pages/account/domain/PingConfigForm.templ b/src/org/cacert/gigi/pages/account/domain/PingConfigForm.templ
new file mode 100644 (file)
index 0000000..7a3c050
--- /dev/null
@@ -0,0 +1,62 @@
+  <tr><th></th><th><?=_Verification mechanisms?></th></tr>
+
+  <tr>
+    <td><input type="checkbox" name="emailType" value="y"<?=$!mail?>></td>
+    <td><?=_Verify by sending an email to authoritative email addresses?> </td>
+  </tr>
+  <tr>
+    <td></td>
+    <td class='radio'>
+        Select the destination mail address:<br/>
+        <? foreach($authEmails) { ?>
+        <input type="radio" id="email_<?=$i?>" name="email" value="<?=$i?>"<?=$!checked?>/>
+        <label for="email_<?=$i?>"><span class='name'><?=$email?>@<span class='exampleDomain'>example.org</span></span></label><div class='elements'></div>
+        <? } ?>
+       </td>
+  </tr>
+
+  <tr>
+    <td><input type="checkbox" name="DNSType" value="y"<?=$!dns?>></td>
+    <td><?=_Verify by reading DNS-TXT entries?> </td>
+  </tr>
+  <tr>
+    <td></td>
+    <td>
+        Please insert the following DNS TXT entry into the Zone-file of your domain:<br/>
+        <pre>
+        <?=$tokenName?>._cacert._auth IN TXT <?=$tokenValue?>
+        </pre>
+    </td>
+  </tr>
+
+  <tr>
+    <td><input type="checkbox" name="HTTPType" value="y"<?=$!http?>></td>
+    <td><?=_Verify by reading HTTP-content?> </td>
+  </tr>
+  <tr>
+    <td></td>
+    <td>
+        <?=_Please make the following content available under ?><pre class='string'>http://<span class='exampleDomain'>example.org</span>/cacert-<?=$tokenName?>.txt</pre><br/>
+        <pre><?=$tokenValue?></pre>
+    </td>
+  </tr>
+
+  <tr>
+    <td><input type="checkbox" name="SSLType" value="y"<?=$!ssl?>></td>
+    <td><?=_Verify by searching for installed certificate.?> </td>
+  </tr>
+  <tr>
+    <td></td>
+    <td>
+        <?=_Please list up to four services using your certificate. You need to have two of them up and using a valid CAcert certificate in order to pass this test?>:
+        <table>
+        <? foreach($ssl-services){ ?>
+        <tr><td><select name='ssl-type-<?=$i?>'>
+               <?foreach($ssl-types){ ?>
+          <option<?=$!selected?>><?=$name?></option><? } ?></select>
+        </td><td>Port: <input type='text' name='ssl-port-<?=$i?>' value='<?=$port?>'></td></tr>
+        <? } ?>
+        </table>
+    </td>
+  </tr>
+  
\ No newline at end of file
diff --git a/src/org/cacert/gigi/pages/account/mail/MailAddForm.java b/src/org/cacert/gigi/pages/account/mail/MailAddForm.java
new file mode 100644 (file)
index 0000000..9439594
--- /dev/null
@@ -0,0 +1,54 @@
+package org.cacert.gigi.pages.account.mail;
+
+import java.io.PrintWriter;
+import java.util.Map;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.cacert.gigi.GigiApiException;
+import org.cacert.gigi.dbObjects.EmailAddress;
+import org.cacert.gigi.dbObjects.User;
+import org.cacert.gigi.localisation.Language;
+import org.cacert.gigi.output.template.Form;
+import org.cacert.gigi.output.template.Template;
+import org.cacert.gigi.pages.Page;
+
+public class MailAddForm extends Form {
+
+    private static Template t;
+
+    private String mail;
+    static {
+        t = new Template(MailAddForm.class.getResource("MailAddForm.templ"));
+    }
+
+    private User target;
+
+    public MailAddForm(HttpServletRequest hsr, User target) {
+        super(hsr);
+        this.target = target;
+    }
+
+    @Override
+    public boolean submit(PrintWriter out, HttpServletRequest req) {
+        String formMail = req.getParameter("newemail");
+        mail = formMail;
+        try {
+            EmailAddress addr = new EmailAddress(target, mail);
+            addr.insert(Page.getLanguage(req));
+        } catch (IllegalArgumentException e) {
+            out.println("<div class='formError'>Error: Invalid address!</div>");
+            return false;
+        } catch (GigiApiException e) {
+            e.format(out, Page.getLanguage(req));
+            return false;
+        }
+        return true;
+    }
+
+    @Override
+    protected void outputContent(PrintWriter out, Language l, Map<String, Object> vars) {
+        t.output(out, l, vars);
+    }
+
+}
diff --git a/src/org/cacert/gigi/pages/account/mail/MailAddForm.templ b/src/org/cacert/gigi/pages/account/mail/MailAddForm.templ
new file mode 100644 (file)
index 0000000..40a6cf6
--- /dev/null
@@ -0,0 +1,16 @@
+<table align="center" valign="middle" border="0" cellspacing="0" cellpadding="0" class="wrapper dataTable">
+  <thead>
+  <tr>
+    <th colspan="2" class="title"><?=_Add Email?></th>
+  </tr>
+  </thead>
+<tbody>
+  <tr>
+    <td width="125"><?=_Email Addresses?> </td>
+    <td width="125"><input type="text" name="newemail" value=""></td>
+  </tr>
+  <tr>
+    <td colspan="2"><input type="submit" name="addmail" value="<?=_I own or am authorised to control this email address?>"></td>
+  </tr>
+</tbody>
+</table>
diff --git a/src/org/cacert/gigi/pages/account/mail/MailManagementForm.java b/src/org/cacert/gigi/pages/account/mail/MailManagementForm.java
new file mode 100644 (file)
index 0000000..c824502
--- /dev/null
@@ -0,0 +1,104 @@
+package org.cacert.gigi.pages.account.mail;
+
+import java.io.PrintWriter;
+import java.util.Map;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.cacert.gigi.GigiApiException;
+import org.cacert.gigi.dbObjects.EmailAddress;
+import org.cacert.gigi.dbObjects.User;
+import org.cacert.gigi.localisation.Language;
+import org.cacert.gigi.output.template.Form;
+import org.cacert.gigi.output.template.IterableDataset;
+import org.cacert.gigi.output.template.Template;
+import org.cacert.gigi.pages.Page;
+
+public class MailManagementForm extends Form {
+
+    private static Template t;
+
+    private User target;
+    static {
+        t = new Template(MailAddForm.class.getResource("MailManagementForm.templ"));
+    }
+
+    public MailManagementForm(HttpServletRequest hsr, User target) {
+        super(hsr);
+        this.target = target;
+    }
+
+    @Override
+    public boolean submit(PrintWriter out, HttpServletRequest req) {
+        if (req.getParameter("makedefault") != null) {
+            try {
+                String mailid = req.getParameter("emailid");
+                if (mailid == null) {
+                    return false;
+                }
+                target.updateDefaultEmail(EmailAddress.getById(Integer.parseInt(mailid.trim())));
+            } catch (GigiApiException e) {
+                e.format(out, Page.getLanguage(req));
+                return false;
+            }
+            return true;
+        }
+        if (req.getParameter("delete") != null) {
+            String[] toDel = req.getParameterValues("delid[]");
+            if (toDel == null) {
+                return false;
+            }
+            for (int i = 0; i < toDel.length; i++) {
+                try {
+                    target.deleteEmail(EmailAddress.getById(Integer.parseInt(toDel[i].trim())));
+                } catch (GigiApiException e) {
+                    e.format(out, Page.getLanguage(req));
+                    return false;
+                }
+            }
+            return true;
+
+        }
+        return false;
+    }
+
+    @Override
+    protected void outputContent(PrintWriter out, Language l, Map<String, Object> vars) {
+        final EmailAddress[] emails = target.getEmails();
+        IterableDataset ds = new IterableDataset() {
+
+            private int point = 0;
+
+            @Override
+            public boolean next(Language l, Map<String, Object> vars) {
+                if (point >= emails.length) {
+                    return false;
+                }
+                EmailAddress emailAddress = emails[point];
+                int mailID = emailAddress.getId();
+                vars.put("id", mailID);
+                if (emailAddress.getAddress().equals(target.getEmail())) {
+                    vars.put("checked", "checked");
+                } else {
+                    vars.put("checked", "");
+                }
+                if (emailAddress.isVerified()) {
+                    vars.put("verification", "Verified");
+                } else {
+                    vars.put("verification", "Unverified");
+                }
+                if (target.getEmail().equals(emailAddress.getAddress())) {
+                    vars.put("delete", "N/A");
+                } else {
+                    vars.put("delete", "<input type=\"checkbox\" name=\"delid[]\" value=\"" + mailID + "\"/>");
+                }
+                vars.put("address", emailAddress.getAddress());
+                point++;
+                return true;
+            }
+
+        };
+        vars.put("emails", ds);
+        t.output(out, l, vars);
+    }
+}
diff --git a/src/org/cacert/gigi/pages/account/mail/MailManagementForm.templ b/src/org/cacert/gigi/pages/account/mail/MailManagementForm.templ
new file mode 100644 (file)
index 0000000..e88190c
--- /dev/null
@@ -0,0 +1,27 @@
+<table align="center" valign="middle" border="0" cellspacing="0" cellpadding="0" class="wrapper dataTable">
+  <thead>
+  <tr>
+    <th colspan="4"><?=_Email Accounts?></th>
+  </tr>
+  </thead>
+  <tbody>
+  <tr>
+    <td><?=_Default?></td>
+    <td><?=_Status?></td>
+    <td><?=_Delete?></td>
+    <td><?=_Address?></td>
+  </tr>
+ <? foreach($emails) {?>
+       <tr>
+               <td><input type="radio" name="emailid" value="<?=$id?>" <?=$!checked?>></td>
+               <td><?=$verification?></td>
+               <td><?=$!delete?></td>
+               <td><?=$address?></td>
+       </tr>
+ <? } ?>
+  <tr>
+    <td colspan="2"><input type="submit" name="makedefault" value="Setze als Standard"></td>
+    <td colspan="2"><input type="submit" name="delete" value="Löschen"></td>
+  </tr>
+  </tbody>
+</table>
diff --git a/src/org/cacert/gigi/pages/account/mail/MailOverview.java b/src/org/cacert/gigi/pages/account/mail/MailOverview.java
new file mode 100644 (file)
index 0000000..593307d
--- /dev/null
@@ -0,0 +1,50 @@
+package org.cacert.gigi.pages.account.mail;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.HashMap;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.cacert.gigi.dbObjects.User;
+import org.cacert.gigi.localisation.Language;
+import org.cacert.gigi.output.template.Form;
+import org.cacert.gigi.pages.Page;
+
+public class MailOverview extends Page {
+
+    public static final String DEFAULT_PATH = "/account/mails";
+
+    public MailOverview(String title) {
+        super(title);
+    }
+
+    @Override
+    public void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
+        final User us = getUser(req);
+        Language lang = Page.getLanguage(req);
+        HashMap<String, Object> vars = new HashMap<>();
+        vars.put("addForm", new MailAddForm(req, us));
+        vars.put("manForm", new MailManagementForm(req, us));
+        getDefaultTemplate().output(resp.getWriter(), lang, vars);
+    }
+
+    @Override
+    public void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException {
+        PrintWriter out = resp.getWriter();
+        if (req.getParameter("addmail") != null) {
+            MailAddForm f = Form.getForm(req, MailAddForm.class);
+            if (f.submit(out, req)) {
+                resp.sendRedirect(MailOverview.DEFAULT_PATH);
+            }
+        } else if (req.getParameter("makedefault") != null || req.getParameter("delete") != null) {
+            MailManagementForm f = Form.getForm(req, MailManagementForm.class);
+            if (f.submit(out, req)) {
+                resp.sendRedirect(MailOverview.DEFAULT_PATH);
+            }
+        }
+        super.doPost(req, resp);
+    }
+
+}
diff --git a/src/org/cacert/gigi/pages/account/mail/MailOverview.templ b/src/org/cacert/gigi/pages/account/mail/MailOverview.templ
new file mode 100644 (file)
index 0000000..06465e6
--- /dev/null
@@ -0,0 +1,9 @@
+<?=$manForm?>
+<p>
+<?=_Please Note: You can not set an unverified account as a default account, and you can not remove a default account. To remove the default account you must set another verified account as the default.?>
+</p>
+<h2><?=_Add Email?></h2>
+<?=$addForm?>
+<p>
+<?=_Currently we only issue certificates for Punycode domains if the person requesting them has code signing attributes attached to their account, as these have potentially slightly higher security risk.?>
+</p>
diff --git a/src/org/cacert/gigi/pages/admin/TTPAdminForm.java b/src/org/cacert/gigi/pages/admin/TTPAdminForm.java
new file mode 100644 (file)
index 0000000..58bf2ee
--- /dev/null
@@ -0,0 +1,46 @@
+package org.cacert.gigi.pages.admin;
+
+import java.io.PrintWriter;
+import java.util.Map;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.cacert.gigi.GigiApiException;
+import org.cacert.gigi.dbObjects.User;
+import org.cacert.gigi.localisation.Language;
+import org.cacert.gigi.output.DateSelector;
+import org.cacert.gigi.output.template.Form;
+import org.cacert.gigi.output.template.Template;
+import org.cacert.gigi.pages.LoginPage;
+
+public class TTPAdminForm extends Form {
+
+    private static Template t = new Template(TTPAdminForm.class.getResource("TTPAdminForm.templ"));
+
+    User u;
+
+    User ttpAdmin;
+
+    public TTPAdminForm(HttpServletRequest hsr, User u) {
+        super(hsr);
+        this.u = u;
+        ttpAdmin = LoginPage.getUser(hsr);
+    }
+
+    @Override
+    public boolean submit(PrintWriter out, HttpServletRequest req) throws GigiApiException {
+        if (req.getParameter("deny") != null) {
+            u.revokeGroup(ttpAdmin, TTPAdminPage.TTP_APPLICANT);
+        }
+        return false;
+    }
+
+    @Override
+    protected void outputContent(PrintWriter out, Language l, Map<String, Object> vars) {
+        vars.put("name", u.getName());
+        vars.put("email", u.getEmail());
+        vars.put("DoB", DateSelector.getDateFormat().format(u.getDoB()));
+        vars.put("uid", Integer.toString(u.getId()));
+        t.output(out, l, vars);
+    }
+}
diff --git a/src/org/cacert/gigi/pages/admin/TTPAdminForm.templ b/src/org/cacert/gigi/pages/admin/TTPAdminForm.templ
new file mode 100644 (file)
index 0000000..1495d4a
--- /dev/null
@@ -0,0 +1,7 @@
+<input type="hidden" name="uid" value="<?=$uid?>"/>
+<table class="dataTable wrapper">
+<tr><td><?=_Name?></td><td><?=$name?></td></tr>
+<tr><td><?=_Email?></td><td><?=$email?></td></tr>
+<tr><td><?=_Date of Birth?></td><td><?=$DoB?></td></tr>
+<tr><td colspan="2"><input type="submit" name="deny" value="<?=_Deny Request?>"></td></tr>
+</table>
\ No newline at end of file
diff --git a/src/org/cacert/gigi/pages/admin/TTPAdminPage.java b/src/org/cacert/gigi/pages/admin/TTPAdminPage.java
new file mode 100644 (file)
index 0000000..04d325c
--- /dev/null
@@ -0,0 +1,93 @@
+package org.cacert.gigi.pages.admin;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.cacert.gigi.GigiApiException;
+import org.cacert.gigi.dbObjects.Group;
+import org.cacert.gigi.dbObjects.User;
+import org.cacert.gigi.localisation.Language;
+import org.cacert.gigi.output.template.Form;
+import org.cacert.gigi.output.template.IterableDataset;
+import org.cacert.gigi.output.template.SprintfCommand;
+import org.cacert.gigi.pages.Page;
+import org.cacert.gigi.pages.error.PageNotFound;
+
+public class TTPAdminPage extends Page {
+
+    public static final String PATH = "/admin/ttp";
+
+    public static final Group TTP_APPLICANT = Group.getByString("ttp-applicant");
+
+    public TTPAdminPage() {
+        super("TTP-Admin");
+    }
+
+    @Override
+    public void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException {
+        try {
+            Form.getForm(req, TTPAdminForm.class).submit(resp.getWriter(), req);
+        } catch (GigiApiException e) {
+            e.format(resp.getWriter(), getLanguage(req));
+        }
+        resp.sendRedirect(PATH);
+    }
+
+    private static final int PAGE_LEN = 30;
+
+    @Override
+    public void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
+        String path = req.getPathInfo();
+        if (path != null && path.length() > PATH.length() + 1) {
+            int id = Integer.parseInt(path.substring(1 + PATH.length()));
+            User u = User.getById(id);
+            if (u == null || !u.isInGroup(TTP_APPLICANT)) {
+                SprintfCommand command = new SprintfCommand("The TTP-request is not available anymore. You might want to go %sback%s.", Arrays.asList("!\"<a href='" + PATH + "'>", "!\"</a>"));
+                req.setAttribute(PageNotFound.MESSAGE_ATTRIBUTE, command);
+                resp.sendError(404);
+                return;
+            }
+            new TTPAdminForm(req, u).output(resp.getWriter(), getLanguage(req), new HashMap<String, Object>());
+            return;
+        }
+        int offset = 0;
+        String offsetS = req.getParameter("offset");
+        if (offsetS != null) {
+            offset = Integer.parseInt(offsetS);
+        }
+
+        final User[] users = TTP_APPLICANT.getMembers(offset, PAGE_LEN + 1);
+        HashMap<String, Object> vars = new HashMap<>();
+        vars.put("users", new IterableDataset() {
+
+            int i = 0;
+
+            @Override
+            public boolean next(Language l, Map<String, Object> vars) {
+                if (i >= Math.min(PAGE_LEN, users.length)) {
+                    return false;
+                }
+                vars.put("id", Integer.toString(users[i].getId()));
+                vars.put("name", users[i].getName().toString());
+                vars.put("email", users[i].getEmail());
+
+                i++;
+                return true;
+            }
+        });
+        if (users.length == PAGE_LEN + 1) {
+            vars.put("next", Integer.toString(offset + 30));
+        }
+        getDefaultTemplate().output(resp.getWriter(), getLanguage(req), vars);
+    }
+
+    @Override
+    public boolean isPermitted(User u) {
+        return u != null && u.isInGroup(Group.getByString("ttp-assurer"));
+    }
+}
diff --git a/src/org/cacert/gigi/pages/admin/TTPAdminPage.templ b/src/org/cacert/gigi/pages/admin/TTPAdminPage.templ
new file mode 100644 (file)
index 0000000..2e0de03
--- /dev/null
@@ -0,0 +1,9 @@
+<table class="wrapper dataTable">
+<tr><th><?=_Name?></th><th><?=_Email?></th><th></th></tr>
+<? foreach($users) { ?>
+<tr><td><?=$name?></td><td><?=$email?></td><td><a href="/admin/ttp/<?=$id?>">Process</a></td></tr>
+<? } ?>
+</table>
+<? if($next) { ?>
+<a href="?offset=<?=$next?>"><?=_next?></a>
+<? } ?>
diff --git a/src/org/cacert/gigi/pages/admin/support/FindDomainForm.java b/src/org/cacert/gigi/pages/admin/support/FindDomainForm.java
new file mode 100644 (file)
index 0000000..dca6b34
--- /dev/null
@@ -0,0 +1,55 @@
+package org.cacert.gigi.pages.admin.support;
+
+import java.io.PrintWriter;
+import java.util.Map;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.cacert.gigi.GigiApiException;
+import org.cacert.gigi.dbObjects.Domain;
+import org.cacert.gigi.localisation.Language;
+import org.cacert.gigi.output.template.Form;
+import org.cacert.gigi.output.template.Template;
+
+public class FindDomainForm extends Form {
+
+    private int userId = -1;
+
+    private static Template t;
+    static {
+        t = new Template(FindDomainForm.class.getResource("FindDomainForm.templ"));
+    }
+
+    public FindDomainForm(HttpServletRequest hsr) {
+        super(hsr);
+    }
+
+    @Override
+    public boolean submit(PrintWriter out, HttpServletRequest req) throws GigiApiException {
+        String request = req.getParameter("domain");
+        if (request.matches("#[0-9]+")) {
+            try {
+                Domain domainById = Domain.getById(Integer.parseInt(request.substring(1)));
+                userId = domainById.getOwner().getId();
+            } catch (IllegalArgumentException e) {
+                throw (new GigiApiException("No personal domains found matching the id " + request.substring(1) + "."));
+            }
+        } else {
+            userId = Domain.searchUserIdByDomain(request);
+        }
+        if (userId == -1) {
+            throw (new GigiApiException("No personal domains found matching " + request));
+        }
+        return true;
+    }
+
+    @Override
+    protected void outputContent(PrintWriter out, Language l, Map<String, Object> vars) {
+        t.output(out, l, vars);
+    }
+
+    public int getUserId() {
+        return userId;
+    }
+
+}
diff --git a/src/org/cacert/gigi/pages/admin/support/FindDomainForm.templ b/src/org/cacert/gigi/pages/admin/support/FindDomainForm.templ
new file mode 100644 (file)
index 0000000..a35b188
--- /dev/null
@@ -0,0 +1,13 @@
+<table class="wrapper dataTable">
+  <tbody><tr>
+    <th colspan="2"><?=_Find User by Domain?></th>
+  </tr>
+  <tr>
+    <td><?=_Domain?>:</td>
+    <td><input type="text" value="" name="domain" placeholder="<?=_For search by ID use # prefix e.g. #123456?>"></td>
+  </tr>
+  <tr>
+    <td colspan="2"><input type="submit" value="<?=_Next?>" name="process"></td>
+  </tr>
+</tbody>
+</table>
diff --git a/src/org/cacert/gigi/pages/admin/support/FindDomainPage.java b/src/org/cacert/gigi/pages/admin/support/FindDomainPage.java
new file mode 100644 (file)
index 0000000..fec8bc1
--- /dev/null
@@ -0,0 +1,28 @@
+package org.cacert.gigi.pages.admin.support;
+
+import org.cacert.gigi.dbObjects.Group;
+import org.cacert.gigi.dbObjects.User;
+import org.cacert.gigi.output.template.Form;
+import org.cacert.gigi.pages.OneFormPage;
+
+public class FindDomainPage extends OneFormPage {
+
+    public static final String PATH = "/support/find/domain";
+
+    public FindDomainPage(String title) {
+        super(title, FindDomainForm.class);
+    }
+
+    @Override
+    public String getSuccessPath(Form f) {
+        return SupportUserDetailsPage.PATH + ((FindDomainForm) f).getUserId();
+    }
+
+    @Override
+    public boolean isPermitted(User u) {
+        if (u == null) {
+            return false;
+        }
+        return u.isInGroup(Group.SUPPORTER);
+    }
+}
diff --git a/src/org/cacert/gigi/pages/admin/support/FindUserForm.java b/src/org/cacert/gigi/pages/admin/support/FindUserForm.java
new file mode 100644 (file)
index 0000000..17799e3
--- /dev/null
@@ -0,0 +1,46 @@
+package org.cacert.gigi.pages.admin.support;
+
+import java.io.PrintWriter;
+import java.util.Map;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.cacert.gigi.GigiApiException;
+import org.cacert.gigi.dbObjects.User;
+import org.cacert.gigi.localisation.Language;
+import org.cacert.gigi.output.template.Form;
+import org.cacert.gigi.output.template.Template;
+
+public class FindUserForm extends Form {
+
+    private User users[];
+
+    private static Template t;
+    static {
+        t = new Template(FindDomainForm.class.getResource("FindUserForm.templ"));
+    }
+
+    public FindUserForm(HttpServletRequest hsr) {
+        super(hsr);
+    }
+
+    @Override
+    public boolean submit(PrintWriter out, HttpServletRequest req) throws GigiApiException {
+        User[] users = User.findByEmail(req.getParameter("email"));
+        if (users.length == 0) {
+            throw (new GigiApiException("No users found matching " + req.getParameter("email")));
+        }
+        this.users = users;
+        return true;
+    }
+
+    @Override
+    protected void outputContent(PrintWriter out, Language l, Map<String, Object> vars) {
+        t.output(out, l, vars);
+    }
+
+    public User[] getUsers() {
+        return users;
+    }
+
+}
diff --git a/src/org/cacert/gigi/pages/admin/support/FindUserForm.templ b/src/org/cacert/gigi/pages/admin/support/FindUserForm.templ
new file mode 100644 (file)
index 0000000..b48bf4f
--- /dev/null
@@ -0,0 +1,24 @@
+<? if($usertable) {?>
+<p>Multiple users where found.</p>
+<table class="dataTable wrapper">
+<tr>
+<th>Id</th><th>E-Mail</th></tr>
+<? foreach($usertable) {?>
+       <tr><td><a href="/support/user/<?=$usrid?>"><?=$usrid?></a></td><td><a href="/support/user/<?=$usrid?>"><?=$usermail?></a></td></tr>
+<? } ?>
+</table>
+<? } ?>
+<? if($first) {?>
+<table class="wrapper dataTable">
+  <tbody><tr>
+    <th colspan="2"><?=_Find User?></th>
+  </tr>
+  <tr>
+    <td><?=_Email?>:</td>
+    <td><input name="email" value="" size="30" title="<?=_use % as wildcard?>" placeholder="<?=_use % as wildcard?>" type="text"/></td>
+  </tr>
+  <tr>
+    <td colspan="2"><input name="process" value="<?=_Next?>" type="submit"/></td>
+  </tr>
+</tbody></table>
+<? } ?>
diff --git a/src/org/cacert/gigi/pages/admin/support/FindUserPage.java b/src/org/cacert/gigi/pages/admin/support/FindUserPage.java
new file mode 100644 (file)
index 0000000..6f4fc87
--- /dev/null
@@ -0,0 +1,75 @@
+package org.cacert.gigi.pages.admin.support;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.cacert.gigi.GigiApiException;
+import org.cacert.gigi.dbObjects.Group;
+import org.cacert.gigi.dbObjects.User;
+import org.cacert.gigi.localisation.Language;
+import org.cacert.gigi.output.template.Form;
+import org.cacert.gigi.output.template.IterableDataset;
+import org.cacert.gigi.pages.Page;
+
+public class FindUserPage extends Page {
+
+    public static final String PATH = "/support/find/user";
+
+    public FindUserPage(String title) {
+        super(title);
+    }
+
+    @Override
+    public void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
+        HashMap<String, Object> vars = new HashMap<String, Object>();
+        vars.put("first", true);
+        new FindUserForm(req).output(resp.getWriter(), Page.getLanguage(req), vars);
+    }
+
+    @Override
+    public void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException {
+        FindUserForm form = Form.getForm(req, FindUserForm.class);
+        try {
+            form.submit(resp.getWriter(), req);
+            final User[] users = form.getUsers();
+            if (users.length == 1) {
+                resp.sendRedirect(SupportUserDetailsPage.PATH + users[0].getId());
+            } else {
+                HashMap<String, Object> vars = new HashMap<String, Object>();
+                vars.put("first", false);
+                vars.put("usertable", new IterableDataset() {
+
+                    int i = 0;
+
+                    @Override
+                    public boolean next(Language l, Map<String, Object> vars) {
+                        if (i == users.length) {
+                            return false;
+                        }
+                        vars.put("usrid", users[i].getId());
+                        vars.put("usermail", users[i].getEmail());
+                        i++;
+                        return true;
+                    }
+                });
+                form.output(resp.getWriter(), getLanguage(req), vars);
+            }
+        } catch (GigiApiException e) {
+            e.format(resp.getWriter(), Page.getLanguage(req));
+            doGet(req, resp);
+        }
+    }
+
+    @Override
+    public boolean isPermitted(User u) {
+        if (u == null) {
+            return false;
+        }
+        return u.isInGroup(Group.SUPPORTER);
+    }
+
+}
diff --git a/src/org/cacert/gigi/pages/admin/support/SupportEnterTicketForm.java b/src/org/cacert/gigi/pages/admin/support/SupportEnterTicketForm.java
new file mode 100644 (file)
index 0000000..5db93f3
--- /dev/null
@@ -0,0 +1,44 @@
+package org.cacert.gigi.pages.admin.support;
+
+import java.io.PrintWriter;
+import java.util.Map;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.cacert.gigi.GigiApiException;
+import org.cacert.gigi.dbObjects.User;
+import org.cacert.gigi.localisation.Language;
+import org.cacert.gigi.output.template.Form;
+import org.cacert.gigi.output.template.Template;
+
+public class SupportEnterTicketForm extends Form {
+
+    private static Template t;
+
+    private User target;
+    static {
+        t = new Template(SupportEnterTicketForm.class.getResource("SupportEnterTicketForm.templ"));
+    }
+
+    public SupportEnterTicketForm(HttpServletRequest hsr, User target) {
+        super(hsr);
+        this.target = target;
+    }
+
+    @Override
+    public boolean submit(PrintWriter out, HttpServletRequest req) throws GigiApiException {
+        // [asdmASDM]\d{8}\.\d+
+        String ticket = req.getParameter("ticketno");
+        if (ticket.matches("[asdmASDM]\\d{8}\\.\\d+")) {
+            req.getSession().setAttribute("ticketNo" + target.getId(), ticket);
+            return true;
+        }
+        return false;
+    }
+
+    @Override
+    protected void outputContent(PrintWriter out, Language l, Map<String, Object> vars) {
+        t.output(out, l, vars);
+    }
+
+}
diff --git a/src/org/cacert/gigi/pages/admin/support/SupportEnterTicketForm.templ b/src/org/cacert/gigi/pages/admin/support/SupportEnterTicketForm.templ
new file mode 100644 (file)
index 0000000..fe9c34f
--- /dev/null
@@ -0,0 +1,12 @@
+<table class="wrapper dataTable centertext">
+<tr>
+            <th colspan="2"><?=_Ticket handling?></th>
+        </tr>
+        <tr>
+            <td><?=_Ticket no?>:</td>
+            <td><input type="text" <? if($ticketNo) {?>value="<?=$ticketNo?>" <? } ?>name="ticketno"></td>
+        </tr>
+        <tr>
+            <td colspan="2"><input type="submit" name="setTicket" value="<?=_Set ticket number?>"></td>
+        </tr>
+</table>
\ No newline at end of file
diff --git a/src/org/cacert/gigi/pages/admin/support/SupportRevokeCertificatesForm.java b/src/org/cacert/gigi/pages/admin/support/SupportRevokeCertificatesForm.java
new file mode 100644 (file)
index 0000000..af02cb5
--- /dev/null
@@ -0,0 +1,90 @@
+package org.cacert.gigi.pages.admin.support;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.security.GeneralSecurityException;
+import java.util.Date;
+import java.util.Map;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.cacert.gigi.GigiApiException;
+import org.cacert.gigi.dbObjects.Certificate;
+import org.cacert.gigi.dbObjects.CertificateProfile;
+import org.cacert.gigi.dbObjects.SupportedUser;
+import org.cacert.gigi.localisation.Language;
+import org.cacert.gigi.output.DateSelector;
+import org.cacert.gigi.output.template.Form;
+import org.cacert.gigi.output.template.IterableDataset;
+import org.cacert.gigi.output.template.Template;
+
+public class SupportRevokeCertificatesForm extends Form {
+
+    private static Template t;
+
+    private SupportedUser user;
+    static {
+        t = new Template(SupportRevokeCertificatesForm.class.getResource("SupportRevokeCertificatesForm.templ"));
+    }
+
+    public SupportRevokeCertificatesForm(HttpServletRequest hsr, SupportedUser user) {
+        super(hsr);
+        this.user = user;
+    }
+
+    @Override
+    public boolean submit(PrintWriter out, HttpServletRequest req) throws GigiApiException {
+        if (user.getTicket() != null) {
+            user.revokeAllCertificates();
+            return true;
+        }
+        return false;
+    }
+
+    @Override
+    protected void outputContent(PrintWriter out, Language l, Map<String, Object> vars) {
+        final Certificate[] certs = user.getCertificates(true);
+        final CertificateProfile[] profiles = CertificateProfile.getAll();
+        vars.put("types", new IterableDataset() {
+
+            int typeIndex = 0;
+
+            @Override
+            public boolean next(Language l, Map<String, Object> vars) {
+                if (typeIndex > profiles.length - 1) {
+                    return false;
+                }
+                int valid = 0;
+                int total = 0;
+                long lastExpire = Long.MIN_VALUE;
+                for (int i = 0; i < certs.length; i++) {
+                    try {
+                        if (certs[i].getProfile().getId() != profiles[typeIndex].getId()) {
+                            continue;
+                        }
+                        total++;
+                        certs[i].cert().checkValidity();
+                        lastExpire = Math.max(lastExpire, certs[i].cert().getNotAfter().getTime());
+                        valid++;
+                    } catch (GeneralSecurityException | IOException e) {
+                        continue;
+                    }
+                }
+                vars.put("total", total);
+                vars.put("profile", profiles[typeIndex].getVisibleName());
+                vars.put("valid", valid);
+                vars.put("exp", total - valid);
+                vars.put("rev", "TODO");
+                if (lastExpire == Long.MIN_VALUE) {
+                    vars.put("lastdate", "-");
+                } else {
+                    vars.put("lastdate", DateSelector.getDateFormat().format(new Date(lastExpire)));
+                }
+                typeIndex++;
+                return true;
+            }
+        });
+        t.output(out, l, vars);
+    }
+
+}
diff --git a/src/org/cacert/gigi/pages/admin/support/SupportRevokeCertificatesForm.templ b/src/org/cacert/gigi/pages/admin/support/SupportRevokeCertificatesForm.templ
new file mode 100644 (file)
index 0000000..5fabb3c
--- /dev/null
@@ -0,0 +1,28 @@
+<table class="wrapper dataTable">
+        <tbody><tr>
+            <th colspan="6"><?=_Certificates?></td>
+        </tr>
+        <tr>
+            <th><?=_Cert Type?></th>
+            <th><?=_Total?></th>
+            <th><?=_Valid?></th>
+            <th><?=_Expired?></th>
+            <th><?=_Revoked?></th>
+            <th><?=_Latest Expire?></th>
+        </tr>
+       <? foreach($types) { ?>
+        <tr>
+            <td><?=$profile?></th>
+                <td><?=$total?></td>
+            <td><?=$valid?></td>
+            <td><?=$exp?></td>
+            <td><?=$rev?></td>
+            <td><?=$lastdate?></td>
+            </tr>
+            <? } ?>
+        <tr>
+            <th colspan="6">
+                    <input name="revokeall" value="<?=_revoke certificates?>" type="submit">
+            </th>
+        </tr>
+    </tbody></table>
\ No newline at end of file
diff --git a/src/org/cacert/gigi/pages/admin/support/SupportUserDetailsForm.java b/src/org/cacert/gigi/pages/admin/support/SupportUserDetailsForm.java
new file mode 100644 (file)
index 0000000..eff2b0f
--- /dev/null
@@ -0,0 +1,58 @@
+package org.cacert.gigi.pages.admin.support;
+
+import java.io.PrintWriter;
+import java.util.Map;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.cacert.gigi.GigiApiException;
+import org.cacert.gigi.dbObjects.Group;
+import org.cacert.gigi.dbObjects.User;
+import org.cacert.gigi.localisation.Language;
+import org.cacert.gigi.output.DateSelector;
+import org.cacert.gigi.output.template.Form;
+import org.cacert.gigi.output.template.Template;
+
+public class SupportUserDetailsForm extends Form {
+
+    private static Template t;
+
+    private User user;
+
+    static {
+        t = new Template(FindDomainForm.class.getResource("SupportUserDetailsForm.templ"));
+    }
+
+    public SupportUserDetailsForm(HttpServletRequest hsr, User user) {
+        super(hsr);
+        this.user = user;
+    }
+
+    @Override
+    public boolean submit(PrintWriter out, HttpServletRequest req) throws GigiApiException {
+        return false;
+    }
+
+    @Override
+    protected void outputContent(PrintWriter out, Language l, Map<String, Object> vars) {
+        vars.put("mail", user.getEmail());
+        vars.put("fname", user.getFName());
+        vars.put("mname", user.getMName());
+        vars.put("lname", user.getLName());
+        vars.put("suffix", user.getSuffix());
+        vars.put("assurer", user.canAssure());
+        vars.put("dob", new DateSelector("dobd", "dobm", "doby", user.getDoB()));
+        vars.put("blockedassurer", user.isInGroup(Group.BLOCKEDASSURER));
+        vars.put("codesign", user.isInGroup(Group.CODESIGNING));
+        vars.put("orgassurer", user.isInGroup(Group.ORGASSURER));
+        vars.put("assurancepoints", user.getAssurancePoints());
+        vars.put("blockedassuree", user.isInGroup(Group.BLOCKEDASSUREE));
+        vars.put("ttpassurer", user.isInGroup(Group.TTP_ASSURER));
+        vars.put("ttpapplicant", user.isInGroup(Group.TTP_APPLICANT));
+        vars.put("blockedlogin", user.isInGroup(Group.BLOCKEDLOGIN));
+        vars.put("supporter", user.isInGroup(Group.SUPPORTER));
+        vars.put("id", user.getId());
+        t.output(out, l, vars);
+    }
+
+}
diff --git a/src/org/cacert/gigi/pages/admin/support/SupportUserDetailsForm.templ b/src/org/cacert/gigi/pages/admin/support/SupportUserDetailsForm.templ
new file mode 100644 (file)
index 0000000..c032952
--- /dev/null
@@ -0,0 +1,129 @@
+<table class="wrapper dataTable centertext">
+        <tbody><tr>
+            <th colspan="2"><?=$mail?>'s Account Details</th>
+        </tr>
+        <tr>
+            <td><?=_Email?>:</td>
+            <td><?=$mail?></td>
+        </tr>
+        <tr>
+            <td><?=_First Name?>:</td>
+            <td>
+                <input type="text" value="<?=$fname?>" name="fname">
+           </td>
+        </tr>
+        <tr>
+            <td><?=_Middle Name?>:</td>
+            <td><input type="text" value="<?=$mname?>" name="mname"></td>
+        </tr>
+        <tr>
+            <td><?=_Last Name?>:</td>
+            <td>
+                <input type="text" value="<?=$lname?>" name="lname">
+            </td>
+        </tr>
+        <tr>
+            <td><?=_Suffix?>:</td>
+            <td><input type="text" value="<?=$suffix?>" name="suffix"></td>
+        </tr>
+        <tr>
+            <td><?=_Date of Birth?>:</td>
+            <td>
+                       <?=$dob?>
+            </td>
+        </tr>
+        <tr>
+            <td><?=_Trainings?>:</td>
+            <td><a href="./<?=$id?>/trainings"><?=_Show?></a></td>
+        </tr>
+        <tr>
+            <td><?=_Is Assurer?>:</td>
+            <td>
+            <? if($assurer) { ?>
+            <?=_Yes?>
+            <? } else { ?>
+            <?=_No?>
+            <? } ?>
+            </td>
+        </tr>
+        <tr>
+            <td><?=_Blocked Assurer?>:</td>
+            <td>
+            <? if($blockedassurer) { ?>
+            <?=_Yes?>
+            <? } else { ?>
+            <?=_No?>
+            <? } ?>
+            </td>
+        </tr>
+        <tr>
+            <td><?=_Account Locking?>:</td>
+            <td>
+            <? if($locked) { ?>
+            <?=_Yes?>
+            <? } else { ?>
+            <?=_No?>
+            <? } ?>
+            </td>
+        </tr>
+        <tr>
+            <td><?=_Code Signing?>:</td>
+            <td><? if($codesign) { ?>
+            <?=_Yes?>
+            <? } else { ?>
+            <?=_No?>
+            <? } ?>
+            </td>
+        </tr>
+        <tr>
+            <td><?=_Org Assurer?>:</td>
+            <td>
+       <? if($orgassurer) { ?>
+            <?=_Yes?>
+            <? } else { ?>
+            <?=_No?>
+            <? } ?>
+</td>
+        </tr>
+        <tr>
+            <td><?=_TTP Admin?>:</td>
+            <td>
+<? if($ttpadmin) { ?>
+            <?=_Yes?>
+            <? } else { ?>
+            <?=_No?>
+            <? } ?>
+</td>
+        </tr>
+        <tr>
+            <td><?=_Supporter?>:</td>
+            <td>
+<? if($supporter) { ?>
+            <?=_Yes?>
+            <? } else { ?>
+            <?=_No?>
+            <? } ?>
+</td>
+        </tr>
+           <tr>
+            <td><?=_Change Password?>:</td>
+            <td><?=_Change Password?></td>
+        </tr>
+        <tr>
+            <td><?=_Delete Account?>:</td>
+            <td><?=_Delete Account?></td>
+        </tr>
+            <tr>
+            <td><?=_Show Lost Password Details?></td>
+        </tr>
+            <tr>
+            <td><?=_Assurance Points?>:</td>
+            <td><?=$assurancepoints?></td>
+        </tr>
+            <tr>
+            <td colspan="2"><a href="./<?=$id?>/history"><?=_Show account history?></a></td>
+        </tr>
+        <tr><td colspan="2"><input type="submit" value="<?=_Update?>"/></td></tr>
+    </tbody>
+</table>
+<br/>
\ No newline at end of file
diff --git a/src/org/cacert/gigi/pages/admin/support/SupportUserDetailsPage.java b/src/org/cacert/gigi/pages/admin/support/SupportUserDetailsPage.java
new file mode 100644 (file)
index 0000000..f1eed52
--- /dev/null
@@ -0,0 +1,89 @@
+package org.cacert.gigi.pages.admin.support;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.cacert.gigi.GigiApiException;
+import org.cacert.gigi.dbObjects.EmailAddress;
+import org.cacert.gigi.dbObjects.Group;
+import org.cacert.gigi.dbObjects.SupportedUser;
+import org.cacert.gigi.dbObjects.User;
+import org.cacert.gigi.localisation.Language;
+import org.cacert.gigi.output.template.Form;
+import org.cacert.gigi.output.template.IterableDataset;
+import org.cacert.gigi.pages.Page;
+
+public class SupportUserDetailsPage extends Page {
+
+    public static final String PATH = "/support/user/";
+
+    public SupportUserDetailsPage(String title) {
+        super(title);
+    }
+
+    @Override
+    public void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
+        int id;
+        String[] idP = req.getPathInfo().split("/");
+        id = Integer.parseInt(idP[idP.length - 1]);
+        final User user = User.getById(id);
+        SupportUserDetailsForm f = new SupportUserDetailsForm(req, user);
+        HashMap<String, Object> vars = new HashMap<String, Object>();
+        vars.put("details", f);
+        String ticket = (String) req.getSession().getAttribute("ticketNo" + user.getId());
+        vars.put("ticketNo", ticket);
+        final EmailAddress[] addrs = user.getEmails();
+        vars.put("emails", new IterableDataset() {
+
+            int i = 0;
+
+            @Override
+            public boolean next(Language l, Map<String, Object> vars) {
+                if (i == addrs.length) {
+                    return false;
+                }
+                String address = addrs[i].getAddress();
+                i++;
+                if ( !address.equals(user.getEmail())) {
+                    vars.put("secmail", address);
+                }
+                return true;
+            }
+        });
+        vars.put("certifrevoke", new SupportRevokeCertificatesForm(req, new SupportedUser(user, getUser(req), ticket)));
+        vars.put("tickethandling", new SupportEnterTicketForm(req, user));
+        getDefaultTemplate().output(resp.getWriter(), getLanguage(req), vars);
+    }
+
+    @Override
+    public void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException {
+        try {
+            if (req.getParameter("setTicket") != null) {
+
+                if ( !Form.getForm(req, SupportEnterTicketForm.class).submit(resp.getWriter(), req)) {
+                    throw new GigiApiException("Invalid ticket number!");
+                }
+            } else if (req.getParameter("revokeall") != null) {
+                if ( !Form.getForm(req, SupportRevokeCertificatesForm.class).submit(resp.getWriter(), req)) {
+                    throw new GigiApiException("No ticket number set.");
+                }
+            }
+        } catch (GigiApiException e) {
+            e.printStackTrace();
+            e.format(resp.getWriter(), getLanguage(req));
+        }
+        super.doPost(req, resp);
+    }
+
+    @Override
+    public boolean isPermitted(User u) {
+        if (u == null) {
+            return false;
+        }
+        return u.isInGroup(Group.SUPPORTER);
+    }
+}
diff --git a/src/org/cacert/gigi/pages/admin/support/SupportUserDetailsPage.templ b/src/org/cacert/gigi/pages/admin/support/SupportUserDetailsPage.templ
new file mode 100644 (file)
index 0000000..04195c4
--- /dev/null
@@ -0,0 +1,18 @@
+<?=$tickethandling?>
+<br/>
+<?=$details?>
+<table class="wrapper dataTable centertext">
+        <tbody><tr>
+            <th><?=_Alternate Verified Email Addresses?></th>
+        </tr>
+        <? foreach($emails) {?>
+        <? if($secmail) { ?>
+        <tr>
+            <td><?=$secmail?></td>
+        </tr>
+        <? } ?>
+        <? } ?>
+        </tbody>
+</table>
+<br/>
+<?=$certifrevoke?>
\ No newline at end of file
diff --git a/src/org/cacert/gigi/pages/error/AccessDenied.java b/src/org/cacert/gigi/pages/error/AccessDenied.java
new file mode 100644 (file)
index 0000000..60c48bf
--- /dev/null
@@ -0,0 +1,26 @@
+package org.cacert.gigi.pages.error;
+
+import java.io.IOException;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.cacert.gigi.pages.Page;
+
+public class AccessDenied extends Page {
+
+    public AccessDenied() {
+        super("Access denied");
+    }
+
+    @Override
+    public void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
+        getDefaultTemplate().output(resp.getWriter(), Page.getLanguage(req), null);
+    }
+
+    @Override
+    public boolean needsLogin() {
+        return false;
+    }
+
+}
diff --git a/src/org/cacert/gigi/pages/error/AccessDenied.templ b/src/org/cacert/gigi/pages/error/AccessDenied.templ
new file mode 100644 (file)
index 0000000..69069ca
--- /dev/null
@@ -0,0 +1 @@
+<p><?=_The access to this page has been denied to you.?></p>
diff --git a/src/org/cacert/gigi/pages/error/PageNotFound.java b/src/org/cacert/gigi/pages/error/PageNotFound.java
new file mode 100644 (file)
index 0000000..ffc107f
--- /dev/null
@@ -0,0 +1,35 @@
+package org.cacert.gigi.pages.error;
+
+import java.io.IOException;
+import java.util.HashMap;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.cacert.gigi.pages.Page;
+
+public class PageNotFound extends Page {
+
+    public static final String MESSAGE_ATTRIBUTE = "message-Str";
+
+    public PageNotFound() {
+        super("File not found!");
+    }
+
+    @Override
+    public void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
+        HashMap<String, Object> vars = new HashMap<>();
+        Object customMessage = req.getAttribute(MESSAGE_ATTRIBUTE);
+        if (customMessage == null) {
+            customMessage = getLanguage(req).getTranslation("Due to recent site changes bookmarks may no longer be valid, please update your bookmarks.");
+        }
+        vars.put("message", customMessage);
+        getDefaultTemplate().output(resp.getWriter(), Page.getLanguage(req), vars);
+    }
+
+    @Override
+    public boolean needsLogin() {
+        return false;
+    }
+
+}
diff --git a/src/org/cacert/gigi/pages/error/PageNotFound.templ b/src/org/cacert/gigi/pages/error/PageNotFound.templ
new file mode 100644 (file)
index 0000000..1c1b95c
--- /dev/null
@@ -0,0 +1 @@
+<p><?=$message?></p>
diff --git a/src/org/cacert/gigi/pages/main/RegisterPage.java b/src/org/cacert/gigi/pages/main/RegisterPage.java
new file mode 100644 (file)
index 0000000..72adc23
--- /dev/null
@@ -0,0 +1,62 @@
+package org.cacert.gigi.pages.main;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.HashMap;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpSession;
+
+import org.cacert.gigi.dbObjects.User;
+import org.cacert.gigi.output.template.Form;
+import org.cacert.gigi.pages.Page;
+
+public class RegisterPage extends Page {
+
+    private static final String SIGNUP_PROCESS = "signupProcess";
+
+    public static final String PATH = "/register";
+
+    public RegisterPage() {
+        super("Register");
+    }
+
+    @Override
+    public void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
+        Signup s = new Signup(req);
+        outputGet(req, resp, s);
+    }
+
+    private void outputGet(HttpServletRequest req, HttpServletResponse resp, Signup s) throws IOException {
+        PrintWriter out = resp.getWriter();
+        HashMap<String, Object> vars = new HashMap<String, Object>();
+        getDefaultTemplate().output(out, getLanguage(req), vars);
+        s.output(out, getLanguage(req), vars);
+    }
+
+    @Override
+    public void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException {
+        Signup s = Form.getForm(req, Signup.class);
+        if (s == null) {
+            resp.getWriter().println(translate(req, "CSRF token check failed."));
+        } else if (s.submit(resp.getWriter(), req)) {
+            HttpSession hs = req.getSession();
+            hs.setAttribute(SIGNUP_PROCESS, null);
+            resp.getWriter().println(translate(req, "Your information has been submitted" + " into our system. You will now be sent an email with a web link," + " you need to open that link in your web browser within 24 hours" + " or your information will be removed from our system!"));
+            return;
+        }
+
+        outputGet(req, resp, s);
+    }
+
+    @Override
+    public boolean needsLogin() {
+        return false;
+    }
+
+    @Override
+    public boolean isPermitted(User u) {
+        return u == null;
+    }
+}
diff --git a/src/org/cacert/gigi/pages/main/RegisterPage.templ b/src/org/cacert/gigi/pages/main/RegisterPage.templ
new file mode 100644 (file)
index 0000000..4fbb41d
--- /dev/null
@@ -0,0 +1,11 @@
+<p><?=_By joining CAcert and becoming a member, you agree to the CAcert Community Agreement. Please take a moment now to read that and agree to it; this will be required to complete the process of joining.?></p>
+<p><?=_Warning! This site requires cookies to be enabled to ensure your privacy and security. This site uses session cookies to store temporary values to prevent people from copying and pasting the session ID to someone else exposing their account, personal details and identity theft as a result.?></p>
+<p style="border:dotted 1px #900;padding:0.3em;background-color:#ffe;">
+<b><?=_Note: Please enter your date of birth and names as they are written in your official documents.?></b><br /><br />
+<?=_Because CAcert is a certificate authority (CA) people rely on us knowing about the identity of the users of our certificates. So even as we value privacy very much, we need to collect at least some basic information about our members. This is especially the case for everybody who wants to take part in our web of trust.?>
+<?=_Your private information will be used for internal procedures only and will not be shared with third parties.?>
+</p>
+<p style="border:dotted 1px #900;padding:0.3em;background-color:#ffe;">
+<?=_A proper password wouldn't match your name or email at all, it contains at least 1 lower case letter, 1 upper case letter, a number, white space and a misc symbol. You get additional security for being over 15 characters and a second additional point for having it over 30. The system starts reducing security if you include any section of your name, or password or email address or if it matches a word from the english dictionary...?><br><br>
+<b><?=_Note: White spaces at the beginning and end of a password will be removed.?></b>
+</p>
diff --git a/src/org/cacert/gigi/pages/main/Signup.java b/src/org/cacert/gigi/pages/main/Signup.java
new file mode 100644 (file)
index 0000000..844a227
--- /dev/null
@@ -0,0 +1,199 @@
+package org.cacert.gigi.pages.main;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.sql.Date;
+import java.sql.SQLException;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.cacert.gigi.GigiApiException;
+import org.cacert.gigi.database.DatabaseConnection;
+import org.cacert.gigi.database.GigiPreparedStatement;
+import org.cacert.gigi.database.GigiResultSet;
+import org.cacert.gigi.dbObjects.EmailAddress;
+import org.cacert.gigi.dbObjects.User;
+import org.cacert.gigi.email.EmailProvider;
+import org.cacert.gigi.localisation.Language;
+import org.cacert.gigi.output.DateSelector;
+import org.cacert.gigi.output.template.Form;
+import org.cacert.gigi.output.template.Template;
+import org.cacert.gigi.pages.Page;
+import org.cacert.gigi.util.HTMLEncoder;
+import org.cacert.gigi.util.Notary;
+import org.cacert.gigi.util.PasswordStrengthChecker;
+
+public class Signup extends Form {
+
+    private User buildup = new User();
+
+    private Template t;
+
+    boolean general = true, country = true, regional = true, radius = true;
+
+    public Signup(HttpServletRequest hsr) {
+        super(hsr);
+        t = new Template(Signup.class.getResource("Signup.templ"));
+        buildup.setFName("");
+        buildup.setMName("");
+        buildup.setLName("");
+        buildup.setSuffix("");
+        buildup.setEmail("");
+        buildup.setDoB(new Date(0));
+    }
+
+    DateSelector myDoB = new DateSelector("day", "month", "year");
+
+    @Override
+    public void outputContent(PrintWriter out, Language l, Map<String, Object> outerVars) {
+        HashMap<String, Object> vars = new HashMap<String, Object>();
+        vars.put("fname", HTMLEncoder.encodeHTML(buildup.getFName()));
+        vars.put("mname", HTMLEncoder.encodeHTML(buildup.getMName()));
+        vars.put("lname", HTMLEncoder.encodeHTML(buildup.getLName()));
+        vars.put("suffix", HTMLEncoder.encodeHTML(buildup.getSuffix()));
+        vars.put("dob", myDoB);
+        vars.put("email", HTMLEncoder.encodeHTML(buildup.getEmail()));
+        vars.put("general", general ? " checked=\"checked\"" : "");
+        vars.put("country", country ? " checked=\"checked\"" : "");
+        vars.put("regional", regional ? " checked=\"checked\"" : "");
+        vars.put("radius", radius ? " checked=\"checked\"" : "");
+        vars.put("helpOnNames", String.format(l.getTranslation("Help on Names %sin the wiki%s"), "<a href=\"//wiki.cacert.org/FAQ/HowToEnterNamesInJoinForm\" target=\"_blank\">", "</a>"));
+        vars.put("csrf", getCSRFToken());
+        t.output(out, l, vars);
+    }
+
+    private void update(HttpServletRequest r) {
+        if (r.getParameter("fname") != null) {
+            buildup.setFName(r.getParameter("fname"));
+        }
+        if (r.getParameter("lname") != null) {
+            buildup.setLName(r.getParameter("lname"));
+        }
+        if (r.getParameter("mname") != null) {
+            buildup.setMName(r.getParameter("mname"));
+        }
+        if (r.getParameter("suffix") != null) {
+            buildup.setSuffix(r.getParameter("suffix"));
+        }
+        if (r.getParameter("email") != null) {
+            buildup.setEmail(r.getParameter("email"));
+        }
+        general = "1".equals(r.getParameter("general"));
+        country = "1".equals(r.getParameter("country"));
+        regional = "1".equals(r.getParameter("regional"));
+        radius = "1".equals(r.getParameter("radius"));
+        try {
+            myDoB.update(r);
+        } catch (GigiApiException e) {
+        }
+    }
+
+    @Override
+    public synchronized boolean submit(PrintWriter out, HttpServletRequest req) {
+        update(req);
+        if (buildup.getLName().trim().equals("")) {
+            outputError(out, req, "Last name were blank.");
+        }
+        if ( !myDoB.isValid()) {
+            outputError(out, req, "Invalid date of birth");
+        }
+        if ( !"1".equals(req.getParameter("cca_agree"))) {
+            outputError(out, req, "You have to agree to the CAcert Community agreement.");
+        }
+        if (buildup.getEmail().equals("")) {
+            outputError(out, req, "Email Address was blank");
+        }
+        String pw1 = req.getParameter("pword1");
+        String pw2 = req.getParameter("pword2");
+        if (pw1 == null || pw1.equals("")) {
+            outputError(out, req, "Pass Phrases were blank");
+        } else if ( !pw1.equals(pw2)) {
+            outputError(out, req, "Pass Phrases don't match");
+        }
+        int pwpoints = PasswordStrengthChecker.checkpw(pw1, buildup);
+        if (pwpoints < 3) {
+            outputError(out, req, "The Pass Phrase you submitted failed to contain enough" + " differing characters and/or contained words from" + " your name and/or email address.");
+        }
+        if (isFailed(out)) {
+            return false;
+        }
+        GigiPreparedStatement q1 = DatabaseConnection.getInstance().prepare("select * from `emails` where `email`=? and `deleted` IS NULL");
+        GigiPreparedStatement q2 = DatabaseConnection.getInstance().prepare("select * from certOwners inner join users on users.id=certOwners.id where `email`=? and `deleted` IS NULL");
+        q1.setString(1, buildup.getEmail());
+        q2.setString(1, buildup.getEmail());
+        GigiResultSet r1 = q1.executeQuery();
+        GigiResultSet r2 = q2.executeQuery();
+        if (r1.next() || r2.next()) {
+            outputError(out, req, "This email address is currently valid in the system.");
+        }
+        r1.close();
+        r2.close();
+        GigiPreparedStatement q3 = DatabaseConnection.getInstance().prepare("select `domain` from `baddomains` where `domain`=RIGHT(?, LENGTH(`domain`))");
+        q3.setString(1, buildup.getEmail());
+
+        GigiResultSet r3 = q3.executeQuery();
+        if (r3.next()) {
+            String domain = r3.getString(1);
+            outputError(out, req, "We don't allow signups from people using email addresses from %s", domain);
+        }
+        r3.close();
+        String mailResult = EmailProvider.FAIL;
+        try {
+            mailResult = HTMLEncoder.encodeHTML(EmailProvider.getInstance().checkEmailServer(0, buildup.getEmail()));
+        } catch (IOException e) {
+        }
+        if ( !mailResult.equals(EmailProvider.OK)) {
+            if (mailResult.startsWith("4")) {
+                outputError(out, req, "The mail server responsible for your domain indicated" + " a temporary failure. This may be due to anti-SPAM measures, such" + " as greylisting. Please try again in a few minutes.");
+            } else {
+                outputError(out, req, "Email Address given was invalid, or a test connection" + " couldn't be made to your server, or the server" + " rejected the email address as invalid");
+            }
+            if (mailResult.equals(EmailProvider.FAIL)) {
+                outputError(out, req, "Failed to make a connection to the mail server");
+            } else {
+                outputErrorPlain(out, mailResult);
+            }
+        }
+
+        if (isFailed(out)) {
+            return false;
+        }
+        try {
+            run(req, pw1);
+        } catch (SQLException e) {
+            e.printStackTrace();
+        } catch (GigiApiException e) {
+            outputError(out, req, e.getMessage());
+            return false;
+        }
+        return true;
+    }
+
+    private void run(HttpServletRequest req, String password) throws SQLException, GigiApiException {
+        try {
+            DatabaseConnection.getInstance().beginTransaction();
+            buildup.setPreferredLocale(Page.getLanguage(req).getLocale());
+            buildup.setDoB(myDoB.getDate());
+            buildup.insert(password);
+            int memid = buildup.getId();
+            EmailAddress ea = new EmailAddress(buildup, buildup.getEmail());
+            ea.insert(Page.getLanguage(req));
+
+            GigiPreparedStatement ps = DatabaseConnection.getInstance().prepare("insert into `alerts` set `memid`=?," + " `general`=?, `country`=?, `regional`=?, `radius`=?");
+            ps.setInt(1, memid);
+            ps.setString(2, general ? "1" : "0");
+            ps.setString(3, country ? "1" : "0");
+            ps.setString(4, regional ? "1" : "0");
+            ps.setString(5, radius ? "1" : "0");
+            ps.execute();
+            Notary.writeUserAgreement(buildup, "CCA", "account creation", "", true, 0);
+
+            DatabaseConnection.getInstance().commitTransaction();
+        } finally {
+            DatabaseConnection.getInstance().quitTransaction();
+        }
+
+    }
+}
diff --git a/src/org/cacert/gigi/pages/main/Signup.templ b/src/org/cacert/gigi/pages/main/Signup.templ
new file mode 100644 (file)
index 0000000..d38cefc
--- /dev/null
@@ -0,0 +1,83 @@
+<table class="wrapper dataTable" width="400">
+  <thead>
+  <tr>
+    <th colspan="3"><?=_My Details?></th>
+  </tr>
+  </thead>
+  <tbody>
+  <tr>
+    <td width="125"><?=_First Name?>: </td>
+    <td width="125"><input type="text" name="fname" size="30" value="<?=$fname?>" autocomplete="off"></td>
+    <td rowspan="4" width="125"><?=$!helpOnNames?></td>
+  </tr>
+
+  <tr>
+    <td valign="top"><?=_Middle Name(s)?><br>
+      (<?=_optional?>)
+    </td>
+    <td><input type="text" name="mname" size="30" value="<?=$mname?>" autocomplete="off"></td>
+  </tr>
+
+  <tr>
+    <td><?=_Last Name?>: </td>
+    <td><input type="text" name="lname" size="30" value="<?=$lname?>" autocomplete="off"></td>
+  </tr>
+
+  <tr>
+    <td><?=_Suffix?><br>
+      (<?=_optional?>)</td>
+    <td><input type="text" name="suffix" size="30" value="<?=$suffix?>" autocomplete="off"><br><?=_Please only write Name Suffixes into this field.?></td>
+  </tr>
+
+  <tr>
+    <td><?=_Date of Birth?><br>
+           (<?=_yyyy-mm-dd?>)</td>
+    <td><?=$dob?></td>
+    <td>&nbsp;</td>
+  </tr>
+
+  <tr>
+    <td><?=_Email Address?>: </td>
+    <td><input type="text" name="email" size="30" value="<?=$email?>" autocomplete="off"></td>
+    <td><?=_I own or am authorised to control this email address?></td>
+  </tr>
+
+  <tr>
+    <td><?=_Pass Phrase?><font color="red">*</font>: </td>
+    <td><input type="password" name="pword1" size="30" autocomplete="off"></td>
+    <td rowspan="2">&nbsp;</td>
+  </tr>
+  <tr>
+    <td><?=_Pass Phrase Again?><font color="red">*</font>: </td>
+    <td><input type="password" name="pword2" size="30" autocomplete="off"></td>
+  </tr>
+
+  <tr>
+    <td colspan="3"><font color="red">*</font><?=_Please note, in the interests of good security, the pass phrase must be made up of an upper case letter, lower case letter, number and symbol.?></td>
+  </tr>
+  <tr>
+    <td colspan="3"><?=_It's possible to get notifications of up and coming events and even just general announcements, untick any notifications you don't wish to receive. For country, regional and radius notifications to work you must choose your location once you've verified your account and logged in.?></td>
+  </tr>
+
+  <tr>
+    <td valign="top"><?=_Alert me if?>: </td>
+    <td align="left">
+        <input type="checkbox" name="general" value="1"<?=$!general?>><?=_General Announcements?><br>
+       <input type="checkbox" name="country" value="1"<?=$!country?>><?=_Country Announcements?><br>
+       <input type="checkbox" name="regional" value="1"<?=$!regional?>><?=_Regional Announcements?><br>
+       <input type="checkbox" name="radius" value="1"<?=$!radius?>><?=_Within 200km Announcements?></td>
+    <td>&nbsp;</td>
+  </tr>
+
+  <tr>
+    <td colspan="3"><?=_When you click on next, we will send a confirmation email to the email address you have entered above.?></td>
+  </tr>
+  <tr>
+    <td colspan="3"><input type="checkbox" name="cca_agree" value="1"><?=_I agree to the terms and conditions of the CAcert Community Agreement?>: <a href="/policy/CAcertCommunityAgreement.php">http://www.cacert.org/policy/CAcertCommunityAgreement.php</a></td>
+  </tr>
+
+  <tr>
+    <td colspan="3"><input type="submit" name="process" value="<?=_Next?>"></td>
+  </tr>
+  </tbody>
+</table>
diff --git a/src/org/cacert/gigi/pages/orga/AffiliationForm.java b/src/org/cacert/gigi/pages/orga/AffiliationForm.java
new file mode 100644 (file)
index 0000000..0191756
--- /dev/null
@@ -0,0 +1,76 @@
+package org.cacert.gigi.pages.orga;
+
+import java.io.PrintWriter;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.cacert.gigi.GigiApiException;
+import org.cacert.gigi.dbObjects.Organisation;
+import org.cacert.gigi.dbObjects.Organisation.Affiliation;
+import org.cacert.gigi.dbObjects.User;
+import org.cacert.gigi.localisation.Language;
+import org.cacert.gigi.output.template.Form;
+import org.cacert.gigi.output.template.IterableDataset;
+import org.cacert.gigi.output.template.Template;
+import org.cacert.gigi.pages.LoginPage;
+import org.cacert.gigi.pages.Page;
+
+public class AffiliationForm extends Form {
+
+    Organisation o;
+
+    private static final Template t = new Template(AffiliationForm.class.getResource("AffiliationForm.templ"));
+
+    public AffiliationForm(HttpServletRequest hsr, Organisation o) {
+        super(hsr);
+        this.o = o;
+    }
+
+    @Override
+    public boolean submit(PrintWriter out, HttpServletRequest req) throws GigiApiException {
+        if (req.getParameter("del") != null) {
+            User toRemove = User.getByEmail(req.getParameter("del"));
+            if (toRemove != null) {
+                o.removeAdmin(toRemove, LoginPage.getUser(req));
+                return true;
+            }
+        } else if (req.getParameter("do_affiliate") != null) {
+            User byEmail = User.getByEmail(req.getParameter("email"));
+            if (byEmail != null) {
+                o.addAdmin(byEmail, LoginPage.getUser(req), req.getParameter("master") != null);
+                return true;
+            }
+        }
+        out.println(Page.getLanguage(req).getTranslation("No action could have been carried out."));
+        return false;
+    }
+
+    @Override
+    protected void outputContent(PrintWriter out, Language l, Map<String, Object> vars) {
+        final List<Affiliation> admins = o.getAllAdmins();
+        vars.put("admins", new IterableDataset() {
+
+            Iterator<Affiliation> iter = admins.iterator();
+
+            @Override
+            public boolean next(Language l, Map<String, Object> vars) {
+                if ( !iter.hasNext()) {
+                    return false;
+                }
+                Affiliation aff = iter.next();
+                vars.put("name", aff.getTarget().getName());
+                vars.put("master", aff.isMaster() ? l.getTranslation("master") : "");
+                vars.put("e-mail", aff.getTarget().getEmail());
+                return true;
+            }
+        });
+        t.output(out, l, vars);
+    }
+
+    public Organisation getOrganisation() {
+        return o;
+    }
+}
diff --git a/src/org/cacert/gigi/pages/orga/AffiliationForm.templ b/src/org/cacert/gigi/pages/orga/AffiliationForm.templ
new file mode 100644 (file)
index 0000000..9cda4d8
--- /dev/null
@@ -0,0 +1,22 @@
+<table align="center" valign="middle" border="0" cellspacing="0" cellpadding="0" class="wrapper dataTable">
+  <tr>
+    <th><?=_Name?></th>
+    <th><?=_Email?></th>
+    <th><?=_Master?></th>
+    <th> </th>
+  </tr>
+<? foreach($admins) { ?>
+  <tr>
+    <td><?=$name?></td>
+    <td><?=$e-mail?></td>
+    <td><?=$master?></td>
+    <td><button type="submit" name="del" value="<?=$e-mail?>">X</button> </td>
+  </tr>
+<? } ?>
+  <tr>
+    <td></td>
+    <td><input type="text" name="email"></td>
+    <td><input type="checkbox" name="master" value="y"></td>
+    <td><input type="submit" name="do_affiliate" value="<?=_Add?>"></td>
+  </tr>
+</table>
diff --git a/src/org/cacert/gigi/pages/orga/CreateOrgForm.java b/src/org/cacert/gigi/pages/orga/CreateOrgForm.java
new file mode 100644 (file)
index 0000000..32a9ceb
--- /dev/null
@@ -0,0 +1,80 @@
+package org.cacert.gigi.pages.orga;
+
+import java.io.PrintWriter;
+import java.util.Map;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.cacert.gigi.GigiApiException;
+import org.cacert.gigi.dbObjects.Organisation;
+import org.cacert.gigi.localisation.Language;
+import org.cacert.gigi.output.template.Form;
+import org.cacert.gigi.output.template.Template;
+import org.cacert.gigi.pages.LoginPage;
+
+public class CreateOrgForm extends Form {
+
+    private final static Template t = new Template(CreateOrgForm.class.getResource("CreateOrgForm.templ"));
+
+    private Organisation result;
+
+    private String o = "";
+
+    private String c = "";
+
+    private String st = "";
+
+    private String l = "";
+
+    private String email = "";
+
+    private boolean isEdit = false;
+
+    public CreateOrgForm(HttpServletRequest hsr) {
+        super(hsr);
+    }
+
+    public CreateOrgForm(HttpServletRequest hsr, Organisation t) {
+        super(hsr);
+        isEdit = true;
+        result = t;
+        o = t.getName();
+        c = t.getState();
+        st = t.getProvince();
+        l = t.getCity();
+        email = t.getContactEmail();
+    }
+
+    @Override
+    public boolean submit(PrintWriter out, HttpServletRequest req) throws GigiApiException {
+        o = req.getParameter("O");
+        c = req.getParameter("C");
+        st = req.getParameter("ST");
+        l = req.getParameter("L");
+        email = req.getParameter("contact");
+        if (result != null) {
+            result.update(o, c, st, l, email);
+            return true;
+        }
+        Organisation ne = new Organisation(o, c, st, l, email, LoginPage.getUser(req));
+        result = ne;
+        return true;
+    }
+
+    public Organisation getResult() {
+        return result;
+    }
+
+    @Override
+    protected void outputContent(PrintWriter out, Language l, Map<String, Object> vars) {
+        vars.put("O", o);
+        vars.put("C", c);
+        vars.put("ST", st);
+        vars.put("L", this.l);
+        vars.put("email", email);
+        if (isEdit) {
+            vars.put("edit", true);
+        }
+        t.output(out, l, vars);
+    }
+}
diff --git a/src/org/cacert/gigi/pages/orga/CreateOrgForm.templ b/src/org/cacert/gigi/pages/orga/CreateOrgForm.templ
new file mode 100644 (file)
index 0000000..fc94fe3
--- /dev/null
@@ -0,0 +1,45 @@
+<table align="center" valign="middle" border="0" cellspacing="0" cellpadding="0" class="wrapper dataTable">
+  <tr>
+    <th colspan="2">
+    <? if($edit) { ?>
+    <?=_Edit Organisation?>
+    <? } else { ?>
+    <?=_New Organisation?>
+    <? } ?></th>
+  </tr>
+  <tr>
+    <td><?=_Organisation Name?>:</td>
+    <td><input type="text" name="O" value="<?=$O?>" maxlength="64" size="90"></td>
+  </tr>
+  <tr>
+    <td><?=_Contact Email?>:</td>
+    <td><input type="text" name="contact" value="<?=$email?>" maxlength="255" size="90"></td>
+  </tr>
+  <tr>
+    <td><?=_Town/Suburb?>:</td>
+    <td><input type="text" name="L" value="<?=$L?>" maxlength="255" size="90"></td>
+  </tr>
+  <tr>
+    <td><?=_State/Province?>:</td>
+    <td><input type="text" name="ST" value="<?=$ST?>" maxlength="255" size="90"></td>
+  </tr>
+  <tr>
+    <td><?=_Country?>:</td>
+    <td><input type="text" name="C" value="<?=$C?>" maxlength="2" size="5">
+        <?=_(2 letter !'<a href="http://www.iso.org/iso/home/standards/country_codes/iso-3166-1_decoding_table.htm">'ISO code!'</a>')?>
+    </td>
+  </tr>
+  <tr>
+    <td><?=_Comments?>:</td>
+    <td><textarea name="comments" cols="60" rows="10"></textarea></td>
+  </tr>
+  <? if($edit) { ?>
+  <tr>
+    <td></td>
+    <td><?=_WARNING: updating the data will revoke all issued certificates.?></td>
+  </tr>
+  <? } ?>
+  <tr>
+    <td colspan="2"><input type="submit" value="<?=_Submit?>"></td>
+  </tr>
+</table>
diff --git a/src/org/cacert/gigi/pages/orga/CreateOrgPage.java b/src/org/cacert/gigi/pages/orga/CreateOrgPage.java
new file mode 100644 (file)
index 0000000..f1e8ea6
--- /dev/null
@@ -0,0 +1,47 @@
+package org.cacert.gigi.pages.orga;
+
+import java.io.IOException;
+import java.util.HashMap;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.cacert.gigi.GigiApiException;
+import org.cacert.gigi.dbObjects.Group;
+import org.cacert.gigi.dbObjects.User;
+import org.cacert.gigi.output.template.Form;
+import org.cacert.gigi.pages.Page;
+
+public class CreateOrgPage extends Page {
+
+    public static final Group ORG_ASSURER = Group.getByString("orgassurer");
+
+    public static final String DEFAULT_PATH = "/orga/new";
+
+    public CreateOrgPage() {
+        super("Create Organisation");
+    }
+
+    @Override
+    public boolean isPermitted(User u) {
+        return u != null && u.isInGroup(ORG_ASSURER);
+    }
+
+    @Override
+    public void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException {
+        try {
+            CreateOrgForm form = Form.getForm(req, CreateOrgForm.class);
+            if (form.submit(resp.getWriter(), req)) {
+                resp.sendRedirect(ViewOrgPage.DEFAULT_PATH + "/" + form.getResult().getId());
+                return;
+            }
+        } catch (GigiApiException e) {
+            e.format(resp.getWriter(), getLanguage(req));
+        }
+    }
+
+    @Override
+    public void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
+        new CreateOrgForm(req).output(resp.getWriter(), getLanguage(req), new HashMap<String, Object>());
+    }
+}
diff --git a/src/org/cacert/gigi/pages/orga/EditOrg.templ b/src/org/cacert/gigi/pages/orga/EditOrg.templ
new file mode 100644 (file)
index 0000000..bd415d7
--- /dev/null
@@ -0,0 +1,3 @@
+<?=$editForm?>
+<br/>
+<?=$affForm?>
diff --git a/src/org/cacert/gigi/pages/orga/ViewOrgPage.java b/src/org/cacert/gigi/pages/orga/ViewOrgPage.java
new file mode 100644 (file)
index 0000000..16c8bc5
--- /dev/null
@@ -0,0 +1,111 @@
+package org.cacert.gigi.pages.orga;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.cacert.gigi.GigiApiException;
+import org.cacert.gigi.dbObjects.Organisation;
+import org.cacert.gigi.dbObjects.User;
+import org.cacert.gigi.localisation.Language;
+import org.cacert.gigi.output.template.Form;
+import org.cacert.gigi.output.template.IterableDataset;
+import org.cacert.gigi.output.template.Template;
+import org.cacert.gigi.pages.LoginPage;
+import org.cacert.gigi.pages.Page;
+
+public class ViewOrgPage extends Page {
+
+    private final Template orgas = new Template(ViewOrgPage.class.getResource("ViewOrgs.templ"));
+
+    private final Template mainTempl = new Template(ViewOrgPage.class.getResource("EditOrg.templ"));
+
+    public static final String DEFAULT_PATH = "/orga";
+
+    public ViewOrgPage() {
+        super("View Organisation");
+    }
+
+    @Override
+    public boolean isPermitted(User u) {
+        return u != null && (u.isInGroup(CreateOrgPage.ORG_ASSURER) || u.getOrganisations().size() != 0);
+    }
+
+    @Override
+    public void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException {
+        try {
+            User u = LoginPage.getUser(req);
+            if ( !u.isInGroup(CreateOrgPage.ORG_ASSURER)) {
+                return;
+            }
+            if (req.getParameter("do_affiliate") != null || req.getParameter("del") != null) {
+                AffiliationForm form = Form.getForm(req, AffiliationForm.class);
+                if (form.submit(resp.getWriter(), req)) {
+                    resp.sendRedirect(DEFAULT_PATH + "/" + form.getOrganisation().getId());
+                }
+            } else {
+                Form.getForm(req, CreateOrgForm.class).submit(resp.getWriter(), req);
+            }
+        } catch (GigiApiException e) {
+            e.format(resp.getWriter(), getLanguage(req));
+        }
+    }
+
+    @Override
+    public void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
+        User u = LoginPage.getUser(req);
+        String idS = req.getPathInfo();
+        Language lang = getLanguage(req);
+        PrintWriter out = resp.getWriter();
+        if (idS.length() < DEFAULT_PATH.length() + 2) {
+            final Organisation[] orgas = Organisation.getOrganisations(0, 30);
+            HashMap<String, Object> map = new HashMap<>();
+            final List<Organisation> myOrgs = u.getOrganisations();
+            final boolean orgAss = u.isInGroup(CreateOrgPage.ORG_ASSURER);
+            if (orgAss) {
+                map.put("orgas", makeOrgDataset(orgas));
+            } else {
+                map.put("orgas", makeOrgDataset(myOrgs.toArray(new Organisation[myOrgs.size()])));
+            }
+            this.orgas.output(out, lang, map);
+            return;
+        }
+        idS = idS.substring(DEFAULT_PATH.length() + 1);
+        int id = Integer.parseInt(idS);
+        Organisation o = Organisation.getById(id);
+        final List<Organisation> myOrgs = u.getOrganisations();
+        final boolean orgAss = u.isInGroup(CreateOrgPage.ORG_ASSURER);
+        if (o == null || ( !orgAss && !myOrgs.contains(o))) {
+            resp.sendError(404);
+            return;
+        }
+        HashMap<String, Object> vars = new HashMap<>();
+        vars.put("editForm", new CreateOrgForm(req, o));
+        vars.put("affForm", new AffiliationForm(req, o));
+        mainTempl.output(out, lang, vars);
+    }
+
+    private IterableDataset makeOrgDataset(final Organisation[] orgas) {
+        return new IterableDataset() {
+
+            int count = 0;
+
+            @Override
+            public boolean next(Language l, Map<String, Object> vars) {
+                if (count >= orgas.length) {
+                    return false;
+                }
+                Organisation org = orgas[count++];
+                vars.put("id", Integer.toString(org.getId()));
+                vars.put("name", org.getName());
+                vars.put("country", org.getState());
+                return true;
+            }
+        };
+    }
+}
diff --git a/src/org/cacert/gigi/pages/orga/ViewOrgs.templ b/src/org/cacert/gigi/pages/orga/ViewOrgs.templ
new file mode 100644 (file)
index 0000000..2b22950
--- /dev/null
@@ -0,0 +1,8 @@
+<table align="center" valign="middle" border="0" cellspacing="0" cellpadding="0" class="wrapper dataTable">
+<? foreach($orgas) { ?>
+  <tr>
+    <td><?=$country?></td>
+    <td><a href='orga/<?=$id?>'><?=$name?></a></td>
+  </tr>
+<? } ?>
+</table>
diff --git a/src/org/cacert/gigi/pages/wot/AssuranceForm.java b/src/org/cacert/gigi/pages/wot/AssuranceForm.java
new file mode 100644 (file)
index 0000000..36e5247
--- /dev/null
@@ -0,0 +1,91 @@
+package org.cacert.gigi.pages.wot;
+
+import java.io.PrintWriter;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.cacert.gigi.GigiApiException;
+import org.cacert.gigi.dbObjects.Name;
+import org.cacert.gigi.dbObjects.User;
+import org.cacert.gigi.localisation.Language;
+import org.cacert.gigi.output.template.Form;
+import org.cacert.gigi.output.template.Template;
+import org.cacert.gigi.pages.Page;
+import org.cacert.gigi.util.Notary;
+
+public class AssuranceForm extends Form {
+
+    private User assuree;
+
+    private Name assureeName;
+
+    private Date dob;
+
+    private static final Template templ;
+    static {
+        templ = new Template(AssuranceForm.class.getResource("AssuranceForm.templ"));
+    }
+
+    public AssuranceForm(HttpServletRequest hsr, User assuree) {
+        super(hsr);
+        this.assuree = assuree;
+        assureeName = this.assuree.getName();
+        dob = this.assuree.getDoB();
+    }
+
+    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
+
+    SimpleDateFormat sdf2 = new SimpleDateFormat("dd. MMM yyyy");
+
+    @Override
+    public void outputContent(PrintWriter out, Language l, Map<String, Object> vars) {
+        HashMap<String, Object> res = new HashMap<String, Object>();
+        res.putAll(vars);
+        res.put("nameExplicit", assuree.getName());
+        res.put("name", assuree.getName().toString());
+        res.put("maxpoints", assuree.getMaxAssurePoints());
+        res.put("dob", sdf.format(assuree.getDoB()));
+        res.put("dobFmt2", sdf2.format(assuree.getDoB()));
+        templ.output(out, l, res);
+    }
+
+    @Override
+    public boolean submit(PrintWriter out, HttpServletRequest req) {
+        if ( !"1".equals(req.getParameter("certify")) || !"1".equals(req.getParameter("rules")) || !"1".equals(req.getParameter("CCAAgreed")) || !"1".equals(req.getParameter("assertion"))) {
+            outputError(out, req, "You failed to check all boxes to validate" + " your adherence to the rules and policies of CAcert");
+
+        }
+        int pointsI = 0;
+        String points = req.getParameter("points");
+        if (points == null || "".equals(points)) {
+            outputError(out, req, "For an assurance, you need to enter points.");
+        } else {
+            try {
+                pointsI = Integer.parseInt(points);
+            } catch (NumberFormatException e) {
+                outputError(out, req, "The points entered were not a number.");
+            }
+        }
+
+        if (isFailed(out)) {
+            return false;
+        }
+        try {
+            Notary.assure(Page.getUser(req), assuree, assureeName, dob, pointsI, req.getParameter("location"), req.getParameter("date"));
+            return true;
+        } catch (GigiApiException e) {
+            e.format(out, Page.getLanguage(req));
+        }
+
+        return false;
+    }
+
+    public User getAssuree() {
+        return assuree;
+    }
+
+}
diff --git a/src/org/cacert/gigi/pages/wot/AssuranceForm.templ b/src/org/cacert/gigi/pages/wot/AssuranceForm.templ
new file mode 100644 (file)
index 0000000..21691e1
--- /dev/null
@@ -0,0 +1,60 @@
+<table class="wrapper dataTable" width="600">
+<thead>
+<tr><th colspan="2"><?=_Assurance Confirmation?></th></tr>
+</thead>
+<tbody>
+<tr><td colspan="2"><?=_Please check the following details match against what you witnessed when you met $name in person. You MUST NOT proceed unless you are sure the details are correct. You may be held responsible by the CAcert Arbitrator for any issues with this Assurance.?>
+</td></tr>
+
+       <tr>
+               <td><?=_Name?>: </td>
+               <td><span class="accountdetail"><?=$nameExplicit?></span></td>
+       </tr>
+       <tr>
+               <td><?=_Date of Birth?>: </td>
+               <td><span class="accountdetail dob"><?=$dob?> (<?=$dobFmt2?>)</span></td>
+       </tr>
+       <tr>
+               <td><input type="checkbox" name="certify" value="1"></td>
+               <td><?=_I certify that $name has appeared in person.?></td>
+       </tr>
+       <tr>
+               <td><input type="checkbox" name="CCAAgreed" value="1"></td>
+               <td><?=_I verify that $name has accepted the CAcert Community Agreement.?></td>
+       </tr>
+       <tr>
+               <td><?=_Location?></td>
+               <td><input type="text" name="location"></td>
+       </tr>
+       <tr>
+               <td><?=_Date?></td>
+               <td><input type="text" name="date"><br/><?=_The date when the assurance took place. Please adjust the date if you assured the person on a different day (YYYY-MM-DD).?></td>
+       </tr>
+       <tr>
+               <td><input type="checkbox" name="assertion" value="1"></td>
+               <td><?=_I believe that the assertion of identity I am making is correct, complete and verifiable. I have seen original documentation attesting to this identity. I accept that the CAcert Arbitrator may call upon me to provide evidence in any dispute, and I may be held responsible.?></td>
+       </tr>
+       <tr>
+               <td><input type="checkbox" name="rules" value="1"></td>
+               <td><?=_I have read and understood the CAcert Community Agreement (CCA), Assurance Policy and the Assurance Handbook. I am making this Assurance subject to and in compliance with the CCA, Assurance policy and handbook.?></td>
+       </tr>
+       <tr>
+               <td><?=_Policy?>: </td>
+               <td>
+                       <a href="/policy/CAcertCommunityAgreement.php" target="_blank"><?=_CAcert Community Agreement?></a>
+                        - <a href="/policy/AssurancePolicy.php" target="_blank"><?=_Assurance Policy?></a>
+                        - <a href="http://wiki.cacert.org/AssuranceHandbook2" target="_blank"><?=_Assurance Handbook?></a>
+               </td>
+       </tr>
+       <tr>
+               <td><?=_Points?></td>
+               <td><input type="text" name="points"><br/>(Max. <?=$maxpoints?>)</td>
+       </tr>
+       <tr>
+               <td colspan="2">
+                       <input type="submit" name="process" value="<?=_I confirm this Assurance?>" />
+                       <input type="submit" name="cancel" value="<?=_Cancel?>" />
+               </td>
+       </tr>
+       </tbody>
+</table>
diff --git a/src/org/cacert/gigi/pages/wot/AssurePage.java b/src/org/cacert/gigi/pages/wot/AssurePage.java
new file mode 100644 (file)
index 0000000..cc20c4d
--- /dev/null
@@ -0,0 +1,113 @@
+package org.cacert.gigi.pages.wot;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.HashMap;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.cacert.gigi.GigiApiException;
+import org.cacert.gigi.database.DatabaseConnection;
+import org.cacert.gigi.database.GigiPreparedStatement;
+import org.cacert.gigi.database.GigiResultSet;
+import org.cacert.gigi.dbObjects.User;
+import org.cacert.gigi.output.DateSelector;
+import org.cacert.gigi.output.template.Form;
+import org.cacert.gigi.output.template.Template;
+import org.cacert.gigi.pages.LoginPage;
+import org.cacert.gigi.pages.Page;
+import org.cacert.gigi.util.Notary;
+
+public class AssurePage extends Page {
+
+    public static final String PATH = "/wot/assure";
+
+    DateSelector ds = new DateSelector("day", "month", "year");
+
+    Template t;
+
+    public AssurePage() {
+        super("Assure someone");
+        t = new Template(AssuranceForm.class.getResource("AssureeSearch.templ"));
+
+    }
+
+    @Override
+    public void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
+
+        PrintWriter out = resp.getWriter();
+        String pi = req.getPathInfo().substring(PATH.length());
+        HashMap<String, Object> vars = new HashMap<String, Object>();
+        vars.put("DoB", ds);
+        t.output(out, getLanguage(req), vars);
+    }
+
+    @Override
+    public boolean isPermitted(User u) {
+        return u != null && u.canAssure();
+    }
+
+    private void outputForm(HttpServletRequest req, PrintWriter out, AssuranceForm form) {
+        User myself = LoginPage.getUser(req);
+        try {
+            Notary.checkAssuranceIsPossible(myself, form.getAssuree());
+        } catch (GigiApiException e) {
+            e.format(out, Page.getLanguage(req));
+        }
+
+        form.output(out, getLanguage(req), new HashMap<String, Object>());
+    }
+
+    @Override
+    public void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException {
+        PrintWriter out = resp.getWriter();
+        if (req.getParameter("search") == null) {
+            AssuranceForm form = Form.getForm(req, AssuranceForm.class);
+            if (form.submit(out, req)) {
+                out.println(translate(req, "Assurance complete."));
+            } else {
+                outputForm(req, resp.getWriter(), form);
+            }
+
+            return;
+        }
+
+        GigiResultSet rs = null;
+        try {
+            GigiPreparedStatement ps = DatabaseConnection.getInstance().prepare("SELECT users.id, verified FROM users inner join certOwners on certOwners.id=users.id WHERE email=? AND dob=? AND deleted is null");
+            ps.setString(1, req.getParameter("email"));
+            String day = req.getParameter("year") + "-" + req.getParameter("month") + "-" + req.getParameter("day");
+            ps.setString(2, day);
+            rs = ps.executeQuery();
+            int id = 0;
+            if (rs.next()) {
+                id = rs.getInt(1);
+                int verified = rs.getInt(2);
+                if (rs.next()) {
+                    out.println("Error, ambigous user. Please contact support@cacert.org.");
+                } else {
+                    if (verified == 0) {
+                        out.println(translate(req, "User is not yet verified. Please try again in 24 hours!"));
+                    } else if (getUser(req).getId() == id) {
+
+                    } else {
+                        AssuranceForm form = new AssuranceForm(req, User.getById(id));
+                        outputForm(req, out, form);
+                    }
+                }
+            } else {
+                out.print("<div class='formError'>");
+
+                out.println(translate(req, "I'm sorry, there was no email and date of birth matching" + " what you entered in the system. Please double check" + " your information."));
+                out.print("</div>");
+            }
+
+            rs.close();
+        } finally {
+            if (rs != null) {
+                rs.close();
+            }
+        }
+    }
+}
diff --git a/src/org/cacert/gigi/pages/wot/AssureeSearch.templ b/src/org/cacert/gigi/pages/wot/AssureeSearch.templ
new file mode 100644 (file)
index 0000000..acc0416
--- /dev/null
@@ -0,0 +1,23 @@
+<form method="POST">
+<table class="wrapper dataTable" width="300">
+  <thead>
+  <tr>
+    <th colspan="2" class="title"><?=_Assure Someone?></th>
+  </tr>
+  </thead>
+  <tbody>
+  <tr>
+    <td width="125"><?=_Email?>: </td>
+    <td width="125"><input type="text" name="email"></td>
+  </tr>
+  <tr>
+    <td width="125"><?=_Date of Birth?><br>
+           (<?=_yyyy-mm-dd?>)</td>
+    <td width="125"><?=$DoB?></td>
+  </tr>
+  <tr>
+    <td colspan="2"><input type="submit" name="search" value="<?=_Next?>"></td>
+  </tr>
+  </tbody>
+</table>
+</form>
diff --git a/src/org/cacert/gigi/pages/wot/MyPoints.java b/src/org/cacert/gigi/pages/wot/MyPoints.java
new file mode 100644 (file)
index 0000000..ffe6898
--- /dev/null
@@ -0,0 +1,36 @@
+package org.cacert.gigi.pages.wot;
+
+import java.io.IOException;
+import java.util.HashMap;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.cacert.gigi.dbObjects.User;
+import org.cacert.gigi.output.AssurancesDisplay;
+import org.cacert.gigi.pages.Page;
+
+public class MyPoints extends Page {
+
+    public static final String PATH = "/wot/mypoints";
+
+    private AssurancesDisplay myDisplay = new AssurancesDisplay("asArr", false);
+
+    private AssurancesDisplay toOtherDisplay = new AssurancesDisplay("otherAsArr", true);
+
+    public MyPoints(String title) {
+        super(title);
+    }
+
+    @Override
+    public void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
+        HashMap<String, Object> vars = new HashMap<String, Object>();
+        vars.put("pointlist", myDisplay);
+        vars.put("madelist", toOtherDisplay);
+        User user = getUser(req);
+        vars.put("asArr", user.getReceivedAssurances());
+        vars.put("otherAsArr", user.getMadeAssurances());
+        getDefaultTemplate().output(resp.getWriter(), getLanguage(req), vars);
+    }
+
+}
diff --git a/src/org/cacert/gigi/pages/wot/MyPoints.templ b/src/org/cacert/gigi/pages/wot/MyPoints.templ
new file mode 100644 (file)
index 0000000..2344ae7
--- /dev/null
@@ -0,0 +1,3 @@
+<?=$pointlist?>
+<h2><?=_Assurance Points You Issued?></h2>
+<?=$madelist?>
\ No newline at end of file
diff --git a/src/org/cacert/gigi/pages/wot/RequestTTPForm.java b/src/org/cacert/gigi/pages/wot/RequestTTPForm.java
new file mode 100644 (file)
index 0000000..18b97c2
--- /dev/null
@@ -0,0 +1,63 @@
+package org.cacert.gigi.pages.wot;
+
+import java.io.PrintWriter;
+import java.util.Map;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.cacert.gigi.GigiApiException;
+import org.cacert.gigi.dbObjects.Group;
+import org.cacert.gigi.dbObjects.User;
+import org.cacert.gigi.localisation.Language;
+import org.cacert.gigi.output.template.Form;
+import org.cacert.gigi.output.template.OutputableArrayIterable;
+import org.cacert.gigi.output.template.Template;
+import org.cacert.gigi.pages.LoginPage;
+
+public class RequestTTPForm extends Form {
+
+    public static final Group TTP_APPLICANT = Group.getByString("ttp-applicant");
+
+    private static final Template t = new Template(RequestTTPForm.class.getResource("RequestTTPForm.templ"));
+
+    private User u;
+
+    public RequestTTPForm(HttpServletRequest hsr) {
+        super(hsr);
+        u = LoginPage.getUser(hsr);
+    }
+
+    private final String[] COUNTRIES = new String[] {
+            "Australia", "Puerto Rico", "USA"
+    };
+
+    @Override
+    public boolean submit(PrintWriter out, HttpServletRequest req) throws GigiApiException {
+        String country = req.getParameter("country");
+        if (country != null) {
+            int cid = Integer.parseInt(country);
+            if (cid < 0 || cid >= COUNTRIES.length) {
+                throw new GigiApiException("Invalid country id");
+            }
+            country = COUNTRIES[cid];
+        }
+
+        User uReq = LoginPage.getUser(req);
+
+        if ( !u.equals(uReq)) {
+            return false;
+        }
+
+        u.grantGroup(u, TTP_APPLICANT);
+
+        return false;
+    }
+
+    @Override
+    protected void outputContent(PrintWriter out, Language l, Map<String, Object> map) {
+        map.put("countries", new OutputableArrayIterable(COUNTRIES, "country"));
+
+        t.output(out, l, map);
+    }
+
+}
diff --git a/src/org/cacert/gigi/pages/wot/RequestTTPForm.templ b/src/org/cacert/gigi/pages/wot/RequestTTPForm.templ
new file mode 100644 (file)
index 0000000..6f8ff66
--- /dev/null
@@ -0,0 +1,19 @@
+<table align="center" valign="middle" border="0" cellspacing="0" cellpadding="0" class="wrapper dataTable">
+       <tr>
+               <td class="DataTD"><?=_Country where you want to visit the TTP?></td>
+               <td class="DataTD"><select size="1" name="country">
+                       <? foreach($countries) {?>
+                               <option value="<?=$i?>"><?=$country?></option>
+                       <? } ?>
+               </select></td>
+       </tr>
+<!--   <tr>
+               <td class="DataTD"><?=_I want to take part in the TTP Topup programme?></td>
+               <td class="DataTD"><input type="checkbox" name="ttptopup" value="1"></td>
+       </tr>-->
+       <tr>
+               <td colspan="2" >
+                       <input type="submit" name="ttp" value="<?=_I need a TTP assurance?>">
+               </td>
+       </tr>
+</table>
diff --git a/src/org/cacert/gigi/pages/wot/RequestTTPPage.java b/src/org/cacert/gigi/pages/wot/RequestTTPPage.java
new file mode 100644 (file)
index 0000000..041014d
--- /dev/null
@@ -0,0 +1,61 @@
+package org.cacert.gigi.pages.wot;
+
+import java.io.IOException;
+import java.util.HashMap;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.cacert.gigi.GigiApiException;
+import org.cacert.gigi.dbObjects.Assurance;
+import org.cacert.gigi.dbObjects.User;
+import org.cacert.gigi.output.template.Form;
+import org.cacert.gigi.pages.LoginPage;
+import org.cacert.gigi.pages.Page;
+
+public class RequestTTPPage extends Page {
+
+    public static final String PATH = "/wot/ttp";
+
+    public RequestTTPPage() {
+        super("Request TTP");
+    }
+
+    @Override
+    public void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException {
+        try {
+            Form.getForm(req, RequestTTPForm.class).submit(resp.getWriter(), req);
+        } catch (GigiApiException e) {
+            e.format(resp.getWriter(), getLanguage(req));
+        }
+    }
+
+    @Override
+    public void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
+        User u = LoginPage.getUser(req);
+        HashMap<String, Object> map = new HashMap<String, Object>();
+        if (u.isInGroup(RequestTTPForm.TTP_APPLICANT)) {
+            map.put("inProgress", true);
+        } else {
+            if (u.getAssurancePoints() < 100) {
+                int ttpCount = 0;
+                for (Assurance a : u.getReceivedAssurances()) {
+                    if (a.getMethod().equals(Assurance.AssuranceType.TTP_ASSISTED.getDescription())) {
+                        ttpCount++;
+                    }
+                }
+                if (ttpCount < 2) {
+                    map.put("ttp", true);
+                    map.put("form", new RequestTTPForm(req));
+                } else {
+                    map.put("nothing", true);
+                }
+            } else {
+                map.put("nothing", true);
+            }
+        }
+        map.put("form", new RequestTTPForm(req));
+        getDefaultTemplate().output(resp.getWriter(), getLanguage(req), map);
+    }
+
+}
diff --git a/src/org/cacert/gigi/pages/wot/RequestTTPPage.templ b/src/org/cacert/gigi/pages/wot/RequestTTPPage.templ
new file mode 100644 (file)
index 0000000..a6898fe
--- /dev/null
@@ -0,0 +1,35 @@
+<h3><?=_Trusted Third Parties?></h3>
+
+<p><?=_The Trusted Third Party (TTP) programme is intended to be used in areas without many CAcert Assurers.?></p>
+
+<p><?=_A Trusted Third Party (TTP) is simply someone in your country that is responsible for witnessing signatures and ID documents. This role is covered by many different titles such as public notary, justice of the peace and so on.?></p>
+
+<p><?=_With the TTP programme you can potentially gain assurance up to a maximum of 100 assurance points.?></p>
+
+<p><?=_Currently CAcert has only developed the TTP programme to the level that you can gain 70 assurance points by TTP assurances.?></p>
+
+<p><?=_We are working to develop a process that will fill the gap of the missing 30 assurance points to allow you to get the maximum 100 assurance points.?> </p>
+
+<p><?=_In the meanwhile you would need to close this gap with face to face assurances with CAcert Assurers. Think not only travelling to populated countries, but also remember that assurers may occasionally visit your country or area.?></p>
+
+<p><?=_If you are interested in the TTP programme, read the pages !'<a href="//wiki.cacert.org/TTP/TTPuser">https://wiki.cacert.org/TTP/TTPuser</a>' for the basic way how the TTP programme works for you, and !'<a href="//wiki.cacert.org/TTP/TTPAL">https://wiki.cacert.org/TTP/TTPAL</a>' whether the TTP programme affects the country where you are located.?> </p>
+
+<? if($ttp) { ?>
+<p><?=_If you want to ask for TTP assurances fill out the missing data and send the request to support@cacert.org to start the process. CAcert will then inform you about the next steps.?></p>
+<?=$form?>
+<? } ?>
+
+<? if($topup) { ?>
+<p><?=_As you have already got 2 TTP assurances you can only take part in the TTP TOPUP programme. If you want to ask for the TTP TOPUP programme use the submit button to send the request to support@cacert.org to start the process. CAcert will then inform you about the next steps.?></p>
+<form method="post" action="ttp">
+       <input type="submit" name="ttptopup" value="<?=_I need a TTP TOPUP?>">
+</form>
+<p><?=_We are working to develop the TTP TOPUP process to be able to fill the gap of the missing 30 assurance points to 100 assurance points. Meanwhile you have to close this gap with face to face assurances from CAcert Assurers. Think not only travelling to populated countries, but as well to assurers visiting your country or area.?></p>  
+<? } ?>
+
+<? if($nothing) { ?>
+<p><?=_You reached the maximum points that can be granted by the TTP programme and therefore you cannot take part in the TTP programme any more.?></p>
+<? } ?>
+<? if($inProgress) { ?>
+<p><?=_Your request for a TTP assurance is in progress. Please be patient.?></p>
+<? } ?>
diff --git a/src/org/cacert/gigi/pages/wot/Rules.templ b/src/org/cacert/gigi/pages/wot/Rules.templ
new file mode 100644 (file)
index 0000000..9b61da6
--- /dev/null
@@ -0,0 +1,30 @@
+<h3><?=_CAcert Web of Trust Rules?></h3>
+
+<p><?=_It is essential that CAcert Assurers understand and follow the rules below to ensure that applicants for assurance are suitably identified, which, in turn, maintains trust in the system.?></p>
+
+<p><?=_Contact?><br>
+<br>
+* <?=_You must meet the applicant in person;?><br>
+* <?=_You must sight at least one form of government issued photo identification.  It's preferable if 2 forms of Government issued photo ID are presented, as less points may be issued if there is any doubt on the person by the person issuing points;?><br>
+* <?=_Complete the assurance form if the applicant has not already done so.  Ensure that all information matches.?><br>
+</p>
+
+<p><?=_Processing?><br>
+<?=_After the meeting, visit the CAcert Web site's make an Assurance page and:?><br>
+<br>
+* <?=_Enter the applicant's email address;?><br>
+* <?=_Compare the online information to the information recorded on the paper form;?><br>
+* <?=_If, and only if, the two match completely - you may award trust points up to the maximum points you are able to allocate;?><br>
+</p>
+
+<p><?=_Privacy?><br>
+<?=_It is imperative that you maintain the confidentiality and privacy of the applicant, and never disclose the information obtained without the applicant's consent.?></p>
+
+<p><?=_Fees?><br>
+<?=_You may charge a fee for your expenses if the applicant has been advised of the amount prior to the meeting.?></p>
+
+<p><?=_Liability?><br>
+<?=_A CAcert Assurer who knowingly, or reasonably ought to have known, assures an applicant contrary to this policy may be held liable.?></p>
+
+<p><?=_Assurance Points?><br>
+<?=_CAcert may, from time to time, alter the amount of Assurance Points that a class of assurer may assign as is necessary to effect a policy or rule change.  We may also alter the amount of Assurance Points available to an individual, or new class of assurer, should another policy of CAcert require this.?></p>
diff --git a/src/org/cacert/gigi/ping/DNSPinger.java b/src/org/cacert/gigi/ping/DNSPinger.java
new file mode 100644 (file)
index 0000000..b1053c0
--- /dev/null
@@ -0,0 +1,56 @@
+package org.cacert.gigi.ping;
+
+import java.util.Arrays;
+import java.util.List;
+
+import javax.naming.NamingException;
+
+import org.cacert.gigi.dbObjects.Domain;
+import org.cacert.gigi.dbObjects.User;
+import org.cacert.gigi.util.DNSUtil;
+
+public class DNSPinger extends DomainPinger {
+
+    @Override
+    public String ping(Domain domain, String expToken, User u) {
+        String[] tokenParts = expToken.split(":", 2);
+        List<String> nameservers;
+        try {
+            nameservers = Arrays.asList(DNSUtil.getNSNames(domain.getSuffix()));
+        } catch (NamingException e) {
+            return "No authorative nameserver found.";
+        }
+        StringBuffer result = new StringBuffer();
+        result.append("failed: ");
+        boolean failed = nameservers.isEmpty();
+        nameservers:
+        for (String NS : nameservers) {
+            boolean found = false;
+            try {
+                for (String token : DNSUtil.getTXTEntries(tokenParts[0] + "._cacert._auth." + domain.getSuffix(), NS)) {
+                    if (token.isEmpty()) {
+                        continue;
+                    }
+                    found = true;
+                    if (token.equals(tokenParts[1])) {
+                        continue nameservers;
+                    }
+                }
+            } catch (NamingException e) {
+                found = false;
+            }
+            result.append(NS);
+            if (found) {
+                result.append(" DIFFER;");
+            } else {
+                result.append(" EMPTY;");
+            }
+            failed = true;
+
+        }
+        if ( !failed) {
+            return PING_SUCCEDED;
+        }
+        return result.toString();
+    }
+}
diff --git a/src/org/cacert/gigi/ping/DomainPinger.java b/src/org/cacert/gigi/ping/DomainPinger.java
new file mode 100644 (file)
index 0000000..2a14cc1
--- /dev/null
@@ -0,0 +1,13 @@
+package org.cacert.gigi.ping;
+
+import org.cacert.gigi.dbObjects.Domain;
+import org.cacert.gigi.dbObjects.User;
+
+public abstract class DomainPinger {
+
+    public static final String PING_STILL_PENDING = null;
+
+    public static final String PING_SUCCEDED = "";
+
+    public abstract String ping(Domain domain, String configuration, User user);
+}
diff --git a/src/org/cacert/gigi/ping/EmailPinger.java b/src/org/cacert/gigi/ping/EmailPinger.java
new file mode 100644 (file)
index 0000000..449af0a
--- /dev/null
@@ -0,0 +1,25 @@
+package org.cacert.gigi.ping;
+
+import java.io.IOException;
+
+import org.cacert.gigi.dbObjects.Domain;
+import org.cacert.gigi.dbObjects.User;
+import org.cacert.gigi.email.MailProbe;
+import org.cacert.gigi.localisation.Language;
+
+public class EmailPinger extends DomainPinger {
+
+    @Override
+    public String ping(Domain domain, String configuration, User u) {
+        String[] parts = configuration.split(":", 2);
+        String mail = parts[0] + "@" + domain.getSuffix();
+        try {
+            MailProbe.sendMailProbe(Language.getInstance(u.getPreferredLocale()), "domain", domain.getId(), parts[1], mail);
+        } catch (IOException e) {
+            e.printStackTrace();
+            return "Mail connection interrupted";
+        }
+        return PING_STILL_PENDING;
+    }
+
+}
diff --git a/src/org/cacert/gigi/ping/HTTPFetch.java b/src/org/cacert/gigi/ping/HTTPFetch.java
new file mode 100644 (file)
index 0000000..a4671bd
--- /dev/null
@@ -0,0 +1,37 @@
+package org.cacert.gigi.ping;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.net.HttpURLConnection;
+import java.net.URL;
+
+import org.cacert.gigi.dbObjects.Domain;
+import org.cacert.gigi.dbObjects.User;
+
+public class HTTPFetch extends DomainPinger {
+
+    @Override
+    public String ping(Domain domain, String expToken, User user) {
+        try {
+            String[] tokenParts = expToken.split(":", 2);
+            URL u = new URL("http://" + domain.getSuffix() + "/cacert-" + tokenParts[0] + ".txt");
+            HttpURLConnection huc = (HttpURLConnection) u.openConnection();
+            if (huc.getResponseCode() != 200) {
+                return "Invalid status code.";
+            }
+            BufferedReader br = new BufferedReader(new InputStreamReader(huc.getInputStream(), "UTF-8"));
+            String line = br.readLine();
+            if (line == null) {
+                return "No response from your server.";
+            }
+            if (line.trim().equals(tokenParts[1])) {
+                return PING_SUCCEDED;
+            }
+            return "Challange tokens differed.";
+        } catch (IOException e) {
+            e.printStackTrace();
+            return "Connection closed.";
+        }
+    }
+}
diff --git a/src/org/cacert/gigi/ping/PingerDaemon.java b/src/org/cacert/gigi/ping/PingerDaemon.java
new file mode 100644 (file)
index 0000000..ceb88e9
--- /dev/null
@@ -0,0 +1,93 @@
+package org.cacert.gigi.ping;
+
+import java.security.KeyStore;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.Queue;
+
+import org.cacert.gigi.database.DatabaseConnection;
+import org.cacert.gigi.database.GigiPreparedStatement;
+import org.cacert.gigi.database.GigiResultSet;
+import org.cacert.gigi.dbObjects.Domain;
+import org.cacert.gigi.dbObjects.DomainPingConfiguration;
+import org.cacert.gigi.dbObjects.DomainPingConfiguration.PingType;
+import org.cacert.gigi.util.RandomToken;
+
+public class PingerDaemon extends Thread {
+
+    HashMap<PingType, DomainPinger> pingers = new HashMap<>();
+
+    private GigiPreparedStatement searchNeededPings;
+
+    private GigiPreparedStatement enterPingResult;
+
+    private KeyStore truststore;
+
+    private Queue<DomainPingConfiguration> toExecute = new LinkedList<>();
+
+    public PingerDaemon(KeyStore truststore) {
+        this.truststore = truststore;
+    }
+
+    @Override
+    public void run() {
+        searchNeededPings = DatabaseConnection.getInstance().prepare("SELECT pingconfig.id FROM pingconfig LEFT JOIN domainPinglog ON domainPinglog.configId=pingconfig.id INNER JOIN domains ON domains.id=pingconfig.domainid WHERE ( domainPinglog.configId IS NULL) AND domains.deleted IS NULL GROUP BY pingconfig.id");
+        enterPingResult = DatabaseConnection.getInstance().prepare("INSERT INTO domainPinglog SET configId=?, state=?, result=?, challenge=?");
+        pingers.put(PingType.EMAIL, new EmailPinger());
+        pingers.put(PingType.SSL, new SSLPinger(truststore));
+        pingers.put(PingType.HTTP, new HTTPFetch());
+        pingers.put(PingType.DNS, new DNSPinger());
+
+        while (true) {
+            synchronized (this) {
+                DomainPingConfiguration conf;
+                while ((conf = toExecute.peek()) != null) {
+                    handle(conf);
+                    toExecute.remove();
+                }
+                notifyAll();
+            }
+
+            GigiResultSet rs = searchNeededPings.executeQuery();
+            while (rs.next()) {
+                handle(DomainPingConfiguration.getById(rs.getInt("id")));
+            }
+            try {
+                Thread.sleep(5000);
+            } catch (InterruptedException e) {
+            }
+        }
+    }
+
+    private void handle(DomainPingConfiguration conf) {
+        PingType type = conf.getType();
+        String config = conf.getInfo();
+        DomainPinger dp = pingers.get(type);
+        if (dp != null) {
+            String token = null;
+            if (dp instanceof EmailPinger) {
+                token = RandomToken.generateToken(16);
+                config = config + ":" + token;
+            }
+            enterPingResult.setInt(1, conf.getId());
+            Domain target = conf.getTarget();
+            String resp = dp.ping(target, config, target.getOwner());
+            enterPingResult.setString(2, DomainPinger.PING_STILL_PENDING == resp ? "open" : DomainPinger.PING_SUCCEDED.equals(resp) ? "success" : "failed");
+            enterPingResult.setString(3, resp);
+            enterPingResult.setString(4, token);
+            enterPingResult.execute();
+        }
+    }
+
+    public synchronized void queue(DomainPingConfiguration toReping) {
+        interrupt();
+        toExecute.add(toReping);
+        while (toExecute.size() > 0) {
+            try {
+                wait();
+            } catch (InterruptedException e) {
+                e.printStackTrace();
+            }
+        }
+    }
+}
diff --git a/src/org/cacert/gigi/ping/SSLPinger.java b/src/org/cacert/gigi/ping/SSLPinger.java
new file mode 100644 (file)
index 0000000..85189e9
--- /dev/null
@@ -0,0 +1,226 @@
+package org.cacert.gigi.ping;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.math.BigInteger;
+import java.net.InetSocketAddress;
+import java.net.Socket;
+import java.nio.ByteBuffer;
+import java.nio.channels.SocketChannel;
+import java.security.KeyManagementException;
+import java.security.KeyStore;
+import java.security.KeyStoreException;
+import java.security.NoSuchAlgorithmException;
+import java.security.SecureRandom;
+import java.util.Arrays;
+
+import javax.net.ssl.SNIHostName;
+import javax.net.ssl.SNIServerName;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLEngine;
+import javax.net.ssl.SSLEngineResult.HandshakeStatus;
+import javax.net.ssl.SSLEngineResult.Status;
+import javax.net.ssl.SSLException;
+import javax.net.ssl.SSLParameters;
+import javax.net.ssl.TrustManagerFactory;
+import javax.security.cert.X509Certificate;
+
+import org.cacert.gigi.dbObjects.Certificate;
+import org.cacert.gigi.dbObjects.Domain;
+import org.cacert.gigi.dbObjects.User;
+
+public class SSLPinger extends DomainPinger {
+
+    public static final String[] TYPES = new String[] {
+            "xmpp", "server-xmpp", "smtp", "imap"
+    };
+
+    private KeyStore truststore;
+
+    public SSLPinger(KeyStore truststore) {
+        this.truststore = truststore;
+    }
+
+    @Override
+    public String ping(Domain domain, String configuration, User u) {
+        try (SocketChannel sch = SocketChannel.open()) {
+            String[] parts = configuration.split(":", 2);
+            sch.connect(new InetSocketAddress(domain.getSuffix(), Integer.parseInt(parts[0])));
+            if (parts.length == 2) {
+                switch (parts[1]) {
+                case "xmpp":
+                    startXMPP(sch, false, domain.getSuffix());
+                    break;
+                case "server-xmpp":
+                    startXMPP(sch, true, domain.getSuffix());
+                    break;
+                case "smtp":
+                    startSMTP(sch);
+                    break;
+                case "imap":
+                    startIMAP(sch);
+                    break;
+
+                }
+            }
+            return test(sch, domain.getSuffix(), u);
+        } catch (IOException e) {
+            return "Connecton failed";
+        }
+
+    }
+
+    private void startIMAP(SocketChannel sch) throws IOException {
+        Socket s = sch.socket();
+        InputStream is = s.getInputStream();
+        OutputStream os = s.getOutputStream();
+        scanFor(is, "\n");
+        os.write("ENABLE STARTTLS\r\n".getBytes("UTF-8"));
+        os.flush();
+        scanFor(is, "\n");
+    }
+
+    private void startXMPP(SocketChannel sch, boolean server, String domain) throws IOException {
+        Socket s = sch.socket();
+        InputStream is = s.getInputStream();
+        OutputStream os = s.getOutputStream();
+        os.write(("<stream:stream to=\"" + domain + "\" xmlns=\"jabber:" + (server ? "server" : "client") + "\"" + " xmlns:stream=\"http://etherx.jabber.org/streams\" version=\"1.0\">").getBytes("UTF-8"));
+        os.flush();
+        os.write("<starttls xmlns=\"urn:ietf:params:xml:ns:xmpp-tls\"/>".getBytes("UTF-8"));
+        os.flush();
+        scanFor(is, "<proceed");
+        scanFor(is, ">");
+
+    }
+
+    private void scanFor(InputStream is, String scanFor) throws IOException {
+        int pos = 0;
+        while (pos < scanFor.length()) {
+            if (is.read() == scanFor.charAt(pos)) {
+                pos++;
+            } else {
+                pos = 0;
+            }
+        }
+    }
+
+    private void startSMTP(SocketChannel sch) throws IOException {
+        Socket s = sch.socket();
+        InputStream is = s.getInputStream();
+        readSMTP(is);
+        s.getOutputStream().write("EHLO ssl.pinger\r\n".getBytes("UTF-8"));
+        s.getOutputStream().flush();
+        readSMTP(is);
+        s.getOutputStream().write("HELP\r\n".getBytes("UTF-8"));
+        s.getOutputStream().flush();
+        readSMTP(is);
+        s.getOutputStream().write("STARTTLS\r\n".getBytes("UTF-8"));
+        s.getOutputStream().flush();
+        readSMTP(is);
+    }
+
+    private void readSMTP(InputStream is) throws IOException {
+        int counter = 0;
+        boolean finish = true;
+        while (true) {
+            char c = (char) is.read();
+            if (counter == 3) {
+                if (c == ' ') {
+                    finish = true;
+                } else if (c == '-') {
+                    finish = false;
+                } else {
+                    throw new Error("Invalid smtp: " + c);
+                }
+            }
+            if (c == '\n') {
+                if (finish) {
+                    return;
+                }
+                counter = 0;
+            } else {
+                counter++;
+            }
+        }
+    }
+
+    private String test(SocketChannel sch, String domain, User subject) {
+        try {
+            SSLContext sc = SSLContext.getInstance("SSL");
+            try {
+                TrustManagerFactory tmf = TrustManagerFactory.getInstance("X509");
+                tmf.init(truststore);
+                sc.init(null, tmf.getTrustManagers(), new SecureRandom());
+            } catch (KeyManagementException e) {
+                e.printStackTrace();
+            } catch (KeyStoreException e) {
+                e.printStackTrace();
+            }
+            SSLEngine se = sc.createSSLEngine();
+            ByteBuffer enc_in = ByteBuffer.allocate(se.getSession().getPacketBufferSize());
+            ByteBuffer enc_out = ByteBuffer.allocate(se.getSession().getPacketBufferSize());
+            ByteBuffer dec_in = ByteBuffer.allocate(se.getSession().getApplicationBufferSize());
+            ByteBuffer dec_out = ByteBuffer.allocate(se.getSession().getApplicationBufferSize());
+            se.setUseClientMode(true);
+            SSLParameters sp = se.getSSLParameters();
+            sp.setServerNames(Arrays.<SNIServerName>asList(new SNIHostName(domain)));
+            se.setSSLParameters(sp);
+            se.beginHandshake();
+            enc_in.limit(0);
+            while (se.getHandshakeStatus() != HandshakeStatus.FINISHED && se.getHandshakeStatus() != HandshakeStatus.NOT_HANDSHAKING) {
+                switch (se.getHandshakeStatus()) {
+                case NEED_WRAP:
+                    dec_out.limit(0);
+                    se.wrap(dec_out, enc_out);
+                    enc_out.flip();
+                    while (enc_out.remaining() > 0) {
+                        sch.write(enc_out);
+                    }
+                    enc_out.clear();
+                    break;
+                case NEED_UNWRAP:
+                    if (enc_in.remaining() == 0) {
+                        enc_in.clear();
+                        sch.read(enc_in);
+                        enc_in.flip();
+                    }
+                    while (se.unwrap(enc_in, dec_in).getStatus() == Status.BUFFER_UNDERFLOW) {
+                        enc_in.position(enc_in.limit());
+                        enc_in.limit(enc_in.capacity());
+                        sch.read(enc_in);
+                        enc_in.flip();
+                    }
+                    enc_in.compact();
+                    enc_in.flip();
+                    break;
+                case NEED_TASK:
+                    se.getDelegatedTask().run();
+                    break;
+                case NOT_HANDSHAKING:
+                case FINISHED:
+
+                }
+
+            }
+            X509Certificate[] peerCertificateChain = se.getSession().getPeerCertificateChain();
+            X509Certificate first = peerCertificateChain[0];
+
+            BigInteger serial = first.getSerialNumber();
+            Certificate c = Certificate.getBySerial(serial.toString(16));
+            if (c.getOwner().getId() != subject.getId()) {
+                return "Owner mismatch";
+            }
+            return PING_SUCCEDED;
+        } catch (NoSuchAlgorithmException e) {
+            // e.printStackTrace(); TODO log for user debugging?
+            return "Security failed";
+        } catch (SSLException e) {
+            // e.printStackTrace(); TODO log for user debugging?
+            return "Security failed";
+        } catch (IOException e) {
+            // e.printStackTrace(); TODO log for user debugging?
+            return "Connection closed";
+        }
+    }
+}
diff --git a/src/org/cacert/gigi/util/CipherInfo.java b/src/org/cacert/gigi/util/CipherInfo.java
new file mode 100644 (file)
index 0000000..0160093
--- /dev/null
@@ -0,0 +1,307 @@
+package org.cacert.gigi.util;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.TreeSet;
+
+import sun.security.ssl.SSLContextImpl;
+
+public class CipherInfo implements Comparable<CipherInfo> {
+
+    private static class CipherInfoGenerator {
+
+        private Class<?> cipherSuite;
+
+        private Field cipherSuiteNameMap;
+
+        private Field exchange;
+
+        private Field cipher;
+
+        private Field keySize;
+
+        private Field algortihm;
+
+        private Field transformation;
+
+        private HashMap<?, ?> names;
+
+        private Field macAlg;
+
+        private Field macName;
+
+        private Field macSize;
+
+        public CipherInfoGenerator() throws ReflectiveOperationException {
+            SSLContextImpl sc = new SSLContextImpl.TLS12Context();
+            Method m = SSLContextImpl.class.getDeclaredMethod("getSupportedCipherSuiteList");
+            m.setAccessible(true);
+            Object o = m.invoke(sc);
+            Class<?> cipherSuiteList = o.getClass();
+            Method collection = cipherSuiteList.getDeclaredMethod("collection");
+            collection.setAccessible(true);
+            Collection<?> suites = (Collection<?>) collection.invoke(o);
+            Object oneSuite = suites.iterator().next();
+            cipherSuite = oneSuite.getClass();
+            cipherSuiteNameMap = cipherSuite.getDeclaredField("nameMap");
+            cipherSuiteNameMap.setAccessible(true);
+            names = (HashMap<?, ?>) cipherSuiteNameMap.get(null);
+            exchange = cipherSuite.getDeclaredField("keyExchange");
+            exchange.setAccessible(true);
+            cipher = cipherSuite.getDeclaredField("cipher");
+            cipher.setAccessible(true);
+            Class<?> bulkCipher = cipher.getType();
+            keySize = bulkCipher.getDeclaredField("keySize");
+            keySize.setAccessible(true);
+            algortihm = bulkCipher.getDeclaredField("algorithm");
+            algortihm.setAccessible(true);
+            transformation = bulkCipher.getDeclaredField("transformation");
+            transformation.setAccessible(true);
+
+            macAlg = cipherSuite.getDeclaredField("macAlg");
+            macAlg.setAccessible(true);
+            Class<?> mac = macAlg.getType();
+            macName = mac.getDeclaredField("name");
+            macName.setAccessible(true);
+            macSize = mac.getDeclaredField("size");
+            macSize.setAccessible(true);
+        }
+
+        public CipherInfo generateInfo(String suiteName) throws IllegalArgumentException, IllegalAccessException {
+            Object suite = names.get(suiteName);
+            String keyExchange = exchange.get(suite).toString();
+            Object bulkCipher = cipher.get(suite);
+            Object mac = macAlg.get(suite);
+
+            String transform = (String) transformation.get(bulkCipher);
+            String[] transformationParts = transform.split("/");
+            int keysize = keySize.getInt(bulkCipher);
+
+            String macNam = (String) macName.get(mac);
+            int macSiz = macSize.getInt(mac);
+
+            String chaining = null;
+            String padding = null;
+            if (transformationParts.length > 1) {
+                chaining = transformationParts[1];
+                padding = transformationParts[2];
+            }
+
+            return new CipherInfo(suiteName, keyExchange, transformationParts[0], keysize * 8, chaining, padding, macNam, macSiz * 8);
+
+        }
+    }
+
+    String keyExchange;
+
+    String cipher;
+
+    int keySize;
+
+    String cipherChaining;
+
+    String cipherPadding;
+
+    String macName;
+
+    int macSize;
+
+    String suiteName;
+
+    private CipherInfo(String suiteName, String keyExchange, String cipher, int keySize, String cipherChaining, String cipherPadding, String macName, int macSize) {
+        this.suiteName = suiteName;
+        this.keyExchange = keyExchange;
+        this.cipher = cipher;
+        this.keySize = keySize;
+        this.cipherChaining = cipherChaining;
+        this.cipherPadding = cipherPadding;
+        this.macName = macName;
+        this.macSize = macSize;
+    }
+
+    static CipherInfoGenerator cig;
+    static {
+        try {
+            cig = new CipherInfoGenerator();
+        } catch (ReflectiveOperationException e) {
+            e.printStackTrace();
+        }
+    }
+
+    public static CipherInfo generateInfo(String name) {
+        if (cig == null) {
+            return null;
+        }
+        try {
+            return cig.generateInfo(name);
+        } catch (IllegalArgumentException e) {
+            e.printStackTrace();
+        } catch (IllegalAccessException e) {
+            e.printStackTrace();
+        }
+        return null;
+    }
+
+    public String getSuiteName() {
+        return suiteName;
+    }
+
+    /**
+     * 5: ECDHE, AES||CAMELLIA, keysize >=256 <br>
+     * 4: DHE, AES||CAMELLIA, keysize >= 256<br>
+     * 3: ECDHE|| DHE, AES||CAMELLIA<br>
+     * 2: ECDHE||DHE<br>
+     * 1: RSA||DSA <br>
+     * 0: Others
+     * 
+     * @return the strength
+     */
+    public int getStrength() {
+        if (cipher.equals("NULL") || cipher.equals("RC4") || cipher.contains("DES")) {
+            return 0;
+        }
+        boolean ecdhe = keyExchange.startsWith("ECDHE");
+        boolean dhe = keyExchange.startsWith("DHE");
+        boolean pfs = ecdhe || dhe;
+        boolean goodCipher = cipher.equals("AES") || cipher.equals("CAMELLIA");
+        if (ecdhe && goodCipher && keySize >= 256) {
+            return 5;
+        }
+        if (dhe && goodCipher && keySize >= 256) {
+            return 4;
+        }
+        if (pfs && goodCipher) {
+            return 3;
+        }
+        if (pfs) {
+            return 2;
+        }
+        if (keyExchange.equals("RSA") || keyExchange.equals("DSA")) {
+            return 1;
+        }
+        return 0;
+    }
+
+    private static final String[] CIPHER_RANKING = new String[] {
+            "CAMELLIA", "AES", "RC4", "3DES", "DES", "DES40"
+    };
+
+    @Override
+    public String toString() {
+        return "CipherInfo [keyExchange=" + keyExchange + ", cipher=" + cipher + ", keySize=" + keySize + ", cipherChaining=" + cipherChaining + ", cipherPadding=" + cipherPadding + ", macName=" + macName + ", macSize=" + macSize + "]";
+    }
+
+    /**
+     * ECDHE<br>
+     * GCM<br>
+     * Cipher {@link #CIPHER_RANKING}<br>
+     * Cipher {@link #keySize}<br>
+     * HMAC<br>
+     * HMAC size<br>
+     * 
+     * @return
+     */
+    @Override
+    public int compareTo(CipherInfo o) {
+        int myStrength = getStrength();
+        int oStrength = o.getStrength();
+        if (myStrength > oStrength) {
+            return -1;
+        }
+        if (myStrength < oStrength) {
+            return 1;
+        }
+        // TODO sort SSL/TLS
+        boolean myEcdhe = keyExchange.startsWith("ECDHE");
+        boolean oEcdhe = o.keyExchange.startsWith("ECDHE");
+        if (myEcdhe && !oEcdhe) {
+            return -1;
+        }
+        if ( !myEcdhe && oEcdhe) {
+            return 1;
+        }
+        boolean myGCM = "GCM".equals(cipherChaining);
+        boolean oGCM = "GCM".equals(o.cipherChaining);
+        if (myGCM && !oGCM) {
+            return -1;
+        }
+        if ( !myGCM && oGCM) {
+            return 1;
+        }
+        if ( !cipher.equals(o.cipher)) {
+
+            for (String testCipher : CIPHER_RANKING) {
+                if (cipher.equals(testCipher)) {
+                    return -1;
+                }
+                if (o.cipher.equals(testCipher)) {
+                    return 1;
+                }
+            }
+            if (cipher.equals("NULL")) {
+                return 1;
+            }
+            if (o.cipher.equals("NULL")) {
+                return -1;
+            }
+        }
+        if (keySize > o.keySize) {
+            return -1;
+        }
+        if (keySize < o.keySize) {
+            return 1;
+        }
+        boolean mySHA = macName.startsWith("SHA");
+        boolean oSHA = o.macName.startsWith("SHA");
+        if ( !mySHA && oSHA) {
+            return -1;
+        }
+        if (mySHA && !oSHA) {
+            return 1;
+        }
+        if (macSize > o.macSize) {
+            return -1;
+        }
+        if (macSize < o.macSize) {
+            return 1;
+        }
+
+        return suiteName.compareTo(o.suiteName);
+    }
+
+    static String[] cipherRanking = null;
+
+    public static String[] getCompleteRanking() {
+        if (cipherRanking == null) {
+            String[] ciphers = filterCiphers((Iterable<String>) cig.names.keySet());
+            cipherRanking = ciphers;
+        }
+        return cipherRanking;
+    }
+
+    private static String[] filterCiphers(Iterable<String> toFilter) {
+        TreeSet<CipherInfo> chosenCiphers = new TreeSet<CipherInfo>();
+        for (String o : toFilter) {
+            String s = o;
+            CipherInfo info = CipherInfo.generateInfo(s);
+            if (info != null) {
+                if (info.getStrength() > 1) {
+                    chosenCiphers.add(info);
+                }
+            }
+        }
+        String[] ciphers = new String[chosenCiphers.size()];
+        int counter = 0;
+        for (CipherInfo i : chosenCiphers) {
+            ciphers[counter++] = i.getSuiteName();
+        }
+        return ciphers;
+    }
+
+    public static String[] filter(String[] supportedCipherSuites) {
+        return filterCiphers(Arrays.asList(supportedCipherSuites));
+    }
+}
diff --git a/src/org/cacert/gigi/util/DNSUtil.java b/src/org/cacert/gigi/util/DNSUtil.java
new file mode 100644 (file)
index 0000000..f1d5f9f
--- /dev/null
@@ -0,0 +1,79 @@
+package org.cacert.gigi.util;
+
+import java.util.Arrays;
+import java.util.Hashtable;
+
+import javax.naming.Context;
+import javax.naming.NamingException;
+import javax.naming.directory.Attribute;
+import javax.naming.directory.Attributes;
+import javax.naming.directory.InitialDirContext;
+
+public class DNSUtil {
+
+    private static InitialDirContext context;
+    static {
+        Hashtable<String, String> env = new Hashtable<String, String>();
+        env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.dns.DnsContextFactory");
+        try {
+            context = new InitialDirContext(env);
+        } catch (NamingException e) {
+            e.printStackTrace();
+        }
+
+    }
+
+    public static String[] getNSNames(String name) throws NamingException {
+        Attributes dnsLookup = context.getAttributes(name, new String[] {
+            "NS"
+        });
+        return extractTextEntries(dnsLookup.get("NS"));
+    }
+
+    public static String[] getTXTEntries(String name, String server) throws NamingException {
+        Hashtable<String, String> env = new Hashtable<String, String>();
+        env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.dns.DnsContextFactory");
+        env.put(Context.AUTHORITATIVE, "true");
+        env.put(Context.PROVIDER_URL, "dns://" + server);
+        InitialDirContext context = new InitialDirContext(env);
+        try {
+
+            Attributes dnsLookup = context.getAttributes(name, new String[] {
+                "TXT"
+            });
+            return extractTextEntries(dnsLookup.get("TXT"));
+        } finally {
+            context.close();
+        }
+
+    }
+
+    private static String[] extractTextEntries(Attribute nsRecords) throws NamingException {
+        if (nsRecords == null) {
+            return new String[] {};
+        }
+        String[] result = new String[nsRecords.size()];
+        for (int i = 0; i < result.length; i++) {
+            result[i] = (String) nsRecords.get(i);
+        }
+        return result;
+    }
+
+    public static String[] getMXEntries(String domain) throws NamingException {
+        Attributes dnsLookup = context.getAttributes(domain, new String[] {
+            "MX"
+        });
+        return extractTextEntries(dnsLookup.get("MX"));
+    }
+
+    public static void main(String[] args) throws NamingException {
+        if (args[0].equals("MX")) {
+            System.out.println(Arrays.toString(getMXEntries(args[1])));
+        } else if (args[0].equals("NS")) {
+            System.out.println(Arrays.toString(getNSNames(args[1])));
+        } else if (args[0].equals("TXT")) {
+            System.out.println(Arrays.toString(getTXTEntries(args[1], args[2])));
+        }
+    }
+
+}
diff --git a/src/org/cacert/gigi/util/HTMLEncoder.java b/src/org/cacert/gigi/util/HTMLEncoder.java
new file mode 100644 (file)
index 0000000..ed943cb
--- /dev/null
@@ -0,0 +1,13 @@
+package org.cacert.gigi.util;
+
+public class HTMLEncoder {
+
+    public static String encodeHTML(String s) {
+        s = s.replace("&", "&amp;");
+        s = s.replace("<", "&lt;");
+        s = s.replace(">", "&gt;");
+        s = s.replace("\"", "&quot;");
+        s = s.replace("'", "&#39;");
+        return s;
+    }
+}
diff --git a/src/org/cacert/gigi/util/Job.java b/src/org/cacert/gigi/util/Job.java
new file mode 100644 (file)
index 0000000..f528771
--- /dev/null
@@ -0,0 +1,70 @@
+package org.cacert.gigi.util;
+
+import java.sql.Date;
+
+import org.cacert.gigi.GigiApiException;
+import org.cacert.gigi.database.DatabaseConnection;
+import org.cacert.gigi.database.GigiPreparedStatement;
+import org.cacert.gigi.database.GigiResultSet;
+import org.cacert.gigi.dbObjects.Certificate;
+import org.cacert.gigi.output.CertificateValiditySelector;
+
+public class Job {
+
+    private int id;
+
+    private Job(int id) {
+        this.id = id;
+    }
+
+    public static enum JobType {
+        SIGN("sign"), REVOKE("revoke");
+
+        private final String name;
+
+        private JobType(String name) {
+            this.name = name;
+        }
+
+        public String getName() {
+            return name;
+        }
+    }
+
+    public static Job sign(Certificate targetId, Date start, String period) throws GigiApiException {
+        CertificateValiditySelector.checkValidityLength(period);
+        GigiPreparedStatement ps = DatabaseConnection.getInstance().prepare("INSERT INTO `jobs` SET targetId=?, task=?, executeFrom=?, executeTo=?");
+        ps.setInt(1, targetId.getId());
+        ps.setString(2, JobType.SIGN.getName());
+        ps.setDate(3, start);
+        ps.setString(4, period);
+        ps.execute();
+        return new Job(ps.lastInsertId());
+    }
+
+    public static Job revoke(Certificate targetId) {
+
+        GigiPreparedStatement ps = DatabaseConnection.getInstance().prepare("INSERT INTO `jobs` SET targetId=?, task=?");
+        ps.setInt(1, targetId.getId());
+        ps.setString(2, JobType.REVOKE.getName());
+        ps.execute();
+        return new Job(ps.lastInsertId());
+    }
+
+    public boolean waitFor(int max) throws InterruptedException {
+        long start = System.currentTimeMillis();
+        GigiPreparedStatement ps = DatabaseConnection.getInstance().prepare("SELECT 1 FROM `jobs` WHERE id=? AND state='open'");
+        ps.setInt(1, id);
+        GigiResultSet rs = ps.executeQuery();
+        while (rs.next()) {
+            rs.close();
+            if (max != 0 && System.currentTimeMillis() - start > max) {
+                return false;
+            }
+            Thread.sleep((long) (2000 + Math.random() * 2000));
+            rs = ps.executeQuery();
+        }
+        rs.close();
+        return true;
+    }
+}
diff --git a/src/org/cacert/gigi/util/KeyStorage.java b/src/org/cacert/gigi/util/KeyStorage.java
new file mode 100644 (file)
index 0000000..7ce0d28
--- /dev/null
@@ -0,0 +1,26 @@
+package org.cacert.gigi.util;
+
+import java.io.File;
+
+public class KeyStorage {
+
+    private static final File csr = new File("keys/csr");
+
+    private static final File crt = new File("keys/crt");
+
+    public static File locateCrt(int id) {
+        File parent = new File(crt, (id / 1000) + "");
+        if ( !parent.exists() && !parent.mkdirs()) {
+            throw new Error("cert folder could not be created");
+        }
+        return new File(parent, id + ".crt");
+    }
+
+    public static File locateCsr(int id) {
+        File parent = new File(csr, (id / 1000) + "");
+        if ( !parent.exists() && !parent.mkdirs()) {
+            throw new Error("csr folder could not be created");
+        }
+        return new File(parent, id + ".csr");
+    }
+}
diff --git a/src/org/cacert/gigi/util/Notary.java b/src/org/cacert/gigi/util/Notary.java
new file mode 100644 (file)
index 0000000..767230b
--- /dev/null
@@ -0,0 +1,128 @@
+package org.cacert.gigi.util;
+
+import java.text.ParseException;
+import java.util.Date;
+
+import org.cacert.gigi.GigiApiException;
+import org.cacert.gigi.database.DatabaseConnection;
+import org.cacert.gigi.database.GigiPreparedStatement;
+import org.cacert.gigi.database.GigiResultSet;
+import org.cacert.gigi.dbObjects.Group;
+import org.cacert.gigi.dbObjects.Name;
+import org.cacert.gigi.dbObjects.User;
+import org.cacert.gigi.output.DateSelector;
+
+public class Notary {
+
+    public static void writeUserAgreement(User member, String document, String method, String comment, boolean active, int secmemid) {
+        GigiPreparedStatement q = DatabaseConnection.getInstance().prepare("insert into `user_agreements` set `memid`=?, `secmemid`=?," + " `document`=?,`date`=NOW(), `active`=?,`method`=?,`comment`=?");
+        q.setInt(1, member.getId());
+        q.setInt(2, secmemid);
+        q.setString(3, document);
+        q.setInt(4, active ? 1 : 0);
+        q.setString(5, method);
+        q.setString(6, comment);
+        q.execute();
+    }
+
+    public static void checkAssuranceIsPossible(User assurer, User target) throws GigiApiException {
+        if (assurer.getId() == target.getId()) {
+            throw new GigiApiException("You cannot assure yourself.");
+        }
+        GigiPreparedStatement ps = DatabaseConnection.getInstance().prepare("SELECT 1 FROM `notary` where `to`=? and `from`=? AND `deleted` IS NULL");
+        ps.setInt(1, target.getId());
+        ps.setInt(2, assurer.getId());
+        GigiResultSet rs = ps.executeQuery();
+        if (rs.next()) {
+            rs.close();
+            throw new GigiApiException("You have already assured this member.");
+        }
+        rs.close();
+        if ( !assurer.canAssure()) {
+            throw new GigiApiException("You are not an assurer.");
+        }
+    }
+
+    public static final Group ASSURER_BLOCKED = Group.getByString("blockedassurer");
+
+    public static final Group ASSUREE_BLOCKED = Group.getByString("blockedassuree");
+
+    /**
+     * This method assures another user.
+     * 
+     * @see User#canAssure() (for assurer)
+     * @see #checkAssuranceIsPossible(User, User) (for assurer or assuree)
+     * @param assurer
+     *            the person that wants to assure
+     * @param assuree
+     *            the person that should be assured
+     * @param assureeName
+     *            the Name that was personally verified
+     * @param dob
+     *            the Date of birth that the assurer verified
+     * @param awarded
+     *            the points that should be awarded in total
+     * @param location
+     *            the location where the assurance took place
+     * @param date
+     *            the date when the assurance took place
+     * @throws GigiApiException
+     *             if the assurance fails (for various reasons)
+     */
+    public synchronized static void assure(User assurer, User assuree, Name assureeName, Date dob, int awarded, String location, String date) throws GigiApiException {
+        GigiApiException gae = new GigiApiException();
+        if (assuree.isInGroup(ASSUREE_BLOCKED)) {
+            gae.mergeInto(new GigiApiException("The assuree is blocked."));
+        }
+        if (assurer.isInGroup(ASSURER_BLOCKED)) {
+            gae.mergeInto(new GigiApiException("The assurer is blocked."));
+        }
+        if ( !gae.isEmpty()) {
+            throw gae;
+        }
+        if (date == null || date.equals("")) {
+            gae.mergeInto(new GigiApiException("You must enter the date when you met the assuree."));
+        } else {
+            try {
+                Date d = DateSelector.getDateFormat().parse(date);
+                if (d.getTime() > System.currentTimeMillis()) {
+                    gae.mergeInto(new GigiApiException("You must not enter a date in the future."));
+                }
+            } catch (ParseException e) {
+                gae.mergeInto(new GigiApiException("You must enter the date in this format: YYYY-MM-DD."));
+            }
+        }
+        // check location, min 3 characters
+        if (location == null || location.equals("")) {
+            gae.mergeInto(new GigiApiException("You failed to enter a location of your meeting."));
+        } else if (location.length() <= 2) {
+            gae.mergeInto(new GigiApiException("You must enter a location with at least 3 characters eg town and country."));
+        }
+
+        try {
+            checkAssuranceIsPossible(assurer, assuree);
+        } catch (GigiApiException e) {
+            gae.mergeInto(e);
+        }
+
+        if ( !assuree.getName().equals(assureeName) || !assuree.getDoB().equals(dob)) {
+            gae.mergeInto(new GigiApiException("The person you are assuring changed his personal details."));
+        }
+        if (awarded > assurer.getMaxAssurePoints() || awarded < 0) {
+            gae.mergeInto(new GigiApiException("The points you are trying to award are out of range."));
+        }
+        if ( !gae.isEmpty()) {
+            throw gae;
+        }
+
+        GigiPreparedStatement ps = DatabaseConnection.getInstance().prepare("INSERT INTO `notary` SET `from`=?, `to`=?, `points`=?, `location`=?, `date`=?");
+        ps.setInt(1, assurer.getId());
+        ps.setInt(2, assuree.getId());
+        ps.setInt(3, awarded);
+        ps.setString(4, location);
+        ps.setString(5, date);
+        ps.execute();
+        assurer.invalidateMadeAssurances();
+        assuree.invalidateReceivedAssurances();
+    }
+}
diff --git a/src/org/cacert/gigi/util/PEM.java b/src/org/cacert/gigi/util/PEM.java
new file mode 100644 (file)
index 0000000..049822c
--- /dev/null
@@ -0,0 +1,28 @@
+package org.cacert.gigi.util;
+
+import java.util.Base64;
+import java.util.regex.Pattern;
+
+public class PEM {
+
+    public static final Pattern LINE = Pattern.compile("(.{64})(?=.)");
+
+    public static String encode(String type, byte[] data) {
+        return "-----BEGIN " + type + "-----\n" + //
+                formatBase64(data) + //
+                "\n-----END " + type + "-----";
+    }
+
+    public static byte[] decode(String type, String data) {
+        data = data.replaceAll("-----BEGIN " + type + "-----", "").replace("\n", "").replace("\r", "");
+        // Remove the first and last lines
+        data = data.replaceAll("-----END " + type + "-----", "");
+        // Base64 decode the data
+        return Base64.getDecoder().decode(data);
+
+    }
+
+    public static String formatBase64(byte[] bytes) {
+        return LINE.matcher(Base64.getEncoder().encodeToString(bytes)).replaceAll("$1\n");
+    }
+}
diff --git a/src/org/cacert/gigi/util/PasswordHash.java b/src/org/cacert/gigi/util/PasswordHash.java
new file mode 100644 (file)
index 0000000..ed0d4f3
--- /dev/null
@@ -0,0 +1,69 @@
+package org.cacert.gigi.util;
+
+import java.io.UnsupportedEncodingException;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+
+import com.lambdaworks.crypto.SCryptUtil;
+
+public class PasswordHash {
+
+    /**
+     * Verifies a password hash.
+     * 
+     * @param password
+     *            The password that should result in the given hash.
+     * @param hash
+     *            The hash to verify the password against.
+     * @return <ul>
+     *         <li><code>null</code>, if the password was valid</li>
+     *         <li><code>hash</code>, if the password is valid and the hash
+     *         doesn't need to be updated</li>
+     *         <li>a new hash, if the password is valid but the hash in the
+     *         database needs to be updated.</li>
+     *         </ul>
+     */
+    public static String verifyHash(String password, String hash) {
+        if (hash.contains("$")) {
+            if (SCryptUtil.check(password, hash)) {
+                return hash;
+            } else {
+                return null;
+            }
+        }
+        String newhash = sha1(password);
+        boolean match = true;
+        if (newhash.length() != hash.length()) {
+            match = false;
+        }
+        for (int i = 0; i < newhash.length(); i++) {
+            match &= newhash.charAt(i) == hash.charAt(i);
+        }
+        if (match) {
+            return hash(password);
+        } else {
+            return null;
+        }
+    }
+
+    private static String sha1(String password) {
+        try {
+            MessageDigest md = MessageDigest.getInstance("SHA1");
+            byte[] digest = md.digest(password.getBytes("UTF-8"));
+            StringBuffer res = new StringBuffer(digest.length * 2);
+            for (int i = 0; i < digest.length; i++) {
+                res.append(Integer.toHexString((digest[i] & 0xF0) >> 4));
+                res.append(Integer.toHexString(digest[i] & 0xF));
+            }
+            return res.toString();
+        } catch (NoSuchAlgorithmException e) {
+            throw new Error(e);
+        } catch (UnsupportedEncodingException e) {
+            throw new Error(e);
+        }
+    }
+
+    public static String hash(String password) {
+        return SCryptUtil.scrypt(password, 1 << 14, 8, 1);
+    }
+}
diff --git a/src/org/cacert/gigi/util/PasswordStrengthChecker.java b/src/org/cacert/gigi/util/PasswordStrengthChecker.java
new file mode 100644 (file)
index 0000000..6ae7918
--- /dev/null
@@ -0,0 +1,96 @@
+package org.cacert.gigi.util;
+
+import java.util.regex.Pattern;
+
+import org.cacert.gigi.GigiApiException;
+import org.cacert.gigi.dbObjects.User;
+
+public class PasswordStrengthChecker {
+
+    private static Pattern digits = Pattern.compile("\\d");
+
+    private static Pattern lower = Pattern.compile("[a-z]");
+
+    private static Pattern upper = Pattern.compile("[A-Z]");
+
+    private static Pattern whitespace = Pattern.compile("\\s");
+
+    private static Pattern special = Pattern.compile("(?!\\s)\\W");
+
+    private PasswordStrengthChecker() {}
+
+    private static int checkpwlight(String pw) {
+        int points = 0;
+        if (pw.length() > 15) {
+            points++;
+        }
+        if (pw.length() > 20) {
+            points++;
+        }
+        if (pw.length() > 25) {
+            points++;
+        }
+        if (pw.length() > 30) {
+            points++;
+        }
+        if (digits.matcher(pw).find()) {
+            points++;
+        }
+        if (lower.matcher(pw).find()) {
+            points++;
+        }
+        if (upper.matcher(pw).find()) {
+            points++;
+        }
+        if (special.matcher(pw).find()) {
+            points++;
+        }
+        if (whitespace.matcher(pw).find()) {
+            points++;
+        }
+        return points;
+    }
+
+    public static int checkpw(String pw, User u) {
+        if (pw == null) {
+            return 0;
+        }
+        int light = checkpwlight(pw);
+        if (contained(pw, u.getEmail())) {
+            light -= 2;
+        }
+        if (contained(pw, u.getFName())) {
+            light -= 2;
+        }
+        if (contained(pw, u.getLName())) {
+            light -= 2;
+        }
+        if (contained(pw, u.getMName())) {
+            light -= 2;
+        }
+        if (contained(pw, u.getSuffix())) {
+            light -= 2;
+        }
+        // TODO dictionary check
+        return light;
+    }
+
+    public static void assertStrongPassword(String pw, User u) throws GigiApiException {
+        if (checkpw(pw, u) < 3) {
+            throw new GigiApiException("The Pass Phrase you submitted failed to contain enough" + " differing characters and/or contained words from" + " your name and/or email address.");
+        }
+    }
+
+    private static boolean contained(String pw, String check) {
+        if (check == null || check.equals("")) {
+            return false;
+        }
+        if (pw.contains(check)) {
+            return true;
+        }
+        if (check.contains(pw)) {
+            return true;
+        }
+        return false;
+    }
+}
diff --git a/src/org/cacert/gigi/util/PublicSuffixes.java b/src/org/cacert/gigi/util/PublicSuffixes.java
new file mode 100644 (file)
index 0000000..51c2edf
--- /dev/null
@@ -0,0 +1,131 @@
+package org.cacert.gigi.util;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.net.IDN;
+import java.util.HashSet;
+
+public class PublicSuffixes {
+
+    HashSet<String> suffixes = new HashSet<>();
+
+    HashSet<String> wildcards = new HashSet<>();
+
+    HashSet<String> exceptions = new HashSet<>();
+
+    private static final String url = "https://publicsuffix.org/list/effective_tld_names.dat";
+
+    private static PublicSuffixes instance;
+
+    private static PublicSuffixes generateDefault() throws IOException {
+        try (BufferedReader br = new BufferedReader(new InputStreamReader(PublicSuffixes.class.getResourceAsStream("effective_tld_names.dat"), "UTF-8"))) {
+            return new PublicSuffixes(br);
+        }
+    }
+
+    public synchronized static PublicSuffixes getInstance() {
+        if (instance == null) {
+            try {
+                instance = generateDefault();
+            } catch (IOException e) {
+                throw new Error(e);
+            }
+        }
+        return instance;
+    }
+
+    private PublicSuffixes(BufferedReader br) throws IOException {
+        String line;
+        while ((line = br.readLine()) != null) {
+            if (line.startsWith("//")) {
+                continue;
+            }
+            if (line.isEmpty()) {
+                continue;
+            }
+            String[] lineParts = line.split("\\s", 2);
+            if (lineParts.length == 0) {
+                throw new Error("split had strange behavior");
+            }
+            line = lineParts[0];
+            if (line.startsWith("*.")) {
+                String data = line.substring(2);
+                if (data.contains("*") || data.contains("!")) {
+                    System.out.println("Error! unparsable public suffix line: " + line);
+                    continue;
+                }
+                addWildcard(IDN.toASCII(data));
+            } else if (line.startsWith("!")) {
+                String data = line.substring(1);
+                if (data.contains("*") || data.contains("!")) {
+                    System.out.println("Error! unparsable public suffix line: " + line);
+                    continue;
+                }
+                addException(IDN.toASCII(data));
+            } else {
+                if (line.contains("*") || line.contains("!")) {
+                    System.out.println("Error! unparsable public suffix line: " + line);
+                    continue;
+                }
+                addSuffix(IDN.toASCII(line));
+            }
+        }
+    }
+
+    private void addWildcard(String data) {
+        wildcards.add(data);
+    }
+
+    private void addException(String data) {
+        exceptions.add(data);
+    }
+
+    private void addSuffix(String line) {
+        suffixes.add(line);
+    }
+
+    public String getRegistrablePart(String domain) {
+        if (domain == null) {
+            return null;
+        }
+        if (domain.startsWith(".")) {
+            return null;
+        }
+        if (isSuffix(domain) && !exceptions.contains(domain)) {
+            return null;
+        }
+        return getPublicSuffix0(domain);
+    }
+
+    private String getPublicSuffix0(String domain) {
+
+        int d = domain.indexOf('.');
+        if (d == -1) {
+            return null;
+        }
+        if (exceptions.contains(domain)) {
+            return domain;
+        }
+        String nextDomain = domain.substring(d + 1);
+        if (isSuffix(nextDomain)) {
+            return domain;
+        }
+
+        return getPublicSuffix0(nextDomain);
+    }
+
+    private boolean isSuffix(String domain) {
+        if (suffixes.contains(domain)) {
+            return true;
+        }
+        if (exceptions.contains(domain)) {
+            return false;
+        }
+        int idx = domain.indexOf('.');
+        if (idx != -1 && wildcards.contains(domain.substring(idx + 1))) {
+            return true;
+        }
+        return false;
+    }
+}
diff --git a/src/org/cacert/gigi/util/RandomToken.java b/src/org/cacert/gigi/util/RandomToken.java
new file mode 100644 (file)
index 0000000..8a11c8f
--- /dev/null
@@ -0,0 +1,27 @@
+package org.cacert.gigi.util;
+
+import java.security.SecureRandom;
+
+public class RandomToken {
+
+    private static SecureRandom sr = new SecureRandom();
+
+    public static String generateToken(int length) {
+        StringBuffer token = new StringBuffer();
+        for (int i = 0; i < length; i++) {
+            int rand = sr.nextInt(26 * 2 + 10);
+            if (rand < 10) {
+                token.append((char) ('0' + rand));
+                continue;
+            }
+            rand -= 10;
+            if (rand < 26) {
+                token.append((char) ('a' + rand));
+                continue;
+            }
+            rand -= 26;
+            token.append((char) ('A' + rand));
+        }
+        return token.toString();
+    }
+}
diff --git a/src/org/cacert/gigi/util/ServerConstants.java b/src/org/cacert/gigi/util/ServerConstants.java
new file mode 100644 (file)
index 0000000..8689209
--- /dev/null
@@ -0,0 +1,86 @@
+package org.cacert.gigi.util;
+
+import java.util.Properties;
+
+public class ServerConstants {
+
+    private static String wwwHostName = "www.cacert.local";
+
+    private static String secureHostName = "secure.cacert.local";
+
+    private static String staticHostName = "static.cacert.local";
+
+    private static String apiHostName = "api.cacert.local";
+
+    private static String securePort, port;
+
+    public static void init(Properties conf) {
+        securePort = port = "";
+        if ( !conf.getProperty("https.port").equals("443")) {
+            securePort = ":" + conf.getProperty("https.port");
+        }
+        if ( !conf.getProperty("http.port").equals("80")) {
+            port = ":" + conf.getProperty("http.port");
+        }
+        wwwHostName = conf.getProperty("name.www");
+        secureHostName = conf.getProperty("name.secure");
+        staticHostName = conf.getProperty("name.static");
+        apiHostName = conf.getProperty("name.api");
+
+    }
+
+    public static String getSecureHostName() {
+        return secureHostName;
+    }
+
+    public static String getStaticHostName() {
+        return staticHostName;
+    }
+
+    public static String getWwwHostName() {
+        return wwwHostName;
+    }
+
+    public static String getApiHostName() {
+        return apiHostName;
+    }
+
+    public static String getSecureHostNamePort() {
+        return secureHostName + securePort;
+    }
+
+    public static String getStaticHostNamePortSecure() {
+        return staticHostName + securePort;
+    }
+
+    public static String getWwwHostNamePortSecure() {
+        return wwwHostName + securePort;
+    }
+
+    public static String getStaticHostNamePort() {
+        return staticHostName + port;
+    }
+
+    public static String getWwwHostNamePort() {
+        return wwwHostName + port;
+    }
+
+    public static String getApiHostNamePort() {
+        return apiHostName + securePort;
+    }
+
+    public static int getSecurePort() {
+        if (securePort.isEmpty()) {
+            return 443;
+        }
+        return Integer.parseInt(securePort.substring(1, securePort.length()));
+    }
+
+    public static int getPort() {
+        if (port.isEmpty()) {
+            return 80;
+        }
+        return Integer.parseInt(port.substring(1, port.length()));
+    }
+
+}
diff --git a/static/static/default.css b/static/static/default.css
new file mode 100644 (file)
index 0000000..86c271b
--- /dev/null
@@ -0,0 +1,821 @@
+/***********************************************/
+/* emx_nav_right.css                           */
+/* Use with template Halo_rightNav.html        */
+/***********************************************/
+
+/***********************************************/
+/* HTML tag styles                             */
+/***********************************************/
+
+body {
+       font-family: Arial,sans-serif;
+       color: #333333;
+       line-height: 1.166;
+       margin: 0px;
+       padding: 0px;
+       background: #cccccc;
+/*     url("/siteimages/bg_grad.jpg") fixed;   */
+}
+
+
+/******* hyperlink and anchor tag styles *******/
+
+a:link, a:visited {
+       color: #005fa9;
+       text-decoration: none;
+}
+
+a:hover {
+       text-decoration: underline;
+}
+
+
+/************** header tag styles **************/
+
+h1 {
+       font: bold 120% Arial ,sans-serif;
+       color: #334d55;
+       margin: 10px;
+       padding: 0px;
+}
+
+h2 {
+       font: bold 114% Arial ,sans-serif;
+       color: #006699;
+       margin: 10px;
+       padding: 0px;
+}
+
+h3 {
+       font: bold 100% Arial ,sans-serif;
+       color: #334d55;
+       margin: 0px;
+       padding: 0px;
+}
+
+h3.pointer {
+       cursor: pointer;
+       /* cursor: hand; */
+}
+
+h4 {
+       font: bold 100% Arial ,sans-serif;
+       color: #333333;
+       margin: 0px;
+       padding: 0px;
+}
+
+h5 {
+       font: 100% Arial ,sans-serif;
+       color: #334d55;
+       margin: 0px;
+       padding: 0px;
+}
+
+
+/*************** list tag styles ***************/
+
+ul.menu {
+       list-style: none;
+       margin: 0px 0px 0px 15px;
+       padding-left: 5px;
+       border-left: 1px dotted #000;
+}
+
+ul.top {
+       list-style: none;
+       margin: 0px 0px 0px 15px;
+       padding-left: 5px;
+       border-left: 0px;
+}
+
+ul.no_indent {
+       list-style: none;
+       padding: 0px;
+}
+
+.attach_ul {
+       margin-bottom: 0px;
+}
+
+.attach_ul + ul {
+       margin-top: 0px;
+}
+
+
+/***********************************************/
+/* Layout Divs                                 */
+/***********************************************/
+
+#pagecell1 {
+       position: absolute;
+       top: 2%;
+       left: 2%;
+       right: 2%;
+       width: 96%;
+       background-color: #ffffff;
+}
+
+#tl {
+       position: absolute;
+       top: -1px;
+       left: -1px;
+       margin: 0px;
+       padding: 0px;
+       z-index: 100;
+}
+
+#tr {
+       position: absolute;
+       top: -1px;
+       right: -1px;
+       margin: 0px;
+       padding: 0px;
+       z-index: 100;
+}
+
+#masthead {
+       position: absolute;
+       top: 0px;
+       left: 2%;
+       right: 2%;
+       width: 95.6%;
+}
+
+#pageNav {
+       float: right;
+       width: 178px;
+       padding: 0px;
+       background-color: #F5f7f7;
+       border-left: 1px solid #cccccc;
+       font: small Verdana,sans-serif;
+}
+
+#content {
+       padding: 0px 10px 0px 0px;
+       margin: 0px 178px 0px 0px;
+}
+
+
+/***********************************************/
+/* Component Divs                              */
+/***********************************************/
+#siteName {
+       margin: 0px;
+       padding: 16px 0px 8px 0px;
+       color: #ffffff;
+       font-weight: normal;
+}
+
+
+/************** utility styles *****************/
+
+#utility {
+       font: 75% Verdana,sans-serif;
+       position: absolute;
+       top: 16px;
+       right: 0px;
+       color: #919999;
+}
+
+#utility a {
+       color: #ffffff;
+}
+
+#utility a:hover {
+       text-decoration: underline;
+}
+
+
+/************** pageName styles ****************/
+
+#pageName {
+       padding: 0px 0px 14px 10px;
+       margin: 0px;
+       border-bottom: 1px solid #ccd2d2;
+       z-index: 2;
+}
+
+#pageName h2 {
+       font: bold 175% Arial,sans-serif;
+       color: #000000;
+       margin: 0px;
+       padding: 0px;
+}
+
+/*
+#pageLogo {
+       position: absolute;
+       top: 8px;
+       left: 10px;
+       z-index: 5;
+}
+*/
+
+
+/************* globalNav styles ****************/
+
+#globalNav {
+       position: relative;
+       width: 100%;
+       min-width: 640px;
+       height: 32px;
+       color: #cccccc;
+       padding: 0px;
+       margin: 0px;
+       background-image: url("siteimages/glbnav_background.gif");
+}
+
+#globalNav img {
+       margin-bottom: -4px;
+}
+
+#gnl {
+       position: absolute;
+       top: 0px;
+       left:0px;
+}
+
+#gnr {
+       position: absolute;
+       top: 0px;
+       right:0px;
+}
+
+#globalLink {
+       position: absolute;
+       top: 6px;
+       height: 22px;
+       min-width: 640px;
+       padding: 0px;
+       margin: 0px;
+       left: 10px;
+       z-index: 100;
+}
+
+
+a.glink, a.glink:visited {
+       font-size: small;
+       color: #000000;
+       font-weight: bold;
+       margin: 0px;
+       padding: 2px 5px 4px 5px;
+       border-right: 1px solid #8fb8bc;
+}
+
+a.glink:hover {
+       background-image: url("siteimages/glblnav_selected.gif");
+       text-decoration: none;
+}
+
+.skipLinks {
+       display: none;
+}
+
+
+/************ subglobalNav styles **************/
+
+.subglobalNav {
+       position: absolute;
+       top: 84px;
+       left: 0px;
+       /*width: 100%;*/
+       min-width: 640px;
+       height: 20px;
+       padding: 0px 0px 0px 10px;
+       visibility: hidden;
+       color: #ffffff;
+}
+
+.subglobalNav a:link, .subglobalNav a:visited {
+       font-size: 80%;
+       color: #ffffff;
+}
+
+.subglobalNav a:hover {
+       color: #cccccc;
+}
+
+
+/*************** search styles *****************/
+/*
+#listshow {
+       z-order: 101;
+}
+*/
+
+#search {
+       position: absolute;
+       top: 125px;
+       right: 0px;
+}
+
+#search form {
+       position: absolute;
+       top: 125px;
+       right: 300px;
+}
+#search input {
+       font-size: 11px;
+}
+
+#search1 {
+       position: absolute;
+       top: 85px;
+       right: 300px;
+}
+
+#search2 {
+       position: absolute;
+       top: 100px;
+       right: 300px;
+}
+
+#search3 {
+       position: absolute;
+       top: 85px;
+       right: 240px;
+}
+
+#search4 {
+       position: absolute;
+       top: 100px;
+       right: 226px;
+}
+
+#googlead {
+       position: absolute;
+       top: 5px;
+       right: 0px;
+       z-index: -10;
+}
+
+#search input {
+       font-size: 70%;
+       margin: 0px 0px 0px 10px;
+}
+
+#search a:link, #search a:visited {
+       font-size: 80%;
+       font-weight: bold;
+
+}
+
+#search a:hover {
+       margin: 0px;
+}
+
+
+/************* breadCrumb styles ***************/
+
+#breadCrumb {
+       padding: 5px 0px 5px 10px;
+       font: small Verdana,sans-serif;
+       color: #aaaaaa;
+}
+
+#breadCrumb a {
+       color: #aaaaaa;
+}
+
+#breadCrumb a:hover {
+       color: #005fa9;
+       text-decoration: underline;
+}
+
+
+/************** feature styles *****************/
+
+.feature {
+       padding: 0px 0px 10px 10px;
+       font-size: 80%;
+       min-height: 200px;
+       height: 200px;
+}
+
+.feature {
+       height: auto;
+}
+
+.feature h3 {
+       font: bold 175% Arial,sans-serif;
+       color: #000000;
+       padding: 30px 0px 5px 0px;
+}
+
+.feature img {
+       float: left;
+       padding: 0px 10px 0px 0px;
+}
+
+
+/*************** content styles ******************/
+
+.content {
+       padding: 10px 0px 0px 10px;
+       font-size: 80%;
+       min-height: 450px;
+}
+
+.content h3 {
+       font: bold 125% Arial,sans-serif;
+       color: #000000;
+}
+
+.content a.capsule {
+       font: bold 1em Arial,sans-serif;
+       color: #005FA9;
+       display: block;
+       padding-bottom: 5px;
+}
+
+.content a.capsule:hover {
+       text-decoration: underline;
+}
+
+td.storyLeft {
+       padding-right: 12px;
+}
+
+
+/************** siteInfo styles ****************/
+
+#siteInfo {
+       clear: both;
+       border-top: 1px solid #cccccc;
+       font-size: small;
+       color: #cccccc;
+       padding: 10px 10px 10px 10px;
+}
+
+
+/************ sectionLinks styles **************/
+
+#sectionLinks {
+       margin: 0px;
+       padding: 0px;
+}
+
+#sectionLinks h3 {
+       padding: 10px 0px 2px 10px;
+       border-bottom: 1px solid #cccccc;
+}
+
+#sectionLinks a:link, #sectionLinks a:visited {
+       display: block;
+       border-top: 1px solid #ffffff;
+       border-bottom: 1px solid #cccccc;
+       background-image: url("siteimages/bg_nav.jpg");
+       font-weight: bold;
+       padding: 3px 0px 3px 10px;
+       color: #21536A;
+}
+
+#sectionLinks a:hover {
+       border-top: 1px solid #cccccc;
+       background-color: #DDEEFF;
+       background-image: none;
+       font-weight: bold;
+       text-decoration: none;
+}
+
+
+/************* relatedLinks styles **************/
+
+#pageNav div {
+       margin: 0px;
+       padding: 0px 0px 10px 10px;
+       border-bottom: 1px solid #cccccc;
+}
+
+#pageNav div h3 {
+       padding: 10px 0px 2px 0px;
+}
+
+#pageNav div a {
+       display: block;
+}
+
+/*** hide top menu ***/
+#pageNav > div > ul.menu {
+       padding-left: 0;
+       border-left: none;
+       margin: 0;
+}
+#pageNav > div{
+       padding: 0;
+}
+
+#pageNav > div > h3.pointer {
+       display: none;
+}
+
+/**************** advert styles *****************/
+
+#advert {
+       padding: 10px;
+}
+
+#advert img {
+       display: block;
+}
+
+
+/********************* end **********************/
+.DataTDGrey {
+       background-color: #EFEDED;
+       border-style: inset;
+       border-width: 1px;
+       font-size: 8pt;
+       color: #000000;
+       font-family: Arial, Tahoma, Verdana, Helvetica, sans-serif;
+
+       padding: 1px 5px 1px 5px;
+       border: 1px #CFCFCF solid;
+       border-left: 1px #cfcfcf dotted;
+       border-right: 1px #cfcfcf dotted;
+}
+
+.DataTDNotDotted {
+       background-color: #e2e2e2;
+       border-style: inset;
+       border-width: 1px;
+       font-size: 8pt;
+       color: #000000;
+       font-family: Arial, Tahoma, Verdana, Helvetica, sans-serif;
+
+       background: #ffffff;
+       padding: 1px 5px 1px 5px;
+       border: 1px #CFCFCF solid;
+       border-left: 1px #cfcfcf solid;
+       border-right: 1px #cfcfcf solid;
+}
+
+.DataTDError {
+    border-style: inset;
+    border-width: 1px;
+    font-size: 8pt;
+    color: #ff0000;
+    font-family: Arial, Tahoma, Verdana, Helvetica, sans-serif;
+
+    background: #ffffff;
+    padding: 1px 5px 1px 5px;
+    border: 1px #cfcfcf solid;
+    border-left: 1px #cfcfcf dotted;
+    border-right: 1px #cfcfcf dotted;
+}
+.wrapper {
+       border-collapse: collapse;
+       font-family: verdana, sans-serif;
+       font-size: 11px;
+       margin-left:auto;
+       margin-right:auto;
+       width: 700px;
+}
+
+.wrapper .check{
+       text-align: center;
+}
+.wrapper td.radio input[type=radio]{
+       float: left;
+       width: 13px;
+       margin: 3px;
+}
+.wrapper td.radio span.name{
+       font-weight: bold;
+       padding-bottom: 4px;
+       margin-top: 3px;
+/*     display: inline;*/
+}
+.wrapper td.radio div.addinfo{
+/*     display: inline;*/
+       padding-left: 19px;
+}
+
+.wrapper td.radio div.elements{
+       clear: both;
+       height: 10px;
+}
+
+td.greytxt {
+       color: #cccccc;
+       font-size: smaller;
+       text-align: right;
+       vertical-align: bottom;
+}
+.bold, .primaryemailaddress {
+       font-weight:bold;
+}
+.italic, .deletedemailaddress {
+       font-style:italic;
+}
+.title {
+       background: #e2e2e2;
+       font-weight: bold;
+       padding: 1px 5px 1px 5px;
+       border: 1px solid #cfcfcf;
+       border-bottom: 3px double #cfcfcf;
+       border-top: 1px solid #656565;
+       text-align: center;
+}
+
+.errmsg {
+       font-weight: bold;
+       color: #FF0000;
+}
+
+.ac_menu {
+       border: 1px solid black
+}
+
+.ac_normal {
+       background-color: #ffffff;
+       cursor: pointer;
+}
+
+.ac_highlight {
+       background-color: #3366cc;
+       color: white;
+       cursor: pointer;
+}
+
+.ac_normal .a {
+       font-size: 13px;
+       color: black;
+}
+
+.ac_highlight .a {
+       font-size: 13px;
+}
+
+.ac_normal .d {
+       float: right;
+       font-size: 10px;
+       color: green;
+}
+
+.ac_highlight .d {
+       float: right;
+       font-size: 10px;
+}
+
+
+/************** sponsorInfo styles ****************/
+
+div.sponsorinfo {
+       clear: both;
+       border-top: 1px solid #cccccc;
+       font-size: small;
+       color: #000000;
+       padding: 10px 10px 10px 10px;
+}
+
+img.sponsorlogo {
+       margin-left: 10px;
+       margin-right: 10px;
+       border: 0px none;
+       vertical-align: middle;
+}
+
+
+/************ Newsbox *************/
+
+#lnews {       /* class for the text "Latest News" */
+       font-size: small;
+       font-variant: small-caps;
+}
+
+div.newsbox {
+       border-top: 1px solid #cccccc;
+       color: #101010;
+       padding: 10px 10px 10px 10px;
+}
+
+
+/************ SQL Performance ***********/
+
+div.footerbar {
+       clear: both;
+       border-top: 1px solid #cccccc;
+       font-size: small;
+       color: black;
+       padding: 10px 10px 10px 10px;
+}
+
+
+/************ Honeypot  ***********/
+
+.robotic {
+       display: none;
+}
+
+
+/************  unicode fallbacks ***********/
+
+/* Some embedding of font */
+@font-face {
+       font-family: 'Source Code Pro';
+       src: local('Source Code Pro');
+/*  src: url(/res/fonts/SourceCodePro-Medium.ttf); */
+}
+
+@font-face {
+       font-family: 'Last Resort';
+       src: local('LastResort');
+/*  src: url(/res/fonts/LastResort.ttf); */
+}
+
+.accountdetail {
+       font-family: 'Source Code Pro', 'Lucida Console', 'Arial Unicode MS', monospace, 'Last Resort';
+       font-size: 1.1em;
+}
+
+.accountdetail.fname, .accountdetail .fname {
+}
+
+.accountdetail.mname {
+}
+
+.accountdetail.lname, .accountdetail .lname {
+       font-weight: bold;
+}
+
+.accountdetail.suffix {
+}
+ul.menu.hidden{
+       display: none;
+}
+img{
+       border: 0;
+}
+formMandatory{
+       color: red;
+}
+.experthidden{
+       display:none;   
+}
+.expertoff{
+       display:none;   
+}
+pre{
+       word-wrap: break-word;
+       white-space: pre-wrap;
+}
+
+.dataTable td {
+       background-color: #e2e2e2;
+       border-style: inset;
+       border-width: 1px;
+       font-size: 8pt;
+       color: #000000;
+       font-family: Arial, Tahoma, Verdana, Helvetica, sans-serif;
+
+       background: #ffffff;
+       padding: 1px 5px 1px 5px;
+       border: 1px #cfcfcf solid;
+       border-left: 1px #cfcfcf dotted;
+       border-right: 1px #cfcfcf dotted;
+}
+
+.dataTable td input {
+       width: 98%;
+       margin-left: 1%;
+       margin-right: 1%;
+}
+.dataTable td nobr input {
+       width: auto;
+}
+.centertext {
+       text-align: center;
+}
+.dataTable th {
+       background: #e2e2e2;
+       font-weight: bold;
+       padding: 1px 5px 1px 5px;
+       border: 1px solid #cfcfcf;
+       border-bottom: 3px double #cfcfcf;
+       border-top: 1px solid #656565;
+       text-align: center;
+}
+
+.dataTable input, .dataTable textarea {
+       font-size: 92%;
+}
+
+.dataTable select, .dataTable option {
+       font-size: 92%;
+}
+.dataTable textarea{
+       width: 100%;
+}
+
+pre.string{
+       display: inline;
+}
+
+.loginbox {background:#F5F7F7;border:2px solid #cccccc;margin:0px auto;height:auto;width:300px;padding:1em;text-align:center;}
+.loginbox .smalltext {font-size:10px;}
+.loginbox label {width:100px;display:block;float:left;}
+.loginbox text {width:166px;display:block;float:left;}
+.loginbox br {clear:left;}
+.loginbox h1 {font-size:1.9em;text-align:center;}
\ No newline at end of file
diff --git a/static/static/images/bit.png b/static/static/images/bit.png
new file mode 100644 (file)
index 0000000..5597e3b
Binary files /dev/null and b/static/static/images/bit.png differ
diff --git a/static/static/images/cacert4-test.png b/static/static/images/cacert4-test.png
new file mode 100644 (file)
index 0000000..d742232
Binary files /dev/null and b/static/static/images/cacert4-test.png differ
diff --git a/static/static/images/cacert4.png b/static/static/images/cacert4.png
new file mode 100644 (file)
index 0000000..e4650a0
Binary files /dev/null and b/static/static/images/cacert4.png differ
diff --git a/static/static/images/nlnet.png b/static/static/images/nlnet.png
new file mode 100644 (file)
index 0000000..c7e5c46
Binary files /dev/null and b/static/static/images/nlnet.png differ
diff --git a/static/static/images/oan.png b/static/static/images/oan.png
new file mode 100644 (file)
index 0000000..548bb12
Binary files /dev/null and b/static/static/images/oan.png differ
diff --git a/static/static/images/tunix.png b/static/static/images/tunix.png
new file mode 100644 (file)
index 0000000..29adb60
Binary files /dev/null and b/static/static/images/tunix.png differ
diff --git a/static/static/keygenIE.js b/static/static/keygenIE.js
new file mode 100644 (file)
index 0000000..4c15b23
--- /dev/null
@@ -0,0 +1,611 @@
+/*
+LibreSSL - CAcert web application
+Copyright (C) 2004-2012  CAcert Inc.
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; version 2 of the License.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+*/
+
+var CAcert_keygen_IE = function () {
+
+       /// Makes a new DOM text node
+       var textnode = function (text) {
+               return document.createTextNode(text);
+       }
+
+       /// makes a new <p> element
+       var paragraph = function (text) {
+               var paragraph = document.createElement("p");
+               paragraph.appendChild(textnode(text));
+               return paragraph;
+       }
+
+       /// makes a new <pre> elemtent
+       var pre = function (text) {
+               var pre = document.createElement("pre");
+               pre.appendChild(textnode(text));
+               return pre;
+       }
+
+       /// makes a new <option> element
+       var option = function (text, value) {
+               var option = document.createElement("option");
+               if (value !== undefined) {
+                       option.setAttribute("value", value);
+               }
+               option.appendChild(textnode(text));
+               return option;
+       }
+
+       /// Removes all child nodes from the element
+       var removeChildren = function (element) {
+               element.innerHTML = "";
+       }
+
+       /// Show error message to user from exception
+       var showError = function (message, exception) {
+               window.alert(
+                       message +
+                       "\n\nError: " + exception.message +
+                       " (0x" + (0xFFFFFFFF + exception.number + 1).toString(16) +
+                       " / " + exception.number + ")"
+                       );
+       }
+
+       // Get important elements from the DOM
+       var form = document.getElementById("CertReqForm");
+       var securityLevel = document.getElementById("SecurityLevel");
+       var customSettings = document.getElementById("customSettings");
+       var provider = document.getElementById("CspProvider");
+       var algorithm = document.getElementById("algorithm");
+       var algorithmParagraph = document.getElementById("algorithmParagraph");
+       var keySize = document.getElementById("keySize");
+       var keySizeMin = document.getElementById("keySizeMin");
+       var keySizeMax = document.getElementById("keySizeMax");
+       var keySizeStep = document.getElementById("keySizeStep");
+       var genReq = document.getElementById("GenReq");
+       var csr = document.getElementById("CSR");
+       var noActiveX = document.getElementById("noActiveX");
+       var generatingKeyNotice = document.getElementById("generatingKeyNotice");
+       var createRequestErrorChooseAlgorithm = document.getElementById("createRequestErrorChooseAlgorithm");
+       var createRequestErrorConfirmDialogue = document.getElementById("createRequestErrorConfirmDialogue");
+       var createRequestErrorConnectDevice = document.getElementById("createRequestErrorConnectDevice");
+       var createRequestError = document.getElementById("createRequestError");
+       var invalidKeySizeError = document.getElementById("invalidKeySizeError");
+       var unsupportedPlatformError = document.getElementById("unsupportedPlatformError");
+
+       /// Initialise the CertEnroll code (Vista and higher)
+       /// returns false if initialisation fails
+       var initCertEnroll = function () {
+               var factory = null;
+               var providerList = null;
+               var cspStats = null;
+
+               // Try to initialise the ActiveX element. Requires permissions by the user
+               try {
+                       factory = new ActiveXObject("X509Enrollment.CX509EnrollmentWebClassFactory");
+                       if (!factory) {
+                               throw {
+                                       name: "NoObjectError",
+                                       message: "Got null at object creation"
+                                       };
+                       }
+
+                       // also try to create a useless object here so the library gets
+                       // initialised and we don't need to check everytime later
+                       factory.CreateObject("X509Enrollment.CObjectId");
+
+                       form.style.display = "";
+                       noActiveX.style.display = "none";
+               } catch (e) {
+                       return false;
+               }
+
+               /// Get the selected provider
+               var getProvider = function () {
+                       var providerIndex = provider.options[provider.selectedIndex].value;
+                       return providerList.ItemByIndex(providerIndex);
+               }
+
+               /// Get the selected algorithm
+               var getAlgorithm = function () {
+                       var algorithmIndex = algorithm.options[algorithm.selectedIndex].value;
+                       return alg = cspStats.ItemByIndex(algorithmIndex).CspAlgorithm;
+               }
+
+               /// Get the selected key size
+               var getKeySize = function () {
+                       var alg = getAlgorithm();
+
+                       var bits = parseInt(keySize.value, 10);
+                       if (
+                               (bits < alg.MinLength) ||
+                               (bits > alg.MaxLength) ||
+                               (
+                                       alg.IncrementLength &&
+                                       ((bits - alg.MinLength) % alg.IncrementLength !== 0)
+                               )
+                       ) {
+                               return false;
+                       }
+
+                       return bits;
+               }
+
+               /// Fill the key size list
+               var getKeySizeList = function () {
+                       if (!cspStats) {
+                               return false;
+                       }
+
+                       var alg = getAlgorithm();
+
+                       // HTML5 attributes
+                       keySize.setAttribute("min", alg.MinLength);
+                       keySize.setAttribute("max", alg.MaxLength);
+                       keySize.setAttribute("step", alg.IncrementLength);
+                       keySize.setAttribute("value", alg.DefaultLength);
+                       keySize.value = ""+alg.DefaultLength;
+
+                       // ugly, but buggy otherwise if done with text nodes
+                       keySizeMin.innerHTML = alg.MinLength;
+                       keySizeMax.innerHTML = alg.MaxLength;
+                       keySizeStep.innerHTML = alg.IncrementLength;
+
+                       return true;
+               }
+
+               /// Fill the algorithm list
+               var getAlgorithmList = function () {
+                       var i;
+                       
+                       if (!providerList) {
+                               return false;
+                       }
+
+                       var csp = getProvider();
+
+                       cspStats = providerList.GetCspStatusesFromOperations(
+                               0x1c, //XCN_NCRYPT_ANY_ASYMMETRIC_OPERATION
+                               //0x10, //XCN_NCRYPT_SIGNATURE_OPERATION
+                               //0x8, //XCN_NCRYPT_SECRET_AGREEMENT_OPERATION
+                               //0x4, //XCN_NCRYPT_ASYMMETRIC_ENCRYPTION_OPERATION
+                               csp
+                               );
+
+                       removeChildren(algorithm);
+                       for (i = 0; i < cspStats.Count; i++) {
+                               var alg = cspStats.ItemByIndex(i).CspAlgorithm;
+                               algorithm.appendChild(option(alg.Name, i));
+                       }
+
+                       return getKeySizeList();
+               }
+
+               /// Fill the crypto provider list
+               var getProviderList = function () {
+                       var i;
+                       
+                       var csps = factory.CreateObject("X509Enrollment.CCspInformations");
+
+                       // Get provider information
+                       csps.AddAvailableCsps();
+
+                       removeChildren(provider);
+
+                       for (i = 0; i < csps.Count; i++) {
+                               var csp = csps.ItemByIndex(i);
+                               provider.appendChild(option(csp.Name, i));
+                       }
+
+                       providerList = csps;
+
+                       return getAlgorithmList();
+               }
+
+               /// Generate a key and create and submit the actual CSR
+               var createCSR = function () {
+                       var providerName, algorithmOid, bits;
+
+                       var level = securityLevel.options[securityLevel.selectedIndex];
+                       if (level.value === "custom") {
+                               providerName = getProvider().Name;
+                               var alg = getAlgorithm();
+                               algorithmOid = alg.GetAlgorithmOid(0, 0)
+                               bits = getKeySize();
+                               if (!bits) {
+                                       window.alert(invalidKeySizeError.innerHTML);
+                                       return false;
+                               }
+                       } else {
+                               providerName = "Microsoft Software Key Storage Provider";
+
+                               algorithmOid = factory.CreateObject("X509Enrollment.CObjectId");
+                               algorithmOid.InitializeFromValue("1.2.840.113549.1.1.1"); // RSA
+                               // "1.2.840.10040.4.1" == DSA
+                               // "1.2.840.10046.2.1" == DH
+
+                               if (level.value === "high") {
+                                       bits = 4096;
+                               } else { // medium
+                                       bits = 2048;
+                               }
+                       }
+
+                       var privateKey = factory.CreateObject("X509Enrollment.CX509PrivateKey");
+                       privateKey.ProviderName = providerName;
+                       privateKey.Algorithm = algorithmOid;
+                       privateKey.Length = bits;
+                       privateKey.KeyUsage = 0xffffff; // XCN_NCRYPT_ALLOW_ALL_USAGES
+                       privateKey.ExportPolicy = 0x1; // XCN_NCRYPT_ALLOW_EXPORT_FLAG
+
+                       var request = factory.CreateObject("X509Enrollment.CX509CertificateRequestPkcs10");
+                       request.InitializeFromPrivateKey(
+                               1, // ContextUser
+                               privateKey,
+                               "" // don't use a template
+                               );
+
+                       var enroll = factory.CreateObject("X509Enrollment.CX509Enrollment");
+                       enroll.InitializeFromRequest(request);
+
+                       generatingKeyNotice.style.display = "";
+
+                       // The request needs to be created after we return so the "please wait"
+                       // message gets rendered
+                       var createCSRHandler = function () {
+                               try {
+                                       csr.value = enroll.CreateRequest(0x1); //XCN_CRYPT_STRING_BASE64
+                                       form.submit();
+                               } catch (e) {
+                                       showError(createRequestErrorChooseAlgorithm.innerHTML, e);
+                               }
+
+                               generatingKeyNotice.style.display = "none";
+                       }
+
+                       window.setTimeout(createCSRHandler, 0);
+
+                       // Always return false, form is submitted by deferred method
+                       return false;
+               }
+
+               /// Call if securityLevel has changed
+               var refreshSecurityLevel = function () {
+                       var level = securityLevel.options[securityLevel.selectedIndex];
+                       if (level.value === "custom") {
+                               getProviderList();
+                               customSettings.style.display = "";
+                       } else {
+                               customSettings.style.display = "none";
+                       }
+               }
+
+               securityLevel.onchange = refreshSecurityLevel;
+               provider.onchange = getAlgorithmList;
+               algorithm.onchange = getKeySizeList;
+               genReq.onclick = createCSR;
+
+               return true;
+       } // end of initCertEnroll()
+
+       /// Initialise Xenroll code (XP and lower)
+       /// returns false if initialisation fails
+       var initXEnroll = function () {
+               cenroll = null;
+
+               providerTypes = Array(
+                                1, //PROV_RSA_FULL
+                                2, //PROV_RSA_SIG
+                                3, //PROV_DSS
+                                4, //PROV_FORTEZZA
+                                5, //PROV_MS_EXCHANGE
+                                6, //PROV_SSL
+                               12, //PROV_RSA_SCHANNEL
+                               13, //PROV_DSS_DH
+                               14, //PROV_EC_ECDSA_SIG
+                               15, //PROV_EC_ECNRA_SIG
+                               16, //PROV_EC_ECDSA_FULL
+                               17, //PROV_EC_ECNRA_FULL
+                               18, //PROV_DH_SCHANNEL
+                               20, //PROV_SPYRUS_LYNKS
+                               21, //PROV_RNG
+                               22, //PROV_INTEL_SEC
+                               23, //PROV_REPLACE_OWF
+                               24  //PROV_RSA_AES
+                       );
+
+               algClasses = Array(
+                       1 << 13, //ALG_CLASS_SIGNATURE
+                       //2 << 13, //ALG_CLASS_MSG_ENCRYPT
+                       //3 << 13, //ALG_CLASS_DATA_ENCRYPT
+                       //4 << 13, //ALG_CLASS_HASH
+                       5 << 13  //ALG_CLASS_KEY_EXCHANGE
+                       );
+
+               // Try to initialise the ActiveX element.
+               try {
+                       cenroll = new ActiveXObject("CEnroll.CEnroll");
+
+                       if (!cenroll) {
+                               throw {
+                                       name: "NoObjectError",
+                                       message: "Got null at object creation"
+                               };
+                       }
+
+                       form.style.display = "";
+                       algorithm.disabled = true;
+                       noActiveX.style.display = "none";
+               } catch (e) {
+                       return false;
+               }
+
+               /// Get the name of the selected provider
+               var getProviderName = function () {
+                       return provider.options[provider.selectedIndex].text;
+               }
+
+               /// Get the type of the selected provider
+               var getProviderType = function () {
+                       return parseInt(provider.options[provider.selectedIndex].value, 10);
+               }
+
+               var refreshProvider = function () {
+                       cenroll.ProviderName = getProviderName();
+                       cenroll.ProviderType = getProviderType();
+               }
+
+               /// Get the ID of the selected algorithm
+               var getAlgorithmId = function () {
+                       return parseInt(algorithm.options[algorithm.selectedIndex].value, 10);
+               }
+
+               /// Minimum bit length for exchange keys
+               var getMinExKeyLength = function () {
+                       refreshProvider();
+
+                       try {
+                               return cenroll.GetKeyLen(true, true);
+                       } catch (e) {
+                               return false;
+                       }
+               }
+
+               /// Maximum bit length for exchange keys
+               var getMaxExKeyLength = function () {
+                       refreshProvider();
+
+                       try {
+                               return cenroll.GetKeyLen(false, true);
+                       } catch (e) {
+                               return false;
+                       }
+               }
+
+               /// Step size for exchange keys
+               /// This might not be available on older platforms
+               var getStepExKeyLength = function () {
+                       refreshProvider();
+
+                       try {
+                               return cenroll.GetKeyLenEx(3, 1);
+                       } catch (e) {
+                               return false;
+                       }
+               }
+
+               /// Minimum bit length for signature keys
+               var getMinSigKeyLength = function () {
+                       refreshProvider();
+
+                       try {
+                               return cenroll.GetKeyLen(true, false);
+                       } catch (e) {
+                               return false;
+                       }
+               }
+
+               /// Maximum bit length for signature keys
+               var getMaxSigKeyLength = function () {
+                       refreshProvider();
+
+                       try {
+                               return cenroll.GetKeyLen(false, false);
+                       } catch (e) {
+                               return false;
+                       }
+               }
+
+               /// Step size for signature keys
+               /// This might not be available on older platforms
+               var getStepSigKeyLength = function () {
+                       refreshProvider();
+
+                       try {
+                               return cenroll.GetKeyLenEx(3, 2);
+                       } catch (e) {
+                               return false;
+                       }
+               }
+
+               /// Get the selected key size
+               var getKeySize = function () {
+                       var bits = parseInt(keySize.value, 10);
+                       if (
+                               (bits < getMinSigKeyLength()) ||
+                               (bits > getMaxSigKeyLength()) ||
+                               (
+                                       getStepSigKeyLength() &&
+                                       ((bits - getMinSigKeyLength()) % getStepSigKeyLength() !== 0)
+                               )
+                       ) {
+                               return false;
+                       }
+
+                       return bits;
+               }
+
+               var getKeySizeLimits = function () {
+                       // HTML5 attributes
+                       keySize.setAttribute("min", getMinSigKeyLength());
+                       keySize.setAttribute("max", getMaxSigKeyLength());
+                       if (getStepSigKeyLength()) {
+                               keySize.setAttribute("step", getStepSigKeyLength());
+                       }
+
+                       // ugly, but buggy otherwise if done with text nodes
+                       keySizeMin.innerHTML = getMinSigKeyLength();
+                       keySizeMax.innerHTML = getMaxSigKeyLength();
+                       keySizeStep.innerHTML = getStepSigKeyLength();
+
+                       if (getMinSigKeyLength() === getMaxSigKeyLength()) {
+                               keySize.value = getMaxSigKeyLength();
+                       }
+
+                       return true;
+               }
+
+               /// Fill the algorithm selection box
+               var getAlgorithmList = function () {
+                       var i, j;
+                       
+                       refreshProvider();
+
+                       removeChildren(algorithm);
+
+                       for (i = 0; i < algClasses.length; ++i) {
+                               for (j = 0; true; ++j) {
+                                       try {
+                                               var algId = cenroll.EnumAlgs(j, algClasses[i]);
+                                               var algName = cenroll.GetAlgName(algId);
+                                               algorithm.appendChild(option(algName, algId));
+                                       } catch (e) {
+                                               break;
+                                       }
+                               }
+                       }
+
+                       getKeySizeLimits();
+               }
+
+               /// Fill the provider selection box
+               var getProviderList = function () {
+                       var i, j;
+                       
+                       removeChildren(provider);
+
+                       for (i = 0; i < providerTypes.length; ++i) {
+                               cenroll.providerType = providerTypes[i];
+
+                               var providerName = "invalid";
+                               for (j = 0; true; ++j) {
+                                       try {
+                                               providerName = cenroll.enumProviders(j, 0);
+                                               provider.appendChild(option(providerName, providerTypes[i]));
+                                       } catch (e) {
+                                               break;
+                                       }
+                               }
+                       }
+
+                       return getAlgorithmList();
+               }
+
+               var createCSR = function () {
+                       var providerName, bits;
+
+                       var level = securityLevel.options[securityLevel.selectedIndex];
+                       if (level.value === "custom") {
+                               refreshProvider();
+
+                               bits = getKeySize();
+                               if (bits === false) {
+                                       window.alert(invalidKeySizeError.innerHTML);
+                                       return false;
+                               }
+                       } else {
+                               cenroll.ProviderName = "Microsoft Enhanced Cryptographic Provider v1.0";
+                               cenroll.ProviderType = 1; //PROV_RSA_FULL
+
+                               if (level.value === "high") {
+                                       bits = 4096;
+                               } else { // medium
+                                       bits = 2048;
+                               }
+                       }
+
+                       cenroll.GenKeyFlags = bits << 16; // keysize is encoded in the uper 16 bits
+                       // Allow exporting the private key
+                       cenroll.GenKeyFlags = cenroll.GenKeyFlags | 0x1; //CRYPT_EXPORTABLE
+
+                       generatingKeyNotice.style.display = "";
+
+                       // The request needs to be created after we return so the "please wait"
+                       // message gets rendered
+                       var createCSRHandler = function () {
+                               try {
+                                       csr.value = cenroll.createPKCS10("", "1.3.6.1.5.5.7.3.2");
+                                       form.submit();
+                               } catch (e) {
+                                       if (e.number === -2147023673) {
+                                               // 0x800704c7 => dialogue declined
+                                               showError(createRequestErrorConfirmDialogue.innerHTML, e);
+                                       } else if (e.number === -2146435043) {
+                                               // 0x8010001d => crypto-device not connected
+                                               showError(createRequestErrorConnectDevice.innerHTML, e);
+                                       } else {
+                                               showError(createRequestError.innerHTML, e);
+                                       }
+                               }
+
+                               generatingKeyNotice.style.display = "none";
+                               cenroll.Reset();
+                       }
+
+                       window.setTimeout(createCSRHandler, 0);
+
+                       // Always return false, form is submitted by deferred method
+                       return false;
+               }
+
+               /// Call if securityLevel has changed
+               var refreshSecurityLevel = function () {
+                       var level = securityLevel.options[securityLevel.selectedIndex];
+                       if (level.value === "custom") {
+                               getProviderList();
+                               customSettings.style.display = "";
+                       } else {
+                               customSettings.style.display = "none";
+                       }
+               }
+
+               securityLevel.onchange = refreshSecurityLevel;
+               provider.onchange = getAlgorithmList;
+               algorithm.onchange = getKeySizeLimits;
+               genReq.onclick = createCSR;
+
+               return true;
+       };
+
+       // Run the init functions until one is successful
+       if (initCertEnroll()) {
+               form.style.display = "";
+               noActiveX.style.display = "none";
+       } else if (initXEnroll()) {
+               form.style.display = "";
+               noActiveX.style.display = "none";
+       } else {
+               window.alert(unsupportedPlatformError.innerHTML);
+       }
+} ();
diff --git a/static/static/menu.js b/static/static/menu.js
new file mode 100644 (file)
index 0000000..023e624
--- /dev/null
@@ -0,0 +1,58 @@
+(function() {
+       function explodeMenu(e) {
+               if (e.className == 'menu hidden') {
+                       e.className = 'menu';
+               } else {
+                       e.className = 'menu hidden';
+               }
+       }
+
+       function initMenu() {
+               var Nodes = document.getElementsByTagName('ul');
+               var max = Nodes.length;
+               for (var i = 0; i < max; i++) {
+                       var nodeObj = Nodes.item(i);
+                       if (nodeObj.className.indexOf("menu") > -1 && nodeObj.id != "recom") {
+                               nodeObj.previousSibling.previousSibling.onclick = (function(node) {
+                                       return function() {
+                                               explodeMenu(node);
+                                       };
+                               })(nodeObj);
+                       }
+               }
+       }
+       function showExpert(a)
+       {
+         b=document.getElementsByClassName("expert");
+         for(i=0;b.length>i;i++)
+         {
+           if(!a) {b[i].setAttribute("class","expert experthidden"); }
+           else {b[i].setAttribute("class","expert");}
+         }
+         b=document.getElementsByClassName("expertoff");
+         for(i=0;b.length>i;i++)
+         {
+          b[i].setAttribute("class","");
+         }
+
+       }
+       function init(){
+               initMenu();
+               showExpert(false);
+               var expert = document.getElementById("expertbox");
+               if(expert !== null) {
+                       expert.onchange = (function(expert){return function(){showExpert(expert.checked)}})(expert);
+               }
+       }
+       (function(oldLoad) {
+               if (oldLoad == undefined) {
+                       window.onload = init;
+               } else {
+                       window.onload = function() {
+                               init();
+                               oldLoad();
+                       }
+               }
+       })(window.onload);
+
+})();
diff --git a/static/www/policy/AssurancePolicy.html b/static/www/policy/AssurancePolicy.html
new file mode 100644 (file)
index 0000000..752da2d
--- /dev/null
@@ -0,0 +1,723 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
+<html><head>
+<title>Assurance Policy</title>
+
+<meta name="CREATED" content="20080530;0">
+<meta name="CHANGEDBY" content="Teus Hagen">
+<meta name="CHANGED" content="20080709;12381800">
+<meta name="CREATEDBY" content="Ian Grigg">
+<meta name="CHANGEDBY" content="Teus Hagen">
+<meta name="CHANGEDBY" content="Robert Cruikshank">
+<meta name="CHANGEDBY" content="Teus Hagen">
+<style type="text/css">
+<!--
+P { color: #000000 }
+TD P { color: #000000 }
+H1 { color: #000000 }
+H2 { color: #000000 }
+DT { color: #000000 }
+DD { color: #000000 }
+H3 { color: #000000 }
+TH P { color: #000000 }
+-->
+</style></head>
+<body style="direction: ltr; color: rgb(0, 0, 0);" lang="en-GB">
+<h1>Assurance Policy for CAcert Community Members</h1>
+<p><a href="PolicyOnPolicy.html"><img src="/images/cacert-policy.png" id="graphics1" alt="CAcert Policy Status == POLICY" align="bottom" border="0" height="33" width="90"></a>
+<br>
+Editor: Teus Hagen<br>
+Creation date: 2008-05-30<br>
+Last change by: Iang<br>
+Last change date: 2009-01-08<br>
+Status: POLICY p20090105.2
+</p>
+
+<h2><a name="0">0.</a> Preamble</h2>
+<h3><a name="0.1">0.1.</a> Definition of Terms</h3>
+<dl>
+<dt><i>Member</i> </dt>
+<dd> A Member is an individual who has agreed to the CAcert
+Community Agreement
+(<a href="http://www.cacert.org/policy/CAcertCommunityAgreement.html" target="_blank">CCA</a>)
+and has created successfully
+a CAcert login account on the CAcert web site. </dd>
+<dt> <i>Assurance</i> </dt>
+<dd> Assurance is the process by which a Member of CAcert
+Community (Assurer) identifies an individual (<span lang="en-US">Assuree</span>).
+</dd>
+<dt> <i>Prospective Member</i> </dt>
+<dd> An individual who participates in the process of Assurance,
+but has not yet created a CAcert login account. </dd>
+<dt> <i>Name</i> </dt>
+<dd> A Name is the full name of an individual.
+</dd>
+<dt> <i>Secondary Distinguishing Feature</i>
+</dt>
+<dd> An additional personal data item of the Member
+that assists discrimination from Members with similar full names.
+(Currently this is the Date of Birth (DoB).)
+</dd>
+</dl>
+
+<h3><a name="0.2">0.2.</a> The CAcert Web of Trust</h3>
+<p>
+In face-to-face meetings,
+an Assurer allocates a number of Assurance Points
+to the Member being Assured.
+CAcert combines the Assurance Points
+into a global <i>Web-of-Trust</i> (or "WoT").
+</p>
+<p>
+CAcert explicitly chooses to meet its various goals by
+construction of a Web-of-Trust of all Members.
+</p>
+
+<h3><a name="0.3">0.3.</a> Related Documentation</h3>
+<p>
+Documentation on Assurance is split between this
+Assurance Policy (AP) and the
+<a href="http://wiki.cacert.org/wiki/AssuranceHandbook2" target="_blank">Assurance
+Handbook</a>. The policy is controlled by Configuration Control
+Specification
+(<a href="http://wiki.cacert.org/wiki/PolicyDrafts/ConfigurationControlSpecification" target="_blank">CCS</a>)
+under Policy on Policy
+(<a href="http://www.cacert.org/policy/PolicyOnPolicy.html" target="_blank">PoP</a>)
+policy document regime.  Because Assurance is an active area, much
+of the practice is handed over to the Assurance Handbook, which is
+not a controlled policy document, and can more easily respond to
+experience and circumstances. It is also more readable.
+</p>
+<p>
+See also Organisation Assurance Policy (<a href="http://www.cacert.org/policy/OrganisationAssurancePolicy.html" target="_blank">OAP</a>)
+and CAcert Policy Statement (<a href="http://www.cacert.org/policy/CertificationPracticeStatement.html" target="_blank">CPS</a>).
+</p>
+
+<h2><a name="1">1.</a> Assurance Purpose</h2>
+<p>The purpose of Assurance is to add confidence
+in the Assurance Statement made by the CAcert Community of a Member. </p>
+<p>With sufficient assurances, a Member may: (a) issue certificates
+with their assured Name included, (b) participate in assuring others,
+and (c) other related activities. The strength of these activities is
+based on the strength of the assurance. </p>
+
+<h3><a name="1.1">1.1.</a>The Assurance Statement</h3>
+<p>
+The Assurance Statement makes the following claims
+about a person:
+</p>
+<ol>
+<li>
+<p>The person is a bona fide Member. In other words, the
+person is a member of the CAcert Community as defined by the CAcert
+Community Agreement (<a href="http://www.cacert.org/policy/CAcertCommunityAgreement.html" target="_blank">CCA</a>); </p>
+</li>
+<li>
+<p>The Member has a (login) account with CAcert's on-line
+registration and service system; </p>
+</li>
+<li>
+<p>The Member can be determined from any CAcert certificate
+issued by the Account; </p>
+</li>
+<li>
+<p>The Member is bound into CAcert's Arbitration as defined
+by the CAcert Community Agreement; </p>
+</li>
+<li>
+<p>Some personal details of the Member are known to CAcert:
+the individual Name(s), primary and other listed individual email
+address(es), secondary distinguishing feature (e.g. DoB). </p>
+</li>
+</ol>
+<p>The confidence level of the Assurance Statement is expressed by
+the Assurance Points. </p>
+<h3><a name="1.2">1.2.</a>Relying Party Statement</h3>
+<p>The primary goal of the Assurance Statement is for the express
+purpose of certificates to meet the needs of the <i>Relying Party
+Statement</i>, which latter is found in the Certification Practice
+Statement (<a href="http://www.cacert.org/policy/CertificationPracticeStatement.html" target="_blank">CPS</a>).
+</p>
+<p>When a certificate is issued, some of the Assurance Statement may
+be incorporated, e.g. Name. Other parts may be implied, e.g.
+Membership, exact account and status. They all are part of the
+<i>Relying Party Statement</i>. In short, this means that other
+Members of the Community may rely on the information verified by
+Assurance and found in the certificate.</p>
+<p>In particular, certificates are sometimes considered to provide
+reliable indications of e.g. the Member's Name and email address. The
+nature of Assurance, the number of Assurance Points, and other
+policies and processes should be understood as limitations on any
+reliance. </p>
+<h2><a name="2">2.</a> The Member</h2>
+<h3><a name="2.1">2.1.</a> The Member's Name </h3>
+<p>
+At least one individual Name is recorded in the Member's
+CAcert login account.  The general standard of a Name is:
+</p>
+<ul>
+<li>
+<p>
+The Name should be recorded as written in a
+government-issued photo identity document (ID).
+</p>
+</li>
+<li>
+<p>
+The Name should be recorded as completely as possible.
+That is, including all middle names, any titles and extensions,
+without abbreviations, and without transliteration of characters.
+</p>
+</li>
+<li>
+<p>The Name is recorded as a string of characters,
+encoded in <span lang="en-US">unicode</span>
+transformation format.</p>
+</li>
+</ul>
+<h3><a name="2.2">2.2.</a> Multiple Names and variations</h3>
+<p>
+In order to handle the contradictions in the above general standard,
+a Member may record multiple Names or multiple variations of a Name
+in her CAcert online Account.
+Examples of variations include married names,
+variations of initials of first or middle names,
+abbreviations of a first name,
+different language or country variations,
+and transliterations of characters in a name.
+</p>
+
+<h3><a name="2.3">2.3.</a> Status and Capabilities</h3>
+<p>
+A Name which has reached
+the level of 50 Assurance Points is defined as an Assured
+Name. An Assured Name can be used in a certificate issued by CAcert.
+A Member with at least one Assured Name has reached the Assured
+Member status.
+Additional capabilities are described in Table 1.
+</p>
+
+<blockquote>
+<p align="left"><font size="2"><i>Table 1:
+Assurance Capability</i></font></p>
+<table border="1" cellpadding="5" cellspacing="0">
+<tbody>
+<tr>
+<td width="10%">
+<p align="left"><i>Minimum Assurance Points</i></p>
+</td>
+<td width="15%">
+<p align="left"><i>Capability</i></p>
+</td>
+<td width="15%">
+<p align="left"><i>Status</i></p>
+</td>
+<td width="60%">
+<p align="left"><i>Comment</i></p>
+</td>
+</tr>
+<tr valign="top">
+<td>
+<p align="center">0</p>
+</td>
+<td>
+<p align="left">Request Assurance</p>
+</td>
+<td>
+<p align="left">Prospective Member</p>
+</td>
+<td>
+<p align="left">Individual taking part of an
+Assurance, who does not have created a CAcert login account (yet). The
+allocation of Assurance Points is awaiting login account creation.</p>
+</td>
+</tr>
+<tr valign="top">
+<td>
+<p align="center">0</p>
+</td>
+<td>
+<p align="left">Request unnamed certificates</p>
+</td>
+<td>
+<p align="left">Member</p>
+</td>
+<td>
+<p align="left">Although the Member's details are
+recorded in the account, they are not highly assured.</p>
+</td>
+</tr>
+<tr valign="top">
+<td>
+<p align="center">50</p>
+</td>
+<td>
+<p align="left">Request named certificates</p>
+</td>
+<td>
+<p align="left">Assured Member</p>
+</td>
+<td>
+<p align="left">Statements of Assurance: the Name is
+assured to 50 Assurance Points or more</p>
+</td>
+</tr>
+<tr valign="top">
+<td>
+<p align="center">100</p>
+</td>
+<td>
+<p align="left">Become an Assurer</p>
+</td>
+<td>
+<p align="left">Prospective Assurer</p>
+</td>
+<td>
+<p align="left">Assured to 100 Assurance Points (or
+more) on at least one Name, and passing the Assurer Challenge.</p>
+</td>
+</tr>
+</tbody>
+</table>
+</blockquote>
+
+
+<p>
+A Member may check the status of another Member, especially
+for an assurance process.
+Status may be implied from information in a certificate.
+The number of Assurance Points for each Member is not published.
+</p>
+
+<p>
+The CAcert Policy Statement
+(<a href="http://www.cacert.org/policy/CertificationPracticeStatement.html" target="_blank">CPS</a>)
+and other policies may list other capabilities that rely on Assurance
+Points.
+</p>
+
+<h2><a name="3">3.</a> The Assurer</h2>
+<p>An Assurer is a Member with the following: </p>
+<ul>
+<li>
+<p>Is assured to a minimum of 100 Assurance Points; </p>
+</li>
+<li>
+<p>Has passed the CAcert Assurer Challenge. </p>
+</li>
+</ul>
+<p>The Assurer Challenge is administered by the Education Team on
+behalf of the Assurance Officer. </p>
+<h3><a name="3.1">3.1.</a> The Obligations of the Assurer</h3>
+<p>The Assurer is obliged to: </p>
+<ul>
+<li>
+<p>Follow this Assurance Policy; </p>
+</li>
+<li>
+<p>Follow any additional rules of detail laid out by the
+CAcert Assurance Officer; </p>
+</li>
+<li>
+<p>Be guided by the CAcert <a href="http://wiki.cacert.org/wiki/AssuranceHandbook2" target="_blank">Assurance Handbook</a> in their
+judgement; </p>
+</li>
+<li>
+<p>Make a good faith effort at identifying and verifying
+Members; </p>
+</li>
+<li>
+<p>Maintain the documentation on each Assurance; </p>
+</li>
+<li>
+<p>Deliver documentation to Arbitration, or as otherwise
+directed by the Arbitrator; </p>
+</li>
+<li>
+<p>Keep up-to-date with developments within the CAcert
+Community. </p>
+</li>
+</ul>
+<h2><a name="4">4.</a> The Assurance</h2>
+<h3><a name="4.1">4.1.</a> The Assurance Process</h3>
+<p>The Assurer conducts the process of Assurance with each
+Member. </p>
+<p>The process consists of: </p>
+<ol>
+<li>
+<p>Voluntary agreement by both Assurer and Member or
+Prospective Member to conduct the Assurance; </p>
+</li>
+<li>
+<p>Personal meeting of Assurer and Member or Prospective
+Member; </p>
+</li>
+<li>
+<p>Recording of essential details on CAcert Assurance
+Programme form; </p>
+</li>
+<li>
+<p>Examination of Identity documents by Assurer and
+verification of recorded details (the Name(s) and Secondary
+Distinguishing Feature, e.g., DoB); </p>
+</li>
+<li>
+<p>Allocation of Assurance Points by Assurer; </p>
+</li>
+<li>
+<p>Optional: supervision of reciprocal Assurance made by
+Assuree (Mutual Assurance); </p>
+</li>
+<li>
+<p>Safekeeping of the CAcert Assurance Programme (<a href="http://www.cacert.org/cap.php" target="_blank">CAP</a>)
+forms by Assurer. </p>
+</li>
+</ol>
+<h3><a name="4.2">4.2.</a> Mutual Assurance</h3>
+<p>Mutual Assurance follows the principle of reciprocity. This
+means
+that the Assurance may be two-way, and that each member participating
+in the Assurance procedure should be able to show evidence of their
+identity to the other. </p>
+<p>In the event that an Assurer is assured by a Member who is not
+certified as an Assurer, the Assurer supervises the Assurance
+procedure and process, and is responsible for the results. </p>
+<p>Reciprocity maintains a balance between the (new) member and
+the
+Assurer, and reduces any sense of power. It is also an important aid
+to the assurance training for future Assurers. </p>
+
+<h3><a name="4.3">4.3.</a> Assurance Points</h3>
+<p>The Assurance applies Assurance Points to each Member which
+measure the increase of confidence in the Statement (above).
+Assurance Points should not be interpreted for any other purpose.
+Note that, even though they are sometimes referred to as <i>Web-of-Trust</i>
+(Assurance) Points, or <i>Trust</i> Points, the meaning
+of the word
+'Trust' is not well defined. </p>
+<p><i>Assurance Points Allocation</i><br>
+An Assurer can allocate a
+number of Assurance Points to the Member according to the Assurer's
+experience (Experience Point system, see below). The allocation of
+the maximum means that the Assurer is 100% confident in the
+information presented: </p>
+<ul>
+<li>
+<p>Detail on form, system, documents, person in accordance; </p>
+</li>
+<li>
+<p>Sufficient quality identity documents have been checked; </p>
+</li>
+<li>
+<p>Assurer's familiarity with identity documents; </p>
+</li>
+<li>
+<p>The Assurance Statement is confirmed. </p>
+</li>
+</ul>
+<p>
+Any lesser confidence should result in less Assurance Points for a
+Name. If the Assurer has no confidence in the information presented,
+then <i>zero</i> Assurance Points may be allocated by the Assurer.
+For example, this may happen if the identity documents are totally
+unfamiliar to the Assurer. The number of Assurance Points from <i>zero</i>
+to <i>maximum</i> is guided by the Assurance Handbook
+and the judgement of the Assurer.
+If there is negative confidence the Assurer should consider
+filing a dispute.
+</p>
+<p>Multiple Names should be allocated Assurance Points
+independently within a single Assurance. </p>
+<p>
+A Member who is not an Assurer may award an Assurer in a
+reciprocal process a maximum of 2 Assurance Points, according to
+her judgement. The Assurer should strive to have the Member allocate
+according to the Member's judgement, and stay on the cautious side;
+the Member new to the assurance process
+should allocate <i>zero</i> Assurance Points
+until she gains some confidence in what is happening.
+</p>
+<p>
+In general, for a Member to reach 50 Assurance Points, the Member must
+have participated in at least two assurances, and
+at least one Name will have been assured to that level.
+</p>
+<p>
+To reach 100 Assurance
+Points, at least one Name of the Assured Member must have been
+assured at least three times.
+</p>
+<p>
+The maximum number of Assurance
+Points which can be allocated for an Assurance under this policy
+and under any act under any
+Subsidiary Policy (below) is 50 Assurance Points.
+</p>
+
+<h3><a name="4.4">4.4.</a> Experience Points</h3>
+<p>The maximum number of Assurance Points that may be awarded by
+an
+Assurer is determined by the Experience Points of the Assurer. </p>
+<blockquote>
+<p align="left"><font size="2"><i>Table 2:
+Maximum of Assurance Points </i></font>
+</p>
+<table border="1" cellpadding="2" cellspacing="0" width="15%">
+<tbody>
+<tr>
+<td>
+<p><i>Assurer's Experience Points</i></p>
+</td>
+<td>
+<p><i>Allocatable Assurance Points</i></p>
+</td>
+</tr>
+<tr>
+<td>
+<p align="center">0</p>
+</td>
+<td>
+<p align="center">10</p>
+</td>
+</tr>
+<tr>
+<td>
+<p align="center">10</p>
+</td>
+<td>
+<p align="center">15</p>
+</td>
+</tr>
+<tr>
+<td>
+<p align="center">20</p>
+</td>
+<td>
+<p align="center">20</p>
+</td>
+</tr>
+<tr>
+<td>
+<p align="center">30</p>
+</td>
+<td>
+<p align="center">25</p>
+</td>
+</tr>
+<tr>
+<td>
+<p align="center">40</p>
+</td>
+<td>
+<p align="center">30</p>
+</td>
+</tr>
+<tr>
+<td>
+<p align="center">&gt;=50</p>
+</td>
+<td>
+<p align="center">35</p>
+</td>
+</tr>
+</tbody>
+</table>
+</blockquote>
+<p>An Assurer is given a maximum of 2 Experience Points for every
+completed Assurance. On reaching Assurer status, the Experience
+Points start at 0 (zero). </p>
+<p>Less Experience Points (1) may be given for mass Assurance
+events,
+where each Assurance is quicker. </p>
+<p>Additional Experience Points may be granted temporarily or
+permanently to an Assurer by CAcert Inc.'s Committee (board), on
+recommendation from the Assurance Officer. </p>
+<p>Experience Points are not to be confused with Assurance
+Points. </p>
+<h3><a name="4.5">4.5.</a> CAcert Assurance Programme (CAP) form</h3>
+<p>The CAcert Assurance Programme (<a href="http://www.cacert.org/cap.php" target="_blank">CAP</a>)
+form requests the following details of each Member or Prospective
+Member: </p>
+<ul>
+<li>
+<p>Name(s), as recorded in the on-line account; </p>
+</li>
+<li>
+<p>Primary email address, as recorded in the on-line account;
+</p>
+</li>
+<li>
+<p>Secondary Distinguishing Feature, as recorded in the
+on-line account (normally, date of birth); </p>
+</li>
+<li>
+<p>Statement of agreement with the CAcert Community
+Agreement; </p>
+</li>
+<li>
+<p>Permission to the Assurer to conduct the Assurance
+(required for privacy reasons); </p>
+</li>
+<li>
+<p>Date and signature of the Assuree. </p>
+</li>
+</ul>
+<p>The CAP form requests the following details of the Assurer: </p>
+<ul>
+<li>
+<p>At least one Name as recorded in the on-line account of
+the Assurer; </p>
+</li>
+<li>
+<p>Assurance Points for each Name in the identity
+document(s); </p>
+</li>
+<li>
+<p>Statement of Assurance; </p>
+</li>
+<li>
+<p>Optional: If the Assurance is reciprocal, then the
+Assurer's email address and Secondary Distinguishing Feature are
+required as well; </p>
+</li>
+<li>
+<p>Date, location of Assurance and signature of Assurer. </p>
+</li>
+</ul>
+<p>The CAP forms are to be kept at least for 7 years by the
+Assurer. </p>
+<h2><a name="5">5.</a> The Assurance Officer</h2>
+<p>The Committee (board) of CAcert Inc. appoints an Assurance
+Officer
+with the following responsibilities: </p>
+<ul>
+<li>
+<p>Reporting to the Committee and advising on all matters to
+do with Assurance; </p>
+</li>
+<li>
+<p>Training and testing of Assurers, in association with the
+Education Team; </p>
+</li>
+<li>
+<p>Updating this Assurance Policy, under the process
+established by Policy on Policy (<a href="https://www.cacert.org/policy/PolicyOnPolicy.html" target="_blank">PoP</a>); </p>
+</li>
+<li>
+<p>Management of all Subsidiary Policies (see below) for
+Assurances, under Policy on Policy; </p>
+</li>
+<li>
+<p>Managing and creating rules of detail or procedure where
+inappropriate for policies; </p>
+</li>
+<li>
+<p>Incorporating rulings from Arbitration into policies,
+procedures or guidelines; </p>
+</li>
+<li>
+<p>Assisting the Arbitrator in any requests; </p>
+</li>
+<li>
+<p>Managing the Assurer Handbook; </p>
+</li>
+<li>
+<p>Maintaining a sufficient strength in the Assurance process
+(web-of-trust) to meet the agreed needs of the Community. </p>
+</li>
+</ul>
+<h2><a name="6">6.</a> Subsidiary Policies</h2>
+<p>The Assurance Officer manages various exceptions and additional
+processes. Each must be covered by an approved Subsidiary Policy
+(refer to Policy on Policy =&gt; CAcert Official Document COD1).
+Subsidiary Policies specify any additional tests of knowledge
+required and variations to process and documentation, within the
+general standard stated here. </p>
+<h3><a name="6.1">6.1.</a> Standard</h3>
+<p>Each Subsidiary Policy must augment and improve the general
+standards in this Assurance Policy. It is the responsibility of each
+Subsidiary Policy to describe how it maintains and improves the
+specific and overall goals. It must describe exceptions and potential
+areas of risk. </p>
+
+<h3><a name="6.2">6.2.</a> High Risk Applications</h3>
+<p>In addition to the Assurance or Experience Points ratings set
+here and in other subsidiary policies, the Assurance Officer or policies can
+designate certain applications as high risk. If so, additional
+measures may be added to the Assurance process that specifically
+address the risks.</p>
+<p>Additional measures may include:
+</p>
+<ul>
+<li>
+<p>Additional information can be required in process of assurance: </p>
+<ul>
+<li>unique numbers of identity documents,</li>
+<li>photocopy of identity documents,</li>
+<li>photo of User,</li>
+<li>address of User.</li>
+</ul>
+<p>Additional Information is to be kept by Assurer, attached to
+CAcert Assurance Programme (<a href="http://www.cacert.org/cap.php" target="_blank">CAP</a>)
+form. Assurance Points allocation by this assurance is unchanged.
+User's CAcert login account should be annotated to record type of
+additional information;</p>
+</li>
+<li>
+<p>Arbitration: </p>
+<ul>
+<li> Member to participate in Arbitration. This confirms
+their acceptance of the forum as well as trains in the process and
+import,
+</li>
+<li> Member to file Arbitration to present case. This
+allows Arbitrator as final authority;
+</li>
+</ul>
+</li>
+<li>
+<p>Additional training; </p>
+</li>
+<li>
+<p>Member to be Assurer (at least 100 Assurance Points and
+passed Assurer Challenge); </p>
+</li>
+<li>
+<p>Member agrees to additional specific agreement(s); </p>
+</li>
+<li>
+<p>Additional checking/auditing of systems data by CAcert
+support administrators. </p>
+</li>
+</ul>
+<p>Applications that might attract additional measures include
+code-signing certificates and administration roles. </p>
+<h2><a name="7">7.</a> Privacy</h2>
+<p>CAcert is a "privacy" organisation, and takes the
+privacy of its Members seriously. The process maintains the security
+and privacy of both parties. </p>
+<p>Information is collected primarily to make claims within the
+certificates requested by users and to contact the Members. It is
+used secondarily for training, testing, administration and other
+internal purposes. </p>
+<p>The Member's information can be accessed under these
+circumstances: </p>
+<ul>
+<li>
+<p>Under Arbitrator ruling, in a duly filed dispute (<a href="http://www.cacert.org/policy/DisputeResolutionPolicy.html" target="_blank">Dispute Resolution Policy</a>
+=&gt; COD7); </p>
+</li>
+<li>
+<p>An Assurer in the process of an Assurance, as permitted on
+the CAcert Assurance Programme (<a href="http://www.cacert.org/cap.php" target="_blank">CAP</a>)
+form; </p>
+</li>
+<li>
+<p>CAcert support administration and CAcert systems
+administration when operating under the authority of Arbitrator or
+under CAcert policy. </p>
+</li>
+</ul>
+<p><a href="http://validator.w3.org/check?uri=referer"><img src="/images/valid-xhtml11-blue" id="graphics2" alt="Valid XHTML 1.1" align="bottom" border="0" height="33" width="90"></a>
+</p>
+</body></html>
+
diff --git a/static/www/policy/CAcertCommunityAgreement.html b/static/www/policy/CAcertCommunityAgreement.html
new file mode 100644 (file)
index 0000000..868d196
--- /dev/null
@@ -0,0 +1,512 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+
+<html>
+<head><title>CAcert Community Agreement</title></head>
+<body>
+
+
+
+
+<h3> <a name="0"> 0. </a>  Introduction </h3>
+
+<p>
+This agreement is between
+you, being a registered member ("Member")
+within CAcert's community at large ("Community")
+and CAcert Incorporated ("CAcert"),
+being an operator of services to the Community.
+</p>
+
+<h4> <a name="0.1"> 0.1 </a>  Terms </h4>
+<ol><li>
+    "CAcert"
+    means CAcert Inc.,
+    a non-profit Association of Members incorporated in
+    New South Wales, Australia.
+    Note that Association Members are distinct from
+    the Members defined here.
+  </li><li>
+    "Member"
+    means you, a registered participant within CAcert's Community,
+    with an account on the website and the
+    facility to request certificates.
+    Members may be individuals ("natural persons")
+    or organisations ("legal persons").
+  </li><li>
+    "Organisation"
+    is defined under the Organisation Assurance programme,
+    and generally includes corporations and other entities
+    that become Members and become Assured.
+  </li><li>
+    "Community"
+    means all of the Members
+    that are registered by this agreement
+    and other parties by other agreements,
+    all being under CAcert's Arbitration.
+  </li><li>
+    "Non-Related Person" ("NRP"),
+    being someone who is not a
+    Member, is not part of the Community,
+    and has not registered their agreement.
+    Such people are offered the NRP-DaL
+    another agreement allowing the USE of certificates.
+  </li><li>
+    "Non-Related Persons - Disclaimer and Licence" ("NRP-DaL"),
+    another agreement that is offered to persons outside the
+    Community.
+  </li><li>
+    "Arbitration"
+    is the Community's forum for
+    resolving disputes, or jurisdiction.
+  </li><li>
+    "Dispute Resolution Policy" ("DRP" => COD7)
+    is the policy and
+    rules for resolving disputes.
+  </li><li>
+    "USE"
+    means the act by your software
+    to conduct its tasks, incorporating
+    the certificates according to software procedures.
+  </li><li>
+    "RELY"
+    means your human act in taking on a
+    risk and liability on the basis of the claim(s)
+    bound within a certificate.
+  </li><li>
+    "OFFER"
+    means the your act
+    of making available your certificate to another person.
+    Generally, you install and configure your software
+    to act as your agent and facilite this and other tasks.
+    OFFER does not imply suggestion of reliance.
+  </li><li>
+    "Issue"
+    means creation of a certificate by CAcert.
+    To create a certificate,
+    CAcert affixes a digital signature from the root
+    onto a public key and other information.
+    This act would generally bind a statement or claim,
+    such as your name, to your key.
+  </li><li>
+    "Root"
+    means CAcert's top level key,
+    used for signing certificates for Members.
+    In this document, the term includes any subroots.
+  </li><li>
+    "CAcert Official Document" ("COD" => COD3)
+    in a standard format for describing the details of
+    operation and governance essential to a certificate authority.
+    Changes are managed and controlled.
+    CODs define more technical terms.
+    See 4.2 for listing of relevant CODs.
+  </li><li>
+    "Certification Practice Statement" ("CPS" => COD6)
+    is the document that controls details
+    about operational matters within CAcert.
+</li></ol>
+
+
+<h3> <a name="1"> 1. </a>  Agreement and Licence </h3>
+
+<h4> <a name="1.1"> 1.1 </a>  Agreement </h4>
+
+<p>
+You and CAcert both agree to the terms and conditions
+in this agreement.
+Your agreement is given by any of
+</p>
+
+<ul><li>
+    your signature on a form to request assurance of identity
+    ("CAP" form),
+  </li><li>
+    your request on the website
+    to join the Community and create an account,
+  </li><li>
+    your request for Organisation Assurance,
+  </li><li>
+    your request for issuing of certificates, or
+  </li><li>
+    if you USE, RELY, or OFFER
+    any certificate issued to you.
+</li></ul>
+
+<p>
+Your agreement
+is effective from the date of the first event above
+that makes this agreement known to you.
+This Agreement
+replaces and supercedes prior agreements,
+including the NRP-DaL.
+</p>
+
+
+<h4> <a name="1.2"> 1.2 </a>  Licence </h4>
+
+<p>
+As part of the Community, CAcert offers you these rights:
+</p>
+
+<ol><li>
+    You may USE any certificates issued by CAcert.
+  </li><li>
+    You may RELY on any certificate issued by CAcert,
+    as explained and limited by CPS (COD6).
+  </li><li>
+    You may OFFER certificates issued to you by CAcert
+    to Members for their RELIANCE.
+  </li><li>
+    You may OFFER certificates issued to you by CAcert
+    to NRPs for their USE, within the general principles
+    of the Community.
+  </li><li>
+    This Licence is free of cost,
+    non-exclusive, and non-transferrable.
+</li></ol>
+
+<h4> <a name="1.3"> 1.3 </a>  Your Contributions </h4>
+
+
+<p>
+You agree to a non-exclusive non-restrictive non-revokable
+transfer of Licence to CAcert for your contributions.
+That is, if you post an idea or comment on a CAcert forum,
+or email it to other Members,
+your work can be used freely by the Community for
+CAcert purposes, including placing under CAcert's licences
+for wider publication.
+</p>
+
+<p>
+You retain authorship rights, and the rights to also transfer
+non-exclusive rights to other parties.
+That is, you can still use your
+ideas and contributions outside the Community.
+</p>
+
+<p>
+Note that the following exceptions override this clause:
+</p>
+
+<ol><li>
+    Contributions to controlled documents are subject to
+    Policy on Policy ("PoP" => COD1)
+  </li><li>
+    Source code is subject to an open source licence regime.
+</li></ol>
+
+<h4> <a name="1.4"> 1.4 </a>  Privacy </h4>
+
+
+<p>
+You give rights to CAcert to store, verify and process
+and publish your data in accordance with policies in force.
+These rights include shipping the data to foreign countries
+for system administration, support and processing purposes.
+Such shipping will only be done among
+CAcert Community administrators and Assurers.
+</p>
+
+<p>
+Privacy is further covered in the Privacy Policy ("PP" => COD5).
+</p>
+
+<h3> <a name="2"> 2. </a>  Your Risks, Liabilities and Obligations </h3>
+
+<p>
+As a Member, you have risks, liabilities
+and obligations within this agreement.
+</p>
+
+<h4> <a name="2.1"> 2.1 </a>  Risks </h4>
+
+<ol><li>
+    A certificate may prove unreliable.
+  </li><li>
+    Your account, keys or other security tools may be
+    lost or otherwise compromised.
+  </li><li>
+    You may find yourself subject to Arbitration
+    (DRP => COD7).
+</li></ol>
+
+<h4> <a name="2.2"> 2.2 </a>  Liabilities </h4>
+
+<ol><li>
+    You are liable for any penalties
+    as awarded against you by the Arbitrator.
+  </li><li>
+    Remedies are as defined in the DRP (COD7).
+    An Arbitrator's ruling may
+    include monetary amounts, awarded against you.
+  </li><li>
+    Your liability is limited to
+    a total maximum of
+    <b>1000 Euros</b>.
+  </li><li>
+    "Foreign Courts" may assert jurisdiction.
+    These include your local courts, and are outside our Arbitration.
+    Foreign Courts will generally refer to the Arbitration
+    Act of their country, which will generally refer
+    civil cases to Arbitration.
+    The Arbitration Act will not apply to criminal cases.
+</li></ol>
+
+<h4> <a name="2.3"> 2.3 </a>  Obligations </h4>
+
+<p>
+    You are obliged
+</p>
+
+<ol><li>
+    to provide accurate information
+    as part of Assurance.
+    You give permission for verification of the information
+    using CAcert-approved methods.
+  </li><li>
+    to make no false representations.
+  </li><li>
+    to submit all your disputes to Arbitration
+    (DRP => COD7).
+</li></ol>
+
+<h4> <a name="2.4"> 2.4 </a>  Principles </h4>
+
+<p>
+As a Member of CAcert, you are a member of
+the Community.
+    You are further obliged to 
+    work within the spirit of the Principles
+    of the Community.
+    These are described in
+    <a href="http://svn.cacert.org/CAcert/principles.html">Principles of the Community</a>.
+</p>
+
+<h4> <a name="2.5"> 2.5 </a>  Security </h4>
+<p>
+CAcert exists to help you to secure yourself.
+You are primarily responsible for your own security.
+Your security obligations include
+</p>
+
+<ol><li>
+    to secure yourself and your computing platform (e.g., PC),
+  </li><li>
+    to keep your email account in good working order,
+  </li><li>
+    to secure your CAcert account
+    (e.g., credentials such as username, password),
+  </li><li>
+    to secure your private keys,
+  </li><li>
+    to review certificates for accuracy,
+    and
+  </li><li>
+    when in doubt, notify CAcert,
+  </li><li>
+    when in doubt, take other reasonable actions, such as
+    revoking certificates,
+    changing account credentials,
+    and/or generating new keys.
+</li></ol>
+
+<p>
+Where, above, 'secure' means to protect to a reasonable
+degree, in proportion with your risks and the risks of
+others.
+</p>
+
+<h3> <a name="3"> 3. </a>  Law and Jurisdiction </h3>
+
+<h4> <a name="3.1"> 3.1 </a>  Governing Law </h4>
+
+<p>
+This agreement is governed under the law of
+New South Wales, Australia,
+being the home of the CAcert Inc. Association.
+</p>
+
+<h4> <a name="3.2"> 3.2 </a>  Arbitration as Forum of Dispute Resolution </h4>
+
+<p>
+You agree, with CAcert and all of the Community,
+that all disputes arising out
+of or in connection to our use of CAcert services
+shall be referred to and finally resolved
+by Arbitration under the rules within the
+Dispute Resolution Policy of CAcert
+(DRP => COD7).
+The rules select a single Arbitrator chosen by CAcert
+from among senior Members in the Community.
+The ruling of the Arbitrator is binding and
+final on Members and CAcert alike.
+</p>
+
+<p>
+In general, the jurisdiction for resolution of disputes
+is within CAcert's own forum of Arbitration,
+as defined and controlled by its own rules (DRP => COD7).
+</p>
+
+<p>
+We use Arbitration for many purposes beyond the strict
+nature of disputes, such as governance and oversight.
+A systems administrator may
+need authorisation to conduct a non-routine action,
+and Arbitration may provide that authorisation.
+Thus, you may find yourself party to Arbitration
+that is simply support actions, and you may file disputes in
+order to initiate support actions.
+</p>
+
+<h4> <a name="3.3"> 3.3 </a>  Termination </h4>
+<p>
+You may terminate this agreement by resigning
+from CAcert.  You may do this at any time by
+writing to CAcert's online support forum and
+filing dispute to resign.
+All services will be terminated, and your
+certificates will be revoked.
+However, some information will continue to
+be held for certificate processing purposes.
+</p>
+
+<p>
+The provisions on Arbitration survive any termination
+by you by leaving CAcert.
+That is, even if you resign from CAcert,
+you are still bound by the DRP (COD7),
+and the Arbitrator may reinstate any provision of this
+agreement or bind you to a ruling.
+</p>
+
+<p>
+Only the Arbitrator may terminate this agreement with you.
+</p>
+
+<h4> <a name="3.4"> 3.4 </a>  Changes of Agreement </h4>
+
+<p>
+CAcert may from time to time vary the terms of this Agreement.
+Changes will be done according to the documented CAcert policy
+for changing policies, and is subject to scrutiny and feedback
+by the Community.
+Changes will be notified to you by email to your primary address.
+</p>
+
+<p>
+If you do not agree to the changes, you may terminate as above.
+Continued use of the service shall be deemed to be agreement
+by you.
+</p>
+
+<h4> <a name="3.5"> 3.5 </a>  Communication </h4>
+
+<p>
+Notifications to CAcert are to be sent by
+email to the address
+<b>support</b> <i>at</i> CAcert.org.
+You should attach a digital signature,
+but need not do so in the event of security
+or similar urgency.
+</p>
+
+<p>
+Notifications to you are sent
+by CAcert to the primary email address
+registered with your account.
+You are responsible for keeping your email
+account in good working order and able
+to receive emails from CAcert.
+</p>
+
+<p>
+Arbitration is generally conducted by email.
+</p>
+
+<h3> <a name="4"> 4. </a> Miscellaneous </h3>
+
+<h4> <a name="4.1"> 4.1 </a>  Other Parties Within the Community </h4>
+
+<p>
+As well as you and other Members in the Community,
+CAcert forms agreements with third party
+vendors and others.
+Thus, such parties will also be in the Community.
+Such agreements are also controlled by the same
+policy process as this agreement, and they should
+mirror and reinforce these terms.
+</p>
+
+
+<h4> <a name="4.2"> 4.2 </a>  References and Other Binding Documents </h4>
+
+<p>
+This agreement is CAcert Official Document 9 (COD9)
+and is a controlled document.
+</p>
+
+<p>
+You are also bound by
+</p>
+
+<ol><li>
+    <a href="http://www.cacert.org/policy/CertificationPracticeStatement.html">
+    Certification Practice Statement</a> (CPS => COD6).
+  </li><li>
+    <a href="http://www.cacert.org/policy/DisputeResolutionPolicy.html">
+    Dispute Resolution Policy</a> (DRP => COD7).
+  </li><li>
+    <a href="PrivacyPolicy.html">
+    Privacy Policy</a> (PP => COD5).
+  </li><li>
+    <a href="http://svn.cacert.org/CAcert/principles.html">
+    Principles of the Community</a>.
+</li></ol>
+
+<p>
+Where documents are referred to as <i>=> COD x</i>,
+they are controlled documents
+under the control of Policy on Policies (COD1).
+</p>
+
+<p>
+This agreement and controlled documents above are primary,
+and may not be replaced or waived except
+by formal policy channels and by Arbitration.
+</p>
+
+<h4> <a name="4.3"> 4.3 </a>  Informative References </h4>
+
+<p>
+The governing documents are in English.
+Documents may be translated for convenience.
+Because we cannot control the legal effect of translations,
+the English documents are the ruling ones.
+</p>
+
+<p>
+You are encouraged to be familiar with the
+Assurer Handbook,
+which provides a more readable introduction for much of
+the information needed.
+The Handbook is not however an agreement, and is overruled
+by this agreement and others listed above.
+</p>
+
+<h4> <a name="4.4"> 4.4 </a>  Not Covered in this Agreement </h4>
+
+<p>
+<b>Intellectual Property.</b>
+This Licence does not transfer any intellectual
+property rights ("IPR") to you.  CAcert asserts and
+maintains its IPR over its roots, issued certificates,
+brands, logos and other assets.
+Note that the certificates issued to you
+are CAcert's intellectual property
+and you do not have rights other than those stated.
+</p>
+
+
+</body>
+</html>
diff --git a/static/www/policy/CertificationPracticeStatement.html b/static/www/policy/CertificationPracticeStatement.html
new file mode 100644 (file)
index 0000000..78f28cc
--- /dev/null
@@ -0,0 +1,4088 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
+<html>
+<head>
+    <meta name="copyright" content="CAcert Inc http://www.cacert.org/">
+    <title>Certification Practice Statement (CPS)</title>
+
+<style type="text/css">
+<!--
+body {
+       font-family : verdana, helvetica, arial, sans-serif;
+}
+
+pre, code, kbd, tt, samp {
+       font-family : courier, monospace;
+}
+
+th {
+       text-align : left;
+}
+
+.blockpar {
+       text-indent : 2em;
+       margin-top : 0em;
+       margin-bottom : 0.5em;
+       text-align : justify;
+}
+
+.figure {
+       text-align : center;
+       color : gray;
+       margin-top : 0.5em;
+}
+
+.center {
+       text-align : center;
+}
+
+.q {
+       color : green;
+       font-weight: bold;
+       text-align: center;
+       font-style:italic;
+}
+
+.error {
+       color : red;
+       font-weight: bold;
+       text-align: center;
+       font-style:italic;
+}
+
+.change {
+       color : blue;
+       font-weight: bold;
+}
+
+a:hover {
+       color : gray;
+}
+-->
+</style>
+
+
+</head>
+<body>
+
+<h1>CAcert CPS and CP</h1>
+
+<a href="PolicyOnPolicy.html"><img src="cacert-draft.png" alt="CAcert Policy Status" height="31" width="88" style="border-style: none;" /></a><br />
+Creation date: 20060726<br />
+Status: DRAFT p20091108<br />
+<!-- $Id: CertificationPracticeStatement.html,v 1.3 2012-07-27 16:00:29 wytze Exp $ -->
+
+
+<font size="-1">
+
+<ol>
+  <li> <a href="#p1">INTRODUCTION</a>
+   <ul>
+    <li><a href="#p1.1">1.1. Overview</a></li>
+    <li><a href="#p1.2">1.2. Document name and identification</a></li>
+    <li><a href="#p1.3">1.3. PKI participants</a> </li>
+    <li><a href="#p1.4">1.4. Certificate usage</a> </li>
+    <li><a href="#p1.5">1.5. Policy administration</a> </li>
+    <li><a href="#p1.6">1.6. Definitions and acronyms</a></li>
+   </ul>
+  </li>
+  <li> <a href="#p2">PUBLICATION AND REPOSITORY RESPONSIBILITIES</a>
+   <ul>
+    <li><a href="#p2.1">2.1. Repositories</a></li>
+    <li><a href="#p2.2">2.2. Publication of certification information</a></li>
+    <li><a href="#p2.3">2.3. Time or frequency of publication</a></li>
+    <li><a href="#p2.4">2.4. Access controls on repositories</a></li>
+   </ul>
+  </li>
+  <li> <a href="#p3">IDENTIFICATION AND AUTHENTICATION (I&amp;A)</a>
+   <ul>
+    <li><a href="#p3.1">3.1. Naming</a> </li>
+    <li><a href="#p3.2">3.2. Initial Identity Verification</a> </li>
+    <li><a href="#p3.3">3.3. I&amp;A for Re-key Requests</a> </li>
+    <li><a href="#p3.4">3.4. I&amp;A for Revocation Request</a></li>
+   </ul>
+  </li>
+  <li><a href="#p4">CERTIFICATE LIFE-CYCLE OPERATIONAL REQUIREMENTS</a>
+   <ul>
+    <li><a href="#p4.1">4.1. Certificate Application</a> </li>
+    <li><a href="#p4.2">4.2. Certificate application processing</a> </li>
+    <li><a href="#p4.3">4.3. Certificate issuance</a> </li>
+    <li><a href="#p4.4">4.4. Certificate acceptance</a> </li>
+    <li><a href="#p4.5">4.5. Key pair and certificate usage</a> </li>
+    <li><a href="#p4.6">4.6. Certificate renewal</a> </li>
+    <li><a href="#p4.7">4.7. Certificate re-key</a> </li>
+    <li><a href="#p4.8">4.8. Certificate modification</a> </li>
+    <li><a href="#p4.9">4.9. Certificate revocation and suspension</a> </li>
+    <li><a href="#p4.10">4.10. Certificate status services</a> </li>
+    <li><a href="#p4.11">4.11. End of subscription</a></li>
+    <li><a href="#p4.12">4.12. Key escrow and recovery</a> </li>
+   </ul>
+  </li>
+  <li><a href="#p5">FACILITY, MANAGEMENT, AND OPERATIONAL CONTROLS</a>
+   <ul>
+    <li><a href="#p5.1">5.1. Physical controls</a> </li>
+    <li><a href="#p5.2">5.2. Procedural controls</a> </li>
+    <li><a href="#p5.3">5.3. Personnel controls</a> </li>
+    <li><a href="#p5.4">5.4. Audit logging procedures</a> </li>
+    <li><a href="#p5.5">5.5. Records archival</a> </li>
+    <li><a href="#p5.6">5.6. Key changeover</a></li>
+    <li><a href="#p5.7">5.7. Compromise and disaster recovery</a> </li>
+    <li><a href="#p5.8">5.8. CA or RA termination</a></li>
+   </ul>
+  </li>
+  <li><a href="#p6">TECHNICAL SECURITY CONTROLS</a>
+   <ul>
+    <li><a href="#p6.1">6.1. Key pair generation and installation</a> </li>
+    <li><a href="#p6.2">6.2. Private Key Protection and Cryptographic Module Engineering Controls</a> </li>
+    <li><a href="#p6.3">6.3. Other aspects of key pair management</a> </li>
+    <li><a href="#p6.4">6.4. Activation data</a> </li>
+    <li><a href="#p6.5">6.5. Computer security controls</a> </li>
+    <li><a href="#p6.6">6.6. Life cycle technical controls</a> </li>
+    <li><a href="#p6.7">6.7. Network security controls</a></li>
+    <li><a href="#p6.8">6.8. Time-stamping</a></li>
+   </ul>
+  </li>
+  <li><a href="#p7">CERTIFICATE, CRL, AND OCSP PROFILES</a>
+   <ul>
+    <li><a href="#p7.1">7.1. Certificate profile</a> </li>
+    <li><a href="#p7.2">7.2. CRL profile</a> </li>
+    <li><a href="#p7.3">7.3. OCSP profile</a> </li>
+   </ul>
+  </li>
+  <li><a href="#p8">COMPLIANCE AUDIT AND OTHER ASSESSMENTS</a>
+   <ul>
+    <li><a href="#p8.1">8.1. Frequency or circumstances of assessment</a></li>
+    <li><a href="#p8.2">8.2. Identity/qualifications of assessor</a></li>
+    <li><a href="#p8.3">8.3. Assessor's relationship to assessed entity</a></li>
+    <li><a href="#p8.4">8.4. Topics covered by assessment</a></li>
+    <li><a href="#p8.5">8.5. Actions taken as a result of deficiency</a></li>
+    <li><a href="#p8.6">8.6. Communication of results</a></li>
+   </ul>
+  </li>
+  <li><a href="#p9">OTHER BUSINESS AND LEGAL MATTERS</a>
+   <ul>
+    <li><a href="#p9.1">9.1. Fees</a> </li>
+    <li><a href="#p9.2">9.2. Financial responsibility</a> </li>
+    <li><a href="#p9.3">9.3. Confidentiality of business information</a> </li>
+    <li><a href="#p9.4">9.4. Privacy of personal information</a> </li>
+    <li><a href="#p9.5">9.5. Intellectual property rights</a></li>
+    <li><a href="#p9.6">9.6. Representations and warranties</a> </li>
+    <li><a href="#p9.7">9.7. Disclaimers of warranties</a></li>
+    <li><a href="#p9.8">9.8. Limitations of liability</a></li>
+    <li><a href="#p9.9">9.9. Indemnities</a></li>
+    <li><a href="#p9.10">9.10. Term and termination</a> </li>
+    <li><a href="#p9.11">9.11. Individual notices and communications with participants</a></li>
+    <li><a href="#p9.12">9.12. Amendments</a> </li>
+    <li><a href="#p9.13">9.13. Dispute resolution provisions</a></li>
+    <li><a href="#p9.14">9.14. Governing law</a></li>
+    <li><a href="#p9.15">9.15. Compliance with applicable law</a></li>
+    <li><a href="#p9.16">9.16. Miscellaneous provisions</a> </li>
+   </ul>
+  </li>
+</ol>
+
+</font>
+
+
+
+<!-- *************************************************************** -->
+<h2><a name="p1" id="p1">1. INTRODUCTION</a></h2>
+
+<h3><a name="p1.1" id="p1.1">1.1. Overview</a></h3>
+
+<p>
+This document is the Certification Practice Statement (CPS) of
+CAcert, the Community Certification Authority (CA).
+It describes rules and procedures used by CAcert for
+operating its CA,
+and applies to all CAcert PKI Participants,
+including Assurers, Members, and CAcert itself.
+</p>
+
+<p>
+</p>
+
+<h3><a name="p1.2" id="p1.2">1.2. Document name and identification</a></h3>
+
+<p>
+This document is the Certification Practice Statement (CPS) of CAcert.
+The CPS also fulfills the role of the Certificate Policy (CP)
+for each class of certificate.
+</p>
+
+<ul>
+  <li>
+    This document is COD6 under CAcert Official Documents numbering scheme.
+  </li>
+  <li>
+    The document is structured according to
+    Chokhani, et al,
+    <a href="http://www.ietf.org/rfc/rfc3647.txt">RFC3647</a>,
+    <a href="http://tools.ietf.org/html/rfc3647#section-4">chapter 4</a>.
+    All headings derive from that Chapter.
+  </li>
+  <li>
+    It has been improved and reviewed (or will be reviewed)
+    to meet or exceed the criteria of the
+    <cite>Certificate Authority Review Checklist</cite>
+    from <i>David E. Ross</i> ("DRC")
+    and Mozilla Foundation's CA policy.
+  </li>
+  <li>
+    OID assigned to this document: 1.3.6.1.4.1.18506.4.4.x (x=approved Version)
+    (<a href="http://www.iana.org/assignments/enterprise-numbers">iana.org</a>)
+    <p class="q"> .x will change to .1 in the first approved instance.</p>
+  </li>
+  <li>
+    &copy; CAcert Inc. 2006-2009.
+    <!-- note that CCS policies must be controlled by CAcert Inc. -->
+  </li>
+  <li>
+    Issued under the CAcert document licence policy,
+    as and when made policy.
+    See <a href="http://wiki.cacert.org/wiki/PolicyDrafts/DocumentLicence">
+    PolicyDrafts/DocumentLicence</a>.
+     <ul class="q">
+       <li> The cited page discusses 2 options:  CCau Attribute-Share-alike and GNU Free Document License.  Refer to that.  </li>
+       <li> Note that the noun Licence in Australian English has two Cs.  The verb License is spelt the same way as American English. </li>
+     </ul>
+  </li>
+  <li>
+    Earlier notes were written by Christian Barmala
+    in a document placed under GNU Free Document License
+    and under FSF copyright.
+    However this clashed with the control provisions of
+    Configuration-Control Specification
+    (COD2) within Audit criteria.
+  </li>
+  <li>
+    <span class="q">In this document:</span>
+    <ul>
+      <li>
+        <span class="q">green text</span>
+        refers to questions that seek answers,
+      </li><li>
+         <span class="error">red text</span>
+         refers to probably audit fails or serious errors.
+      </li><li>
+         <span class="change">blue text</span>
+         refers to changes written after the document got seriously reviewed.
+    </ul>
+    <span class="q">
+    None is to be considered part of the policy,
+    and they should disappear in the DRAFT
+    and must disappear in the POLICY.
+    </span>
+  </li>
+<!--
+  <li>
+    Some content is incorporated under
+<!--     <a href="http://xkcd.com/license.html">Creative Commons license</a> -->
+<!--     from <a href="http://xkcd.com/">xkcd.com</a>. -->
+<!--   198 177 515
+  </li>
+-->
+</ul>
+
+<p>
+The CPS is an authoritive document,
+and rules other documents
+except where explicitly deferred to.
+See also <a href="#p1.5.1">1.5.1 Organisation Administering the Document</a>.
+</p>
+
+<h3><a name="p1.3" id="p1.3">1.3. PKI participants</a></h3>
+<p>
+The CA is legally operated by CAcert Incorporated,
+an Association registered in 2002 in
+New South Wales, Australia,
+on behalf of the wider Community of Members of CAcert.
+The Association details are at the
+<a href="http://wiki.cacert.org/wiki/CAcertIncorporated">CAcert wiki</a>.
+</p>
+
+<p>
+CAcert is a Community formed of Members who agree to the
+<a href="http://www.cacert.org/policy/CAcertCommunityAgreement.html">
+CAcert Community Agreement</a>.
+The CA is technically operated by the Community,
+under the direction of the Board of CAcert Incorporated.
+(The Members of the Community are not to be confused
+with the <i>Association Members</i>, which latter are
+not referred to anywhere in this CPS.)
+</p>
+
+<h4><a name="p1.3.1" id="p1.3.1">1.3.1. Certification authorities</a></h4>
+<p>
+CAcert does not issue certificates to external
+intermediate CAs under the present CPS.
+</p>
+
+<h4><a name="p1.3.2" id="p1.3.2">1.3.2. Registration authorities</a></h4>
+<p>
+Registration Authorities (RAs) are controlled under Assurance Policy
+(<a href="http://www.cacert.org/policy/AssurancePolicy.html">COD13</a>).
+</p>
+
+<h4><a name="p1.3.3" id="p1.3.3">1.3.3. Subscribers</a></h4>
+
+<p>
+CAcert issues certificates to Members only.
+Such Members then become Subscribers.
+</p>
+
+
+<h4><a name="p1.3.4" id="p1.3.4">1.3.4. Relying parties</a></h4>
+
+<p>
+A relying party is a Member,
+having agreed to the
+CAcert Community Agreement
+(<a href="http://www.cacert.org/policy/CAcertCommunityAgreement.html">COD9</a>),
+who, in the act of using a CAcert certificate,
+makes a decision on the basis of that certificate.
+</p>
+
+<h4><a name="p1.3.5" id="p1.3.5">1.3.5. Other participants</a></h4>
+
+<p>
+<b>Member.</b>
+Membership of the Community is as defined in the
+<a href="http://www.cacert.org/policy/CAcertCommunityAgreement.html">COD9</a>.
+Only Members may RELY or may become Subscribers.
+Membership is free.
+</p>
+
+<p>
+<b>Arbitrator.</b>
+A senior and experienced Member of the CAcert Community
+who resolves disputes between Members, including ones
+of certificate reliance, under
+Dispute Resolution Policy
+(<a href="http://www.cacert.org/policy/DisputeResolutionPolicy.html">COD7</a>).
+</p>
+
+<p>
+<b>Vendor.</b>
+Software suppliers who integrate the root certificates of CAcert
+into their software also assume a proxy role of Relying Parties,
+and are subject to another licence.
+<span class="q">
+At the time of writing, the
+"3rd Party Vendor - Disclaimer and Licence"
+is being worked upon, but is neither approved nor offered.
+</span>
+</p>
+
+<p>
+<b>Non-Related Persons</b> (NRPs).
+These are users of browsers and similar software who are
+unaware of the CAcert certificates they may use, and
+are unaware of the ramifications of usage.
+Their relationship with CAcert
+is described by the
+Non-related Persons - Disclaimer and Licence
+(<a href="http://www.cacert.org/policy/NRPDisclaimerAndLicence.html">COD4</a>).
+No other rights nor relationship is implied or offered.
+</p>
+
+
+<h3><a name="p1.4" id="p1.4">1.4. Certificate usage</a></h3>
+
+<p>CAcert serves as issuer of certificates for
+individuals, businesses, governments, charities,
+associations, churches, schools,
+non-governmental organisations or other groups.
+CAcert certificates are intended for low-cost
+community applications especially where volunteers can
+become Assurers and help CAcert to help the Community.
+</p>
+
+<p>
+Types of certificates and their appropriate and
+corresponding applications are defined in
+<a href="#p1.4.1">&sect;1.4.1</a>.
+Prohibited applications are defined in <a href="#p1.4.2">&sect;1.4.2</a>.
+Specialist uses may be agreed by contract or within
+a specific environment, as described in
+<a href="#p1.4.4">&sect;1.4.4</a>.
+Note also the
+unreliable applications in
+<a href="#p1.4.3">&sect;1.4.3</a>
+and risks, liabilities and obligations in
+<a href="#p9">&sect;9</a>.
+</p>
+
+
+<center>
+<table border="1" cellpadding="5">
+ <tr>
+  <td colspan="2"><center><i>Type</i></center></td>
+  <td colspan="2"><center><i>Appropriate Certificate uses</i></center></td>
+ </tr>
+ <tr>
+  <th>General</th>
+  <th>Protocol</th>
+  <th><center>Description</center></th>
+  <th><center>Comments</center></th>
+ </tr>
+ <tr>
+  <td rowspan="2"><center>Server</center></td>
+  <td> TLS </td>
+  <td> web server encryption </td>
+  <td> enables encryption </td>
+ </tr>
+ <tr>
+  <td> embedded </td>
+  <td> embedded server authentication </td>
+  <td> mail servers, IM-servers </td>
+ </tr>
+ <tr>
+  <td rowspan="4"><center>Client</center></td>
+  <td> S/MIME </td>
+  <td> email encryption </td>
+  <td> "digital signatures" employed in S/MIME
+       are not legal / human signatures,
+       but instead enable the encryption mode of S/MIME </td>
+ </tr>
+ <tr>
+  <td> TLS </td>
+  <td> client authentication </td>
+  <td> the nodes must be secure </td>
+ </tr>
+ <tr>
+  <td> TLS </td>
+  <td> web based signature applications </td>
+  <td> the certificate authenticates only.  See <a href="#p1.4.3">&sect;1.4.3</a>. </td>
+ </tr>
+ <tr>
+  <td> &quot;Digital Signing&quot; </td>
+  <td> for human signing over documents </td>
+  <td> Only within a wider application and rules
+       such as by separate policy,
+       as agreed by contract, etc.
+       See <a href="#p1.4.4">&sect;1.4.4</a>. 
+       </td>
+ </tr>
+ <tr>
+  <td><center>Code</center></td>
+  <td> Authenticode, ElfSign, Java </td>
+  <td> Code Signing </td>
+  <td> Signatures on packages are evidence of their Membership and indicative of Identity </td>
+ </tr>
+ <tr>
+  <td><center>PGP</center></td>
+  <td> OpenPGP </td>
+  <td> Key Signing </td>
+  <td> Signatures on Member Keys are evidence of their Membership and indicative of Identity </td>
+ </tr>
+ <tr>
+  <td><center>Special</center></td>
+  <td> X.509 </td>
+  <td> OCSP, Timestamping </td>
+  <td> Only available to CAcert Systems Administrators, as controlled by Security Policy </td>
+ </tr>
+</table>
+
+<span class="figure">Table 1.4.  Types of Certificate</span>
+</center>
+
+<h4><a name="p1.4.1" id="p1.4.1">1.4.1. Appropriate certificate uses</a></h4>
+
+<p>
+General uses.
+</p>
+
+<ul><li>
+    CAcert server certificates can be used to enable encryption
+    protection in web servers.
+    Suitable applications include webmail and chat forums.
+  </li><li>
+    CAcert server certificates can be used to enable encryption
+    in SSL/TLS links in embedded protocols such as mail servers
+    and IM-servers.
+  </li><li>
+    CAcert client certificates can be used to enable encryption
+    protection in email clients.
+    (See <a href="#p1.4.3">&sect;1.4.3</a> for caveat on signatures.)
+  </li><li>
+    CAcert client certificates can be used to replace password-based
+    authentication to web servers.
+  </li><li>
+    OpenPGP keys with CAcert signatures can be used
+    to encrypt and sign files and emails,
+    using software compatible with OpenPGP.
+  </li><li>
+    CAcert client certificates can be used in web-based
+    authentication applications.
+  </li><li>
+    CAcert code signing certificates can be used to sign code
+    for distribution to other people.
+  </li><li>
+    Time stamping can be used to attach a time record
+    to a digital document.
+</li></ul>
+
+
+<h4><a name="p1.4.2" id="p1.4.2">1.4.2. Prohibited certificate uses</a></h4>
+<p>
+CAcert certificates are not designed, intended, or authorised for
+the following applications:
+</p>
+<ul><li>
+    Use or resale as control equipment in hazardous circumstances
+    or for uses requiring fail-safe performance such as the operation
+    of nuclear facilities, aircraft navigation or communication systems,
+    air traffic control systems, or weapons control systems,
+    where failure could lead directly to death, personal injury,
+    or severe environmental damage.
+</li></ul>
+
+<h4><a name="p1.4.3" id="p1.4.3">1.4.3. Unreliable Applications</a></h4>
+
+<p>
+CAcert certificates are not designed nor intended for use in
+the following applications, and may not be reliable enough
+for these applications:
+</p>
+
+<ul><li>
+    <b>Signing within Protocols.</b>
+    Digital signatures made by CAcert certificates carry
+    <u>NO default legal or human meaning</u>.
+    See <a href="#p9.15.1">&sect;9.15.1</a>.
+    Especially, protocols such as S/MIME commonly will automatically
+    apply digital signatures as part of their protocol needs.
+    The purpose of the cryptographic signature in S/MIME
+    and similar protocols is limited by default to strictly
+    protocol security purposes: 
+    to provide some confirmation that a familiar certificate
+    is in use, to enable encryption, and to ensure the integrity
+    of the email in transit.
+</li><li>
+    <b>Non-repudiation applications.</b>
+    Non-repudiation is not to be implied from use of
+    CAcert certificates.  Rather, certificates may
+    provide support or evidence of actions, but that
+    evidence is testable in any dispute.
+</li><li>
+    <b>Ecommerce applications.</b>
+    Financial transactions or payments or valuable e-commerce.
+</li><li>
+    Use of anonymous (Class 1 or Member SubRoot) certificates
+    in any application that requires or expects identity.
+</li></ul>
+
+<!-- <center><a href="http://xkcd.com/341/"> <img src="http://imgs.xkcd.com/comics/1337_part_1.png"> </a> </center> -->
+
+<h4><a name="p1.4.4" id="p1.4.4">1.4.4. Limited certificate uses</a></h4>
+
+<p>
+By contract or within a specific environment
+(e.g. internal to a company),
+CAcert Members are permitted to use Certificates
+for higher security, customised or experimental applications.
+Any such usage, however, is limited to such entities
+and these entities take on the whole responsible for
+any harm or liability caused by such usage.
+</p>
+
+<p>
+    <b>Digital signing applications.</b>
+    CAcert client certificates
+    may be used by Assured Members in
+    applications that provide or support the human signing of documents
+    (known here as "digital signing").
+    This must be part of a wider framework and set of rules.
+    Usage and reliance
+    must be documented either under a separate CAcert digital signing 
+    policy or other external regime agreed by the parties.
+</p>
+
+<h4><a name="p1.4.5" id="p1.4.5">1.4.5. Roots and Names</a></h4>
+
+<p>
+<b>Named Certificates.</b>
+Assured Members may be issued certificates
+with their verified names in the certificate.  In this role, CAcert
+operates and supports a network of Assurers who verify the
+identity of the Members.
+All Names are verified, either by Assurance or another defined
+method under policy (c.f. Organisations).
+</p>
+
+<p>
+<b>Anonymous Certificates.</b>
+Members can be issued certificates that are anonymous,
+which is defined as the certificate with no Name included,
+or a shared name such as "Community Member".
+These may be considered to be somewhere between Named certificates
+and self-signed certificates.  They have serial numbers in them
+which is ultimately traceable via dispute to a Member, but
+reliance is undefined.
+In this role, CAcert provides the
+infrastructure, saving the Members from managing a difficult
+and messy process in order to get manufactured certificates.
+</p>
+
+<p>
+<b>Psuedonymous Certificates.</b>
+Note that CAcert does not currently issue pseudonymous certificates,
+being those with a name chosen by the Member and not verifiable
+according to documents.
+</p>
+
+<p>
+<b>Advanced Certificates.</b>
+Members who are as yet unassured are not permitted to create
+advanced forms such as wildcard or subjectAltName
+certificates.
+</p>
+
+
+<p>
+<b> Roots.</b>
+The <span class="q"> (new) </span> CAcert root layout is as below.
+These roots are pending Audit,
+and will be submitted to vendors via the (Top-level) Root.
+</p>
+<ul><li>
+    <b>(Top-level) Root.</b>
+    Used to sign on-line CAcert SubRoots only.
+    This Root is kept offline.
+  </li><li>
+    <b>Member SubRoot.</b>
+    For Community Members who are new and unassured (some restrictions exist).
+    Reliance is undefined.
+    (Replacement for the Class 1 root, matches "Domain Validation" type.)
+  </li><li>
+    <b>Assured SubRoot.</b>
+    Only available for Assured individual Members,
+    intended to sign certificates with Names.
+    Suitable for Reliance under this and other policies.
+    Approximates the type known as Individual Validation.
+  </li><li>
+    <b>Organisation SubRoot.</b>
+    Only available for Assured Organisation Members.
+    Suitable for Reliance under this and other policies.
+    Approximates the type known as Organisational Validation.
+
+</li></ul>
+
+
+
+<center>
+<table border="1" cellpadding="5">
+ <tr>
+  <td></td>
+  <td colspan="5"><center><i>Level of Assurance</i></center></td>
+  <th> </th>
+ </tr>
+ <tr>
+  <th></th>
+  <th colspan="2"><center> Members &dagger; </center></th>
+  <th colspan="2"><center> Assured Members</center></th>
+  <th colspan="1"><center> Assurers </center></th>
+  <th colspan="1"><center> </center></th>
+ </tr>
+ <tr>
+  <td><i>Class of Root</i></td>
+  <th>Anon</th>
+  <td>Name</td>
+  <td>Anon</td>
+  <th>Name</th>
+  <td>Name+Anon</td>
+  <td colspan="1"><center><i>Remarks</i></center></td>
+ </tr>
+ <tr>
+  <td><center>Top level<br><big><b>Root</b></big></center></td>
+  <td> <center> <font title="pass." color="green" size="+3"> &bull; </font> </center> </td>
+  <td> <center> <font title="pass." color="green" size="+3"> &bull; </font> </center> </td>
+  <td> <center> <font title="pass." color="green" size="+3"> &bull; </font> </center> </td>
+  <td> <center> <font title="pass." color="green" size="+3"> &bull; </font> </center> </td>
+  <td> <center> <font title="pass." color="green" size="+3"> &bull; </font> </center> </td>
+  <td> Signs other CAcert SubRoots only. </td>
+ </tr>
+ <tr>
+  <td><center><big><b>Member</b></big><br>SubRoot</center></td>
+  <td> <center> <font title="pass." color="green" size="+3"> &#10004; </font> </center> </td>
+  <td> <center> <font title="pass." color="red" size="+3"> &#10008; </font> </center> </td>
+  <td> <center> <font title="pass." color="green" size="+3"> &#10004; </font> </center> </td>
+  <td> <center> <font title="pass." color="green" size="+3"> &#10004; </font> </center> </td>
+  <td> <center> <font title="pass." color="green" size="+3"> &#10004; </font> </center> </td>
+  <td> &dagger; For Members meeting basic checks in <a href="#p4.2.2">&sect;4.2.2</a><br>(Reliance is undefined.) </td>
+ </tr>
+ <tr>
+  <td><center><big><b>Assured</b></big><br>SubRoot</center></td>
+  <td> <center> <font title="pass." color="red" size="+3"> &#10008; </font> </center> </td>
+  <td> <center> <font title="pass." color="red" size="+3"> &#10008; </font> </center> </td>
+  <td> <center> <font title="pass." color="green" size="+3"> &#10004; </font> </center> </td>
+  <td> <center> <font title="pass." color="green" size="+3"> &#10004; </font> </center> </td>
+  <td> <center> <font title="pass." color="green" size="+3"> &#10004; </font> </center> </td>
+  <td> Assured Members only.<br>Fully intended for reliance. </td>
+ </tr>
+ <tr>
+  <td><center><big><b>Organisation</b></big><br>SubRoot</center></td>
+  <td> <center> <font title="pass." color="red" size="+3"> &#10008; </font> </center> </td>
+  <td> <center> <font title="pass." color="red" size="+3"> &#10008; </font> </center> </td>
+  <td> <center> <font title="pass." color="green" size="+3"> &#10004; </font> </center> </td>
+  <td> <center> <font title="pass." color="green" size="+3"> &#10004; </font> </center> </td>
+  <td> <center> <font title="pass." color="green" size="+3"> &#10004; </font> </center> </td>
+  <td> Assured Organisation Members only.<br>Fully intended for reliance. </td>
+ </tr>
+ <tr>
+  <th>Expiry of Certificates</th>
+  <td colspan="2"><center>6 months</center></td>
+  <td colspan="3"><center>24 months</center></td>
+ </tr>
+ <tr>
+  <th>Types</th>
+  <td colspan="2"><center>client, server</center></td>
+  <td colspan="2"><center>wildcard, subjectAltName</center></td>
+  <td colspan="1"><center>code-signing</center></td>
+  <td> (Inclusive to the left.) </td>
+ </tr>
+</table>
+
+<span class="figure">Table 1.4.5.b  Certificate under Audit Roots</span>
+</center>
+
+
+<p class="q">
+Following information on OLD roots here for
+descriptive and historical purposes only.
+When CPS goes to DRAFT, this needs to be
+converted into a short summary of the way
+OLD roots are used and its relationship to
+this CPS.  E.g., "OLD roots are used for
+testing and other purposes outside this CPS."
+Because ... they still exist, and people will
+look at the CPS to figure it out.
+</p>
+
+<center>
+<table border="1" cellpadding="5">
+ <tr>
+  <td></td>
+  <td colspan="4"><center><i>Level of Assurance</i></center></td>
+  <th> </th>
+ </tr>
+ <tr>
+  <th></th>
+  <th colspan="2"><center>Members</center></th>
+  <th colspan="2"><center>Assured Members</center></th>
+  <th colspan="1"><center> </center></th>
+ </tr>
+ <tr>
+  <td><i>Class of Root</i></td>
+  <th>Anonymous</th>
+  <td>Named</td>
+  <td>Anonymous</td>
+  <th>Named</th>
+  <td colspan="1"><center><i>Remarks</i></center></td>
+ </tr>
+ <tr>
+  <td><center>Class<br><big><b>1</b></big></center></td>
+  <td> <center> <font title="pass." color="green" size="+3"> &#10004; </font> </center> </td>
+  <td> <center> <font title="pass." color="red" size="+3"> &#10008; </font> </center> </td>
+  <td> <center> <font title="pass." color="green" size="+3"> &#10004; </font> </center> </td>
+  <td> <center> <font title="pass." color="green" size="+3"> &#10004; </font> </center> </td>
+  <td> Available for all Members,<br>reliance is undefined.</td>
+ </tr>
+ <tr>
+  <td><center>Class<br><big><b>3</b></big></center></td>
+  <td> <center> <font title="pass." color="red" size="+3"> &#10008; </font> </center> </td>
+  <td> <center> <font title="pass." color="red" size="+3"> &#10008; </font> </center> </td>
+  <td> <center> <font title="pass." color="green" size="+3"> &#10004; </font> </center> </td>
+  <td> <center> <font title="pass." color="green" size="+3"> &#10004; </font> </center> </td>
+  <td> Assured Members only.<br> Intended for Reliance. </td>
+ </tr>
+ <tr>
+  <th>Expiry of Certificates</th>
+  <td colspan="2"><center>6 months</center></td>
+  <td colspan="2"><center>24 months</center></td>
+ </tr>
+ <tr>
+  <th>Types available</th>
+  <td colspan="2"><center>simple only</center></td>
+  <td colspan="2"><center>wildcard, subjectAltName</center></td>
+ </tr>
+</table>
+
+<span class="figure">Table 1.4.5.  Certificates under Old Roots - <b>Audit Fail</b>  </span>
+</center>
+
+<p>
+<b> Old Roots.</b>
+The old CAcert root layout is as below.  These roots are <b>Audit Fail</b>
+and will only be used where new roots do not serve:
+</p>
+<ul><li>
+    (old) <b>Class 1 root.</b>
+    Used primarily for certificates with no names and by
+    unassured Members.
+    For compatibility only,
+    Assured Members may also use this root.
+  </li><li>
+    (old) <b>Class 3 root.</b>
+    Used primarily for certificates including the names
+    of Assured Members.
+    Signed by Class 1 root.
+    Members can decide to rely on these
+    certificates for Assured Members
+    by selecting the Class 3 root for
+    Assured Members as trust anchor.
+</li></ul>
+
+  <ul class="q">
+    <li> Current Mozilla position has drifted from Class 1,2,3s to DV, IV+OV and EV posture.  Except, the actual posture is either unstated or difficult to fathom.</li>
+    <li> scheme for future roots is at <a href="http://wiki.cacert.org/wiki/Roots/NewRootsTaskForce">NewRootsTaskForce</a>.</li>
+    <li>END OLD ROOTS </li>
+  </ul>
+
+<h3><a name="p1.5" id="p1.5">1.5. Policy administration</a></h3>
+
+<p>See <a href="#p1.2">1.2 Document Name and Identification</a>
+  for general scope of this document.</p>
+
+<h4><a name="p1.5.1" id="p1.5.1">1.5.1. Organization administering the document</a></h4>
+
+<p>
+This document is administered by the policy group of
+the CAcert Community under Policy on Policy (<a href="http://www.cacert.org/policy/PolicyOnPolicy.html">COD1</a>).
+</p>
+
+<h4><a name="p1.5.2" id="p1.5.2">1.5.2. Contact person</a></h4>
+<p>
+For questions including about this document:
+</p>
+
+<ul>
+  <li>Join the policy group, by means of the discussion forum at
+  <a href="http://lists.cacert.org/mailman/listinfo">
+  lists.cacert.org</a> . </li>
+  <li>Send email to &lt; support AT cacert DOT org &gt; </li>
+  <li>IRC: irc.cacert.org #CAcert (ssl port 7000, non-ssl port 6667)</li>
+</ul>
+
+<h4><a name="p1.5.3" id="p1.5.3">1.5.3. Person determining CPS suitability for the policy</a></h4>
+<p>
+This CPS and all other policy documents are managed by
+the policy group, which is a group of Members of the
+Community found at policy forum.  See discussion forums above.
+</p>
+
+<h4><a name="p1.5.4" id="p1.5.4">1.5.4. CPS approval procedures</a></h4>
+<p>
+CPS is controlled and updated according to the
+Policy on Policy
+(<a href="http://www.cacert.org/policy/PolicyOnPolicy.html">COD1</a>)
+which is part of
+Configuration-Control Specification (COD2).
+</p>
+
+<p>
+In brief, the policy forum prepares and discusses.
+After a last call, the document moves to DRAFT status
+for a defined period.
+If no challenges have been received in the defined period,
+it moves to POLICY status.
+The process is modelled after some elements of
+the RFC process by the IETF.
+</p>
+
+<h4><a name="p1.5.5" id="p1.5.5">1.5.5 CPS updates</a></h4>
+
+<p>
+As per above.
+</p>
+
+
+<h3><a name="p1.6" id="p1.6">1.6. Definitions and acronyms</a></h3>
+<p>
+<b><a name="d_cert" id="d_cert">Certificate</a></b>.
+  A certificate is a piece of cryptographic data used
+  to validate certain statements, especially those of
+  identity and membership.
+</p>
+<p>
+<b><a name="d_cacert" id="d_cacert">CAcert</a></b>.
+  CAcert is a Community certificate authority as defined under
+  <a href="#p1.2">&sect;1.2 Identification</a>.
+</p>
+<p>
+<b><a name="d_member" id="d_member">Member</a></b>.
+  Everyone who agrees to the
+  CAcert Community Agreement
+  (<a href="http://www.cacert.org/policy/CAcertCommunityAgreement.html">COD9</a>).
+  This generally implies having an account registered
+  at CAcert and making use of CAcert's data, programs or services.
+  A Member may be an individual ("natural person")
+  or an organisation (sometimes, "legal person").
+</p>
+<p>
+<b><a name="d_community" id="d_community">Community</a></b>.
+  The group of Members who agree to the
+  CAcert Community Agreement
+  (<a href="http://www.cacert.org/policy/CAcertCommunityAgreement.html">COD9</a>)
+  or equivalent agreements.
+</p>
+<p>
+<b><a name="d_unassured" id="d_unassured">Unassured Member</a></b>.
+  A Member who has not yet been Assured.
+</p>
+<p>
+<b><a name="d_subscriber" id="d_subscriber">Subscriber</a></b>.
+  A Member who requests and receives a certificate.
+</p>
+<p>
+<b><a name="d_assured" id="d_assured">Assured Member</a></b>.
+  A Member whose identity has been sufficiently
+  verified by Assurers or other
+  approved methods under Assurance Policy.
+</p>
+<p>
+<b><a name="d_assurer" id="d_assurer">Assurer</a></b>.
+  An Assured Member who is authorised under Assurance Policy
+  to verify the identity of other Members.
+</p>
+<p>
+<b><a name="d_name" id="d_name">Name</a></b>.
+    As defined in the
+    Assurance Policy
+    (<a href="http://www.cacert.org/policy/AssurancePolicy.html">COD13</a>),
+    to describe a name of a Member
+    that is verified by the Assurance process.
+<p>
+<b><a name="d_oadmin" id="d_oadmin">Organisation Administrator</a></b>.
+  ("O-Admin")
+  An Assurer who is authorised to act for an Organisation.
+  The O-Admin is authorised by an organisation
+  to vouch for the identity of other users of the organisation.
+</p>
+<p>
+<b><a name="d_org_ass" id="d_org_ass">Organisation Assurer</a></b>.
+  An Assurer who is authorised to conduct assurances on
+  organisations.
+</p>
+<p>
+<b><a name="d_user" id="d_user">Non-Related Persons</a></b>.
+  ("NRPs")
+  are general users of browsers and similar software.
+  The NRPs are generally unaware of
+  CAcert or the certificates that they may use, and
+  are unaware of the ramifications of usage.
+  They are not permitted to RELY, but may USE, under the 
+  Non-Related Persons - Disclaimer and Licence (<a href="http://www.cacert.org/policy/NRPDisclaimerAndLicence.html">COD4</a>).
+</p>
+<p>
+<b><a name="rel" id="d_reliance">Reliance</a></b>.
+  An industry term referring to
+  the act of making a decision, including taking a risk,
+  which decision is in part or in whole
+  informed or on the basis of the contents of a certificate.
+</p>
+<p>
+<b><a name="rel" id="rel">Relying Party</a></b>.
+  An industry term refering to someone who relies
+  (that is, makes decisions or takes risks)
+  in part or in whole on a certificate.
+</p>
+<p>
+    <b>Subscriber Naming.</b>
+    The term used in this CPS to
+    describe all naming data within a certificate.
+    Approximately similar terms from Industry such as
+    "Subject naming" and "Distinguished Name"
+    are not used here.
+</p>
+<p>
+<b><a name="ver" id="d_verification">Verification</a></b>.
+  An industry term referring to
+  the act of checking and controlling
+  the accuracy and utility of a single claim.
+</p>
+<p>
+<b><a name="ver" id="d_validation">Validation</a></b>.
+  An industry term referring to the process of
+  inspecting and verifying the information and
+  subsidiary claims behind a claim.
+</p>
+<p>
+<b><a name="rel" id="rel">Usage</a></b>.
+  The event of allowing a certificate to participate in
+  a protocol, as decided and facilitated by a user's software.
+  Generally, Usage does not require significant input, if any,
+  on the part of the user.
+  This defers all decisions to the user software,
+  thus elevating the software as user's only and complete
+  Validation Authority or Agent.
+</p>
+<p>
+<b><a name="drel" id="drel">CAcert Relying Party</a></b>.
+  CAcert Members who make decisions based in part or in whole
+  on a certificate issued by CAcert.
+  Only CAcert Members are permitted to Rely on CAcert certificates,
+  subject to the CAcert Community Agreement.
+</p>
+<p>
+<b><a name="ddst" id="ddst">Vendors</a></b>.
+  Non-members who distribute CAcert's root or intermediate certificates
+  in any way, including but not limited to delivering these
+  certificates with their products, e.g. browsers, mailers or servers.
+  Vendors are covered under a separate licence.
+  <span class="q"> As of the moment, this licence is not written.</span>
+</p>
+<p>
+<b><a name="d_ccs" id="d_ccs">Configuration-Control Specification</a></b> "CCS".
+  The audit criteria that controls this CPS.
+  The CCS is documented in COD2, itself a controlled document under CCS.
+</p>
+<p>
+<p>
+<b><a name="d_cod" id="d_cod">CAcert Official Document</a></b> (COD).
+  Controlled Documents that are part of the CCS.
+</p>
+
+
+
+<!-- *************************************************************** -->
+<h2><a name="p2" id="p2">2. PUBLICATION AND REPOSITORY RESPONSIBILITIES</a></h2>
+
+
+<h3><a name="p2.1" id="p2.1">2.1. Repositories</a></h3>
+
+<p>
+CAcert operates no repositories in the sense
+of lookup for non-certificate-related information
+for the general public.
+</p>
+
+<p>
+Under the Assurance Policy (<a href="http://www.cacert.org/policy/AssurancePolicy.html">COD13</a>),
+there are means for Members to search, retrieve
+and verify certain data about themselves and others.
+</p>
+
+<h3><a name="p2.2" id="p2.2">2.2. Publication of certification information</a></h3>
+
+<p>
+CAcert publishes:
+</p>
+
+<ul>
+  <li>A repository of CRLs.  An OCSP responder is in operation.</li>
+  <li>The root certificate and intermediate certificates.</li>
+</ul>
+
+<p>
+CAcert does not expressly publish information on issued certificates.
+However, due to the purpose of certificates, and the essential
+public nature of Names and email addresses, all information within
+certificates is presumed to be public and published, once
+issued and delivered to the Member.
+</p>
+
+<h3><a name="p2.3" id="p2.3">2.3. Time or frequency of publication</a></h3>
+
+<p>
+Root and Intermediate Certificates and CRLs are
+made available on issuance.
+</p>
+
+<h3><a name="p2.4" id="p2.4">2.4. Access controls on repositories</a></h3>
+<p> No stipulation.  </p>
+
+
+
+<!-- *************************************************************** -->
+<h2><a name="p3" id="p3">3. IDENTIFICATION AND AUTHENTICATION</a></h2>
+
+<h3><a name="p3.1" id="p3.1">3.1. Naming</a></h3>
+
+<h4><a name="p3.1.1" id="p3.1.1">3.1.1. Types of names</a></h4>
+
+<p>
+<b>Client Certificates.</b>
+The Subscriber Naming consists of:
+</p>
+<ul>
+  <li><tt>subjectAltName=</tt>
+      One, or more, of the Subscriber's verified email addresses,
+      in rfc822Name format.
+
+  <ul class="q">
+    <li>SSO in subjectAltName?.</li>
+  </ul>
+  <li><tt>EmailAddress=</tt>
+      One, or more, of the Subscriber's verified email addresses.
+      This is deprecated under 
+      RFC5280 <a href="http://tools.ietf.org/html/rfc5280#section-4.2.1.6">4
+.1.2.6</a>
+      and is to be phased out. Also includes a SHA1 hash of a random number if 
+      the member selects SSO (Single Sign On ID) during submission of CSR.
+  </li>
+  <li><tt>CN=</tt> The common name takes its value from one of:
+    <ul><li>
+      For all Members,
+      the string "<tt>CAcert WoT Member</tt>" may be used for
+      anonymous certificates.
+    </li><li>
+      For individual Members,
+      a Name of the Subscriber,
+      as Assured under AP.
+    </li><li>
+      For Organisation Members,
+      an organisation-chosen name,
+      as verified under OAP.
+    </li></ul>
+</ul>
+
+  <ul class="q">
+    <li> <a href="http://bugs.cacert.org/view.php?id=672"> bug 672</a> filed on subjectAltName.</li>
+    <li> O-Admin must verify as per <a href="http://wiki.cacert.org/wiki/PolicyDecisions">p20081016</a>. </li>
+    <li> it is a wip for OAP to state how this is done. </li>
+    <li> curiously, (RFC5280) verification is only mandated for subjectAltName not subject field. </li>
+    <li> what Directory String is used in above?  UTF8String is specified by RFC52804.1.2.6?  is this important for the CPS to state?</li>
+  </ul>
+
+<p>
+<b>Individual Server Certificates.</b>
+The Subscriber Naming consists of:
+</p>
+<ul>
+ <li><tt>CN=</tt>
+    The common name is the host name out of a domain
+    for which the Member is a domain master.
+  </li> <li>
+  <tt>subjectAltName=</tt>
+    Additional host names for which the Member
+    is a domain master may be added to permit the
+    certificate to serve multiple domains on one IP number.
+  </li> <li>
+    All other fields are optional and must either match
+    the CN or they must be empty
+</li> </ul>
+
+<p>
+<b>Certificates for Organisations.</b>
+In addition to the above, the following applies:
+</p>
+
+<ul>
+  <li><tt>OU=</tt>
+      organizationalUnitName (set by O-Admin, must be verified by O-Admin).</li>
+  <li><tt>O=</tt>
+      organizationName is the fixed name of the Organisation.</li>
+  <li><tt>L=</tt>
+      localityName</li>
+  <li><tt>ST=</tt>
+      stateOrProvinceName</li>
+  <li><tt>C=</tt>
+      countryName</li>
+  <li><tt>contact=</tt>
+      EMail Address of Contact.
+      <!--  not included in RFC5280 4.1.2.4 list, but list is not restricted -->
+  </li>
+</ul>
+
+<p>
+Except for the OU and CN, fields are taken from the Member's
+account and are as verified by the Organisation Assurance process.
+Other Subscriber information that is collected and/or retained
+does not go into the certificate.
+</p>
+
+<h4><a name="p3.1.2" id="p3.1.2">3.1.2. Need for names to be meaningful</a></h4>
+
+<p>
+Each Member's Name (<tt>CN=</tt> field)
+is assured under the Assurance Policy (<a href="http://www.cacert.org/policy/AssurancePolicy.html">COD13</a>)
+or subsidiary policies (such as Organisation Assurance Policy).
+Refer to those documents for meanings and variations.
+</p>
+
+<p>
+Anonymous certificates have the same <code>subject</code>
+field common name.
+See <a href="#p1.4.5">&sect;1.4.5.</a>.
+</p>
+
+<p>
+Email addresses are verified according to
+<a href="#p4.2.2">&sect;4.2.2.</a>
+</p>
+
+<!-- <center><a href="http://xkcd.com/327/"> <img src="http://imgs.xkcd.com/comics/exploits_of_a_mom.png"> </a> </center> -->
+
+<h4><a name="p3.1.3" id="p3.1.3">3.1.3. Anonymity or pseudonymity of subscribers</a></h4>
+
+<p>
+See <a href="#p1.4.5">&sect;1.4.5</a>.
+</p>
+
+<h4><a name="p3.1.4" id="p3.1.4">3.1.4. Rules for interpreting various name forms</a></h4>
+<p>
+Interpretation of Names is controlled by the Assurance Policy,
+is administered by means of the Member's account,
+and is subject to change by the Arbitrator.
+Changes to the interpretation by means of Arbitration
+should be expected as fraud (e.g., phishing)
+may move too quickly for policies to fully document rules.
+</p>
+
+<h4><a name="p3.1.5" id="p3.1.5">3.1.5. Uniqueness of names</a></h4>
+
+<p>
+Uniqueness of Names within certificates is not guaranteed.
+Each certificate has a unique serial number which maps
+to a unique account, and thus maps to a unique Member.
+See the Assurance Statement within Assurance Policy
+(<a href="http://www.cacert.org/policy/AssurancePolicy.html">COD13</a>).
+</p>
+
+<p>
+Domain names and email address
+can only be registered to one Member.
+</p>
+
+<h4><a name="p3.1.6" id="p3.1.6">3.1.6. Recognition, authentication, and role of trademarks</a></h4>
+
+<p>
+Organisation Assurance Policy
+(<a href="http://www.cacert.org/policy/OrganisationAssurancePolicy.html">COD11</a>)
+controls issues such as trademarks where applicable.
+A trademark can be disputed by filing a dispute.
+See
+<a href="#adr">&sect;9.13</a>.
+</p>
+
+<h4><a name="p3.1.7" id="p3.1.7">3.1.7. International Domain Names</a></h4>
+
+<p>
+Certificates containing International Domain Names, being those containing a 
+ACE prefix (<a href="http://www.ietf.org/rfc/rfc3490#section-5">RFC3490 
+Section 5</a>), will only be issued to domains satisfying one or more 
+of the following conditions:
+</p>
+<ul>
+<li>The Top Level Domain (TLD) Registrar associated with the domain has a policy
+that has taken measures to prevent two homographic domains being registered to 
+different entities down to an accepted level.
+</li>
+<li>Domains contain only code points from a single unicode character script,
+excluding the "Common" script, with the additionally allowed numberic
+characters [0-9], and an ACSII hyphen '-'.
+</li>
+</ul>
+
+
+<p>Email address containing International Domain Names in the domain portion of
+the email address will also be required to satisfy one of the above conditions.
+</p>
+
+<p>
+The following is a list of accepted TLD Registrars:</p>
+    <table>
+
+      <tr>
+        <td>.ac</td>
+        <td><a href="http://www.nic.ac/">Registry</a></td>
+        <td><a href="http://www.nic.ac/pdf/AC-IDN-Policy.pdf">Policy</a></td>
+      </tr>
+      <tr>
+        <td>.ar</td>
+
+        <td><a href="http://www.nic.ar/">Registry</a></td>
+        <td><a href="http://www.nic.ar/616.html">Policy</a></td>
+      </tr>
+      <tr>
+        <td>.at</td>
+        <td><a href="http://www.nic.at/">Registry</a></td>
+        <td><a href="http://www.nic.at/en/service/legal_information/registration_guidelines/">Policy</a> (<a href="http://www.nic.at/en/service/technical_information/idn/charset_converter/">character list</a>)</td>
+
+      </tr>
+      <tr>
+        <td>.biz</td>
+        <td><a href="http://www.neustarregistry.biz/">Registry</a></td>
+        <td><a href="http://www.neustarregistry.biz/products/idns">Policy</a></td>
+      </tr>
+      <tr>
+
+        <td>.br</td>
+        <td><a href="http://registro.br/">Registry</a></td>
+        <td><a href="http://registro.br/faq/faq6.html">Policy</a></td>
+      </tr>
+      <tr>
+        <td>.cat</td>
+        <td><a href="http://www.domini.cat/">Registry</a></td>
+
+        <td><a href="http://www.domini.cat/normativa/en_normativa_registre.html">Policy</a></td>
+      </tr>
+      <tr>
+        <td>.ch</td>
+        <td><a href="http://www.switch.ch/id/">Registry</a></td>
+        <td><a href="http://www.switch.ch/id/terms/agb.html#anhang1">Policy</a></td>
+      </tr>
+
+      <tr>
+        <td>.cl</td>
+        <td><a href="http://www.nic.cl/">Registry</a></td>
+        <td><a href="http://www.nic.cl/CL-IDN-policy.html">Policy</a></td>
+      </tr>
+      <tr>
+        <td>.cn</td>
+
+        <td><a href="http://www.cnnic.net.cn/">Registry</a></td>
+        <td><a href="http://www.faqs.org/rfcs/rfc3743.html">Policy</a> (JET Guidelines)</td>
+      </tr>
+      <tr>
+        <td>.de</td>
+        <td><a href="http://www.denic.de/">Registry</a></td>
+
+        <td><a href="http://www.denic.de/en/richtlinien.html">Policy</a></td>
+      </tr>
+      <tr>
+        <td>.dk</td>
+        <td><a href="http://www.dk-hostmaster.dk/">Registry</a></td>
+        <td><a href="http://www.dk-hostmaster.dk/index.php?id=151">Policy</a></td>
+      </tr>
+
+      <tr>
+        <td>.es</td>
+        <td><a href="https://www.nic.es/">Registry</a></td>
+        <td><a href="https://www.nic.es/media/2008-12/1228818323935.pdf">Policy</a></td>
+      </tr>
+      <tr>
+        <td>.fi</td>
+
+        <td><a href="http://www.ficora.fi/">Registry</a></td>
+        <td><a href="http://www.ficora.fi/en/index/palvelut/fiverkkotunnukset/aakkostenkaytto.html">Policy</a></td>
+      </tr>
+      <tr>
+        <td>.gr</td>
+        <td><a href="https://grweb.ics.forth.gr/english/index.html">Registry</a></td>
+        <td><a href="https://grweb.ics.forth.gr/english/ENCharacterTable1.jsp">Policy</a></td>
+
+      </tr>
+      <tr>
+        <td>.hu</td>
+        <td><a href="http://www.domain.hu/domain/">Registry</a></td>
+        <td><a href="http://www.domain.hu/domain/English/szabalyzat.html">Policy</a> (section 2.1.2)</td>
+      </tr>
+
+      <tr>
+        <td>.info</td>
+        <td><a href="http://www.afilias.info/">Registry</a></td>
+        <td><a href="http://www.afilias.info/register/idn/">Policy</a></td>
+      </tr>
+      <tr>
+        <td>.io</td>
+
+        <td><a href="http://www.nic.io">Registry</a></td>
+        <td><a href="http://www.nic.io/IO-IDN-Policy.pdf">Policy</a></td>
+      </tr>
+      <tr>
+        <td>.ir</td>
+        <td><a href="https://www.nic.ir/">Registry</a></td>
+        <td><a href="https://www.nic.ir/IDN">Policy</a></td>
+
+      </tr>
+      <tr>
+        <td>.is</td>
+        <td><a href="http://www.isnic.is/">Registry</a></td>
+        <td><a href="http://www.isnic.is/english/domain/rules.php">Policy</a></td>
+      </tr>
+      <tr>
+
+        <td>.jp</td>
+        <td><a href="http://jprs.co.jp/">Registry</a></td>
+        <td><a href="http://www.iana.org/assignments/idn/jp-japanese.html">Policy</a></td>
+      </tr>
+      <tr>
+        <td>.kr</td>
+        <td><a href="http://domain.nic.or.kr/">Registry</a></td>
+
+        <td><a href="http://www.faqs.org/rfcs/rfc3743.html">Policy</a> (JET Guidelines)</td>
+      </tr>
+      <tr>
+        <td>.li</td>
+        <td><a href="http://www.switch.ch/id/">Registry</a></td>
+        <td><a href="http://www.switch.ch/id/terms/agb.html#anhang1">Policy</a> (managed by .ch registry)</td>
+
+      </tr>
+      <tr>
+        <td>.lt</td>
+        <td><a href="http://www.domreg.lt/public?pg=&sp=&loc=en">Registry</a></td>
+        <td><a href="http://www.domreg.lt/public?pg=8A7FB6&sp=idn&loc=en">Policy</a> (<a href="http://www.domreg.lt/static/doc/public/idn_symbols-en.pdf">character list</a>)</td>
+
+      </tr>
+      <tr>
+        <td>.museum</td>
+        <td><a href="http://about.museum/">Registry</a></td>
+        <td><a href="http://about.museum/idn/idnpolicy.html">Policy</a></td>
+      </tr>
+      <tr>
+
+        <td>.no</td>
+        <td><a href="http://www.norid.no/">Registry</a></td>
+        <td><a href="http://www.norid.no/domeneregistrering/veiviser.en.html">Policy</a> (section 4)</td>
+      </tr>
+      <tr>
+        <td>.org</td>
+
+        <td><a href="http://www.pir.org/">Registry</a></td>
+        <td><a href="http://pir.org/PDFs/ORG-Extended-Characters-22-Jan-07.pdf">Policy</a></td>
+      </tr>
+      <tr>
+        <td>.pl</td>
+        <td><a href="http://www.nask.pl/">Registry</a></td>
+        <td><a href="http://www.dns.pl/IDN/idn-registration-policy.txt">Policy</a></td>
+
+      </tr>
+      <tr>
+        <td>.pr</td>
+        <td><a href="https://www.nic.pr/">Registry</a></td>
+        <td><a href="https://www.nic.pr/idn_rules.asp">Policy</a></td>
+      </tr>
+      <tr>
+
+        <td>.se</td>
+        <td><a href="http://www.nic-se.se/">Registry</a></td>
+        <td><a href="http://www.iis.se/en/domaner/internationaliserad-doman-idn/">Policy</a> (<a href="http://www.iis.se/docs/teckentabell-03.pdf">character list</a>)</td>
+      </tr>
+      <tr>
+
+        <td>.sh</td>
+        <td><a href="http://www.nic.sh">Registry</a></td>
+        <td><a href="http://www.nic.sh/SH-IDN-Policy.pdf">Policy</a></td>
+      </tr>
+      <tr>
+        <td>.th</td>
+        <td><a href="http://www.thnic.or.th/">Registry</a></td>
+
+        <td><a href="http://www.iana.org/assignments/idn/th-thai.html">Policy</a></td>
+      </tr>
+      <tr>
+        <td>.tm</td>
+        <td><a href="http://www.nic.tm">Registry</a></td>
+        <td><a href="http://www.nic.tm/TM-IDN-Policy.pdf">Policy</a></td>
+      </tr>
+
+      <tr>
+        <td>.tw</td>
+        <td><a href="http://www.twnic.net.tw/">Registry</a></td>
+        <td><a href="http://www.faqs.org/rfcs/rfc3743.html">Policy</a> (JET Guidelines)</td>
+      </tr>
+      <tr>
+
+        <td>.vn</td>
+        <td><a href="http://www.vnnic.net.vn/">Registry</a></td>
+        <td><a href="http://www.vnnic.vn/english/5-6-300-2-2-04-20071115.htm">Policy</a> (<a href="http://vietunicode.sourceforge.net/tcvn6909.pdf">character list</a>)</td>
+      </tr>
+  </table>
+
+
+<p>
+This criteria will apply to the email address and server host name fields for all certificate types.
+</p>
+
+<p>
+The CAcert Inc. Board has the authority to decide to add or remove accepted TLD Registrars on this list.
+</p>
+
+<h3><a name="p3.2" id="p3.2">3.2. Initial Identity Verification</a></h3>
+
+<p>
+Identity verification is controlled by the
+<a href="http://svn.cacert.org/CAcert/Policies/AssurancePolicy.html">
+Assurance Policy</a> (<a href="http://www.cacert.org/policy/AssurancePolicy.html">COD13</a>).
+The reader is refered to the Assurance Policy,
+the following is representative and brief only.
+</p>
+
+
+<h4><a name="p3.2.1" id="p3.2.1">3.2.1. Method to prove possession of private key</a></h4>
+
+<p>
+CAcert uses industry-standard techniques to
+prove the possession of the private key.
+</p>
+
+<p>
+For X.509 server certificates,
+the stale digital signature of the CSR is verified.
+For X.509 client certificates for "Netscape" browsers,
+SPKAC uses a challenge-response protocol
+to check the private key dynamically.
+For X.509 client certificates for "explorer" browsers,
+ActiveX uses a challenge-response protocol
+to check the private key dynamically.
+</p>
+
+<h4><a name="p3.2.2" id="p3.2.2">3.2.2. Authentication of Individual Identity</a></h4>
+
+<p>
+<b>Agreement.</b>
+An Internet user becomes a Member by agreeing to the
+CAcert Community Agreement
+(<a href="http://www.cacert.org/policy/CAcertCommunityAgreement.html">COD9</a>)
+and registering an account on the online website.
+During the registration process Members are asked to
+supply information about themselves:
+</p>
+  <ul>
+    <li>A valid working email.
+        </li>
+    <li>Full Name and Date of Birth such as is 
+        found on Identity documents.
+        </li>
+    <li>Personal Questions used only for Password Retrieval.</li>
+  </ul>
+
+<p>
+The online account establishes the method of authentication
+for all service requests such as certificates.
+</p>
+
+<p>
+<b>Assurance.</b>
+Each Member is assured according to Assurance Policy
+(<a href="http://www.cacert.org/policy/AssurancePolicy.html">COD13</a>).
+</p>
+
+<!-- <center><a href="http://xkcd.com/364/"> <img src="http://imgs.xkcd.com/comics/responsible_behavior.png"> </a> </center> -->
+
+
+
+<p>
+<b>Certificates.</b>
+Based on the total number of Assurance Points
+that a Member (Name) has, the Member
+can get different levels of certificates.
+See <a href="#p1.4.5">&sect;1.4.5</a>.
+See Table 3.2.b.
+When Members have 50 or more points, they
+become <i>Assured Members</i> and may then request
+certificates that state their Assured Name(s).
+</p>
+
+
+<br><br>
+<center>
+
+<table border="1" cellpadding="5">
+ <tr>
+  <th>Assurance Points</th>
+  <th>Level</th>
+  <th>Service</th>
+  <th>Comments</th>
+ </tr>
+ <tr>
+  <td>0</td>
+  <td>Unassured Member</td>
+  <td>Anonymous</td>
+  <td>Certificates with no Name, under Class 1 Root.  Limited to 6 months expiry.</td>
+ </tr>
+ <tr>
+  <td>1-49</td>
+  <td>Unassured Member</td>
+  <td>Anonymous</td>
+  <td>Certificates with no Name under Member SubRoot.  Limited to 6 months expiry.</td>
+ </tr>
+ <tr>
+  <td rowspan="1">50-99</td>
+  <td>Assured Member</td>
+  <td>Verified</td>
+  <td>Certificates with Verified Name for S/MIME, web servers, "digital signing."
+      Expiry after 24 months is available.</td>
+ </tr>
+ <tr>
+  <td rowspan="2">100++</td>
+  <td rowspan="2">Assurer</td>
+  <td>Code-signing</td>
+  <td>Can create Code-signing certificates </td>
+ </tr>
+</table>
+
+<span class="figure">Table 3.2.b - How Assurance Points are used in Certificates</span>
+
+</center>
+<br>
+
+
+
+<h4><a name="p3.2.3" id="p3.2.3">3.2.3. Authentication of organization identity</a></h4>
+
+
+<p>
+Verification of organisations is delegated by
+the Assurance Policy to the
+Organisation Assurance Policy
+(<a href="http://www.cacert.org/policy/OrganisationAssurancePolicy.html">COD11</a>).
+The reader is refered to the Organisation Assurance Policy,
+the following is representative and brief only.
+</p>
+
+<p>
+Organisations present special challenges.
+The Assurance process for Organisations is
+intended to permit the organisational Name to
+appear in certificates.
+The process relies heavily on the Individual
+process described above.
+</p>
+
+<p>
+Organisation Assurance achieves the standard
+stated in the OAP, briefly presented here:
+</p>
+
+<ol type="a"><li>
+   the organisation exists,
+  </li><li>
+   the organisation name is correct and consistent,
+  </li><li>
+   signing rights: requestor can sign on behalf of the organisation, and
+  </li><li>
+   the organisation has agreed to the terms of the
+   CAcert Community Agreement
+   (<a href="http://www.cacert.org/policy/CAcertCommunityAgreement.html">COD9</a>),
+   and is therefore subject to Arbitration. 
+</li></ol>
+
+  <ul class="error">
+    <li> As of the current time of writing, OA lacks critical documentation and there are bugs identified with no response.</li>
+    <li> <a href="http://wiki.cacert.org/wiki/PolicyDrafts/OrganisationAssurance">documented bugs</a>. </li>
+    <li> Therefore Organisations will not participate in the current audit cycle of roots. </li>
+    <li> See <a href="http://wiki.cacert.org/wiki/OrganisationAssurance">wiki</a> for any progress on this. </li>
+  </ul>
+
+
+<h4><a name="p3.2.4" id="p3.2.4">3.2.4. Non-verified subscriber information</a></h4>
+
+<p>
+All information in the certificate is verified,
+see Relying Party Statement, &sect;4.5.2.
+</p>
+
+
+<h4><a name="p3.2.5" id="p3.2.5">3.2.5. Validation of authority</a></h4>
+
+<p>
+The authorisation to obtain a certificate is established as follows:
+</p>
+
+<p>
+<b>Addresses.</b>
+The member claims authority over a domain or email address
+when adding the address,  <a href="#p4.1.2">&sect;4.1.2</a>.
+(Control is tested by means described in <a href="#p4.2.2">&sect;4.2.2</a>.)
+</p>
+
+<p>
+<b>Individuals.</b>
+The authority to participate as a Member is established
+by the CAcert Community Agreement
+(<a href="http://www.cacert.org/policy/CAcertCommunityAgreement.html">COD9</a>).
+Assurances are requested by means of the signed CAP form.
+</p>
+
+<p>
+<b>Organisations.</b>
+The authority for Organisation Assurance is established
+in the COAP form, as signed by an authorised representative
+of the organisation.
+The authority for the
+Organisation Administrator
+(O-Admin) is also established on the
+COAP form.
+See Organisation Assurance Policy.
+</p>
+
+
+<h4><a name="p3.2.6" id="p3.2.6">3.2.6. Criteria for interoperation</a></h4>
+
+<p>
+CAcert does not currently issue certificates to subordinate CAs
+or other PKIs.
+Other CAs may become Members, and are then subject to the
+same reliance provisions as all Members.
+</p>
+
+<h3><a name="p3.3" id="p3.3">3.3. Re-key Requests</a></h3>
+
+<p>
+Via the Member's account.
+</p>
+
+<h3><a name="p3.4" id="p3.4">3.4. Revocations Requests</a></h3>
+
+<p>
+Via the Member's account.
+In the event that the Member has lost the password,
+or similar, the Member emails the support team who
+either work through the lost-password questions
+process or file a dispute.
+</p>
+
+
+
+<!-- *************************************************************** -->
+<h2><a name="p4" id="p4">4. CERTIFICATE LIFE-CYCLE OPERATIONAL REQUIREMENTS</a></h2>
+
+<p>
+The general life-cycle for a new certificate for an Individual Member is:
+</p>
+<ol><li>
+    Member adds claim to an address (domain/email).
+  </li><li>
+    System probes address for control.
+  </li><li>
+    Member creates key pair.
+  </li><li>
+    Member submits CSR with desired options (Anonymous Certificate, SSO, Root Certificate) .
+  </li><li>
+    System validates and accepts CSR based on
+    known information:  claims, assurance, controls, technicalities.
+  </li><li>
+    System signs certificate.
+  </li><li>
+    System makes signed certificate available to Member.
+  </li><li>
+    Member accepts certificate.
+</li></ol>
+    
+
+
+<p>
+(Some steps are not applicable, such as anonymous certificates.)
+</p>
+
+
+<h3><a name="p4.1" id="p4.1">4.1. Certificate Application</a></h3>
+
+<h4><a name="p4.1.1" id="p4.1.1">4.1.1. Who can submit a certificate application</a></h4>
+
+<p>
+Members may submit certificate applications.
+On issuance of certificates, Members become Subscribers.
+</p>
+
+<h4><a name="p4.1.2" id="p4.1.2">4.1.2. Adding Addresses</a></h4>
+
+<p>
+The Member can claim ownership or authorised control of
+a domain or email address on the online system.
+This is a necessary step towards issuing a certificate.
+There are these controls:
+</p>
+<ul><li>
+    The claim of ownership or control is legally significant
+    and may be referred to dispute resolution.
+  </li><li>
+    Each unique address can be handled by one account only.
+  </li><li>
+    When the Member makes the claim,
+    the certificate application system automatically initiates the
+    check of control, as below.
+</li></ul>
+
+
+<h4><a name="p4.1.3" id="p4.1.3">4.1.3. Preparing CSR </a></h4>
+
+<p>
+Members generate their own key-pairs.
+The CAcert Community Agreement
+(<a href="http://www.cacert.org/policy/CAcertCommunityAgreement.html">COD9</a>)
+obliges the Member as responsible for security.
+See CCA2.5, &sect;9.6.
+</p>
+
+<p>
+The Certificate Signing Request (CSR) is prepared by the
+Member for presentation to the automated system.
+</p>
+
+<h3><a name="p4.2" id="p4.2">4.2. Certificate application processing</a></h3>
+
+<!-- states what a CA does on receipt of the request -->
+
+<p>
+The CA's certificate application process is completely automated.
+Requests, approvals and rejections are handled by the website system.
+Each application should be processed in less than a minute.
+</p>
+<p>
+Where certificates are requested for more than one
+purpose, the requirements for each purpose must be
+fulfilled.
+</p>
+
+<!-- all sub headings in 4.2 are local, not from Chokhani. -->
+
+<h4><a name="p4.2.1" id="p4.2.1">4.2.1. Authentication </a></h4>
+
+<p>
+  The Member logs in to her account on the CAcert website
+      and thereby authenticates herself with username
+      and passphrase or with her CAcert client-side digital certificate.
+</p>
+
+<h4><a name="p4.2.2" id="p4.2.2">4.2.2. Verifying Control</a></h4>
+
+<p>
+In principle, at least two controls are placed on each address.
+</p>
+
+<p>
+<b><a name="ping">Email-Ping</a>.</b>
+Email addresses are verified by means of an
+<i><a name="ping">Email-Ping test</a></i>:
+</p>
+
+<ul><li>
+      The system generates a cookie
+      (a random, hard-to-guess code)
+      and formats it as a string.
+  </li><li>
+      The system sends the cookie
+      to the Member in an email.
+  </li><li>
+      Once the Member receives the email,
+      she enters the cookie into the website.
+  </li><li>
+      The entry of the code verifies
+      control of that email account.
+</li></ul>
+
+<p>
+<b><a name="email">Email Control</a>.</b>
+Email addresses for client certificates are verified by passing the
+following checks:
+</p>
+<ol>
+  <li>An Email-ping test
+      is done on the email address.
+      </li>
+  <li>The Member must have signed a CAP form or equivalent,
+      and been awarded at least one Assurance point.
+      </li>
+</ol>
+
+<p>
+<b><a name="domain">Domain Control</a>.</b>
+Domains addresses for server certificates are verified by passing two of the
+following checks:
+</p>
+<ol> <li>
+      An Email-ping test
+      is done on an email address chosen from <i>whois</i>
+      or interpolated from the domain name.
+  </li> <li>
+      The system generates a cookie
+      which is then placed in DNS
+      by the Member.
+  </li> <li>
+      The system generates a cookie
+      which is then placed in HTTP headers or a text file on the website
+      by the Member.
+  </li> <li>
+      Statement by at least 2 Assurers about
+      ownership/control of the domain name.
+  </li> <li>
+      The system generates a cookie
+      which is then placed in whois registry information
+      by the Member.
+</li> </ol>
+
+<p>
+Notes.</p>
+<ul><li>
+    Other methods can be added from time to time by CAcert.
+  </li><li>
+    Static cookies should remain for the duration of a certificate
+    for occasional re-testing.
+  </li><li>
+    Dynamic tests can be repeated at a later time of CAcert's choosing.
+  </li><li>
+    Domain control checks may be extended to apply to email control
+    in the future.
+</li></ul>
+
+
+  <ul class="q">
+    <li> As of the time of writing, only a singular Email-ping is implemented in the technical system. </li>
+    <li> A further approved check is the 1 pt Assurance. </li>
+    <li> Practically, this would mean that certificates can only be issued under Audit Roots to Members with 1 point. </li>
+    <li> Criteria DRC C.7.f, A.2.q, A.2.i indicate registry whois reading. Also A.2.h. </li>
+    <li> Current view is that this will be resolved in BirdShack. </li>
+  </ul>
+
+<h4><a name="p4.2.3" id="p4.2.3">4.2.3. Options Available</a></h4>
+
+<p>
+The Member has options available:
+</p>
+
+<ul>
+  <li>Each Email address that is verified
+      is available for Client Certificates.
+      </li>
+  <li>Each Domain address that is verified
+      is available for Server Certificates.
+      </li>
+  <li>If the Member is unassured then only the Member SubRoot is available.
+      </li>
+  <li>If the Member is Assured then both Assured Member and Member SubRoots
+      are available.
+      </li>
+  <li>If a Name is Assured then it may be
+      put in a client certificate or an OpenPGP signature.
+      </li>
+</ul>
+
+<h4><a name="p4.2.4" id="p4.2.4">4.2.4. Client Certificate Procedures</a></h4>
+
+<p>
+For an individual client certificate, the following is required.</p>
+<ul>
+  <li>The email address is claimed and added. </li>
+  <li>The email address is ping-tested. </li>
+  <li>For the Member Subroot, the Member must have
+      at least one point of Assurance and have signed a CAP form.</li>
+  <li>For the Assured Subroot, the Member must have
+      at least fifty points of Assurance. </li>
+  <li>To include a Name, the Name must be assured to at least fifty points. </li>
+
+</ul>
+
+
+<h4><a name="p4.2.5" id="p4.2.5">4.2.5. Server Certificate Procedures</a></h4>
+
+<p>
+For a server certificate, the following is required:</p>
+<ul>
+  <li>The domain is claimed and added. </li>
+  <li>The domain is checked twice as above. </li>
+  <li>For the Member SubRoot, the Member must have
+      at least one point of Assurance and have signed a CAP form.</li>
+  <li>For the Assured SubRoot, the Member must have
+      at least fifty points of Assurance. </li>
+</ul>
+
+
+
+<h4><a name="p4.2.6" id="p4.2.6">4.2.6. Code-signing Certificate Procedures</a></h4>
+
+<p>
+Code-signing certificates are made available to Assurers only.
+They are processed in a similar manner to client certificates.
+</p>
+
+<h4><a name="p4.2.7" id="p4.2.7">4.2.7. Organisation Domain Verification</a></h4>
+
+<p>
+Organisation Domains are handled under the Organisation Assurance Policy
+and the Organisation Handbook.
+</p>
+
+  <ul class="q">
+     <li> As of time of writing, there is no Handbook for Organisation Assurers or for the Organisation, and the policy needs rework; so (audit) roots will not have OA certs ....  </li>
+     <li> <a href="http://wiki.cacert.org/wiki/PolicyDrafts/OrganisationAssurance"> Drafts </a> for ongoing story. </li>
+  </ul>
+
+<h3><a name="p4.3" id="p4.3">4.3. Certificate issuance</a></h3>
+
+
+<!-- <a href="http://xkcd.com/153/"> <img align="right" src="http://imgs.xkcd.com/comics/cryptography.png"> </a> -->
+<h4><a name="p4.3.1" id="p4.3.1">4.3.1. CA actions during certificate issuance</a></h4>
+
+<p>
+<b>Key Sizes.</b>
+Members may request keys of any size permitted by the key algorithm.
+Many older hardware devices require small keys.
+</p>
+
+<p>
+<b>Algorithms.</b>
+CAcert currently only supports the RSA algorithm for X.509 keys.
+X.509 signing uses the SHA-1 message digest algorithm.
+OpenPGP Signing uses RSA signing over RSA and DSA keys.
+
+</p>
+
+<p>
+<b>Process for Certificates:</b>
+All details in each certificate are verified
+by the website issuance system.
+Issuance is based on a 'template' system that selects
+profiles for certificate lifetime, size, algorithm.
+</p>
+
+
+<ol><li>
+   The CSR is verified.
+  </li><li>
+   Data is extracted from CSR and verified:
+    <ul>
+      <li> Name &sect;3.1, </li>
+      <li> Email address <a href="#p4.2.2">&sect;4.2.2</a>, </li>
+      <li> Domain address <a href="#p4.2.2">&sect;4.2.2</a>. </li>
+    </ul>
+  </li><li>
+   Certificate is generated from template.
+  </li><li>
+   Data is copied from CSR.
+  </li><li>
+   Certificate is signed.
+  </li><li>
+   Certificate is stored as well as mailed.
+</li></ol>
+
+
+<p>
+<b>Process for OpenPGP key signatures:</b>
+All details in each Sub-ID are verified
+by the website issuance system.
+Issuance is based on the configuration that selects
+the profile for signature lifetime, size,
+algorithm following the process:
+</p>
+
+<ol><li>
+   The public key is verified.
+  </li><li>
+   Data is extracted from the key and verified (Name, Emails).
+   Only the combinations of data in Table 4.3.1 are permitted.
+  </li><li>
+   OpenPGP Key Signature is generated.
+  </li><li>
+   Key Signature is applied to the key.
+  </li><li>
+   The signed key is stored as well as mailed.
+</li></ol>
+
+<center>
+<table border="1" align="center" valign="top" cellpadding="5"><tbody>
+  <tr>
+    <td><br></td>
+    <td>Verified Name</td>
+    <td valign="top">Unverified Name<br></td>
+    <td>Empty Name<br></td>
+  </tr>
+  <tr>
+    <td>Verified email<br></td>
+    <td><center> <font title="pass." color="green" size="+3"> &#10004; </font>  </center></td>
+    <td valign="top"><center> <font title="pass." color="red" size="+3"> &#10008; </font> </center></td>
+    <td><center> <font title="pass." color="green" size="+3"> &#10004; </font>  </center></td>
+  </tr>
+  <tr>
+    <td>Unverified email</td>
+    <td><center> <font title="pass." color="red" size="+3"> &#10008; </font> </center></td>
+    <td valign="top"><center> <font title="pass." color="red" size="+3"> &#10008; </font> </center></td>
+    <td><center> <font title="pass." color="red" size="+3"> &#10008; </font> </center></td></tr><tr><td valign="top">Empty email<br></td>
+    <td valign="top"><center> <font title="pass." color="green" size="+3"> &#10004; </font>  </center></td>
+    <td valign="top"><center> <font title="pass." color="red" size="+3"> &#10008; </font> </center></td>
+    <td valign="top"><center> <font title="pass." color="red" size="+3"> &#10008; </font> </center></td>
+  </tr>
+</tbody></table><br>
+
+<span class="figure">Table 4.3.1.  Permitted Data in Signed OpenPgp Keys</span>
+</center>
+
+<h4><a name="p4.3.2" id="p4.3.2">4.3.2. Notification to subscriber by the CA of issuance of certificate</a></h4>
+
+<p>
+Once signed, the certificate is
+made available via the Member's account,
+and emailed to the Member.
+It is also archived internally.
+</p>
+
+<h3><a name="p4.4" id="p4.4">4.4. Certificate acceptance</a></h3>
+
+<h4><a name="p4.4.1" id="p4.4.1">4.4.1. Conduct constituting certificate acceptance</a></h4>
+
+<p>
+There is no need for the Member to explicitly accept the certificate.
+In case the Member does not accept the certificate,
+the certificate has to be revoked and made again.
+</p>
+
+<h4><a name="p4.4.2" id="p4.4.2">4.4.2. Publication of the certificate by the CA</a></h4>
+
+<p>
+CAcert does not currently publish the issued certificates
+in any repository.
+In the event that CAcert will run a repository,
+the publication of certificates and signatures
+there will be at the Member's options.
+However note that certificates that are issued
+and delivered to the Member are presumed to be
+published.  See &sect;2.2.
+</p>
+
+<h4><a name="p4.4.3" id="p4.4.3">4.4.3. Notification of certificate issuance by the CA to other entities</a></h4>
+
+<p>
+There are no external entities that are notified about issued certificates.
+</p>
+
+<h3><a name="p4.5" id="p4.5">4.5. Key pair and certificate usage</a></h3>
+
+<p>
+All Members (subscribers and relying parties)
+are obliged according to the
+CAcert Community Agreement
+(<a href="http://www.cacert.org/policy/CAcertCommunityAgreement.html">COD9</a>)
+See especially 2.3 through 2.5.
+</p>
+<h4><a name="p4.5.1" id="p4.5.1">4.5.1. Subscriber Usage and Responsibilities</a></h4>
+
+<p>
+Subscribers should use keys only for their proper purpose,
+as indicated by the certificate, or by wider agreement with
+others.
+</p>
+
+<h4><a name="p4.5.2" id="p4.5.2">4.5.2. Relying Party Usage and Responsibilities</a></h4>
+
+
+<p>
+Relying parties (Members) may rely on the following.
+</p>
+
+<center>
+  <table border="1" cellpadding="25"><tr><td>
+  <p align="center">
+  <big><b>Relying Party Statement</b></big>
+  <p>
+  Certificates are issued to Members only.<br><br>
+  All information in a certificate is verified.
+  </p>
+  </td></tr></table>
+</center>
+
+
+<p>
+The following notes are in addition to the Relying Party Statement,
+and can be seen as limitations on it.
+</p>
+
+<h5>4.5.2.a Methods of Verification </h5>
+<p>
+The term Verification as used in the Relying Party Statement means one of
+</p>
+<table border="1" cellpadding="5"><tr>
+  <th>Type</th><th>How</th><th>Authority</th><th>remarks</th>
+</tr><tr>
+  <th>Assurance</th><td>under CAcert Assurance Programme (CAP)</td>
+    <td>Assurance Policy</td>
+    <td>only information assured to 50 points under CAP is placed in the certificate </td>
+</tr><tr>
+  <th>Evaluation</th><td>under automated domain and email checks </td>
+    <td>this CPS</td>
+    <td>see &sect;4.2.2</td>
+</tr><tr>
+  <th>Controlled</th><td>programs or "profiles" that check the information within the CSR </td>
+    <td>this CPS</td>
+    <td>see &sect;7.1</td>
+</tr></table>
+
+<h5>4.5.2.b Who may rely</h5>
+<p>
+<b>Members may rely.</b>
+Relying parties are Members,
+and as such are bound by this CPS and the
+CAcert Community Agreement
+(<a href="http://www.cacert.org/policy/CAcertCommunityAgreement.html">COD9</a>).
+The licence and permission to rely is not assignable.
+</p>
+
+<p>
+<b>Suppliers of Software.</b>
+CAcert roots may be distributed in software,
+and those providers may
+enter into agreement with CAcert by means of the
+Third Party Vendor - Disclaimer and Licence
+(wip).
+This licence brings the supplier in to the Community
+to the extent that <span class="q"> ...WIP comment:</span>
+they agree to dispute resolution
+within CAcert's forum.
+</p>
+
+  <ul class="q">
+    <li> Just exactly what the 3PV-DaL says is unclear.</li>
+    <li> The document itself is more a collection of ideas.</li>
+  </ul>
+
+
+<p>
+<b>NRPs may not rely.</b>
+If not related to CAcert by means of an agreement
+that binds the parties to dispute resolution within CAcert's forum,
+a person is a Non-Related-Person (NRP).
+An NRP is not permitted to rely and is not a Relying Party.
+For more details, see the
+NRP - Disclaimer and Licence (<a href="http://www.cacert.org/policy/NRPDisclaimerAndLicence.html">COD4</a>).
+</p>
+
+<h5>4.5.2.c The Act of Reliance </h5>
+
+<p>
+<b>Decision making.</b>
+Reliance means taking a decision that is in part or in whole
+based on the information in the certificate.
+
+A Relying Party may incorporate
+the information in the certificate,
+and the implied information such as Membership,
+into her decision-making.
+In making a decision,
+a Relying Party should also:
+</p>
+
+<ul><li>
+    include her own overall risk equation,
+  </li><li>
+    include the general limitations of the Assurance process,
+    certificates, and wider security considerations,
+  </li><li>
+    make additional checks to provide more information,
+  </li><li>
+    consider any wider agreement with the other Member, and
+  </li><li>
+    use an appropriate protocol or custom of reliance (below).
+</li></ul>
+
+<p>
+<b>Examining the Certificate.</b>
+A Relying Party must make her own decision in using
+each certificate.  She must examine the certificate,
+a process called <i>validation</i>.
+Certificate-related information includes,
+but is not limited to:
+</p> 
+<ul><li>
+    Name,
+  </li><li>
+    expiry time of certificate,
+  </li><li>
+    current certificate revocation list (CRL),
+  </li><li>
+    certificate chain and
+    the validity check of the certificates in the chain,
+  </li><li>
+    issuer of certificate (CAcert),
+  </li><li>
+    SubRoot is intended for reliance (Assured, Organisation and Class 3)
+  </li><li>
+    purpose of certificate.
+</li></ul>
+
+<p>
+<b>Keeping Records.</b>
+Records should be kept, appropriate to the import of the decision.
+The certificate should be preserved.
+This should include sufficient
+evidence to establish who the parties are
+(especially, the certificate relied upon),
+to establish the transaction in question,
+and to establish the wider agreement that
+defines the act.
+</p>
+
+<p>
+<b>Wider Protocol.</b>
+In principle, reliance will be part of a wider protocol
+(customary method in reaching and preserving agreement)
+that presents and preserves sufficient of the evidence
+for dispute resolution under CAcert's forum of Arbitration.
+The protocol should be agreed amongst the parties,
+and tuned to the needs.
+This CPS does not define any such protocol.
+In the absence of such a protocol, reliance will be weakened;
+a dispute without sufficient evidence may be dismissed by an Arbitrator.
+</p>
+
+<p>
+<b>As Compared to Usage</b>.
+Reliance goes beyond Usage.  The latter is limited to
+letting the software act as the total and only Validation
+Authority.  When relying, the Member also augments
+the algorithmic processing of the software with her own
+checks of the business, technical and certificate aspect.
+</p>
+
+<h5>4.5.2.d Risks and Limitations of Reliance </h5>
+<p>
+<b>Roots and Naming.</b>
+Where the Class 1 root is used,
+this Subscriber may be a new Member
+including one with zero points.
+Where the Name is not provided,
+this indicates it is not available.
+In these circumstances,
+reliance is not defined,
+and Relying parties should take more care.
+See Table 4.5.2.
+</p>
+
+<center>
+<table border="1" cellpadding="5">
+ <tr>
+  <td></td>
+  <td colspan="4"><center><i>Statements of Reliance for Members</i></center></td>
+ </tr>
+ <tr>
+  <td><i>Class of Root</i></td>
+  <td><center><b>Anonymous</b><br>(all Members)</center></td>
+  <td><center><b>Named</b><br>(Assured Members only)</center></td>
+ </tr>
+ <tr>
+  <td><center>Class<br><big><b>1</b></big></center></td>
+  <td rowspan="2" bgcolor="red">
+       <b>Do not rely.</b><BR>
+       Relying party must use other methods to check. </td>
+  <td rowspan="2" bgcolor="orange">
+       Do not rely.
+       Although the named Member has been Assured by CAcert,
+       reliance is not defined with Class 1 root.<BR>
+       (issued for compatibility only).</td>
+ </tr>
+ <tr>
+  <td><center><big><b>Member</b></big><br>SubRoot</center></td>
+ </tr>
+ <tr>
+  <td><center>Class<br><big><b>3</b></big></center></td>
+  <td rowspan="2" bgcolor="orange">
+       Do not rely on the Name (being available).
+       The Member has been Assured by CAcert,
+       but reliance is undefined.</td>
+  <td rowspan="2">
+       The Member named in the certificate has been Assured by CAcert.</td>
+ </tr>
+ <tr>
+  <td><center><big><b>Assured</b></big><br>SubRoot</center></td>
+ </tr>
+</table>
+
+<span class="figure">Table 4.5.2.  Statements of Reliance</span>
+</center>
+
+<p>
+<b>Software Agent.</b>
+When relying on a certificate, relying parties should
+note that your software is responsible for the way it
+shows you the information in a certificate.
+If your software agent hides parts of the information,
+your sole remedy may be to choose another software agent.
+</p>
+
+<p>
+<b>Malware.</b>
+When relying on a certificate, relying parties should
+note that platforms that are vulnerable to viruses or
+trojans or other weaknesses may not process any certificates
+properly and may give deceptive or fraudulent results.
+It is your responsibility to ensure you are using a platform
+that is secured according to the needs of the application.
+</p>
+
+<h5>4.5.2.e When something goes wrong </h5>
+<p>
+In the event that an issue arises out of the Member's reliance,
+her sole avenue is <b>to file dispute under DRP</b>.
+See <a href="#p9.13">&sect;9.13</a>.
+<!-- DRC_A&sect;A.4.d -->
+For this purpose, the certificate (and other evidence) should be preserved.
+</p>
+
+<p>
+<b>Which person?</b>
+Members may install certificates for other individuals or in servers,
+but the Member to whom the certificate is issued
+remains the responsible person.
+E.g., under Organisation Assurance, an organisation is issued
+a certificate for the use by individuals
+or servers within that organisation,
+but the Organisation is the responsible person.
+</p>
+
+<!-- <a href="http://xkcd.com/424/"> <img align="right" src="http://imgs.xkcd.com/comics/security_holes.png"> </a>  -->
+<p>
+<b>Software Agent.</b>
+If a Member is relying on a CAcert root embedded in
+the software as supplied by a vendor,
+the risks, liabilities and obligations of the Member
+do not automatically transfer to the vendor.
+</p>
+
+<h3><a name="p4.6" id="p4.6">4.6. Certificate renewal</a></h3>
+
+<p>
+A certificate can be renewed at any time.
+The procedure of certificate renewal is the same
+as for the initial certificate issuance.
+</p>
+
+<h3><a name="p4.7" id="p4.7">4.7. Certificate re-key</a></h3>
+
+<p>
+Certificate "re-keyings" are not offered nor supported.
+A new certificate with a new key has to be requested and issued instead,
+and the old one revoked.
+</p>
+
+<h3><a name="p4.8" id="p4.8">4.8. Certificate modification</a></h3>
+
+<p>
+Certificate "modifications" are not offered nor supported.
+A new certificate has to be requested and issued instead.
+</p>
+
+<h3><a name="p4.9" id="p4.9">4.9. Certificate revocation and suspension</a></h3>
+
+<h4><a name="p4.9.1" id="p4.9.1">4.9.1. Circumstances for revocation</a></h4>
+<p>
+Certificates may be revoked under the following circumstances:
+</p>
+
+<ol><li>
+    As initiated by the Subscriber through her online account.
+  </li><li>
+    As initiated in an emergency action by a
+    support team member.
+    Such action will immediately be referred to dispute resolution
+    for ratification.
+  </li><li>
+    Under direction from the Arbitrator in a duly ordered ruling
+    from a filed dispute.
+</li></ol>
+
+<p>
+These are the only three circumstances under which a
+revocation occurs.
+</p>
+
+<h4><a name="p4.9.2" id="p4.9.2">4.9.2. Who can request revocation</a></h4>
+
+<p>
+As above.
+</p>
+
+<h4><a name="p4.9.3" id="p4.9.3">4.9.3. Procedure for revocation request</a></h4>
+<p>
+The Subscriber logs in to her online account through
+the website at http://www.cacert.org/ .
+</p>
+
+<p>
+In any other event such as lost passwords or fraud,
+a dispute should be filed
+by email at
+    &lt; support AT cacert DOT org &gt;
+</p>
+
+<h4><a name="p4.9.4" id="p4.9.4">4.9.4. Revocation request grace period</a></h4>
+
+<p>No stipulation.</p>
+
+<h4><a name="p4.9.5" id="p4.9.5">4.9.5. Time within which CA must process the revocation request</a></h4>
+
+<p>
+The revocation automated in the Web Interface for subscribers,
+and is handled generally in less than a minute.
+</p>
+
+<p>
+A filed dispute that requests a revocation should be handled
+within a five business days, however the Arbitrator has discretion.
+</p>
+
+<h4><a name="p4.9.6" id="p4.9.6">4.9.6. Revocation checking requirement for relying parties</a></h4>
+
+<p>
+Each revoked certificate is recorded in the
+certificate revocation list (CRL).
+Relying Parties must check a certificate against
+the most recent CRL issued, in order to validate
+the certificate for the intended reliance.
+</p>
+
+<h4><a name="p4.9.7" id="p4.9.7">4.9.7. CRL issuance frequency (if applicable)</a></h4>
+
+<p>
+A new CRL is issued after every certificate revocation.
+</p>
+
+<h4><a name="p4.9.8" id="p4.9.8">4.9.8. Maximum latency for CRLs (if applicable)</a></h4>
+
+<p>
+The maximum latency between revocation and issuance of the CRL is 1 hour.
+</p>
+
+<h4><a name="p4.9.9" id="p4.9.9">4.9.9. On-line revocation/status checking availability</a></h4>
+
+<p>
+OCSP is available at
+http://ocsp.cacert.org/ .
+</p>
+
+<h4><a name="p4.9.10" id="p4.9.10">4.9.10. On-line revocation checking requirements</a></h4>
+<p>
+Relying parties must check up-to-date status before relying.
+</p>
+
+<h4><a name="p4.9.11" id="p4.9.11">4.9.11. Other forms of revocation advertisements available</a></h4>
+<p>
+None.
+</p>
+
+<h4><a name="p4.9.12" id="p4.9.12">4.9.12. Special requirements re key compromise</a></h4>
+<p>
+Subscribers are obliged to revoke certificates at the earliest opportunity.
+</p>
+
+<h4><a name="p4.9.13" id="p4.9.13">4.9.13. Circumstances for suspension</a></h4>
+
+<p>
+Suspension of certificates is not available.
+</p>
+
+<h4><a name="p4.9.14" id="p4.9.14">4.9.14. Who can request suspension</a></h4>
+<p>
+Not applicable.
+</p>
+
+<h4><a name="p4.9.15" id="p4.9.15">4.9.15. Procedure for suspension request</a></h4>
+<p>
+Not applicable.
+</p>
+
+<h4><a name="p4.9.16" id="p4.9.16">4.9.16. Limits on suspension period</a></h4>
+<p>
+Not applicable.
+</p>
+
+
+
+<h3><a name="p4.10" id="p4.10">4.10. Certificate status services</a></h3>
+
+<h4><a name="p4.10.1" id="p4.10.1">4.10.1. Operational characteristics</a></h4>
+<p>
+OCSP is available
+at http://ocsp.cacert.org/ .
+</p>
+
+<h4><a name="p4.10.2" id="p4.10.2">4.10.2. Service availability</a></h4>
+
+<p>
+OCSP is made available on an experimental basis.
+</p>
+
+<h4><a name="p4.10.3" id="p4.10.3">4.10.3. Optional features</a></h4>
+
+<p>
+No stipulation.
+</p>
+
+<h3><a name="p4.11" id="p4.11">4.11. End of subscription</a></h3>
+
+<p>
+Certificates include expiry dates.
+</p>
+
+<h3><a name="p4.12" id="p4.12">4.12. Key escrow and recovery</a></h3>
+
+<h4><a name="p4.12.1" id="p4.12.1">4.12.1. Key escrow and recovery policy and practices</a></h4>
+
+<p>
+CAcert does not generate nor escrow subscriber keys.
+</p>
+
+<h4><a name="p4.12.2" id="p4.12.2">4.12.2. Session key encapsulation and recovery policy and practices</a></h4>
+
+<p>
+No stipulation.
+</p>
+
+
+
+<!-- *************************************************************** -->
+<h2><a name="p5" id="p5">5. FACILITY, MANAGEMENT, AND OPERATIONAL CONTROLS</a></h2>
+
+<!-- <a href="http://xkcd.com/87/"> <img align="right" src="http://imgs.xkcd.com/comics/velociraptors.jpg"> </a>  -->
+
+<h3><a name="p5.1" id="p5.1">5.1. Physical controls</a></h3>
+
+<p>
+Refer to Security Policy (<a href="http://svn.cacert.org/CAcert/Policies/SecurityPolicy.html">COD8</a>)</p>
+<ul><li>
+    Site location and construction - SP2.1
+  </li><li>
+    Physical access - SP2.3
+</li></ul>
+
+
+
+<h4><a name="p5.1.3" id="p5.1.3">5.1.3. Power and air conditioning</a></h4>
+<p>
+Refer to Security Policy 2.1.2 (<a href="http://svn.cacert.org/CAcert/Policies/SecurityPolicy.html">COD8</a>)
+</p>
+<h4><a name="p5.1.4" id="p5.1.4">5.1.4. Water exposures</a></h4>
+<p>
+Refer to Security Policy 2.1.4 (<a href="http://svn.cacert.org/CAcert/Policies/SecurityPolicy.html">COD8</a>)
+</p>
+<h4><a name="p5.1.5" id="p5.1.5">5.1.5. Fire prevention and protection</a></h4>
+<p>
+Refer to Security Policy 2.1.4 (<a href="http://svn.cacert.org/CAcert/Policies/SecurityPolicy.html">COD8</a>)
+</p>
+<h4><a name="p5.1.6" id="p5.1.6">5.1.6. Media storage</a></h4>
+<p>
+Refer to Security Policy 4.3 (<a href="http://svn.cacert.org/CAcert/Policies/SecurityPolicy.html">COD8</a>)
+</p>
+<h4><a name="p5.1.7" id="p5.1.7">5.1.7. Waste disposal</a></h4>
+<p>
+No stipulation.
+</p>
+<h4><a name="p5.1.8" id="p5.1.8">5.1.8. Off-site backup</a></h4>
+<p>
+Refer to Security Policy 4.3 (<a href="http://svn.cacert.org/CAcert/Policies/SecurityPolicy.html">COD8</a>)
+</p>
+
+<h3><a name="p5.2" id="p5.2">5.2. Procedural controls</a></h3>
+
+<h4><a name="p5.2.1" id="p5.2.1">5.2.1. Trusted roles</a></h4>
+
+<ul>
+   <li><b> Technical teams:</b>
+   <ul>
+       <li>User support personnel</li>
+       <li>Systems Administrators -- critical and non-critical</li>
+       <li>Softare Developers</li>
+       <li>controllers of keys</li>
+   </ul>
+   Refer to Security Policy 9.1 (<a href="http://svn.cacert.org/CAcert/Policies/SecurityPolicy.html">COD8</a>)
+   
+   </li>
+
+   <li><b>Assurance:</b>
+   <ul>
+       <li>Assurers</li>
+       <li> Any others authorised under COD13  </li>
+   </ul>
+   Refer to Assurance Policy (<a href="http://www.cacert.org/policy/AssurancePolicy.html">COD13</a>)
+   </li>
+
+   <li><b>Governance:</b>
+   <ul>
+       <li>Directors (members of the CAcert Inc. committee, or "Board") </li>
+       <li>Internal Auditor</li>
+       <li>Arbitrator</li>
+   </ul>
+   </li>
+</ul>
+
+
+<h4><a name="p5.2.2" id="p5.2.2">5.2.2. Number of persons required per task</a></h4>
+<p>
+CAcert operates to the principles of <i>four eyes</i> and <i>dual control</i>.
+All important roles require a minimum of two persons.
+The people may be tasked to operate
+with an additional person observing (<i>four eyes</i>),
+or with two persons controlling (<i>dual control</i>).
+</p>
+
+<h4><a name="p5.2.3" id="p5.2.3">5.2.3. Identification and authentication for each role</a></h4>
+
+<p>
+All important roles are generally required to be assured
+at least to the level of Assurer, as per AP.
+Refer to Assurance Policy (<a href="http://www.cacert.org/policy/AssurancePolicy.html">COD13</a>).
+</p>
+
+<p>
+<b>Technical.</b>
+Refer to Security Policy 9.1 (<a href="http://svn.cacert.org/CAcert/Policies/SecurityPolicy.html">COD8</a>).
+</p>
+
+<h4><a name="p5.2.4" id="p5.2.4">5.2.4. Roles requiring separation of duties</a></h4>
+
+<p>
+Roles strive in general for separation of duties, either along the lines of
+<i>four eyes principle</i> or <i>dual control</i>.
+</p>
+
+<h3><a name="p5.3" id="p5.3">5.3. Personnel controls</a></h3>
+
+<h4><a name="p5.3.1" id="p5.3.1">5.3.1. Qualifications, experience, and clearance requirements</a></h4>
+
+<center>
+<table border="1" cellpadding="5">
+ <tr>
+  <td><b>Role</b></td> <td><b>Policy</b></td> <td><b>Comments</b></td>
+ </tr><tr>
+  <td>Assurer</td>
+  <td><a href="http://www.cacert.org/policy/AssurancePolicy.html"> COD13 </a></td>
+  <td>
+    Passes Challenge, Assured to 100 points.
+  </td>
+ </tr><tr>
+  <td>Organisation Assurer</td>
+  <td><a href="http://www.cacert.org/policy/OrganisationAssurancePolicy.html">COD11</a></td>
+  <td>
+    Trained and tested by two supervising OAs.
+  </td>
+ </tr><tr>
+  <td>Technical</td>
+  <td>SM =&gt; COD08</td>
+  <td>
+    Teams responsible for testing.
+  </td>
+ </tr><tr>
+  <td>Arbitrator</td>
+  <td><a href="http://www.cacert.org/policy/DisputeResolutionPolicy.html">COD7</a></td>
+  <td>
+    Experienced Assurers.
+  </td>
+ </tr>
+</table>
+
+<span class="figure">Table 5.3.1.  Controls on Roles</span>
+</center>
+
+
+<h4><a name="p5.3.2" id="p5.3.2">5.3.2. Background check procedures</a></h4>
+
+<p>
+Refer to Security Policy 9.1.3 (<a href="http://svn.cacert.org/CAcert/Policies/SecurityPolicy.html">COD8</a>).
+</p>
+<!-- <a href="http://xkcd.com/538/"> <img align="right" src="http://imgs.xkcd.com/comics/security.png"> </a> -->
+
+<h4><a name="p5.3.3" id="p5.3.3">5.3.3. Training requirements</a></h4>
+<p>No stipulation.</p>
+<h4><a name="p5.3.4" id="p5.3.4">5.3.4. Retraining frequency and requirements</a></h4>
+<p>No stipulation.</p>
+
+<h4><a name="p5.3.5" id="p5.3.5">5.3.5. Job rotation frequency and sequence</a></h4>
+<p>No stipulation.</p>
+
+<h4><a name="p5.3.6" id="p5.3.6">5.3.6. Sanctions for unauthorized actions</a></h4>
+<p>
+Any actions that are questionable
+- whether uncertain or grossly negligent -
+may be filed as a dispute.
+The Arbitrator has wide discretion in
+ruling on loss of points, retraining,
+or termination of access or status.
+Refer to DRP.
+</p>
+
+<h4><a name="p5.3.7" id="p5.3.7">5.3.7. Independent contractor requirements</a></h4>
+<p>No stipulation.</p>
+
+<h4><a name="p5.3.8" id="p5.3.8">5.3.8. Documentation supplied to personnel</a></h4>
+<p>No stipulation.</p>
+
+<h3><a name="p5.4" id="p5.4">5.4. Audit logging procedures</a></h3>
+
+<p>
+Refer to Security Policy 4.2, 5 (<a href="http://svn.cacert.org/CAcert/Policies/SecurityPolicy.html">COD8</a>).
+</p>
+
+<h3><a name="p5.5" id="p5.5">5.5. Records archival</a></h3>
+<p>
+The standard retention period is 7 years.
+Once archived, records can only be obtained and verified
+by means of a filed dispute.
+Following types of records are archived:
+</p>
+
+<center>
+<table border="1" cellpadding="5">
+ <tr>
+  <td><b>Record</b></td>
+  <td><b>Nature</b></td>
+  <td><b>Exceptions</b></td>
+  <td><b>Documentation</b></td>
+ </tr>
+ <tr>
+  <td>Member</td>
+  <td>username, primary and added addresses, security questions, Date of Birth</td>
+  <td>resigned non-subscribers: 0 years.</td>
+  <td>Security Policy and Privacy Policy</td>
+ </tr>
+ <tr>
+  <td>Assurance</td>
+  <td>CAP forms</td>
+  <td>"at least 7 years."<br> as per subsidiary policies</td>
+  <td>Assurance Policy 4.5</td>
+ </tr>
+ <tr>
+  <td>Organisation Assurance</td>
+  <td>COAP forms</td>
+  <td>as per subsidiary policies</td>
+  <td>Organisation Assurance Policy</td>
+ </tr>
+ <tr>
+  <td>certificates and revocations</td>
+  <td>  for reliance </td>
+  <td> 7 years after termination </td>
+  <td>this CPS</td>
+ </tr>
+ <tr>
+  <td>critical roles</td>
+  <td>background check worksheets</td>
+  <td>under direct Arbitrator control</td>
+  <td>Security Policy 9.1.3</td>
+ </tr>
+</table>
+
+<span class="figure">Table 5.5.  Documents and Retention </span>
+</center>
+
+
+<h3><a name="p5.6" id="p5.6">5.6. Key changeover</a></h3>
+
+<p>
+Refer to Security Policy 9.2 (<a href="http://svn.cacert.org/CAcert/Policies/SecurityPolicy.html">COD8</a>).
+</p>
+
+<h3><a name="p5.7" id="p5.7">5.7. Compromise and disaster recovery</a></h3>
+
+<p>
+Refer to Security Policy 5, 6 (<a href="http://svn.cacert.org/CAcert/Policies/SecurityPolicy.html">COD8</a>).
+(Refer to <a href="#p1.4">&sect;1.4</a> for limitations to service.)
+</p>
+
+
+<h3><a name="p5.8" id="p5.8">5.8. CA or RA termination</a></h3>
+
+<h4><a name="p5.8.1" id="p5.8.1">5.8.1 CA termination</a></h4>
+
+
+<p>
+<s>
+If CAcert should terminate its operation or be
+taken over by another organisation, the actions
+will be conducted according to a plan approved
+by the CAcert Inc. Board.
+</s>
+</p>
+
+<p>
+In the event of operational termination, the
+Roots (including SubRoots)
+and all private Member information will be secured.
+The Roots will be handed over to a responsible
+party for the sole purpose of issuing revocations.
+Member information will be securely destroyed.
+</p>
+
+<p>
+<span class="change">
+The CA cannot be transferrred to another organisation.
+</span>
+</p>
+
+<p>
+<s>
+In the event of takeover,
+the Board will decide if it is in the interest
+of the Members to be converted to the
+new organisation.
+Members will be notified about the conversion
+and transfer of the Member information.
+Such takeover, conversion or transfer may involve termination
+of this CPS and other documents.
+See &sect;9.10.2.
+Members will have reasonable time in which to file a related
+dispute after notification
+(at least one month).
+See &sect;9.13.
+</s>
+</p>
+<s>
+  <ul class="error">
+    <li> The ability to transfer is not given in any of CCA, PP or AP! </li>
+    <li> The Board does not have the power to terminate a policy, that is the role of policy group! </li>
+    <li> The right to transfer was against the principles of the CAcert? </li>
+    <li> Check Association Statutes.... </li>
+  </ul>
+</s>
+
+<p>
+<span class="change">
+<s>
+New root keys and certificates will be made available
+by the new organisation as soon as reasonably practical.
+</s>
+</span>
+</p>
+
+<h4><a name="p5.8.2" id="p5.8.2">5.8.2 RA termination</a></h4>
+
+<p>
+When an Assurer desires to voluntarily terminates
+her responsibilities, she does this by filing a dispute,
+and following the instructions of the Arbitrator.
+</p>
+
+<p>
+In the case of involuntary termination, the process is
+the same, save for some other party filing the dispute.
+</p>
+
+
+
+
+
+<!-- *************************************************************** -->
+<h2><a name="p6" id="p6">6. TECHNICAL SECURITY CONTROLS</a></h2>
+
+
+<!-- <a href="http://xkcd.com/221/"> <img align="right" src="http://imgs.xkcd.com/comics/random_number.png"> </a> -->
+
+<h3><a name="p6.1" id="p6.1">6.1. Key Pair Generation and Installation</a></h3>
+
+<h4><a name="p6.1.1" id="p6.1.1">6.1.1. Key Pair Generation</a></h4>
+
+<p>
+Subscribers generate their own Key Pairs.
+</p>
+
+<h4><a name="p6.1.2" id="p6.1.2">6.1.2. Subscriber Private key security</a></h4>
+
+<p>
+There is no technical stipulation on how Subscribers generate
+and keep safe their private keys,
+however, CCA 2.5 provides for general security obligations.
+See <a href="#p9.6">&sect;9.6</a>.
+</p>
+
+<h4><a name="p6.1.3" id="p6.1.3">6.1.3. Public Key Delivery to Certificate Issuer</a></h4>
+
+<p>
+Members login to their online account.
+Public Keys are delivered by cut-and-pasting
+them into the appropriate window.
+Public Keys are delivered in signed-CSR form
+for X.509 and in self-signed form for OpenPGP.
+</p>
+
+<h4><a name="p6.1.4" id="p6.1.4">6.1.4. CA Public Key delivery to Relying Parties</a></h4>
+
+<p>
+The CA root certificates are distributed by these means:
+</p>
+
+<ul><li>
+    Published on the website of CAcert,
+    in both HTTP and HTTPS.
+  </li><li>
+    Included in Third-Party Software such as
+    Browsers, Email-Clients.
+    Such suppliers are subject to the Third Party Vendor Agreement.
+</li></ul>
+
+<p class="q"> Third Party Vendor Agreement is early wip, only </p>
+
+<h4><a name="p6.1.5" id="p6.1.5">6.1.5. Key sizes</a></h4>
+
+<p>
+No limitation is placed on Subscriber key sizes.
+</p>
+
+<p>
+CAcert X.509 root and intermediate keys are currently 4096 bits.
+X.509 roots use RSA and sign with the SHA-1 message digest algorithm.
+See <a href="#p4.3.1">&sect;4.3.1</a>.
+</p>
+
+<p>
+OpenPGP Signing uses both RSA and DSA (1024 bits).
+</p>
+
+<p>
+CAcert adds larger keys and hashes
+in line with general cryptographic trends,
+and as supported by major software suppliers.
+</p>
+
+  <ul class="q">
+    <li> old Class 3 SubRoot is signed with MD5 </li>
+    <li> likely this will clash with future plans of vendors to drop acceptance of MD5</li>
+    <li> Is this a concern? </li>
+    <li> to users who have these certs, a lot? </li>
+    <li> to audit, not much? </li>
+  </ul>
+
+
+<h4><a name="p6.1.6" id="p6.1.6">6.1.6. Public key parameters generation and quality checking</a></h4>
+
+<p>
+No stipulation.
+</p>
+
+<h4><a name="p6.1.7" id="p6.1.7">6.1.7. Key Usage Purposes</a></h4>
+
+
+  <ul class="q">
+    <li> This section probably needs to detail the key usage bits in the certs. </li>
+  </ul>
+
+
+<p>
+CAcert roots are general purpose.
+Each root key may sign all of the general purposes
+- client, server, code.
+</p>
+
+<p>
+The website controls the usage purposes that may be signed.
+This is effected by means of the 'template' system.
+</p>
+
+
+
+<!-- <a href="http://xkcd.com/257/"> <img align="right" src="http://imgs.xkcd.com/comics/code_talkers.png"> </a> -->
+
+<h3><a name="p6.2" id="p6.2">6.2. Private Key Protection and Cryptographic Module Engineering Controls</a></h3>
+
+
+
+
+<h4><a name="p6.2.1" id="p6.2.1">6.2.1. Cryptographic module standards and controls</a></h4>
+
+<p>
+SubRoot keys are stored on a single machine which acts
+as a Cryptographic Module, or <i>signing server</i>.
+It operates a single daemon for signing only.
+The signing server has these security features:
+</p>
+
+<ul><li>
+    It is connected only by one
+    dedicated (serial USB) link
+    to the online account server.
+    It is not connected to the network,
+    nor to any internal LAN (ethernet),
+    nor to a console switch.
+  </li><li>
+    The protocol over the dedicated link is a custom, simple
+    request protocol that only handles certificate signing requests.
+  </li><li>
+    The daemon is designed not to reveal the key.
+  </li><li>
+    The daemon incorporates a dead-man switch that monitors
+    the one webserver machine that requests access.
+  </li><li>
+    The daemon shuts down if a bad request is detected.
+  </li><li>
+    The daemon resides on an encrypted partition.
+  </li><li>
+    The signing server can only be (re)started with direct
+    systems administration access.
+  </li><li>
+    Physical Access to the signing server is under dual control.
+</li></ul>
+
+<p>
+See &sect;5. and the Security Policy 9.3.1.
+</p>
+
+<p>
+(Hardware-based, commercial and standards-based cryptographic
+modules have been tried and tested, and similar have been tested,
+but have been found wanting, e.g., for short key lengths and
+power restrictions.)
+</p>
+
+<ol class="q"><li>
+    What document is responsible for architecture?  CPS?  SM?
+    <a href="http://www.cacert.org/help.php?id=7">website</a>?
+    SM punts it to CPS, so above stays.
+  </li><li>
+    There is no criteria on Architecture.
+  </li><li>
+    Old questions moved to SM.
+  </li><li>
+    See
+    <a href="http://www.cacert.org/help.php?id=7">
+    CAcert Root key protection</a> which should be deprecated by this CPS.
+</li></ol>
+
+
+<h3><a name="p6.3" id="p6.3">6.3. Other aspects of key pair management</a></h3>
+<h4><a name="p6.3.1" id="p6.3.1">6.3.1. Public key archival</a></h4>
+
+<p>
+Subscriber certificates, including public keys,
+are stored in the database backing the online system.
+They are not made available in a public- or subscriber-accessible
+archive, see &sect;2.
+They are backed-up by CAcert's normal backup procedure,
+but their availability is a subscriber responsibility.
+</p>
+
+<h4><a name="p6.3.2" id="p6.3.2">6.3.2. Certificate operational periods and key pair usage periods</a></h4>
+
+<p>
+The operational period of a certificate and its key pair
+depends on the Assurance status of the Member,
+see <a href="#p1.4.5">&sect;1.4.5</a> and Assurance Policy (<a href="http://www.cacert.org/policy/AssurancePolicy.html">COD13</a>).
+</p>
+
+<p>
+The CAcert (top-level) Root certificate
+has a 30 year expiry.
+SubRoots have 10 years, and are to be rolled over more quickly.
+The keysize of the root certificates are chosen
+in order to ensure an optimum security to CAcert
+Members based on current recommendations from the
+<a href="http://www.keylength.com/">cryptographic community</a>
+and maximum limits in generally available software.
+At time of writing this is 4096 bits.
+</p>
+
+<h3><a name="p6.4" id="p6.4">6.4. Activation data</a></h3>
+<p> No stipulation.  </p>
+
+<h3><a name="p6.5" id="p6.5">6.5. Computer security controls</a></h3>
+<p>
+Refer to Security Policy.
+</p>
+
+<h3><a name="p6.6" id="p6.6">6.6. Life cycle technical controls</a></h3>
+<p>
+Refer to SM7 "Software Development".
+</p>
+
+<h3><a name="p6.7" id="p6.7">6.7. Network security controls</a></h3>
+<p>
+Refer to SM3.1 "Logical Security - Network".
+</p>
+
+<h3><a name="p6.8" id="p6.8">6.8. Time-stamping</a></h3>
+<p>
+Each server synchronises with NTP.
+No "timestamping" service is currently offered.
+</p>
+
+  <ul class="q">
+    <li> How does the signing server syncronise if only connected over serial?</li>
+    <li>  How is timestamping done on records?</li>
+  </ul>
+
+
+
+
+<!-- *************************************************************** -->
+<h2><a name="p7" id="p7">7. CERTIFICATE, CRL, AND OCSP PROFILES</a></h2>
+
+<p>
+CAcert defines all the meanings, semantics and profiles
+applicable to issuance of certificates and signatures
+in its policies, handbooks and other documents.
+Meanings that may be written in external standards or documents
+or found in wider conventions are not
+incorporated, are not used by CAcert, and must not be implied
+by the Member or the Non-related Person.
+</p>
+
+<h3><a name="p7.1" id="p7.1">7.1. Certificate profile</a></h3>
+<h4><a name="p7.1.1" id="p7.1.1">7.1.1. Version number(s)</a></h4>
+<p class="q"> What versions of PGP are signed?  v3?  v4? </p>
+
+<p>
+Issued X.509 certificates are of v3 form.
+The form of the PGP signatures depends on several factors, therefore no stipulation.
+</p>
+
+<h4><a name="p7.1.2" id="p7.1.2">7.1.2. Certificate extensions</a></h4>
+
+<p>
+  Client certificates include the following extensions:
+</p>
+<ul>
+  <li>basicConstraints=CA:FALSE (critical)</li>
+  <li>keyUsage=digitalSignature,keyEncipherment,keyAgreement (critical)</li>
+  <li>extendedKeyUsage=emailProtection,clientAuth,msEFS,msSGC,nsSGC</li>
+  <li>authorityInfoAccess = OCSP;URI:http://ocsp.cacert.org</li>
+  <li>crlDistributionPoints=URI:&lt;crlUri&gt; where &lt;crlUri&gt; is replaced 
+    with the URI where the certificate revocation list relating to the 
+    certificate is found</li>
+  <li>subjectAltName=(as per <a href="#p3.1.1">&sect;3.1.1.</a>).</li>
+</ul>
+  <ul class="q">
+    <li> what about Client Certificates Adobe Signing extensions ?</li>
+    <li> SubjectAltName should become critical if DN is removed http://tools.ietf.org/html/rfc5280#section-4.2.1.6</li>
+  </ul>
+
+<p>
+  Server certificates include the following extensions:
+</p>
+<ul>
+  <li>basicConstraints=CA:FALSE (critical)</li>
+  <li>keyUsage=digitalSignature,keyEncipherment,keyAgreement (critical)</li>
+  <li>extendedKeyUsage=clientAuth,serverAuth,nsSGC,msSGC</li>
+  <li>authorityInfoAccess = OCSP;URI:http://ocsp.cacert.org</li>
+  <li>crlDistributionPoints=URI:&lt;crlUri&gt; where &lt;crlUri&gt; is replaced 
+    with the URI where the certificate revocation list relating to the 
+    certificate is found</li>
+  <li>subjectAltName=(as per <a href="#p3.1.1">&sect;3.1.1.</a>).</li>
+</ul>
+
+<p>
+  Code-Signing certificates include the following extensions:
+</p>
+<ul>
+  <li>basicConstraints=CA:FALSE (critical)</li>
+  <li>keyUsage=digitalSignature,keyEncipherment,keyAgreement (critical)</li>
+  <li>extendedKeyUsage=emailProtection,clientAuth,codeSigning,msCodeInd,msCodeCom,msEFS,msSGC,nsSGC</li>
+  <li>authorityInfoAccess = OCSP;URI:http://ocsp.cacert.org</li>
+  <li>crlDistributionPoints=URI:&lt;crlUri&gt; where &lt;crlUri&gt; is replaced 
+    with the URI where the certificate revocation list relating to the 
+    certificate is found</li>
+  <li>subjectAltName=(as per <a href="#p3.1.1">&sect;3.1.1.</a>).</li>
+</ul>
+  <ul class="q">
+    <li> what about subjectAltName for Code-signing</li>
+  </ul>
+
+<p>
+OpenPGP key signatures currently do not include extensions.
+In the future, a serial number might be included as an extension.
+</p>
+
+
+<h4><a name="p7.1.3" id="p7.1.3">7.1.3. Algorithm object identifiers</a></h4>
+<p>
+No stipulation.
+</p>
+
+<h4><a name="p7.1.4" id="p7.1.4">7.1.4. Name forms</a></h4>
+<p>
+Refer to <a href="#p3.1.1">&sect;3.1.1</a>.
+</p>
+
+<h4><a name="p7.1.5" id="p7.1.5">7.1.5. Name constraints</a></h4>
+<p>
+Refer to <a href="#p3.1.1">&sect;3.1.1</a>.
+</p>
+
+<h4><a name="p7.1.6" id="p7.1.6">7.1.6. Certificate policy object identifier</a></h4>
+<p>
+The following OIDs are defined and should be incorporated
+into certificates:
+</p>
+
+<table border="1" cellpadding="5">
+ <tr>
+  <td>
+    OID
+  </td>
+  <td>
+    Type/Meaning
+  </td>
+  <td>
+    Comment
+  </td>
+ </tr>
+ <tr>
+  <td>
+    1.3.6.1.4.1.18506.4.4
+  </td>
+  <td>
+    Certification Practice Statement
+  </td>
+  <td>
+    (this present document)
+  </td>
+ </tr>
+</table>
+
+<p>
+Versions are defined by additional numbers appended such as .1.
+</p>
+
+<h4><a name="p7.1.7" id="p7.1.7">7.1.7. Usage of Policy Constraints extension</a></h4>
+<p>
+No stipulation.
+</p>
+
+<h4><a name="p7.1.8" id="p7.1.8">7.1.8. Policy qualifiers syntax and semantics</a></h4>
+<p>
+No stipulation.
+</p>
+
+<h4><a name="p7.1.9" id="p7.1.9">7.1.9. Processing semantics for the critical Certificate Policies extension</a></h4>
+<p>
+No stipulation.
+</p>
+
+
+<h3><a name="p7.2" id="p7.2">7.2. CRL profile</a></h3>
+<h4><a name="p7.2.1" id="p7.2.1">7.2.1. Version number(s)</a></h4>
+<p>
+CRLs are created in X.509 v2 format.
+</p>
+
+<h4><a name="p7.2.2" id="p7.2.2">7.2.2. CRL and CRL entry extensions</a></h4>
+
+<p>
+No extensions.
+</p>
+
+<h3><a name="p7.3" id="p7.3">7.3. OCSP profile</a></h3>
+<h4><a name="p7.3.1" id="p7.3.1">7.3.1. Version number(s)</a></h4>
+<p>
+The OCSP responder operates in Version 1.
+</p>
+<h4><a name="p7.3.2" id="p7.3.2">7.3.2. OCSP extensions</a></h4>
+<p>
+No stipulation.
+</p>
+
+
+
+<!-- *************************************************************** -->
+<h2><a name="p8" id="p8">8. COMPLIANCE AUDIT AND OTHER ASSESSMENTS</a></h2>
+
+<p>
+There are two major threads of assessment:
+</p>
+
+<ul><li>
+  <b>Systems Audit</b>.
+  Analyses the CA for business and operations security.
+  This is conducted in two phases:  documents for compliance
+  with criteria, and operations for compliance with documentation.
+  </li><li>
+  <b>Code Audit</b>.
+  Analyses the source code.
+  This is conducted at two levels:
+  Security concepts at the web applications level,
+  and source code security and bugs review.
+</li></ul>
+
+<p>
+See the Audit page at
+<a href="http://wiki.cacert.org/wiki/Audit/">
+wiki.cacert.org/wiki/Audit/</a>
+for more information.
+</p>
+
+<h3><a name="p8.1" id="p8.1">8.1. Frequency or circumstances of assessment</a></h3>
+<p>
+The first audits started in late 2005,
+and since then, assessments have been an
+ongoing task.
+Even when completed, they are expected to
+be permanent features.
+</p>
+
+<ul><li>
+  <b>Systems Audit</b>.
+  <span class="q">
+  The first phase of the first audit is nearing completion.
+  The second phase starts in earnest when documentation is in
+  effect, at lease as DRAFT under PoP.
+  As the second phase is dependent on
+  this CPS and the Security Policy, they will
+  be in effect as DRAFT at least
+  before the first audit is completed.
+  Only prior and completed audits can be reported here.
+  </span>
+  </li><li>
+  <b>Code Audit</b>.
+  <span class="q">
+  A complete review of entire source code has not yet been completed.
+  </span>
+</li></ul>
+
+<h3><a name="p8.2" id="p8.2">8.2. Identity/qualifications of assessor</a></h3>
+
+<p>
+<b>Systems Auditors.</b>
+CAcert uses business systems auditors with broad experience
+across the full range of business, information systems
+and security fields.
+In selecting a business systems auditor, CAcert looks for
+experience that includes but is not limited to
+cryptography, PKI, governance, auditing,
+compliance and regulatory environments,
+business strategy, software engineering,
+networks, law (including multijurisdictional issues),
+identity systems, fraud, IT management.
+</p>
+
+<!-- <center><a href="http://xkcd.com/511/"> <img src="http://imgs.xkcd.com/comics/sleet.png"> </a> </center> -->
+
+<p>
+<b>Code Auditors.</b>
+See Security Policy, sections 7, 9.1.
+</p>
+
+<h3><a name="p8.3" id="p8.3">8.3. Assessor's relationship to assessed entity</a></h3>
+
+<p>
+Specific internal restrictions on audit personnel:
+</p>
+
+<ul><li>
+    Must be Assured by CAcert Assurers
+    and must be background checked.
+  </li><li>
+    Must not have been active in any (other) role in CAcert.
+    Specifically, must not be an Assurer, a member of the association,
+    or in any other defined role or office.
+  </li><li>
+    Although the Auditor may be expected to undertake various
+    of the activities (Assurance, Training)
+    during the process of the audit, any results are frozen
+    until resignation as auditor is effected.
+  </li><li>
+    The Auditor is required to declare to CAcert all
+    potential conflicts of interest on an ongoing basis.
+</li></ul>
+
+<p>
+Specific external restrictions on audit personnel:
+</p>
+
+<ul><li>
+    Should have a verifiable and lengthy history in
+    user privacy and user security.
+  </li><li>
+    Must not have worked for a competitive organisation.
+  </li><li>
+    Must not have worked for national security, intelligence,
+    LEO or similar agencies.
+</li></ul>
+
+<p>
+An Auditor may convene an audit team.
+The same restrictions apply in general
+to all members of the team, but may be varied.
+Any deviations must be documented and approved
+by the CAcert Inc. Board.
+</p>
+
+<h3><a name="p8.4" id="p8.4">8.4. Topics covered by assessment</a></h3>
+
+<p>
+Systems Audits are generally conducted to criteria.
+CAcert requires that the criteria are open:
+</p>
+
+<ul><li>
+    Published.
+    The criteria must be reviewable by all interested parties.
+  </li><li>
+    Understandable.
+    They should be understandable, in that they provide the 
+    sufficient information in a readable form for interested
+    parties to follow the gist and importance.
+    (Arcane security criteria may stretch this requirement.)
+  </li><li>
+    Complete.
+    There must be sufficent background information that the
+    whole story is there.  Especially, criteria that refer
+    to undocumented practices or conventions deliberately
+    kept secret must be avoided.
+  </li><li>
+    Applicable.  The criteria should relate directly
+    and unambiguously to a need of the identified interested parties
+    (Members, Relying Parties, Subscribers, Assurers).
+</li></ul>
+
+<p>
+See
+<a href="http://rossde.com/CA_review/">DRC</a>
+for the current criteria.
+If Auditor determines that a criteria fails to
+follow the meet the above requirements, then the criteria
+should be reworked to conform, or should be dropped
+(both with explanatory notes).
+</p>
+
+<h3><a name="p8.5" id="p8.5">8.5. Actions taken as a result of deficiency</a></h3>
+<p>
+See the current
+<a href="http://wiki.cacert.org/wiki/Audit/Done">Audit Done list</a>
+for work completed, and
+<a href="http://wiki.cacert.org/wiki/AuditToDo">Audit Todo list</a>
+for work in progress.
+</p>
+
+<p>
+Auditor may issue directives instructing changes,
+where essential to audit success or other extreme
+situations.
+Directives should be grounded on criteria,
+on established minimum or safe practices,
+or clearly described logic.
+Adequate discussion with Community
+(e.g., CAcert Inc. Board and with Policy Group)
+should precede any directive.
+They should be presented to the same standard
+as the criteria, above.
+</p>
+
+<p>
+The
+<a href="http://wiki.cacert.org/wiki/AuditDirectives">
+wiki.cacert.org/wiki/AuditDirectives</a>
+documents issued directives and actions.
+</p>
+
+<h3><a name="p8.6" id="p8.6">8.6. Communication of results</a></h3>
+
+<p>
+Current and past Audit information is available at
+<a href="http://wiki.cacert.org/wiki/Audit/">wiki.CAcert.org/wiki/Audit/</a>.
+CAcert runs an open disclosure policy and
+Audit is no exception.
+</p>
+
+<p>
+This CPS and other documents are subject to
+the process in Policy on Policy (<a href="http://www.cacert.org/policy/PolicyOnPolicy.html">COD1</a>).
+Audits cover the overall processes more
+than any one document, and documents may vary
+even as Audit reports are delivered.
+</p>
+
+
+
+
+<!-- *************************************************************** -->
+<h2><a name="p9" id="p9">9. OTHER BUSINESS AND LEGAL MATTERS</a></h2>
+<h3><a name="p9.1" id="p9.1">9.1. Fees</a></h3>
+
+
+<p>
+The current fees structure is posted at
+<a href="http://wiki.cacert.org/wiki/Price">wiki.cacert.org/wiki/Price</a>.
+Changes to the fees structure will be announced
+from time to time on the <a href="http://blog.cacert.org/">blog</a>.
+CAcert retains the right to charge fees for services.
+All fees are non-refundable.
+</p>
+
+
+<h3><a name="p9.2" id="p9.2">9.2. Financial responsibility</a></h3>
+
+<p>
+Financial risks are dealt with primarily by
+the Dispute Resolution Policy
+(<a href="http://www.cacert.org/policy/DisputeResolutionPolicy.html">COD7</a>).
+</p>
+
+<h4><a name="p9.2.1" id="p9.2.1">9.2.1. Insurance coverage</a></h4>
+
+<p>
+No stipulation.
+</p>
+
+<h4><a name="p9.2.2" id="p9.2.2">9.2.2. Other assets</a></h4>
+
+<p>
+No stipulation.
+</p>
+
+<h4><a name="p9.2.3" id="p9.2.3">9.2.3. Insurance or warranty coverage for end-entities</a></h4>
+
+<p>
+No stipulation.
+</p>
+
+<h3><a name="p9.3" id="p9.3">9.3. Confidentiality of business information</a></h3>
+
+<h4><a name="p9.3.1" id="p9.3.1">9.3.1. Scope of confidential information</a></h4>
+
+<p>
+CAcert has a policy of transparency and openness.
+The default posture is that information is public
+to the extent possible,
+unless covered by specific policy provisions
+(for example, passwords)
+or rulings by Arbitrator.
+</p>
+
+<h3><a name="p9.4" id="p9.4">9.4. Privacy of personal information</a></h3>
+
+<!-- <center><a href="http://xkcd.com/46/"> <img src="http://imgs.xkcd.com/comics/secrets.jpg"> </a> </center> -->
+<p>
+Privacy is covered by the
+CCA (COD9)
+and the Privacy Policy
+(<a href="PrivacyPolicy.html">COD5</a>).
+</p>
+
+<h4><a name="p9.4.1" id="p9.4.1">9.4.1. Privacy plan</a></h4>
+<p> No stipulation.  </p>
+<h4><a name="p9.4.2" id="p9.4.2">9.4.2. Information treated as private</a></h4>
+<p>
+Member's Date of Birth and "Lost Password" questions are treated as fully private.
+</p>
+<h4><a name="p9.4.3" id="p9.4.3">9.4.3. Information not deemed private</a></h4>
+<p>
+To the extent that information is put into an issued certificate,
+that information is not deemed private,
+as it is expected to be published by the Member as part of routine use of
+the certificate.
+Such information generally includes
+Names, domains, email addresses, and certificate serial numbers.
+</p>
+<p>
+Under Assurance Policy
+(<a href="http://www.cacert.org/policy/AssurancePolicy.html">COD13</a>)
+the Member's status (as Assured, Assurer, etc) is available
+to other Members.
+</p>
+<p>
+Information placed in forums outside the online system
+(wiki, blogs, policies, etc) is not deemed private, and is
+generally deemed to be published as contributions by Members.
+See
+CCA1.3 (COD9).
+</p>
+<h4><a name="p9.4.4" id="p9.4.4">9.4.4. Responsibility to protect private information</a></h4>
+<p>
+CAcert is a privacy organisation
+and takes privacy more seriously.
+Any privacy issue may be referred to dispute resolution.
+</p>
+<h4><a name="p9.4.5" id="p9.4.5">9.4.5. Notice and consent to use private information</a></h4>
+<p>
+Members are permitted to rely on certificates of other Members.
+As a direct consequence of the general right to rely,
+Members may read and store the certificates
+and/or the information within them, where duly presented in
+a relationship, and to the extent necessary for
+the agreed relationship.
+</p>
+<h4><a name="p9.4.6" id="p9.4.6">9.4.6. Disclosure pursuant to judicial or administrative process</a></h4>
+<p>
+Any disclosure pursuant to process from foreign courts
+(or similar)
+is controlled by the Arbitrator.
+</p>
+<h4><a name="p9.4.7" id="p9.4.7">9.4.7. Other information disclosure circumstances</a></h4>
+<p>
+None.
+</p>
+
+<h3><a name="p9.5" id="p9.5">9.5. Intellectual property rights</a></h3>
+
+<p>
+CAcert is committed to the philosophy of
+an open and free Internet,
+broadly as encapsulated by open and free source.
+However, due to the strict control provisions
+imposed by the audit criteria (CCS),
+and the general environment and role of CAs,
+and the commitment to security of Members,
+some deviations are necessary.
+</p>
+
+<!-- <center><a href="http://xkcd.com/225/"> <img src="http://imgs.xkcd.com/comics/open_source.png"> </a> </center> -->
+
+<h4><a name="p9.5.1" id="p9.5.1">9.5.1. Ownership and Licence</a></h4>
+
+<p>
+Assets that fall under the control of CCS
+must be transferred to CAcert.
+See PoP 6.2
+(<a href="http://www.cacert.org/policy/PolicyOnPolicy.html#6.2">COD1</a>),
+CCA 1.3
+(<a href="http://www.cacert.org/policy/CAcertCommunityAgreement.html#1.3">COD9</a>).
+That is, CAcert is free to use, modify,
+distribute, and otherwise conduct the business
+of the CA as CAcert sees fit with the asset.
+</p>
+
+<h4><a name="p9.5.2" id="p9.5.2">9.5.2. Brand</a></h4>
+<p>
+The brand of CAcert
+is made up of its logo, name, trademark, service marks, etc.
+Use of the brand is strictly limited by the Board,
+and permission is required.
+See <a href="http://wiki.cacert.org/wiki/TopMinutes-20070917">
+m20070917.5</a>.
+</p>
+
+<h4><a name="p9.5.3" id="p9.5.3">9.5.3. Documents</a></h4>
+
+<p>
+CAcert owns or requires full control over its documents,
+especially those covered by CCS.
+See PoP 6.2
+(<a href="http://www.cacert.org/policy/PolicyOnPolicy.html#6.2">COD1</a>).
+Contributors transfer the rights,
+see CCA 1.3
+(<a href="http://www.cacert.org/policy/CAcertCommunityAgreement.html#1.3">COD9</a>).
+Contributors warrant that they have the right to transfer.
+</p>
+
+<p>
+Documents are generally licensed under free and open licence.
+See
+<a href="http://wiki.cacert.org/wiki/PolicyDrafts/DocumentLicence">
+wiki.cacert.org/wiki/PolicyDrafts/DocumentLicence</a>.
+Except where explicitly negotiated,
+CAcert extends back to contributors a
+non-exclusive, unrestricted perpetual
+licence, permitting them to to re-use
+their original work freely.
+See PoP 6.4
+(<a href="http://www.cacert.org/policy/PolicyOnPolicy.html#6.4">COD1</a>),
+CCA 1.3
+(<a href="http://www.cacert.org/policy/CAcertCommunityAgreement.html#1.3">COD9</a>).
+</p>
+
+<h4><a name="p9.5.4" id="p9.5.4">9.5.4. Code</a></h4>
+
+<p>
+CAcert owns its code or requires full control over code in use
+by means of a free and open licence.
+See CCS.
+</p>
+
+<p class="q">
+See the (new, wip)
+<a href="http://svn.cacert.cl/Documents/SourceCodeManifesto.html">
+SourceCodeManifesto</a>.
+Maybe this can replace these two paras?
+</p>
+
+<p>
+CAcert licenses its code under GPL.
+CAcert extends back to contributors a
+non-exclusive, unrestricted perpetual
+licence, permitting them to to re-use
+their original work freely.
+</p>
+
+<h4><a name="p9.5.5" id="p9.5.5">9.5.5. Certificates and Roots</a></h4>
+
+<p>
+CAcert asserts its intellectual property rights over certificates
+issued to Members and over roots.
+See CCA 4.4
+(<a href="http://www.cacert.org/policy/CAcertCommunityAgreement.html#4.4">COD9</a>),
+CCS.
+The certificates may only be used by Members under
+<a href="http://www.cacert.org/policy/CAcertCommunityAgreement.html#4.4">COD9</a>,
+and,
+by others under the licences offered,
+such as
+Non-Related Persons - Disclaimer and Licence
+(<a href="http://www.cacert.org/policy/NRPDisclaimerAndLicence.html">COD4</a>).
+</p>
+
+<h3><a name="p9.6" id="p9.6">9.6. Representations and warranties</a></h3>
+
+
+<p>
+<b>Members.</b>
+All Members of the Community agree to the
+CAcert Community Agreement
+(<a href="http://www.cacert.org/policy/CAcertCommunityAgreement.html">COD9</a>),
+which is the primary document for
+representations and warranties.
+Members include Subscribers, Relying Parties,
+Registration Agents and the CA itself.
+</p>
+
+<p>
+<b>RAs.</b>
+Registration Agents are obliged additionally by Assurance Policy,
+especially 3.1, 4.1
+(<a href="http://www.cacert.org/policy/AssurancePolicy.html">COD13</a>).
+</p>
+
+<p>
+<b>CA.</b>
+The CA is obliged additionally by the CCS.
+</p>
+
+<p>
+<b>Third Party Vendors.</b>
+Distributors of the roots are offered the
+<span class="q">wip</span>
+3rd-Party Vendors - Disclaimer and Licence
+(3PV-DaL =&gt; CODx)
+and are offered
+<span class="q">wip</span>
+the same deal as Members to the extent that they agree
+to be Members in the Community.
+<span class="q">wip</span>
+</p>
+
+<h3><a name="p9.7" id="p9.7">9.7. Disclaimers of Warranties</a></h3>
+
+<p>
+Persons who have not accepted the above Agreements are offered the
+Non-Related Persons - Disclaimer and Licence
+(<a href="http://www.cacert.org/policy/NRPDisclaimerAndLicence.html">COD4</a>).
+Any representations and
+warranties are strictly limited to nominal usage.
+In essence, NRPs may USE but must not RELY.
+</p>
+
+<p>
+In today's aggressive fraud environment,
+and within the context of CAcert as a community CA,
+all parties should understand that CAcert
+and its Subscribers, Assurers and other roles
+provide service on a Best Efforts basis.
+See <a href="#p1.4">&sect;1.4</a>.
+CAcert seeks to provide an adequate minimum
+level of quality in operations for its Members
+without undue risks to NRPs.
+See
+<a href="http://svn.cacert.org/CAcert/principles.html">Principles</a>.
+</p>
+
+<p>
+CAcert on behalf of the Community and itself
+makes no Warranty nor Guarantee nor promise
+that the service or certificates are adequate
+for the needs and circumstances.
+</p>
+
+<h3><a name="p9.8" id="p9.8">9.8. Limitations of liability</a></h3>
+
+<h3><a name="p9.8.1" id="p9.8.1">9.8.1 Non-Related Persons </a></h3>
+
+<p>
+CAcert on behalf of related parties
+(RAs, Subscribers, etc) and itself
+disclaims all liability to NRPs
+in their usage of CA's certificates.
+See <a href="http://www.cacert.org/policy/NRPDisclaimerAndLicence.html">COD4</a>.
+</p>
+
+<h3><a name="p9.8.2" id="p9.8.2">9.8.2 Liabilities Between Members</a></h3>
+
+<p>
+Liabilities between Members
+are dealt with by internal dispute resolution,
+which rules on liability and any limits.
+See
+<a href="#9.13">&sect;9.13</a>.
+</p>
+
+
+<h3><a name="p9.9" id="p9.9">9.9. Indemnities</a></h3>
+
+<p>
+No stipulation.
+</p>
+
+<h3><a name="p9.10" id="p9.10">9.10. Term and termination</a></h3>
+<h4><a name="p9.10.1" id="p9.10.1">9.10.1. Term</a></h4>
+
+<p>
+No stipulation.
+</p>
+
+<h4><a name="p9.10.2" id="p9.10.2">9.10.2. Termination</a></h4>
+
+<p>
+Members file a dispute to terminate their agreement.
+See <a href="#p9.13">&sect;9.13</a> and CCA 3.3
+(<a href="http://www.cacert.org/policy/CAcertCommunityAgreement.html#3.3">COD9</a>).
+</p>
+
+<p>
+Documents are varied (including terminated) under <a href="http://www.cacert.org/policy/PolicyOnPolicy.html">COD1</a>.
+</p>
+
+<p>
+For termination of the CA, see <a href="#p5.8.1">&sect;5.8.1</a>.
+</p>
+
+<h4><a name="p9.10.3" id="p9.10.3">9.10.3. Effect of termination and survival</a></h4>
+
+<p>
+No stipulation.
+</p>
+
+<h3><a name="p9.11" id="p9.11">9.11. Individual notices and communications with participants</a></h3>
+
+<p>
+All participants are obliged to keep their listed
+primary email addresses in good working order.
+See CCA 3.5
+(<a href="http://www.cacert.org/policy/CAcertCommunityAgreement.html#3.5">COD9</a>).
+</p>
+
+
+<h3><a name="p9.12" id="p9.12">9.12. Amendments</a></h3>
+
+<p>
+Amendments to the CPS are controlled by <a href="http://www.cacert.org/policy/PolicyOnPolicy.html">COD1</a>.
+Any changes in Member's Agreements are notified under CCA 3.4
+(<a href="http://www.cacert.org/policy/CAcertCommunityAgreement.html#3.4">COD9</a>).
+</p>
+
+<h3><a name="p9.13" id="p9.13">9.13. Dispute resolution provisions</a></h3>
+
+<p>
+CAcert provides a forum and facility for any Member
+or other related party to file a dispute.
+</p>
+
+<ul><li>
+    The CAcert
+    Dispute Resolution Policy
+    (<a href="http://www.cacert.org/policy/DisputeResolutionPolicy.html">COD7</a>)
+    includes rules for dispute resolution.
+  </li><li>
+    Filing is done via email to
+    &lt; support AT cacert DOT org &gt;
+</li></ul>
+
+<p>
+Members agree to file all disputes through CAcert's
+forum for dispute resolution.
+The rules include specific provisions to assist
+non-Members, etc, to file dispute in this forum.
+</p>
+
+
+<h3><a name="p9.14" id="p9.14">9.14. Governing law</a></h3>
+
+<p>
+The governing law is that of New South Wales, Australia.
+Disputes are generally heard before the Arbitrator
+under this law.
+Exceptionally, the Arbitrator may elect to apply the
+law of the parties and events, where in common,
+but this is unlikely because it may create results
+that are at odds with the Community.
+</p>
+
+<h3><a name="p9.15" id="p9.15">9.15. Compliance with Applicable Law</a></h3>
+
+<h3><a name="p9.15.1" id="p9.15.1">9.15.1 Digital Signature Law</a></h3>
+<p>
+The Commonwealth and States of Australia have passed
+various Electronic Transactions Acts that speak to
+digital signatures.  In summary, these acts follow
+the "technology neutral" model and permit but do not
+regulate the use of digital signatures.
+</p>
+
+<p>
+This especially means that the signatures created by
+certificates issued by CAcert are not in and of themselves
+legally binding human signatures, at least according to
+the laws of Australia.
+See <a href="#p1.4.3">&sect;1.4.3</a>.
+However, certificates may play a part in larger signing
+applications.  See <a href="#p1.4.1">&sect;1.4.1</a> for "digital signing" certificates.
+These applications may impose significant
+obligations, risks and liabilities on the parties.
+</p>
+
+<h3><a name="p9.15.2" id="p9.15.2">9.15.2 Privacy Law</a></h3>
+
+<p>
+See the Privacy Policy
+(<a href="PrivacyPolicy.html">COD5</a>).
+</p>
+
+<h3><a name="p9.15.3" id="p9.15.3">9.15.3 Legal Process from External Forums</a></h3>
+
+<p>
+CAcert will provide information about
+its Members only under legal subpoena or
+equivalent process
+from a court of competent jurisdiction.
+Any requests made by legal subpoena are
+treated as under the Dispute Resolution Policy
+See
+<a href="#p9.13">&sect;9.13</a>
+and
+<a href="http://www.cacert.org/policy/DisputeResolutionPolicy.html">COD7</a>.
+That is, all requests are treated as disputes,
+as only a duly empanelled Arbitrator has the
+authorisation and authority to rule on the
+such requests.
+<p>
+
+<p>
+A subpoena should
+include sufficient legal basis to support
+an Arbitrator in ruling that information
+be released pursuant to the filing,
+including the names of claimants in any civil case
+and an indication as to whether the claimants are
+Members or not
+(and are therefore subject to Dispute Resolution Policy).
+</p>
+
+<h3><a name="p9.16" id="p9.16">9.16. Miscellaneous provisions</a></h3>
+<h4><a name="p9.16.1" id="p9.16.1">9.16.1. Entire agreement</a></h4>
+
+<p>
+All Members of the Community agree to the
+CAcert Community Agreement
+(<a href="http://www.cacert.org/policy/CAcertCommunityAgreement.html">COD9</a>).
+This agreement also incorporates other key
+documents, being this CPS, DRP and PP.
+See CCA 4.2.
+</p>
+
+<p>
+The Configuration-Control Specification
+is the set of policies that rule over the
+Community, of which the above documents are part.
+See COD2.
+Documents that have reached full POLICY status
+are located at
+<a href="http://www.cacert.org/policy/">
+www.cacert.org/policy/</a>.
+Although detailed practices may
+be found in other places on the website
+and on the wiki, the CCS documents that
+have reached DRAFT and POLICY status are
+the ruling documents.
+</p>
+
+<h4><a name="p9.16.2" id="p9.16.2">9.16.2. Assignment</a></h4>
+
+<p>
+The rights within CCA may not be ordinarily assigned.
+</p>
+
+<h4><a name="p9.16.3" id="p9.16.3">9.16.3. Severability</a></h4>
+
+<p>
+No stipulation.
+</p>
+
+<h4><a name="p9.16.4" id="p9.16.4">9.16.4. Enforcement (attorneys' fees and waiver of rights)</a></h4>
+
+<p>
+The Arbitrator will specify fees and remedies, if any.
+</p>
+
+<h4><a name="p9.16.5" id="p9.16.5">9.16.5. Force Majeure</a></h4>
+
+<p>
+No stipulation.
+</p>
+
+<h2>---This is the end of the Policy---</h2>
+
+
+</body>
+</html>
diff --git a/static/www/policy/DisputeResolutionPolicy.html b/static/www/policy/DisputeResolutionPolicy.html
new file mode 100644 (file)
index 0000000..2082bb0
--- /dev/null
@@ -0,0 +1,794 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
+        "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+ <meta http-equiv="CONTENT-TYPE" content="text/html; charset=utf-8" />
+ <title>Dispute Resulution Policy</title>
+<style type="text/css">
+<!--
+.first-does-not-work {
+        color : red;
+}
+.comment {
+        color : steelblue;
+}
+.q {
+        color : green;
+        font-weight: bold;
+        text-align: center;
+        font-style:italic;
+}
+.change {
+        color : blue;
+        font-weight: bold;
+}
+.change2 {
+        color : steelblue;
+}
+.strike {
+        color : blue;
+        text-decoration:line-through;
+}
+.draftadd {
+        color : darkblue;
+        font-weight: bold;
+        font-style: italic;
+}
+.draftdrop {
+        color : darkblue;
+        text-decoration:line-through;
+        font-style: italic;
+}
+-->
+</style>
+
+</head>
+<body>
+
+
+
+<div class="comment">
+<table width="100%">
+
+<tr>
+<td>
+  Name: DRP <a style="color: steelblue" href="//svn.cacert.org/CAcert/Policies/ControlledDocumentList.html">COD7</a><br />
+  Status: POLICY <a style="color: steelblue" href="//wiki.cacert.org/wiki/TopMinutes-20070917">m20070919.3</a><br />
+  &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;           <span class="draftadd">DRAFT p20110108 p20121213</span> <br />
+  Editor: <a style="color: steelblue" href="//wiki.cacert.org/TeusHagen">Teus Hagen
+</a><br />
+   Licence: <a style="color: steelblue" href="//wiki.cacert.org/Policy#Licence" title="this document is Copyright &copy; CAcert Inc., licensed openly under CC-by-sa with all disputes resolved under DRP.  More at wiki.cacert.org/Policy" > CC-by-sa+DRP </a><br /></td>
+<td valign="top" align="right">
+  <a href="//www.cacert.org/policy/PolicyOnPolicy.html"><img src="/images/cacert-policy.png" alt="TTP-Assist Status - POLICY" height="31" width="88" style="border-style: none;" /></a><br />
+  <a href="//www.cacert.org/policy/PolicyOnPolicy.html"><img src="/images/cacert-draft.png" alt="TTP-Assist Status - DRAFT" height="31" width="88" style="border-style: none;" /></a>
+
+</td>
+</tr>
+</table>
+</div>
+
+
+<h1> Dispute&nbsp;Resolution&nbsp;Policy </h1>
+
+<h2 id="s0">  0. Introduction</h2>
+
+<p>
+This is the Dispute Resolution Policy
+<span class="draftdrop">for CAcert</span>
+<span class="draftadd">for the CAcert Community, consisting of CAcert Inc and Members who agree to the CAcert Community Agreement (CCA)</span>.
+Disputes arising out of
+operations by CAcert
+<span class="draftadd">Inc</span>
+and interactions between
+<span class="draftadd">
+Members
+</span>
+may be addressed through this policy.
+This document also presents the rules for
+resolution of disputes.
+</p>
+
+<h3 id="s0.1">  0.1 Nature of Disputes </h3>
+
+<p>
+Disputes include:
+</p>
+
+<ul><li>
+    Requests for non-routine support actions.
+    CAcert support team has no authority to
+    act outside the normal support facilities made
+    available to 
+    <span class="draftadd">
+    Members;
+    </span>
+  </li><li>
+    Classical disputes where a <span class="draftadd">Member</span> or another
+    assert claims and demand remedies;
+  </li><li>
+    Requests by external organisations, including
+    legal processes from foreign courts;
+  </li><li>
+    Events initiated for training purposes.
+</li></ul>
+
+<h2 id="s1">  1. Filing</h2>
+
+<h3 id="s1.1">  1.1 Filing Party</h3>
+<p>
+Anyone may file a dispute.
+In filing, they become <i>Claimants</i>.
+</p>
+
+<h3 id="s1.2"> 1.2 Channel for Filing</h3>
+
+<p>
+Disputes are filed by being sent to the normal
+support channel of CAcert,
+and a fee may be payable.
+</p>
+
+<p>
+Such fees as are imposed on filing will be specified
+on the dispute resolution page of the website.
+</p>
+
+<h3 id="s1.3"> 1.3 Case Manager</h3>
+<p>
+The Case Manager (CM) takes control of the filing.
+</p>
+
+<ol><li>
+    CM makes an initial determination as
+    to whether this filing is a dispute
+    for resolution, or it is a request
+    for routine support.
+  </li><li>
+    CM logs the case and establishes such
+    documentation and communications support as is customary.
+  </li><li>
+    If any party acts immediately on the filing
+    (such as an urgent security action),
+    the CM names these parties to the case.
+  </li><li>
+    CM selects the Arbitrator.
+</li></ol>
+
+<p>
+The personnel within the CAcert support team
+are Case Managers, by default, or as directed
+by the Dispute Resolution Officer <span class="change2">(DRO)</span>.
+</p>
+
+<h3 id="s1.4"> 1.4 Contents</h3>
+<p>
+The filing must specify:
+</p>
+
+<ul><li>
+    The filing party(s), being the <i>Claimant(s)</i>.
+  </li><li>
+    The party(s) to whom the complaint is addressed to,
+    being the <i>Respondent(s)</i>.
+    This will be CAcert in the
+    case of requests for support actions.
+    It may be a <span class="draftadd">Member</span> (possibly unidentified) in the
+    case where one <span class="draftadd">Member</span> has given rise to a complaint against another.
+  </li><li>
+    The <i>Complaint</i>.
+    For example, a trademark has been infringed,
+    privacy has been breached,
+    or a <span class="draftadd">Member</span> has defrauded using a certificate.
+  </li><li>
+    The action(s) requested by the filing party
+    (technically, called the <i>relief</i>).
+    For example, to delete an account,
+    to revoke a certificate, or to stop a
+    trademark infringement.
+</li></ul>
+
+<p>
+If the filing is inadequate for lack of information
+or for format, the Case Manager
+may refile with the additional information,
+attaching the original messages.
+</p>
+
+<h3 id="s1.5"> 1.5 The Arbitrator</h3>
+
+<p>
+The Case Manager selects the Arbitrator according
+to the mechanism managed by the
+<span class="change2">DRO</span> <!-- Dispute Resolution Officer -->
+and approved from time to time.
+This mechanism is to maintain a list of Arbitrators available for
+dispute resolution.
+Each selected Arbitrator has the right to decline the dispute,
+and should decline a dispute with which there exists a conflict
+of interest.
+The reason for declining should be stated.
+If no Arbitrator accepts the dispute, the case is
+closed with status "declined."
+</p>
+
+<p>
+Arbitrators are experienced Assurers <span class="draftdrop">of CAcert</span>.
+They should be independent and impartial, including
+of CAcert <span class="draftadd">Inc.</span> itself where it becomes a party.
+</p>
+
+<h2 id="s2">  2. The Arbitration</h2>
+
+
+<h3 id="s2.1"> 2.1  Authority</h3>
+
+<p>
+The Board of CAcert <span class="draftadd">Inc.</span> and the 
+<span class="draftadd">
+Members of the Community
+</span>
+ vest in Arbitrators
+full authority to hear disputes and deliver rulings
+which are binding on CAcert <span class="draftadd">Inc.</span> and the
+<span class="draftadd">
+Members.
+</span>
+</p>
+
+
+<h3 id="s2.2"> 2.2  Preliminaries</h3>
+
+<p>
+The Arbitrator conducts some preliminaries:
+</p>
+
+<ul><li>
+   The Arbitrator reviews the available documentation
+   and affirms the rules of dispute resolution.
+   Jurisdiction is established, see below.
+  </li><li>
+   The Arbitrator affirms the governing law (NSW, Australia).
+   The Arbitrator may select local law and local
+   procedures where Claimants and all Respondents
+   agree, are under such jurisdiction, and it is deemed
+   more appropriate.
+   However, this is strictly limited to those parties,
+   and especially, CAcert <span class="draftadd">Inc.</span> and other parties
+   remain under the governing law.
+  </li><li>
+   The Arbitrator reviews the Respondents and Claimants
+   with a view to dismissal or joining of additional parties.
+   E.g., support personnel may be joined if emergency action was
+   taken.
+  </li><li>
+   Any parties that are not 
+   <span class="draftadd">
+   Members
+   </span>
+   and are not bound by the
+   <span class="draftdrop">CPS</span> <span class="draftadd">CCA</span>
+   are given the opportunity to enter into
+   CAcert and be bound by the
+   <span class="draftdrop">CPS</span> <span class="draftadd">CCA</span>
+   and these rules of arbitration.
+   If
+   <!-- <span class="draftdrop">these Non-Related Persons (NRPs)</span> <span class="change">they</span> -->
+   these Non-Related Persons (NRPs)
+   remain outside,
+   their rights and remedies under CAcert's policies
+   and forum are strictly limited to
+   <span class="strike">that</span> <span class="change2">those</span>
+   specified in the
+   <span class="draftdrop">Non-Related Persons -- Disclaimer and Licence</span> <span class="draftadd">Root Distribution License</span>.
+   NRPs
+   may proceed with Arbitration subject to preliminary orders
+   of the Arbitrator.
+  </li><li>
+   Participating 
+   <span class="draftadd">
+   Members
+   </span>
+   may not resign
+   <span class="change2">
+   from the Community
+   </span>
+   until the completion of the case.
+  </li><li>
+   The Arbitrator confirms that all parties accept
+   the forum of dispute resolution.
+   This is especially important where a
+   <span class="draftadd">
+   Member
+   </span>
+   might be
+   in a country with no Arbitration Act in law, or
+   where there is reason to believe that a party might
+   go to an external court.
+  </li><li>
+   The Arbitrator confirms that parties are representing
+   themselves.  Parties are entitled to be legally
+   represented, but are not encouraged to do so,
+   bearing in mind the volunteer nature of the
+   organisation and the size of the dispute.
+   If they do so<span class="change2">,</span>
+   they must declare such, including any changes.
+  </li><li>
+   The Arbitrator may appoint experienced Assurers
+   to assist and represent parties, especially for NRPs.
+   The Case Manager must not provide such assistance.
+  </li><li>
+   The Arbitrator is bound to maintain the balance
+   of legal fairness.
+  </li><li>
+   The Arbitrator may make any preliminary orders,
+   including protection orders and orders referring
+   to emergency actions already taken.
+  </li><li>
+   The Arbitrator may request any written pleadings,
+   counterclaims, and/or statements of defence.
+</li></ul>
+
+
+<h3 id="s2.3"> 2.3  Jurisdiction </h3>
+
+<p>
+Jurisdiction - the right or power to hear and rule on
+disputes - is initially established by clauses in the
+<span class="draftadd">
+CAcert Community Agreement.
+</span>
+The agreement must establish:
+</p>
+
+<ul><li>
+    That all Parties agree to binding Arbitration
+    in CAcert's forum of dispute resolution;
+  </li><li>
+    for all disputes relating to activities within
+    CAcert, issued certificates, roles and actions, etc;
+  </li><li>
+    as defined by these rules, including the selection
+    of a single Arbitrator;
+  </li><li>
+    under the Law of NSW, Australia;  and
+  </li><li>
+    the Parties keep email accounts in good working order.
+</li></ul>
+
+<p>
+An external court may have ("assert") jurisdiction to decide on
+issues such as trademark, privacy, contract and fraud,
+and may do so with legal remedies.
+These are areas where jurisdiction may need
+to be considered carefully:
+</p>
+
+<ul><li>
+    Where NRPs, being not Members of CAcert and not
+    bound by agreement, are parties to the dispute.
+    E.g., intellectual property disputes may involve
+    NRPs and their trademarks;
+  </li><li>
+    criminal actions or actions likely to result in criminal
+    proceedings,
+    e.g., fraud;
+  </li><li>
+    Contracts between 
+   <span class="draftadd">
+    Members
+   </span>
+    that were formed without
+    a clause to seek arbitration in the forum;
+  </li><li>
+    Areas where laws fall outside the Arbitration Act,
+    such as privacy;
+  </li><li>
+    Legal process (subpoenas, etc) delivered by
+    an external court of "competent jurisdiction."
+</li></ul>
+
+<p>
+The Arbitrator must consider jurisdiction and rule on a
+case by case basis whether jurisdiction is asserted,
+either wholly or partially, or declines to hear the case.
+In the event of asserting
+jurisdiction, and a NRP later decides to pursue rights in
+another forum, the Arbitrator should seek the agreement
+of the NRP to file the ruling as part of the new case.
+</p>
+
+<h3 id="s2.4"> 2.4  Basis in Law </h3>
+
+<p>
+Each country generally has an Arbitration Act
+that elevates Arbitration as a strong dispute
+resolution forum.
+The Act generally defers to Arbitration
+if the parties have so agreed.
+That is, as 
+   <span class="draftadd">
+   Members
+   </span>
+<span class="draftdrop">users of CAcert</span>,
+you agree to resolve
+all disputes before CAcert's forum.
+This is sometimes called <i>private law</i>
+or <i>alternative dispute resolution</i>.
+</p>
+
+<p>
+As a matter of public policy, courts will generally
+refer any case back to Arbitration.
+   <span class="draftadd">
+   Members
+   </span>
+should understand that they will have
+strictly limited rights to ask the courts to
+seek to have a case heard or to override a Ruling.
+</p>
+
+
+<h3 id="s2.5"> 2.5  External Courts </h3>
+
+<p>
+    When an external court claims and asserts its jurisdiction,
+    and issues a court order, subpoena or other service to CAcert,
+    the CM files the order as a dispute, with the external court
+    as <i>Claimant</i>.
+    The CM and other support staff are granted no authority to
+    act on the basis of any court order, and ordinarily
+    must await the order of the Arbitrator
+    (which might simply be a repeat of the external court order).
+</p>
+
+<p>
+    The Arbitrator establishes the bona fides of the
+    court, and rules.
+    The Arbitrator may rule to reject the order,
+    for jurisdiction or other reasons.
+    By way of example, if all Parties are
+    <span class="draftadd">
+    Members,
+    </span>
+    then jurisdiction more normally falls within the forum.
+    If the Arbitrator rules to reject,
+    he should do so only after consulting with CAcert <span class="draftadd">Inc.</span> counsel.
+    The Arbitrator's jurisidiction is ordinarily that of
+    dealing with the order, and
+    not that which the external court has claimed to.
+</p>
+
+
+<h3 id="s2.6"> 2.6  Process</h3>
+
+<p>
+The Arbitrator follows the procedure:
+</p>
+
+
+<ol><li>
+    Establish the facts.
+    The Arbitrator collects the evidence from the parties.
+    The Arbitrator may order CAcert <span class="draftadd">Inc.</span> or
+   <span class="draftadd">
+   Members
+   </span>
+    under jurisdiction to provide support or information.
+    The Arbitrator may use email, phone or face-to-face
+    meetings as proceedings.
+  </li><li>
+    Apply the Rules of Dispute Resolution,
+    the policies of CAcert and the governing law.
+    The Arbitrator may request that the parties
+    submit their views.
+    The Arbitrator also works to the mission of CAcert,
+    the benefit of all
+   <span class="draftadd">
+   Members
+   </span>
+    , and the community as a whole.
+    The Arbitrator may
+    <span class="draftadd">
+    seek
+    </span>
+    any assistance.
+  </li><li>
+    Makes a considered Ruling.
+</li></ol>
+
+<h2 id="s3">  3. The Ruling</h2>
+
+<h3 id="s3.1"> 3.1  The Contents </h3>
+
+<p>
+The Arbitrator records:
+</p>
+
+<ol><li>
+   The Identification of the Parties,
+  </li><li>
+   The Facts,
+  </li><li>
+   The logic of the rules and law,
+  </li><li>
+   The directions and actions to be taken by each party
+   (the ruling).
+  </li><li>
+   The date and place that the ruling is rendered.
+</li></ol>
+
+
+<h3 id="s3.2"> 3.2  Process </h3>
+<p>
+Once the Ruling is delivered, the case is closed.
+The Case Manager is responsible for recording the
+Ruling, publishing it, and advising <span class="draftadd">Members</span>.
+</p>
+
+<p>
+Proceedings are ordinarily private.
+The Ruling is ordinarily published,
+within the bounds of the Privacy Policy.
+The Ruling is written in English.
+</p>
+
+<p>
+Only under exceptional circumstances can the
+Arbitrator declare the Ruling private <i>under seal</i>.
+Such a declaration must be reviewed in its entirety
+by the Board,
+and the Board must confirm or deny that declaration.
+If it confirms, the existence of any Rulings under seal
+must be published to the
+   <span class="draftadd">
+   Members
+   </span>
+in a timely manner
+(within days).
+</p>
+
+<h3 id="s3.3"> 3.3  Binding and Final </h3>
+
+<p>
+The Ruling is
+<!-- (DRAFT p20110108) -->
+<span class="draftadd">ordinarily final and binding </span>
+<span class="draftdrop">binding and final</span>
+on CAcert <span class="draftadd">Inc.</span> and all
+   <span class="draftadd">
+   Members
+   </span>
+.
+Ordinarily, all
+   <span class="draftadd">
+   Members
+   </span>
+ agree to be bound by this dispute
+resolution policy.
+   <span class="draftadd">
+   Members
+   </span>
+must declare in the Preliminaries
+any default in agreement or binding.
+</p>
+
+<p>
+If a person who is not a
+   <span class="draftadd">
+   Member
+   </span>
+is a party to the dispute,
+then the Ruling is not binding and final on that person,
+but the Ruling must be presented in filing any dispute
+in another forum such as the person's local courts.
+</p>
+
+<h3 id="s3.4"> 3.4  <span class="draftadd">Review for Appeal (DRAFT p20110108)</span> &nbsp;&nbsp;&nbsp;&nbsp;    <span class="draftdrop">Re-opening the Case or Appeal</span> </h3>
+
+<p>
+In the <span class="draftadd">event</span> <span class="draftdrop">case</span> of clear injustices, egregious behaviour or
+unconscionable Rulings,
+<span class="draftadd">
+a review may be requested by filing a dispute  (DRAFT p20110108).
+</span>
+<span class="draftdrop">
+parties may seek to re-open the
+case by filing a dispute.
+</span>
+The new Arbitrator reviews the new dispute,
+re-examines and reviews the entire case, then rules on
+whether the case may be re-opened or not.
+</p>
+
+<p>
+<span class="draftadd">
+If the Review Arbitrator rules the case be re-opened,
+then the Review Arbitrator refers the case to an Appeal Panel of 3.
+The Appeal Panel is led by a Senior Arbitrator,
+and is formed according to procedures established
+by the DRO from time to time.
+The Appeal Panel hears the case and delivers a final and binding Ruling.
+ (DRAFT p20110108)
+</span>
+<span class="draftdrop">
+If the new Arbitrator rules the case be re-opened,
+then it is referred to the Board of CAcert Inc.
+The Board hears the case and delivers a final
+and binding Ruling.
+</span>
+</p>
+
+<h3 id="s3.5"> 3.5  Liability </h3>
+
+<p>
+All liability of the Arbitrator for any act in
+connection with deciding a dispute is excluded
+by all parties, provided such act does not constitute
+an intentional breach of duty.
+All liability of the Arbitrators, CAcert <span class="draftadd">Inc.</span>, its officers and its
+employees (including Case Manager)
+for any other act or omission in connection with
+arbitration proceedings is excluded, provided such acts do not
+constitute an intentional or grossly negligent breach of duty.
+</p>
+
+<p>
+The above provisions may only be overridden by
+appeal process
+ (by means of a new dispute causing referral to the Board).
+
+</p>
+
+<h3 id="s3.6"> 3.6  Remedies </h3>
+
+<p>
+The Arbitrator generally instructs using internal remedies,
+that is ones that are within the general domain of
+<span class="draftdrop">CAcert</span>
+<span class="draftadd">the Community</span>,
+but there are some external remedies at his disposal.
+He may rule and instruct any of the parties on these issues.
+</p>
+
+<ul><li>
+    "community service" typically including
+    <ul><li>
+        attend and assure people at trade shows / open source gatherings,
+      </li><li>
+        writing documentation
+      </li><li>
+        serve in <span class="change2">a</span> role - support, dispute arbitration
+    </li></ul>
+    or others as decided.
+
+  </li><li>
+    Fined by loss of assurance points, which may result
+    in losing Assurer or Assured status.
+
+  </li><li>
+    Retraining in role.
+
+  </li><li>
+    Revoking of any certificates.
+
+  </li><li>
+    Monetary fine up to the liability cap established for
+    each party as described in the
+    <span class="draftadd">
+    CAcert Community Agreement.
+    </span>
+
+  </li><li>
+    Exclusion from community.
+
+  </li><li>
+    Reporting to applicable authorities.
+
+  </li><li>
+    Changes to policies and procedures.
+
+</li></ul>
+
+<p>
+The Arbitrator is not limited within the general domain
+of CAcert, and may instruct novel remedies as seen fit.
+Novel remedies outside the domain may be routinely
+confirmed by the Board by way of appeal process,
+in order to establish precedent.
+
+</p>
+
+<h2 id="s4">  4. Appendix</h2>
+
+
+<h3 id="s4.1"> 4.1  The Advantages of this Forum </h3>
+<p>
+The advantage of this process for
+   <span class="draftadd">
+   Members
+   </span>
+ is:
+</p>
+
+<ul><li>
+    CAcert and <span class="draftadd">Members</span> operate across many jurisdictions.
+    Arbitration allows us to select a single set of
+    rules across all jurisdictions.
+  </li><li>
+    Arbitration allows CAcert to appropriately separate
+    out the routine support actions from difficult dispute
+    actions.  Support personnel have no authority to
+    act, the appropriately selected Arbitrator has all
+    authority to act.
+    Good governance is thus maintained.
+  </li><li>
+    This forum allows CAcert <span class="draftadd">Members</span> to look after themselves
+    in a community, without exposing each other to potentially
+    disastrous results in strange courts from foreign lands.
+  </li><li>
+    By volunteering to resolve things "in-house" the costs
+    are reduced.
+  </li><li>
+    Even simple support issues such as password changing
+    can be improved by treating as a dispute.  A clear
+    chain of request, analysis, ruling and action can be established.
+  </li><li>
+    CAcert Assurers can develop the understanding and the rules
+    for sorting out own problems far better than courts or
+    other external agencies. 
+</li></ul>
+
+<h3 id="s4.2"> 4.2  The Disadvantages of this Forum </h3>
+
+<p>
+Some disadvantages exist.
+</p>
+
+<ul><li>
+     <span class="draftadd">Members</span> may have their rights trampled over.
+     In such a case, the community should strive to
+     re-open the case
+     and refer it to the board.
+
+
+  </li><li>
+     <span class="draftadd">Members</span> may feel overwhelmed by the formality
+     of the process.
+     It is kept formal so as to establish good and proper
+     authority to act;  otherwise, support and other
+     people in power may act without thought and with
+     damaging consequences.
+  </li><li>
+     A country may not have an Arbitration Act.
+     In that case, the parties should enter into
+     spirit of the forum.
+     If they choose to break that spirit,
+     they should also depart the community.
+</li></ul>
+
+<h3 id="s4.3"> 4.3  Process and Flow </h3>
+
+<p>
+To the extent reasonable, the Arbitrator conducts
+the arbitration as with any legal proceedings.
+This means that the process and style should follow
+legal tradition.
+</p>
+
+<p>
+However, the Arbitrator is unlikely to be trained in
+law.  Hence, common sense must be applied, and the
+Arbitrator has wide latitude to rule on any particular
+motion, pleading, submission.  The Arbitrator's ruling
+is final within the arbitration.
+</p>
+
+<p>
+Note also that many elements of legal proceedings are
+deliberately left out of the rules.
+</p>
+
+
+</body>
+</html>
diff --git a/static/www/policy/NRPDisclaimerAndLicence.html b/static/www/policy/NRPDisclaimerAndLicence.html
new file mode 100644 (file)
index 0000000..d77b222
--- /dev/null
@@ -0,0 +1,14 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+
+<html>
+<head><title>NRP-DAL was replaced by the Root Distribution License</title></head>
+<body>
+<table border="1" bgcolor="#EEEEEE"><tr><td>
+
+The document "Non Related Persons - Disclaimer And Licence" was replaced by the Root Distribution Licence, which can be found <a href="/policy/RootDistributionLicense.html">here</a>.
+
+</td>
+</tr>
+</table>
+</body>
+</html>
diff --git a/static/www/policy/OrganisationAssurancePolicy.html b/static/www/policy/OrganisationAssurancePolicy.html
new file mode 100644 (file)
index 0000000..cb4f26b
--- /dev/null
@@ -0,0 +1,403 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
+        "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<title> Organisation Assurance Policy </title>
+<style type="text/css">
+<!--
+.comment {
+        color : steelblue;
+}
+-->
+</style>
+
+</head>
+<body>
+
+<div class="comment">
+<table width="100%">
+
+<tr>
+<td>
+  Name: OAP <a style="color: steelblue" href="//svn.cacert.org/CAcert/Policies/ControlledDocumentList.html">COD11</a><br />
+
+  Status: POLICY/DRAFT <a style="color: steelblue" href="//wiki.cacert.org/wiki/TopMinutes-20070917">m20070918.x </a><br />
+
+  &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;           <span class="draftadd">DRAFT p20080401.1   </span> <br />
+  Editor: Jens Paul <br />
+   Licence: <a style="color: steelblue" href="//wiki.cacert.org/Policy#Licence" title="this document is Copyright &copy; CAcert Inc., licensed openly under CC-by-sa with all disputes resolved under DRP.  More at wiki.cacert.org/Policy" > CC-by-sa+DRP </a><br /></td>
+<td valign="top" align="right">
+  <a href="//www.cacert.org/policy/PolicyOnPolicy.html"><img src="/images/cacert-policy.png" alt="OAP Status - POLICY" height="31" width="88" style="border-style: none;" /></a><br />
+  <a href="//www.cacert.org/policy/PolicyOnPolicy.html"><img src="/images/cacert-draft.png" alt="OAP Status - DRAFT" height="31" width="88" style="border-style: none;" /></a>
+
+</td>
+</tr>
+</table>
+</div>
+
+
+<h1> Organisation&nbsp;Assurance&nbsp;Policy </h1>
+
+<h2 id="s0">0.   Preliminaries </h2>
+
+<p>
+This policy describes how Organisation Assurers ("OAs")
+conduct Assurances on Organisations.
+It fits within the overall web-of-trust
+or Assurance process of CAcert.
+</p>
+
+<p>
+This policy is not a Controlled document, for purposes of
+Configuration Control Specification ("CCS").
+</p>
+
+<h2 id="s1"> 1. Purpose </h2>
+
+<p>
+Organisations with assured status can issue certificates
+directly with their own domains within.
+</p>
+
+<p>
+The purpose and statement of the certificate remains
+the same as with ordinary users (natural persons)
+and as described in the CPS.
+</p>
+
+<ul><li>
+    The organisation named within is identified.
+  </li><li>
+    The organisation has been verified according
+    to this policy.
+  </li><li>
+    The organisation is within the jurisdiction
+    and can be taken to CAcert Arbitration.
+</li></ul>
+
+
+<h2 id="s2"> 2. Roles and Structure </h2> 
+
+<h3 id="s2.1"> 2.1 Assurance Officer </h3> 
+
+<p>
+The Assurance Officer ("AO")
+manages this policy and reports to the CAcert Inc. Committee ("Board").
+</p>
+
+<p>
+The AO manages all OAs and is responsible for process,
+the CAcert Organisation Assurance Programme ("COAP") form,
+OA training and testing, manuals, quality control.
+In these responsibilities, other Officers will assist.
+</p>
+<p>
+The OA is appointed by the Board. 
+Where the OA is failing the Board decides.
+</p>
+
+<h3 id="s2.2"> 2.2 Organisation Assurers </h3> 
+
+<p>
+</p>
+
+<ol type="a"> <li>
+    An OA must be an experienced Assurer
+    <ol type="i">
+      <li>Have 150 assurance points.</li>
+      <li>Be fully trained and tested on all general Assurance processes.</li>
+    </ol>
+
+  </li><li>
+    Must be trained as Organisation Assurer.
+    <ol type="i">
+      <li> Global knowledge:  This policy. </li>
+      <li> Global knowledge:  A OA manual covers how to do the process.</li>
+      <li> Local knowledge:   legal forms of organisations within jurisdiction.</li>
+      <li> Basic governance. </li>
+      <li> Training may be done a variety of ways,
+           such as on-the-job, etc. </li>
+    </ol>
+
+  </li><li>
+    Must be tested.
+    <ol type="i">
+      <li> Global test:  Covers this policy and the process. </li>
+      <li> Local knowledge:   Subsidiary Policy to specify.</li>
+      <li> Tests to be created, approved, run, verified
+           by CAcert only (not outsourced). </li>
+      <li> Tests are conducted manually, not online/automatic. </li>
+      <li> Documentation to be retained. </li>
+      <li> Tests may include on-the-job components. </li>
+    </ol>
+
+  </li><li>
+    Must be approved.
+    <ol type="i">
+      <li> Two supervising OAs must sign-off on new OA,
+           as trained, tested and passed.
+           </li>
+      <li> AO must sign-off on a new OA,
+           as supervised, trained and tested.
+           </li>
+    </ol>
+    </li>
+       <li>The OA can decide when a CAcert
+       (individual) Assurer
+       has done several OA Application Advises to appoint this
+       person to OA Assurer.
+       </li>
+
+</ol>
+
+<h3 id="s2.3"> 2.3 Organisation Assurance Advisor ("OAA") </h3>
+       <p>In countries/states/provinces where no OA Assurers are
+       operating for an OA Application (COAP) the OA
+       can be advised by an experienced local CAcert
+       (individual) Assurer to take the decision
+       to accept the OA Application (COAP) of the organisation.
+       </p>
+       <p>
+       The local Assurer must have at least 150 Points,
+       should know the language, and know
+       the organisation trade office registry culture and quality.
+       </p>
+
+
+<h3 id="s2.4"> 2.4 Organisation Administrator </h3> 
+
+<p>
+The Administrator within each Organisation ("O-Admin")
+is the one who handles the assurance requests
+and the issuing of certificates.
+</p>
+
+<ol type="a"> <li>
+    O-Admin must be Assurer
+    <ol type="i">
+      <li>Have 100 assurance points.</li>
+      <li>Fully trained and tested as Assurer.</li>
+    </ol>
+
+  </li><li>
+    Organisation is required to appoint O-Admin,
+    and appoint ones as required.
+    <ol type="i">
+      <li> On COAP Request Form.</li>
+    </ol>
+
+  </li><li>
+    O-Admin must work with an assigned OA.
+    <ol type="i">
+      <li> Have contact details.</li>
+    </ol>
+  </li>
+</ol>
+
+
+<h2 id="s3"> 3. Policies </h2> 
+
+<h3 id="s3.1"> 3.1 Policy </h3> 
+
+<p>
+There is one policy being this present document,
+and several subsidiary policies.
+</p>
+
+<ol type="a">
+  <li>  This policy authorises the creation of subsidiary policies. </li>
+  <li>  This policy is international. </li>
+  <li>  Subsidiary policies are implementations of the policy. </li>
+  <li>  Organisations are assured under an appropriate subsidiary policy. </li>
+</ol>
+
+<h3 id="s3.2"> 3.2 Subsidiary Policies </h3>
+
+<p>
+The nature of the Subsidiary Policies ("SubPols"):
+</p>
+
+<ol type="a"><li>
+    SubPols are purposed to check the organisation
+    under the rules of the jurisdiction that creates the
+    organisation.  This does not evidence an intention
+    by CAcert to
+    enter into the local jurisdiction, nor an intention
+    to impose the rules of that jurisdiction over any other
+    organisation.
+    CAcert assurances are conducted under the jurisdiction
+    of CAcert.
+  </li><li>
+    For OAs,
+    SubPol specifies the <i>tests of local knowledge</i>
+    including the local organisation assurance COAP forms.
+  </li><li>
+    For assurances,
+    SubPol specifies the <i>local documentation forms</i>
+    which are acceptable under this SubPol to meet the
+    standard.
+  </li><li>
+   SubPols are subjected to the normal 
+   policy approval process.
+</li></ol>
+
+<h3 id="s3.3"> 3.3  Freedom to Assemble </h3>
+
+<p>
+Subsidiary Policies are open, accessible and free to enter. 
+</p>
+
+<ol type="a"><li>
+    SubPols compete but are compatible.
+  </li><li>
+    No SubPol is a franchise.
+  </li><li>
+    Many will be on State or National lines,
+    reflecting the legal
+    tradition of organisations created
+    ("incorporated") by states.
+  </li><li>
+    However, there is no need for strict national lines;
+    it is possible to have 2 SubPols in one country, or one
+    covering several countries with the same language
+    (e.g., Austria with Germany, England with Wales but not Scotland).
+  </li><li>
+    There could also be SubPols for special
+    organisations, one person organisations,
+    UN agencies, churches, etc.
+  </li><li>
+    Where it is appropriate to use the SubPol
+    in another situation (another country?), it
+    can be so approved.
+    (e.g., Austrian SubPol might be approved for Germany.)
+    The SubPol must record this approval.
+</li></ol>
+
+
+<h2 id="s4"> 4.  Process </h2>
+
+<h3 id="s4.1"> 4.1  Standard of Organisation Assurance </h3>
+<p>
+The essential standard of Organisation Assurance is:
+</p>
+
+<ol type="a"><li>
+    the organisation exists
+  </li><li>
+    the organisation name is correct and consistent:
+    <ol type="i">
+      <li>in official documents specified in SubPol.</li>
+      <li>on COAP form.</li>
+      <li>in CAcert database.</li>
+      <li>form or type of legal entity is consistent</li>
+    </ol>
+  </li><li>
+    signing rights:
+    requestor can sign on behalf of the organisation.
+  </li><li>
+    the organisation has agreed to the terms of the
+    CAcert Community Agreement
+    and is therefore subject to Arbitration.
+</li></ol>
+
+<p>
+    Acceptable documents to meet above standard
+    are stated in the SubPol.
+</p>
+
+<h3 id="s4.2"> 4.2  COAP </h3>
+<p>
+The COAP form documents the checks and the resultant
+assurance results to meet the standard.
+Additional information to be provided on form:
+</p>
+
+<ol type="a"><li>
+    CAcert account of O-Admin (email address?)
+  </li><li>
+    location:
+    <ol type="i">
+      <li>country (MUST).</li>
+      <li>city (MUST).</li>
+      <li>additional contact information (as required by SubPol).</li>
+    </ol>
+  </li><li>
+    administrator account name(s) (1 or more)
+  </li><li>
+    domain name(s)
+  </li><li>
+    Agreement with
+    CAcert Community Agreement.
+    Statement and initials box for organisation
+    and also for OA.
+  </li><li>
+    Date of completion of Assurance.
+    Records should be maintained for 7 years from
+    this date.
+</li></ol>
+
+<p>
+The COAP should be in English.  Where translations
+are provided, they should be matched to the English,
+and indication provided that the English is the
+ruling language (due to Arbitration requirements).
+</p>
+
+<h3 id="s4.3"> 4.3 Jurisdiction </h3>
+
+<p>
+Organisation Assurances are carried out by
+CAcert Inc. under its Arbitration jurisdiction.
+Actions carried out by OAs are under this regime.
+</p>
+
+<ol type="a"><li>
+    The organisation has agreed to the terms of the
+    CAcert Community Agreement.
+  </li><li>
+    The organisation, the Organisation Assurers, CAcert and
+    other related parties are bound into CAcert's jurisdiction
+    and dispute resolution.
+  </li><li>
+    The OA is responsible for ensuring that the
+    organisation reads, understands, intends and
+    agrees to the
+    CAcert Community Agreement.
+    This OA responsibility should be recorded on COAP
+    (statement and initials box).
+</li></ol>
+
+<h2 id="s5"> 5. Exceptions </h2>
+
+
+<ol type="a"><li>
+    <b> Conflicts of Interest.</b>
+    An OA must not assure an organisation in which
+    there is a close or direct relationship by, e.g.,
+    employment, family, financial interests.
+    Other conflicts of interest must be disclosed.
+  </li><li>
+    <b> Trusted Third Parties.</b>
+    TTPs are not generally approved to be part of
+    organisation assurance,
+    but may be approved by subsidiary policies according
+    to local needs.
+  </li><li>
+    <b>Exceptional Organisations.</b>
+    (e.g., Vatican, International Space Station, United Nations)
+    can be dealt with as a single-organisation
+    SubPol.
+    The OA creates the checks, documents them,
+    and subjects them to to normal policy approval.
+  </li><li>
+    <b>DBA.</b>
+    Alternative names for organisations
+    (DBA, "doing business as")
+    can be added as long as they are proven independently.
+    E.g., registration as DBA or holding of registered trade mark.
+    This means that the anglo law tradition of unregistered DBAs
+    is not accepted without further proof.
+  </li></ol>
+</body>
+</html>
diff --git a/static/www/policy/PolicyOnPolicy.html b/static/www/policy/PolicyOnPolicy.html
new file mode 100644 (file)
index 0000000..9deec95
--- /dev/null
@@ -0,0 +1,287 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+
+<html>
+<head><title>Policy on Policy</title></head>
+<body>
+
+<table width="100%">
+
+<tr>
+<td> PoP </td>
+<td> </td>
+<td width="20%"> Iang  </td>
+</tr>
+
+<tr>
+<td> POLICY&nbsp;<a href="http://wiki.cacert.org/wiki/PolicyDecisions">p200800204.1</a> </td>
+<td>  </td>
+<td>
+  20080309
+</td>
+</tr>
+
+<tr>
+<td> COD1 </td>
+<td> </td>
+<td> </td>
+</tr>
+
+
+<tr>
+<td> </td>
+<td > <b>Policy&nbsp;on&nbsp;Policy</b> </td>
+<td> </td>
+</tr>
+
+</table>
+
+<h2> 0. Preliminaries </h2>
+<p>
+    Policy on Policy adopts the IETF model of
+    'rough consensus' to create CAcert documents
+    within the open [policy] mail list forum.
+</p>
+
+
+<h2> 1. Scope and Purpose </h2>
+
+<p>
+1.1
+This policy documents and controls the process by which
+CAcert creates and promulgates policies.
+</p>
+
+<p>
+1.2
+The policy covers itself.
+The policy replaces prior ones.
+For Audit purposes,
+the policy is part of the Configuration-Control Specification
+("CCS", <a href="http://rossde.com/CA_review/CA_review_A.html#A1">DRC_A.1</a>)
+and also documents part of the CCS.
+</p>
+
+<p>
+1.3
+The policies so created are generally binding on
+CAcert, registered users and related parties.
+</p>
+
+<p>
+1.4
+The Policy Officer manages all policies
+and the policy group.
+The policy group is formed on the open mailing list
+known as [policy], and is to be open to all
+Community Members of CAcert.
+</p>
+
+<h2> 2. Basic Model </h2>
+
+<p>
+2.1
+The basic concept was drawn from the IETF model.
+</p>
+
+<p>
+2.2
+Policies are documented.
+Documents start as <i>Work-In-Progress</i>, move through to
+<i>DRAFT</i> and finalise in <i>POLICY</i> status.
+</p>
+
+<p>
+2.3
+Decisions are taken by "Rough Consensus."
+A vote may be called to clarify.
+</p>
+
+<p>
+2.4
+Documents should include a minimum of information
+in a standardised format managed by the Documentation Officer:
+the Title,
+short name,
+Document Status,
+date the Status was reached,
+Editor,
+date / time of the last edit,
+Abstract.
+</p>
+
+<h2> 3. Work-In-Progress </h2>
+
+<p>
+3.1
+An Editor is identified.
+This person is responsible for
+drafting the document, following the consensus of the policy group.
+</p>
+
+<p>
+3.2
+The Policy Officer resolves minor disputes and keeps order.
+</p>
+
+<p>
+3.3
+The mail list of the policy group
+is used as the primary debating
+forum. A sub-group may be formed,
+but decision-taking
+should be visible on the main group.
+</p>
+
+<p>
+3.4
+Documents start with the status of
+"Work-In-Progress" or WIP for short.
+</p>
+
+
+<h2> 4. DRAFT status </h2>
+
+<p>
+4.1
+On completion, a document moves to DRAFT status.
+</p>
+
+<p>
+4.2
+A DRAFT is a policy-in-effect for the Community and is
+to be distributed and treated as such.
+</p>
+
+<p>
+4.3
+As far as the Community is concerned, the DRAFT is policy.
+Challenges and concerns can be addressed to the policy group,
+and policy group discussions on a DRAFT
+may be presented in Dispute Resolution.
+</p>
+
+<p>
+4.4
+Revisions of DRAFTs
+must be treated as decisions on the policy group.
+</p>
+
+<p>
+4.5
+The period of the DRAFT status is announced widely,
+which should be at least a month and no longer than a year.
+</p>
+
+<p>
+4.6
+During the period of DRAFT,
+CAcert Inc. retains a veto over 
+policies that effect the running of CAcert Inc.
+</p>
+
+
+<h2> 5. POLICY status </h2>
+<p>
+5.1
+After DRAFT period has elapsed with no revision beyond
+minor and editorial changes,
+there should be a decision
+to move the document from
+DRAFT to POLICY status.
+</p>
+
+<p>
+5.2
+Once POLICY, the Community may only challenge the document
+in Dispute Resolution.
+</p>
+
+<p>
+5.3
+Policy group may propose changes to a POLICY document
+in order to update it.  When changes move to DRAFT status,
+they may be included in the POLICY document,
+but must be clearly indicated within as DRAFT not POLICY.
+</p>
+
+<h2> 6. Open Process </h2>
+
+<p>
+6.1
+All policy discussions and documents should be open
+processes.  There should be a fair chance for
+the Community
+to have their views heard.
+Rough Consensus is the working metric.
+</p>
+
+<p>
+6.2
+Contributions to
+formally controlled documents such as Policies
+are transferred fully to CAcert Inc.
+Copyrights
+and similar intellectual property rights
+required to incorporate the Contribution
+are either transferred to CAcert Inc, or,
+are issued and contributed under free,
+open, non-restrictive,
+irrevocable, exclusive,
+and clear licence to CAcert Inc.
+In all cases, CAcert Inc licenses the
+contributions back to the community
+under an open licence.
+</p>
+
+<p>
+6.3
+Contributors declare any conflicts of interest.
+</p>
+
+<p>
+6.4
+Policies should be issued under free, open,
+non-restrictive,
+irrevocable, non-exclusive,
+and clear licence by CAcert, Inc.
+</p>
+
+<p>
+6.5
+Mailing lists should be archived,
+and important meetings should be minuted.
+</p>
+
+<h2> 7. Disputes. </h2>
+
+<p>
+7.1
+Any questions not resolved by these rules
+may be voted on in the policy group, or
+may be dealt with in Dispute Resolution.
+</p>
+
+<p>
+7.2
+The Policy Officer may decide a tight vote in a minor matter only.
+Failure of Rough Consensus may be declared by
+dissenting members.
+</p>
+
+<p>
+7.3
+Matters unresolved refer back
+to further group discussion.
+</p>
+
+<p>
+7.4
+The external avenue for disputes is to file a dispute
+according to CAcert's
+<a href="http://www.cacert.org/policy/DisputeResolutionPolicy.html">
+Dispute Resolution Policy</a>
+DRP =&gt; COD7.
+</p>
+
+</body>
+</html>
diff --git a/static/www/policy/PrivacyPolicy.html b/static/www/policy/PrivacyPolicy.html
new file mode 100644 (file)
index 0000000..8aa0837
--- /dev/null
@@ -0,0 +1,114 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+
+<html>
+<head><title>Privacy Policy</title></head>
+<body>
+
+<table width="100%">
+
+<tr>
+<td> PP </td>
+<td>&nbsp;</td>
+<td width="20%"> &nbsp; </td>
+</tr>
+
+<tr>
+<td> POLICY&nbsp;<a href="http://wiki.cacert.org/wiki/PolicyDecisions">m20060629</a> </td>
+<td> &nbsp; </td>
+<td>
+  20060629
+</td>
+</tr>
+
+<tr>
+<td> COD5 </td>
+<td>&nbsp;</td>
+<td>&nbsp;</td>
+</tr>
+
+
+<tr>
+<td>&nbsp;</td>
+<td > <b>Privacy&nbsp;Policy</b> </td>
+<td>&nbsp;</td>
+</tr>
+
+</table>
+
+<h2> 0. Preliminaries </h2>
+<p>
+    This policy discloses what information we gather about you when you visit any of our Web site, and when you issue or use our certificates. It describes how we use that information and how you can control it.
+</p>
+
+
+
+<h2>1. Website information</h2>
+<p>
+We collect two kinds of information about website users: 1) data that users volunteer by signing up to our website or when you send us an email via our contact form; and 2) aggregated tracking data we collect when users interact with our site.
+</p>
+
+<h2>2. Personal information</h2>
+<p>
+When you post to the contact form, you must provide your name and email address. When you sign up to the website, you must provide your name, email address, date of birth and some lost pass phrase question and answers.
+</p>
+<p>
+We only share your information with any other organisation when so instructed by a CAcert arbitrator.
+</p>
+
+<h2>3. Aggregated tracking information</h2>
+<p>
+We analyse visitors' use of our sites by tracking information such as page views, traffic flow, search terms, and click through. We use this information to improve our sites. We also share this anonymous traffic and demographic information in aggregate form with advertisers and other business partners. We do not share any information with advertisers that can identify an individual user.
+</p>
+
+<h2>4. Cookies</h2>
+<p>
+Some of our advertisers use a third-party ad server to display ads. These ads may contain cookies. The ad server receives these cookies, and we don't have access to them.
+</p>
+<p>
+We don't use cookies to store personal information, we do use sessions, and if cookies are enabled, the session will be stored in a cookie, and we do not look for cookies, apart from the session id. However if cookies are disabled then no information will be stored on or looked for on your computer.
+</p>
+
+<h2>5. Notification of changes</h2>
+<p>
+If we change our Privacy Policy, we will post those changes on www.CAcert.org. If we decide to use personally identifiable information in a manner different from that stated at the time it was collected, we will notify users via email. Users will be able to opt out of any new use of their personal information.
+</p>
+
+<h2>6. How to update, correct, or delete your information</h2>
+<p>
+You are able to update, add and remove your information at any time via our web interface, log into the 'My Account' and then click on the 'My Details' section, and then click the relevant link
+</p>
+
+<h2>7. Privacy of certificates</h2>
+<p>
+CAcert does not automatically publish the certificates through a directory service or the website to other people than the user who requested the certificate. In the future, the user might be able to opt-in for publication of the certificates through a directory server by CAcert.
+</p>
+
+<h2>8. Privacy of user data</h2>
+<p>
+CAcert Assurers can see the name, birthday and the number of points by looking up the correct email address. No other person related data is published by CAcert.
+</p>
+
+<h2>9. Exceptions</h2>
+<p>
+A CAcert arbitrator may override this policy in a dispute.
+To obtain access to confidential data, a dispute has to be filed.
+</p>
+
+<h2>10. Legal mandates</h2>
+<p>
+CAcert adopts the Australian privacy regulations.
+Please see <a href='http://www.privacy.gov.au/'>http://www.privacy.gov.au/</a> for further details.
+Governmental warrants and civil supoenas will be processed through the dispute resolution system, which ensures that valid authority is given to whoever complies with the supoena or the warrant.
+</p>
+
+
+<p>If you need to contact us in writing, address your mail to:</p>
+<p>
+CAcert Inc.<br>
+PO Box 66 <br>
+Oatley NSW 2223<br>
+Australia
+</p>
+
+</body>
+</html>
diff --git a/static/www/policy/RootDistributionLicense.html b/static/www/policy/RootDistributionLicense.html
new file mode 100644 (file)
index 0000000..7b4ea17
--- /dev/null
@@ -0,0 +1,126 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
+       "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+ <meta http-equiv="CONTENT-TYPE" content="text/html; charset=utf-8" />
+ <title> CAcert - Root Distribution License - DRAFT </title>
+<style type="text/css">  <!-- only for WIP -->
+<!--
+.change {
+        color : blue;
+        font-weight: bold;
+}
+.comment {
+        color : steelblue;
+}
+-->
+</style>
+
+</head>
+<body>
+
+<div class="comment">
+<table width="100%">
+
+<tr>
+<td>
+Name: RDL <a style="color: steelblue" href="https://svn.cacert.org/CAcert/Policies/ControlledDocumentList.html">COD14</a><br />
+Status: DRAFT <a style="color: steelblue" href="https://wiki.cacert.org/PolicyDecisions#p20100710">p20100710</a> <br />
+Editor: Mark Lipscombe<br />
+</td>
+<td align="right">
+  <a href="//www.cacert.org/policy/PolicyOnPolicy.html"><img src="/images/cacert-draft.png" alt="RDL Status - DRAFT" height="31" width="88" style="border-style: none;" /></a>
+
+</td>
+</tr>
+</table>
+</div>
+
+<p><br /><br /></p>
+
+<table border="1"><tr><td>
+
+<h1> Root Distribution License </h1>
+
+<h2 id="s1"> 1. Terms </h2>
+
+<p>
+"CAcert Inc" means CAcert Incorporated, a non-profit association incorporated in New South Wales, Australia.<br />
+"CAcert Community Agreement" means the agreement entered into by each person wishing to RELY. <br />
+"Member" means a natural or legal person who has agreed to the CAcert Community Agreement.<br />
+"Certificate" means any certificate or like device to which CAcert Inc's digital signature has been affixed.<br />
+"CAcert Root Certificates" means any certificate issued by CAcert Inc to itself for the purposes of signing further CAcert Roots or for signing certificates of Members.<br />
+"RELY" means the human act in taking on a risk or liability on the basis of the claim(s) bound within a certificate issued by CAcert.<br />
+"Embedded" means a certificate that is contained within a software application or hardware system, when and only when, that software application or system is distributed in binary form only.<br />
+</p>
+
+<h2 id="s2"> 2. Copyright </h2>
+
+<p>
+CAcert Root Certificates are Copyright CAcert Incorporated. All rights reserved.
+</p>
+
+<h2 id="s3"> 3. License </h2>
+
+<p>
+You may copy and distribute CAcert Root Certificates only in accordance with this license.
+</p>
+
+<p>
+CAcert Inc grants you a free, non-exclusive license to copy and distribute CAcert Root Certificates in any medium, with or without modification, provided that the following conditions are met:
+</p>
+
+<ul><li>
+    Redistributions of Embedded CAcert Root Certificates must take reasonable steps to inform the recipient of the disclaimer in section 4 or reproduce this license and copyright notice in full in the documentation provided with the distribution.
+  </li><li>
+    Redistributions in all other forms must reproduce this license and copyright notice in full.
+</li></ul>
+
+<h2 id="s4"> 4. Disclaimer </h2>
+
+<p>
+THE CACERT ROOT CERTIFICATES ARE PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED TO THE MAXIMUM EXTENT PERMITTED BY LAW. IN NO EVENT SHALL CACERT INC, ITS MEMBERS, AGENTS, SUBSIDIARIES OR RELATED PARTIES BE LIABLE TO THE LICENSEE OR ANY THIRD PARTY FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THESE CERTIFICATES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. IN ANY EVENT, CACERT'S LIABILITY SHALL NOT EXCEED $1,000.00 AUSTRALIAN DOLLARS.
+</p>
+
+<p>
+THIS LICENSE SPECIFICALLY DOES NOT PERMIT YOU TO RELY UPON ANY CERTIFICATES ISSUED BY CACERT INC. IF YOU WISH TO RELY ON CERTIFICATES ISSUED BY CACERT INC, YOU MUST ENTER INTO A SEPARATE AGREEMENT WITH CACERT INC.
+</p>
+
+<h2 id="s5"> 5. Statutory Rights </h2>
+
+<p>
+Nothing in this license affects any statutory rights that cannot be waived or limited by contract. In the event that any provision of this license is held to be invalid or unenforceable, the remaining provisions of this license remain in full force and effect. 
+</p>
+
+</td></tr></table>
+
+<div class="comment">
+<h2> Alternatives </h2>
+
+<p>
+If you find the terms of the above
+Root Distribution License
+difficult or inadequate for your purposes, you may wish to:
+</p>
+
+<ul><li>
+    Enter into the CAcert Community Agreement by
+    <a href="https://www.cacert.org/index.php?id=1">
+    registering as a Member</a>.
+    This is free.
+  </li><li>
+    Delete CAcert Root Certificates from your software.
+    Your software documentation should give
+    directions and assistance for this.
+</li></ul>
+
+<p>
+These alternatives are outside the above
+Root Distribution License
+and do not incorporate.
+</p>
+</div>
+
+
+</body>
+</html>
diff --git a/static/www/policy/cacert-draft.png b/static/www/policy/cacert-draft.png
new file mode 100644 (file)
index 0000000..9d33eb1
Binary files /dev/null and b/static/www/policy/cacert-draft.png differ
diff --git a/tests/org/cacert/gigi/DomainVerification.java b/tests/org/cacert/gigi/DomainVerification.java
new file mode 100644 (file)
index 0000000..bc6f5fd
--- /dev/null
@@ -0,0 +1,76 @@
+package org.cacert.gigi;
+
+import static org.junit.Assert.*;
+
+import org.cacert.gigi.dbObjects.Domain;
+import org.junit.Test;
+
+public class DomainVerification {
+
+    @Test
+    public void testDomainPart() {
+        assertTrue(Domain.isVaildDomainPart("cacert", false));
+        assertTrue(Domain.isVaildDomainPart("de", false));
+        assertTrue(Domain.isVaildDomainPart("ha2-a", false));
+        assertTrue(Domain.isVaildDomainPart("ha2--a", false));
+        assertTrue(Domain.isVaildDomainPart("h--a", false));
+        assertFalse(Domain.isVaildDomainPart("xn--bla", false));
+        assertFalse(Domain.isVaildDomainPart("-xnbla", false));
+        assertFalse(Domain.isVaildDomainPart("xnbla-", false));
+        assertFalse(Domain.isVaildDomainPart("", false));
+        assertTrue(Domain.isVaildDomainPart("2xnbla", false));
+        assertTrue(Domain.isVaildDomainPart("xnbla2", false));
+        assertTrue(Domain.isVaildDomainPart("123", false));
+        assertTrue(Domain.isVaildDomainPart("abcdefghijklmnopqrstuvwxyabcdefghijklmnopqrstuvwxy1234567890123", false));
+        assertFalse(Domain.isVaildDomainPart("abcdefghijklmnopqrstuvwxyabcdefghijklmnopqrstuvwxy12345678901234", false));
+    }
+
+    @Test
+    public void testDomainCertifyable() {
+        isCertifyableDomain(true, "cacert.org", false);
+        isCertifyableDomain(true, "cacert.de", false);
+        isCertifyableDomain(true, "cacert.org", false);
+        isCertifyableDomain(true, "cacert.org", false);
+        isCertifyableDomain(true, "1234.org", false);
+        isCertifyableDomain(false, "a.cacert.org", true);
+        isCertifyableDomain(false, "gigi.local", true);
+        isCertifyableDomain(false, "org", true);
+        isCertifyableDomain(false, "'a.org", true);
+        isCertifyableDomain(false, ".org", true);
+        isCertifyableDomain(false, ".org.", true);
+        // non-real-punycode
+        isCertifyableDomain(true, "xna-ae.de", false);
+        isCertifyableDomain(true, "xn-aae.de", false);
+
+        // illegal punycode:
+        // illegal ace prefix
+        isCertifyableDomain(false, "aa--b.com", true);
+        isCertifyableDomain(false, "xm--ae-a.de", true);
+
+        // illegal punycode content
+        isCertifyableDomain(false, "xn--ae-a.com", true);
+        isCertifyableDomain(false, "xn--ae.de", true);
+        isCertifyableDomain(false, "xn--ae-a.org", true);
+        isCertifyableDomain(false, "xn--ae-a.de", true);
+        // valid punycode requires permission
+        isCertifyableDomain(true, "xn--4ca0bs.de", true);
+        isCertifyableDomain(false, "xn--4ca0bs.de", false);
+        isCertifyableDomain(true, "xn--a-zfa9cya.de", true);
+        isCertifyableDomain(false, "xn--a-zfa9cya.de", false);
+
+        // valid punycode does not help under .com
+        isCertifyableDomain(false, "xn--a-zfa9cya.com", true);
+        isCertifyableDomain(true, "zfa9cya.com", true);
+
+    }
+
+    private void isCertifyableDomain(boolean b, String string, boolean puny) {
+        try {
+            Domain.checkCertifyableDomain(string, puny);
+            assertTrue(b);
+        } catch (GigiApiException e) {
+            assertFalse(e.getMessage(), b);
+        }
+    }
+
+}
diff --git a/tests/org/cacert/gigi/LoginTest.java b/tests/org/cacert/gigi/LoginTest.java
new file mode 100644 (file)
index 0000000..d6c6fc4
--- /dev/null
@@ -0,0 +1,49 @@
+package org.cacert.gigi;
+
+import static org.junit.Assert.*;
+
+import java.io.IOException;
+import java.net.URL;
+
+import org.cacert.gigi.testUtils.ManagedTest;
+import org.junit.Test;
+
+public class LoginTest extends ManagedTest {
+
+    @Test
+    public void testLoginUnverified() throws IOException {
+        String email = createUniqueName() + "@testmail.org";
+        registerUser("an", "bn", email, TEST_PASSWORD);
+        waitForMail();
+        assertFalse(isLoggedin(login(email, TEST_PASSWORD)));
+    }
+
+    @Test
+    public void testLoginVerified() throws IOException {
+        String email = createUniqueName() + "@testmail.org";
+        createVerifiedUser("an", "bn", email, TEST_PASSWORD);
+        assertTrue(isLoggedin(login(email, TEST_PASSWORD)));
+    }
+
+    @Test
+    public void testLoginWrongPassword() throws IOException {
+        String email = createUniqueName() + "@testmail.org";
+        createVerifiedUser("an", "bn", email, TEST_PASSWORD);
+        assertFalse(isLoggedin(login(email, TEST_PASSWORD + "b")));
+    }
+
+    @Test
+    public void testLogoutVerified() throws IOException {
+        String email = createUniqueName() + "@testmail.org";
+        createVerifiedUser("an", "bn", email, TEST_PASSWORD);
+        String cookie = login(email, TEST_PASSWORD);
+        assertTrue(isLoggedin(cookie));
+        logout(cookie);
+        assertFalse(isLoggedin(cookie));
+    }
+
+    private void logout(String cookie) throws IOException {
+        cookie(new URL("https://" + getServerName() + "/logout").openConnection(), cookie).getHeaderField("Location");
+    }
+
+}
diff --git a/tests/org/cacert/gigi/TestCertificate.java b/tests/org/cacert/gigi/TestCertificate.java
new file mode 100644 (file)
index 0000000..d8553e1
--- /dev/null
@@ -0,0 +1,141 @@
+package org.cacert.gigi;
+
+import static org.junit.Assert.*;
+
+import java.io.IOException;
+import java.security.GeneralSecurityException;
+import java.security.KeyPair;
+import java.security.PrivateKey;
+import java.security.cert.X509Certificate;
+import java.sql.SQLException;
+import java.util.Collection;
+import java.util.List;
+
+import org.cacert.gigi.dbObjects.Certificate;
+import org.cacert.gigi.dbObjects.Certificate.CSRType;
+import org.cacert.gigi.dbObjects.Certificate.CertificateStatus;
+import org.cacert.gigi.dbObjects.Certificate.SANType;
+import org.cacert.gigi.dbObjects.Certificate.SubjectAlternateName;
+import org.cacert.gigi.dbObjects.CertificateProfile;
+import org.cacert.gigi.dbObjects.User;
+import org.cacert.gigi.testUtils.ManagedTest;
+import org.junit.Test;
+
+import sun.security.x509.GeneralNameInterface;
+
+public class TestCertificate extends ManagedTest {
+
+    User u = User.getById(createVerifiedUser("fn", "ln", createUniqueName() + "@example.com", TEST_PASSWORD));
+
+    @Test
+    public void testClientCertLoginStates() throws IOException, GeneralSecurityException, SQLException, InterruptedException, GigiApiException {
+        KeyPair kp = generateKeypair();
+        String key1 = generatePEMCSR(kp, "CN=testmail@example.com");
+        Certificate c = new Certificate(u, Certificate.buildDN("CN", "testmail@example.com"), "sha256", key1, CSRType.CSR, CertificateProfile.getById(1));
+        final PrivateKey pk = kp.getPrivate();
+        c.issue(null, "2y").waitFor(60000);
+        final X509Certificate ce = c.cert();
+        assertNotNull(login(pk, ce));
+    }
+
+    @Test
+    public void testSANs() throws IOException, GeneralSecurityException, SQLException, InterruptedException, GigiApiException {
+        KeyPair kp = generateKeypair();
+        String key = generatePEMCSR(kp, "CN=testmail@example.com");
+        Certificate c = new Certificate(u, Certificate.buildDN("CN", "testmail@example.com"), "sha256", key, CSRType.CSR, CertificateProfile.getById(1),//
+                new SubjectAlternateName(SANType.EMAIL, "testmail@example.com"), new SubjectAlternateName(SANType.DNS, "testmail.example.com"));
+
+        testFails(CertificateStatus.DRAFT, c);
+        c.issue(null, "2y").waitFor(60000);
+        X509Certificate cert = c.cert();
+        Collection<List<?>> sans = cert.getSubjectAlternativeNames();
+        assertEquals(2, sans.size());
+        boolean hadDNS = false;
+        boolean hadEmail = false;
+        for (List<?> list : sans) {
+            assertEquals(2, list.size());
+            Integer type = (Integer) list.get(0);
+            switch (type) {
+            case GeneralNameInterface.NAME_RFC822:
+                hadEmail = true;
+                assertEquals("testmail@example.com", list.get(1));
+                break;
+            case GeneralNameInterface.NAME_DNS:
+                hadDNS = true;
+                assertEquals("testmail.example.com", list.get(1));
+                break;
+            default:
+                fail("Unknown type");
+
+            }
+        }
+        assertTrue(hadDNS);
+        assertTrue(hadEmail);
+
+        testFails(CertificateStatus.ISSUED, c);
+
+        Certificate c2 = Certificate.getBySerial(c.getSerial());
+        assertEquals(2, c2.getSANs().size());
+        assertEquals(c.getSANs().get(0).getName(), c2.getSANs().get(0).getName());
+        assertEquals(c.getSANs().get(0).getType(), c2.getSANs().get(0).getType());
+        assertEquals(c.getSANs().get(1).getName(), c2.getSANs().get(1).getName());
+        assertEquals(c.getSANs().get(1).getType(), c2.getSANs().get(1).getType());
+
+        try {
+            c2.getSANs().remove(0);
+            fail("the list should not be modifiable");
+        } catch (UnsupportedOperationException e) {
+            // expected
+        }
+    }
+
+    @Test
+    public void testCertLifeCycle() throws IOException, GeneralSecurityException, SQLException, InterruptedException, GigiApiException {
+        KeyPair kp = generateKeypair();
+        String key = generatePEMCSR(kp, "CN=testmail@example.com");
+        Certificate c = new Certificate(u, Certificate.buildDN("CN", "testmail@example.com"), "sha256", key, CSRType.CSR, CertificateProfile.getById(1));
+        final PrivateKey pk = kp.getPrivate();
+
+        testFails(CertificateStatus.DRAFT, c);
+        c.issue(null, "2y").waitFor(60000);
+
+        testFails(CertificateStatus.ISSUED, c);
+        X509Certificate cert = c.cert();
+        assertNotNull(login(pk, cert));
+        c.revoke().waitFor(60000);
+
+        testFails(CertificateStatus.REVOKED, c);
+        assertNull(login(pk, cert));
+
+    }
+
+    private void testFails(CertificateStatus status, Certificate c) throws IOException, GeneralSecurityException, SQLException, GigiApiException {
+        assertEquals(status, c.getStatus());
+        if (status != CertificateStatus.ISSUED) {
+            try {
+                c.revoke();
+                fail(status + " is in invalid state");
+            } catch (IllegalStateException ise) {
+
+            }
+        }
+        if (status != CertificateStatus.DRAFT) {
+            try {
+                c.issue(null, "2y");
+                fail(status + " is in invalid state");
+            } catch (IllegalStateException ise) {
+
+            }
+        }
+        if (status != CertificateStatus.ISSUED) {
+            try {
+                c.cert();
+                if (status != CertificateStatus.REVOKED) {
+                    fail(status + " is in invalid state");
+                }
+            } catch (IllegalStateException ise) {
+
+            }
+        }
+    }
+}
diff --git a/tests/org/cacert/gigi/TestCrossDomainAccess.java b/tests/org/cacert/gigi/TestCrossDomainAccess.java
new file mode 100644 (file)
index 0000000..26dc35f
--- /dev/null
@@ -0,0 +1,62 @@
+package org.cacert.gigi;
+
+import static org.junit.Assert.*;
+
+import java.io.IOException;
+import java.net.HttpURLConnection;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.URLConnection;
+import java.security.GeneralSecurityException;
+import java.sql.SQLException;
+
+import org.cacert.gigi.testUtils.IOUtils;
+import org.cacert.gigi.testUtils.ManagedTest;
+import org.cacert.gigi.util.ServerConstants;
+import org.junit.Test;
+
+public class TestCrossDomainAccess extends ManagedTest {
+
+    @Test
+    public void testNoOriginHeader() throws MalformedURLException, IOException {
+        URLConnection con = new URL("https://" + ServerConstants.getWwwHostNamePortSecure() + "/login").openConnection();
+        assertTrue( !IOUtils.readURL(con).contains("No cross domain access allowed."));
+    }
+
+    @Test
+    public void testCorrectOriginHeaderFromHttpsToHttps() throws MalformedURLException, IOException {
+        URLConnection con = new URL("https://" + ServerConstants.getWwwHostNamePortSecure() + "/login").openConnection();
+        con.setRequestProperty("Origin", "https://" + ServerConstants.getWwwHostNamePortSecure());
+        assertTrue( !IOUtils.readURL(con).contains("No cross domain access allowed."));
+    }
+
+    @Test
+    public void testCorrectOriginHeaderFromHttpToHttps() throws MalformedURLException, IOException {
+        URLConnection con = new URL("https://" + ServerConstants.getWwwHostNamePortSecure() + "/login").openConnection();
+        con.setRequestProperty("Origin", "http://" + ServerConstants.getWwwHostNamePort());
+        assertTrue( !IOUtils.readURL(con).contains("No cross domain access allowed."));
+    }
+
+    @Test
+    public void testCorrectOriginHeaderFromHttpsToSecure() throws MalformedURLException, IOException, GeneralSecurityException, SQLException, InterruptedException, GigiApiException {
+        URLConnection con = new URL("https://" + ServerConstants.getSecureHostNamePort()).openConnection();
+        con.setRequestProperty("Origin", "https://" + ServerConstants.getWwwHostNamePortSecure());
+        String contains = IOUtils.readURL(con);
+        assertTrue( !contains.contains("No cross domain access allowed."));
+    }
+
+    @Test
+    public void testCorrectOriginHeaderFromHttpsToHttp() throws MalformedURLException, IOException {
+        URLConnection con = new URL("http://" + ServerConstants.getWwwHostNamePort()).openConnection();
+        con.setRequestProperty("Origin", "https://" + ServerConstants.getWwwHostNamePortSecure());
+        assertTrue( !IOUtils.readURL(con).contains("No cross domain access allowed."));
+    }
+
+    @Test
+    public void testIncorrectOriginHeader() throws MalformedURLException, IOException {
+        HttpURLConnection con = (HttpURLConnection) new URL("https://" + ServerConstants.getWwwHostNamePortSecure() + "/login").openConnection();
+        con.setRequestProperty("Origin", "https://evilpageandatleastnotcacert.com");
+        assertTrue(IOUtils.readURL(con).contains("No cross domain access allowed."));
+    }
+
+}
diff --git a/tests/org/cacert/gigi/TestDomain.java b/tests/org/cacert/gigi/TestDomain.java
new file mode 100644 (file)
index 0000000..bf7118d
--- /dev/null
@@ -0,0 +1,87 @@
+package org.cacert.gigi;
+
+import static org.junit.Assert.*;
+
+import org.cacert.gigi.dbObjects.Domain;
+import org.cacert.gigi.dbObjects.User;
+import org.cacert.gigi.testUtils.ManagedTest;
+import org.junit.Test;
+
+public class TestDomain extends ManagedTest {
+
+    private User us;
+
+    public TestDomain() {
+        int uid = createVerifiedUser("fn", "ln", createUniqueName() + "pr@test-email.de", TEST_PASSWORD);
+        us = User.getById(uid);
+    }
+
+    @Test
+    public void testDomain() throws InterruptedException, GigiApiException {
+        assertEquals(0, us.getDomains().length);
+        Domain d = new Domain(us, "v1example.org");
+        assertEquals(0, d.getId());
+        d.insert();
+        Domain[] domains = us.getDomains();
+        assertEquals(1, domains.length);
+        assertEquals("v1example.org", domains[0].getSuffix());
+        assertEquals(domains[0].getOwner().getId(), us.getId());
+        assertNotEquals(0, domains[0].getId());
+        assertNotEquals(0, d.getId());
+        assertEquals(d.getId(), domains[0].getId());
+
+        Domain d2 = new Domain(us, "v2-example.org");
+        assertEquals(0, d2.getId());
+        d2.insert();
+
+        domains = us.getDomains();
+        assertEquals(2, domains.length);
+        if ( !domains[1].getSuffix().equals("v2-example.org")) {
+            Domain d1 = domains[0];
+            domains[0] = domains[1];
+            domains[1] = d1;
+        }
+        assertEquals("v2-example.org", domains[1].getSuffix());
+        assertEquals(domains[0].getOwner().getId(), us.getId());
+        assertEquals(domains[1].getOwner().getId(), us.getId());
+        assertNotEquals(0, domains[0].getId());
+        assertNotEquals(0, d.getId());
+        assertEquals(d.getId(), domains[0].getId());
+
+    }
+
+    @Test
+    public void testDoubleDomain() throws InterruptedException, GigiApiException {
+        Domain d = new Domain(us, "dub-example.org");
+        d.insert();
+        try {
+            Domain d2 = new Domain(us, "dub-example.org");
+            d2.insert();
+            fail("expected exception");
+        } catch (GigiApiException e) {
+            // expected
+        }
+    }
+
+    @Test
+    public void testDoubleDomainDelete() throws InterruptedException, GigiApiException {
+        Domain d = new Domain(us, "delexample.org");
+        d.insert();
+        d.delete();
+        Domain d2 = new Domain(us, "delexample.org");
+        d2.insert();
+    }
+
+    @Test
+    public void testDoubleInsertDomain() throws InterruptedException, GigiApiException {
+        Domain d = new Domain(us, "dins-example.org");
+        d.insert();
+        try {
+            d.insert();
+            fail("expected exception");
+        } catch (GigiApiException e) {
+            // expected
+        }
+    }
+
+}
diff --git a/tests/org/cacert/gigi/TestLanguage.java b/tests/org/cacert/gigi/TestLanguage.java
new file mode 100644 (file)
index 0000000..1cac145
--- /dev/null
@@ -0,0 +1,81 @@
+package org.cacert.gigi;
+
+import static org.hamcrest.CoreMatchers.*;
+import static org.junit.Assert.*;
+
+import java.io.IOException;
+import java.net.URL;
+import java.util.Locale;
+
+import org.cacert.gigi.dbObjects.User;
+import org.cacert.gigi.localisation.Language;
+import org.cacert.gigi.testUtils.IOUtils;
+import org.cacert.gigi.testUtils.ManagedTest;
+import org.junit.Test;
+
+public class TestLanguage extends ManagedTest {
+
+    @Test
+    public void testSignupNoLanguage() {
+        User u = User.getById(createVerifiedUser("fname", "lname", createUniqueName() + "@example.org", TEST_PASSWORD));
+        assertEquals(Locale.ENGLISH, u.getPreferredLocale());
+    }
+
+    @Test
+    public void testSignupDE() {
+        setAcceptLanguage("de");
+        User u = User.getById(createVerifiedUser("fname", "lname", createUniqueName() + "@example.org", TEST_PASSWORD));
+        assertEquals(Locale.GERMAN, u.getPreferredLocale());
+    }
+
+    @Test
+    public void testSignupMulti() {
+        setAcceptLanguage("de,en");
+        User u = User.getById(createVerifiedUser("fname", "lname", createUniqueName() + "@example.org", TEST_PASSWORD));
+        assertEquals(Locale.GERMAN, u.getPreferredLocale());
+    }
+
+    @Test
+    public void testSignupFallback() {
+        setAcceptLanguage("ma,de");
+        User u = User.getById(createVerifiedUser("fname", "lname", createUniqueName() + "@example.org", TEST_PASSWORD));
+        assertEquals(Locale.GERMAN, u.getPreferredLocale());
+    }
+
+    @Test
+    public void testSignupProjection() {
+        setAcceptLanguage("de-de,en");
+        User u = User.getById(createVerifiedUser("fname", "lname", createUniqueName() + "@example.org", TEST_PASSWORD));
+        assertEquals(Locale.GERMAN, u.getPreferredLocale());
+    }
+
+    @Test
+    public void testSelectStandard() throws IOException {
+        String content = IOUtils.readURL(new URL("https://" + getServerName() + "/").openConnection());
+        assertThat(content, containsString("Translations"));
+    }
+
+    @Test
+    public void testSelectGerman() throws IOException {
+        String content = IOUtils.readURL(new URL("https://" + getServerName() + "/?lang=de").openConnection());
+        assertThat(content, containsString(Language.getInstance(Locale.GERMAN).getTranslation("Translations")));
+    }
+
+    @Test
+    public void testLanguageAfterLogin() throws IOException {
+        setAcceptLanguage("de,en");
+        User u = User.getById(createVerifiedUser("fname", "lname", createUniqueName() + "@example.org", TEST_PASSWORD));
+        String cookie = login(u.getEmail(), TEST_PASSWORD);
+        String content = IOUtils.readURL(cookie(new URL("https://" + getServerName() + "/").openConnection(), cookie));
+        assertThat(content, containsString(Language.getInstance(Locale.GERMAN).getTranslation("Translations")));
+    }
+
+    @Test
+    public void testOtherLanguageAfterLogin() throws IOException {
+        setAcceptLanguage("fr,de,en");
+        User u = User.getById(createVerifiedUser("fname", "lname", createUniqueName() + "@example.org", TEST_PASSWORD));
+        String cookie = login(u.getEmail(), TEST_PASSWORD);
+        String content = IOUtils.readURL(cookie(new URL("https://" + getServerName() + "/").openConnection(), cookie));
+        assertThat(content, containsString(Language.getInstance(Locale.FRENCH).getTranslation("Translations")));
+    }
+}
diff --git a/tests/org/cacert/gigi/TestName.java b/tests/org/cacert/gigi/TestName.java
new file mode 100644 (file)
index 0000000..b431166
--- /dev/null
@@ -0,0 +1,44 @@
+package org.cacert.gigi;
+
+import static org.junit.Assert.*;
+
+import org.cacert.gigi.dbObjects.Name;
+import org.junit.Before;
+import org.junit.Test;
+
+public class TestName {
+
+    Name n = new Name("fn", "ln", "mn", "sf");
+
+    @Before
+    public void setUp() throws Exception {}
+
+    @Test
+    public void testHashCode() {
+        assertEquals(new Name("fname", "lname", null, null).hashCode(), new Name("fname", "lname", null, null).hashCode());
+        assertNotEquals(new Name("fname", "lname", null, null).hashCode(), new Name("fname", "lname", null, "b").hashCode());
+        assertNotEquals(new Name("fname", "lname", null, null).hashCode(), new Name("fname", "lname", "b", null).hashCode());
+        assertNotEquals(new Name("fname", "lname", null, null).hashCode(), new Name("fname", "name", null, null).hashCode());
+        assertNotEquals(new Name("fname", "lname", null, null).hashCode(), new Name("name", "lname", null, null).hashCode());
+    }
+
+    @Test
+    public void testEqualsObject() {
+        assertFalse(n.equals(null));
+        assertFalse(n.equals("blargh"));
+        Name nullname = new Name(null, null, null, null);
+        assertFalse(n.equals(nullname));
+        assertFalse(nullname.equals(n));
+        assertTrue(nullname.equals(nullname));
+        assertTrue(n.equals(n));
+    }
+
+    @Test
+    public void testMatches() {
+        assertTrue(n.matches("fn ln"));
+        assertTrue(n.matches("fn ln sf"));
+        assertTrue(n.matches("fn mn ln sf"));
+        assertFalse(n.matches("blargh"));
+    }
+
+}
diff --git a/tests/org/cacert/gigi/TestObjectCache.java b/tests/org/cacert/gigi/TestObjectCache.java
new file mode 100644 (file)
index 0000000..f143c47
--- /dev/null
@@ -0,0 +1,60 @@
+package org.cacert.gigi;
+
+import static org.hamcrest.CoreMatchers.*;
+import static org.junit.Assert.*;
+
+import java.sql.Date;
+import java.sql.SQLException;
+import java.util.Calendar;
+import java.util.Locale;
+
+import org.cacert.gigi.dbObjects.Domain;
+import org.cacert.gigi.dbObjects.EmailAddress;
+import org.cacert.gigi.dbObjects.User;
+import org.cacert.gigi.localisation.Language;
+import org.cacert.gigi.testUtils.ManagedTest;
+import org.junit.Test;
+
+public class TestObjectCache extends ManagedTest {
+
+    int uid = createVerifiedUser("fname", "lname", createUniqueName() + "@example.com", TEST_PASSWORD);
+
+    @Test
+    public void testUserCache() throws SQLException {
+        assertThat(User.getById(uid), is(sameInstance(User.getById(uid))));
+
+        User u = new User();
+        u.setFName("fname");
+        u.setMName("mname");
+        u.setSuffix("suffix");
+        u.setLName("lname");
+        u.setEmail(createUniqueName() + "@example.org");
+        Calendar c = Calendar.getInstance();
+        c.set(1950, 1, 1);
+        u.setDoB(new Date(c.getTime().getTime()));
+        u.setPreferredLocale(Locale.ENGLISH);
+        u.insert(TEST_PASSWORD);
+
+        assertThat(u, is(sameInstance(User.getById(u.getId()))));
+        assertThat(User.getById(u.getId()), is(sameInstance(User.getById(u.getId()))));
+
+    }
+
+    @Test
+    public void testDomainCache() throws GigiApiException {
+        Domain d = new Domain(User.getById(uid), "example.org");
+        d.insert();
+
+        assertThat(d, is(sameInstance(Domain.getById(d.getId()))));
+        assertThat(Domain.getById(d.getId()), is(sameInstance(Domain.getById(d.getId()))));
+    }
+
+    @Test
+    public void testEmailCache() throws GigiApiException {
+        EmailAddress em = new EmailAddress(User.getById(uid), createUniqueName() + "@example.org");
+        em.insert(Language.getInstance(Locale.ENGLISH));
+
+        assertThat(em, is(sameInstance(EmailAddress.getById(em.getId()))));
+        assertThat(EmailAddress.getById(em.getId()), is(sameInstance(EmailAddress.getById(em.getId()))));
+    }
+}
diff --git a/tests/org/cacert/gigi/TestOrga.java b/tests/org/cacert/gigi/TestOrga.java
new file mode 100644 (file)
index 0000000..d614371
--- /dev/null
@@ -0,0 +1,39 @@
+package org.cacert.gigi;
+
+import static org.junit.Assert.*;
+
+import org.cacert.gigi.dbObjects.Organisation;
+import org.cacert.gigi.dbObjects.User;
+import org.cacert.gigi.testUtils.ManagedTest;
+import org.junit.Test;
+
+public class TestOrga extends ManagedTest {
+
+    @Test
+    public void testAddRm() {
+        User u1 = User.getById(createVerifiedUser("fn", "ln", createUniqueName() + "@email.org", TEST_PASSWORD));
+        User u2 = User.getById(createVerifiedUser("fn", "ln", createUniqueName() + "@email.org", TEST_PASSWORD));
+        User u3 = User.getById(createVerifiedUser("fn", "ln", createUniqueName() + "@email.org", TEST_PASSWORD));
+        User u4 = User.getById(createVerifiedUser("fn", "ln", createUniqueName() + "@email.org", TEST_PASSWORD));
+        Organisation o1 = new Organisation("name", "ST", "prov", "city", "email", u1);
+        assertEquals(0, o1.getAllAdmins().size());
+        o1.addAdmin(u2, u1, false);
+        assertEquals(1, o1.getAllAdmins().size());
+        o1.addAdmin(u2, u1, false); // Insert double should be ignored
+        assertEquals(1, o1.getAllAdmins().size());
+        o1.addAdmin(u3, u1, false);
+        assertEquals(2, o1.getAllAdmins().size());
+        o1.addAdmin(u4, u1, false);
+        assertEquals(3, o1.getAllAdmins().size());
+        o1.removeAdmin(u3, u1);
+        assertEquals(2, o1.getAllAdmins().size());
+        o1.addAdmin(u3, u1, false); // add again
+        assertEquals(3, o1.getAllAdmins().size());
+        o1.removeAdmin(u3, u1);
+        assertEquals(2, o1.getAllAdmins().size());
+        o1.removeAdmin(u4, u1);
+        o1.removeAdmin(u2, u1);
+        assertEquals(0, o1.getAllAdmins().size());
+    }
+
+}
diff --git a/tests/org/cacert/gigi/TestSSL.java b/tests/org/cacert/gigi/TestSSL.java
new file mode 100644 (file)
index 0000000..74806ee
--- /dev/null
@@ -0,0 +1,109 @@
+package org.cacert.gigi;
+
+import java.io.EOFException;
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.nio.ByteBuffer;
+import java.nio.channels.SocketChannel;
+import java.security.NoSuchAlgorithmException;
+
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLEngine;
+import javax.net.ssl.SSLEngineResult;
+import javax.net.ssl.SSLEngineResult.HandshakeStatus;
+import javax.net.ssl.SSLException;
+
+import org.cacert.gigi.testUtils.InitTruststore;
+import org.cacert.gigi.testUtils.ManagedTest;
+import org.junit.Test;
+
+public class TestSSL extends ManagedTest {
+
+    private ByteBuffer in;
+
+    private ByteBuffer inC;
+
+    private ByteBuffer outC;
+
+    private ByteBuffer out;
+    static {
+        InitTruststore.run();
+    }
+
+    @Test
+    public void testClientIntitiatedRenegotiation() throws NoSuchAlgorithmException, IOException {
+        SSLContext sc = SSLContext.getDefault();
+        SSLEngine se = sc.createSSLEngine();
+        String[] serverParts = getServerName().split(":", 2);
+        try (SocketChannel s = SocketChannel.open(new InetSocketAddress(serverParts[0], Integer.parseInt(serverParts[1])))) {
+
+            in = ByteBuffer.allocate(se.getSession().getApplicationBufferSize());
+            inC = ByteBuffer.allocate(se.getSession().getPacketBufferSize());
+            inC.limit(0);
+            out = ByteBuffer.allocate(se.getSession().getApplicationBufferSize());
+            outC = ByteBuffer.allocate(se.getSession().getPacketBufferSize());
+            outC.limit(0);
+            se.setUseClientMode(true);
+            se.beginHandshake();
+
+            work(se, s);
+            se.beginHandshake();
+            try {
+                work(se, s);
+                throw new Error("Client re-negotiation succeded (possible DoS vulnerability");
+            } catch (EOFException e) {
+                // Cool, server closed connection
+            }
+        }
+
+    }
+
+    private void work(SSLEngine se, SocketChannel s) throws SSLException, IOException {
+        while (se.getHandshakeStatus() != HandshakeStatus.NOT_HANDSHAKING && se.getHandshakeStatus() != HandshakeStatus.FINISHED) {
+            switch (se.getHandshakeStatus()) {
+            case NEED_WRAP:
+                wrap(se, s);
+                break;
+            case NEED_UNWRAP:
+                unwrap(se, s);
+                break;
+            case NEED_TASK:
+                se.getDelegatedTask().run();
+                break;
+            default:
+                System.out.println(se.getHandshakeStatus());
+            }
+        }
+    }
+
+    private SSLEngineResult unwrap(SSLEngine se, SocketChannel s) throws IOException, SSLException {
+        if (inC.remaining() == 0) {
+            inC.clear();
+            s.read(inC);
+            inC.flip();
+        }
+        SSLEngineResult result = se.unwrap(inC, in);
+        if (result.getStatus() == javax.net.ssl.SSLEngineResult.Status.BUFFER_UNDERFLOW) {
+            int pos = inC.position();
+            int limit = inC.limit();
+            inC.limit(inC.capacity());
+            inC.position(limit);
+            int read = s.read(inC);
+            if (read <= 0) {
+                throw new EOFException();
+            }
+            inC.limit(inC.position());
+            inC.position(pos);
+        }
+        return result;
+    }
+
+    private SSLEngineResult wrap(SSLEngine se, SocketChannel s) throws SSLException, IOException {
+        outC.clear();
+        SSLEngineResult result = se.wrap(out, outC);
+        outC.flip();
+        s.write(outC);
+
+        return result;
+    }
+}
diff --git a/tests/org/cacert/gigi/TestSecurityHeaders.java b/tests/org/cacert/gigi/TestSecurityHeaders.java
new file mode 100644 (file)
index 0000000..16f4f3a
--- /dev/null
@@ -0,0 +1,30 @@
+package org.cacert.gigi;
+
+import static org.junit.Assert.*;
+
+import java.io.IOException;
+import java.net.HttpURLConnection;
+import java.net.URL;
+
+import org.cacert.gigi.testUtils.ManagedTest;
+import org.junit.Test;
+
+public class TestSecurityHeaders extends ManagedTest {
+
+    @Test
+    public void testSTS() throws IOException {
+        HttpURLConnection uc = (HttpURLConnection) new URL("https://" + getServerName()).openConnection();
+        assertNotNull(uc.getHeaderField("Strict-Transport-Security"));
+    }
+
+    public void testCSP() throws IOException {
+        HttpURLConnection uc = (HttpURLConnection) new URL("https://" + getServerName()).openConnection();
+        assertNotNull(uc.getHeaderField("Content-Security-Policy"));
+    }
+
+    public void testAllowOrigin() throws IOException {
+        HttpURLConnection uc = (HttpURLConnection) new URL("https://" + getServerName()).openConnection();
+        assertNotNull(uc.getHeaderField("Access-Control-Allow-Origin"));
+
+    }
+}
diff --git a/tests/org/cacert/gigi/TestSeparateSessionScope.java b/tests/org/cacert/gigi/TestSeparateSessionScope.java
new file mode 100644 (file)
index 0000000..ba5579f
--- /dev/null
@@ -0,0 +1,74 @@
+package org.cacert.gigi;
+
+import static org.junit.Assert.*;
+
+import java.io.IOException;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.security.GeneralSecurityException;
+import java.security.KeyManagementException;
+import java.security.KeyPair;
+import java.security.NoSuchAlgorithmException;
+import java.security.PrivateKey;
+import java.security.cert.X509Certificate;
+import java.sql.SQLException;
+
+import org.cacert.gigi.dbObjects.Certificate;
+import org.cacert.gigi.dbObjects.Certificate.CSRType;
+import org.cacert.gigi.dbObjects.CertificateProfile;
+import org.cacert.gigi.dbObjects.User;
+import org.cacert.gigi.testUtils.ManagedTest;
+import org.cacert.gigi.util.Job;
+import org.junit.Test;
+
+public class TestSeparateSessionScope extends ManagedTest {
+
+    @Test
+    public void testSeparateScope() throws IOException, GeneralSecurityException, SQLException, InterruptedException, GigiApiException {
+        String mail = "thisgo" + createUniqueName() + "@example.com";
+        int user = createAssuranceUser("test", "tugo", mail, TEST_PASSWORD);
+        String cookie = login(mail, TEST_PASSWORD);
+        KeyPair kp = generateKeypair();
+        String csr = generatePEMCSR(kp, "CN=felix@dogcraft.de");
+        Certificate c = new Certificate(User.getById(user), Certificate.buildDN("CN", "testmail@example.com"), "sha256", csr, CSRType.CSR, CertificateProfile.getById(1));
+        final PrivateKey pk = kp.getPrivate();
+        c.issue(null, "2y").waitFor(60000);
+        final X509Certificate ce = c.cert();
+        String scookie = login(pk, ce);
+
+        assertTrue(isLoggedin(cookie));
+        assertFalse(isLoggedin(scookie));
+
+        checkCertLogin(c, pk, scookie, 200);
+        checkCertLogin(c, pk, cookie, 302);
+    }
+
+    @Test
+    public void testSerialSteal() throws IOException, GeneralSecurityException, SQLException, InterruptedException, GigiApiException {
+        String mail = "thisgo" + createUniqueName() + "@example.com";
+        int user = createAssuranceUser("test", "tugo", mail, TEST_PASSWORD);
+        KeyPair kp = generateKeypair();
+        String csr = generatePEMCSR(kp, "CN=felix@dogcraft.de");
+        Certificate c = new Certificate(User.getById(user), Certificate.buildDN("CN", "testmail@example.com"), "sha256", csr, CSRType.CSR, CertificateProfile.getById(1));
+        Certificate c2 = new Certificate(User.getById(user), Certificate.buildDN("CN", "testmail@example.com"), "sha256", csr, CSRType.CSR, CertificateProfile.getById(1));
+        final PrivateKey pk = kp.getPrivate();
+        Job j1 = c.issue(null, "2y");
+        c2.issue(null, "2y").waitFor(60000);
+        j1.waitFor(60000);
+        final X509Certificate ce = c.cert();
+        String scookie = login(pk, ce);
+
+        checkCertLogin(c, pk, scookie, 200);
+        checkCertLogin(c2, pk, scookie, 403);
+        checkCertLogin(c, pk, scookie, 302);
+
+    }
+
+    private void checkCertLogin(Certificate c2, final PrivateKey pk, String scookie, int expected) throws IOException, NoSuchAlgorithmException, KeyManagementException, GeneralSecurityException {
+        URL u = new URL("https://" + getServerName().replaceAll("^www", "secure") + SECURE_REFERENCE);
+        HttpURLConnection huc = (HttpURLConnection) u.openConnection();
+        authenticateClientCert(pk, c2.cert(), huc);
+        huc.setRequestProperty("Cookie", scookie);
+        assertEquals(expected, huc.getResponseCode());
+    }
+}
diff --git a/tests/org/cacert/gigi/TestUser.java b/tests/org/cacert/gigi/TestUser.java
new file mode 100644 (file)
index 0000000..93deeba
--- /dev/null
@@ -0,0 +1,138 @@
+package org.cacert.gigi;
+
+import static org.junit.Assert.*;
+
+import java.sql.Date;
+import java.sql.SQLException;
+import java.util.Locale;
+
+import org.cacert.gigi.dbObjects.Assurance;
+import org.cacert.gigi.dbObjects.Domain;
+import org.cacert.gigi.dbObjects.EmailAddress;
+import org.cacert.gigi.dbObjects.User;
+import org.cacert.gigi.localisation.Language;
+import org.cacert.gigi.testUtils.ManagedTest;
+import org.junit.Test;
+
+public class TestUser extends ManagedTest {
+
+    @Test
+    public void testStoreAndLoad() throws SQLException {
+        User u = new User();
+        u.setFName("user");
+        u.setLName("last");
+        u.setMName("");
+        u.setSuffix("");
+        u.setPreferredLocale(Locale.ENGLISH);
+        long dob = System.currentTimeMillis();
+        dob -= dob % (1000 * 60 * 60 * 24);
+        u.setDoB(new java.sql.Date(dob));
+        u.setEmail(createUniqueName() + "a@email.org");
+        u.insert("password");
+        int id = u.getId();
+        User u2 = User.getById(id);
+        assertEquals(u.getName(), u2.getName());
+        assertEquals(u.getDoB().toString(), u2.getDoB().toString());
+        assertEquals(u.getEmail(), u2.getEmail());
+    }
+
+    @Test
+    public void testWebStoreAndLoad() throws SQLException {
+        int id = createVerifiedUser("aä", "b", createUniqueName() + "a@email.org", TEST_PASSWORD);
+
+        User u = User.getById(id);
+        assertEquals("aä", u.getFName());
+        assertEquals("b", u.getLName());
+        assertEquals("", u.getMName());
+    }
+
+    @Test
+    public void testAssurerUtilMethods() throws SQLException {
+        int id = createAssuranceUser("aä", "b", createUniqueName() + "a@email.org", TEST_PASSWORD);
+
+        User u = User.getById(id);
+        assertTrue(u.canAssure());
+        int assurancePoints = u.getAssurancePoints();
+        int expPoints = u.getExperiencePoints();
+        assertEquals(100, assurancePoints);
+        assertEquals(2, expPoints);
+        assertTrue(u.hasPassedCATS());
+        assertEquals(10, u.getMaxAssurePoints());
+
+        assertEquals("aä", u.getFName());
+        assertEquals("b", u.getLName());
+        assertEquals("", u.getMName());
+    }
+
+    @Test
+    public void testMatcherMethods() throws SQLException, GigiApiException {
+        String uq = createUniqueName();
+        int id = createVerifiedUser("aä", "b", uq + "a@email.org", TEST_PASSWORD);
+
+        User u = User.getById(id);
+        new EmailAddress(u, uq + "b@email.org").insert(Language.getInstance(Locale.ENGLISH));
+        new EmailAddress(u, uq + "c@email.org").insert(Language.getInstance(Locale.ENGLISH));
+        new Domain(u, uq + "a-testdomain.org").insert();
+        new Domain(u, uq + "b-testdomain.org").insert();
+        new Domain(u, uq + "c-testdomain.org").insert();
+        assertEquals(3, u.getEmails().length);
+        assertEquals(3, u.getDomains().length);
+        assertTrue(u.isValidDomain(uq + "a-testdomain.org"));
+        assertTrue(u.isValidDomain(uq + "b-testdomain.org"));
+        assertTrue(u.isValidDomain(uq + "c-testdomain.org"));
+        assertTrue(u.isValidDomain("a." + uq + "a-testdomain.org"));
+        assertTrue(u.isValidDomain("*." + uq + "a-testdomain.org"));
+        assertFalse(u.isValidDomain("a" + uq + "a-testdomain.org"));
+        assertFalse(u.isValidDomain("b" + uq + "a-testdomain.org"));
+
+        assertTrue(u.isValidEmail(uq + "a@email.org"));
+        assertTrue(u.isValidEmail(uq + "b@email.org"));
+        assertFalse(u.isValidEmail(uq + "b+6@email.org"));
+        assertFalse(u.isValidEmail(uq + "b*@email.org"));
+
+        assertTrue(u.isValidName("aä b"));
+        assertFalse(u.isValidName("aä c"));
+        assertFalse(u.isValidName("aä d b"));
+
+    }
+
+    @Test
+    public void testDoubleInsert() {
+        User u = new User();
+        u.setFName("f");
+        u.setLName("l");
+        u.setMName("m");
+        u.setSuffix("s");
+        u.setEmail(createUniqueName() + "@example.org");
+        u.setDoB(new Date(System.currentTimeMillis() + 1000 * 60 * 60 * 24 * 365));
+        u.setPreferredLocale(Locale.ENGLISH);
+        u.insert(TEST_PASSWORD);
+        try {
+            u.insert(TEST_PASSWORD);
+            fail("Error expected");
+        } catch (Error e) {
+            // expected
+        }
+        Assurance[] ma = u.getMadeAssurances();
+        Assurance[] ma2 = u.getMadeAssurances();
+        Assurance[] ra = u.getReceivedAssurances();
+        Assurance[] ra2 = u.getReceivedAssurances();
+        assertEquals(0, u.getCertificates(false).length);
+        assertEquals(0, ma.length);
+        assertEquals(0, ma2.length);
+        assertEquals(0, ra.length);
+        assertEquals(0, ra2.length);
+        assertSame(ma, ma2);
+        assertSame(ra, ra2);
+    }
+
+    @Test
+    public void testGetByMail() {
+        String email = createUniqueName() + "a@email.org";
+        int id = createVerifiedUser("aä", "b", email, TEST_PASSWORD);
+        User emailUser = User.getByEmail(email);
+        User u = User.getById(id);
+        assertSame(u, emailUser);
+    }
+
+}
diff --git a/tests/org/cacert/gigi/TestUserGroupMembership.java b/tests/org/cacert/gigi/TestUserGroupMembership.java
new file mode 100644 (file)
index 0000000..c1834ac
--- /dev/null
@@ -0,0 +1,127 @@
+package org.cacert.gigi;
+
+import static org.hamcrest.CoreMatchers.*;
+import static org.junit.Assert.*;
+
+import java.sql.SQLException;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+
+import org.cacert.gigi.database.DatabaseConnection;
+import org.cacert.gigi.database.GigiPreparedStatement;
+import org.cacert.gigi.database.GigiResultSet;
+import org.cacert.gigi.dbObjects.Group;
+import org.cacert.gigi.dbObjects.ObjectCache;
+import org.cacert.gigi.dbObjects.User;
+import org.cacert.gigi.testUtils.ManagedTest;
+import org.junit.Test;
+
+public class TestUserGroupMembership extends ManagedTest {
+
+    private final Group ttpGroup = Group.getByString("ttp-assurer");
+
+    private final Group supporter = Group.getByString("supporter");
+
+    @Test
+    public void testAddObject() throws GigiApiException, SQLException {
+        User u = User.getById(createVerifiedUser("fname", "lname", createUniqueName() + "@example.org", TEST_PASSWORD));
+
+        User granter = User.getById(createVerifiedUser("grFname", "lname", createUniqueName() + "@example.org", TEST_PASSWORD));
+        assertBehavesEmpty(u);
+
+        u.grantGroup(granter, ttpGroup);
+        assertBehavesTtpGroup(u);
+
+        ObjectCache.clearAllCaches();
+        User u2 = User.getById(u.getId());
+
+        assertThat(u2, is(not(sameInstance(u))));
+        assertBehavesTtpGroup(u2);
+
+        GigiResultSet rs = fetchGroupRowsFor(u);
+
+        assertTrue(rs.next());
+        assertEquals(0, rs.getInt("revokedby"));
+        assertEquals(granter.getId(), rs.getInt("grantedby"));
+        assertEquals(ttpGroup.getDatabaseName(), rs.getString("permission"));
+
+        assertNull(rs.getDate("deleted"));
+        assertNotNull(rs.getDate("granted"));
+
+        assertFalse(rs.next());
+    }
+
+    @Test
+    public void testRemoveObject() throws GigiApiException, SQLException {
+        User u = User.getById(createVerifiedUser("fname", "lname", createUniqueName() + "@example.org", TEST_PASSWORD));
+
+        User granter = User.getById(createVerifiedUser("grFname", "lname", createUniqueName() + "@example.org", TEST_PASSWORD));
+
+        assertBehavesEmpty(u);
+        u.grantGroup(granter, ttpGroup);
+        assertBehavesTtpGroup(u);
+        u.revokeGroup(granter, ttpGroup);
+        assertBehavesEmpty(u);
+
+        ObjectCache.clearAllCaches();
+        User u2 = User.getById(u.getId());
+        assertThat(u2, is(not(sameInstance(u))));
+        assertBehavesEmpty(u);
+
+        GigiResultSet rs = fetchGroupRowsFor(u);
+        assertTrue(rs.next());
+        assertEquals(granter.getId(), rs.getInt("revokedby"));
+        assertEquals(granter.getId(), rs.getInt("grantedby"));
+        assertEquals(ttpGroup.getDatabaseName(), rs.getString("permission"));
+
+        assertNotNull(rs.getDate("deleted"));
+        assertNotNull(rs.getDate("granted"));
+
+        assertFalse(rs.next());
+    }
+
+    private GigiResultSet fetchGroupRowsFor(User u) throws SQLException {
+        GigiPreparedStatement ps = DatabaseConnection.getInstance().prepare("SELECT * FROM user_groups WHERE user=?");
+        ps.setInt(1, u.getId());
+        GigiResultSet rs = ps.executeQuery();
+        return rs;
+    }
+
+    private void assertBehavesEmpty(User u) {
+        assertEquals(Collections.emptySet(), u.getGroups());
+        assertFalse(u.isInGroup(ttpGroup));
+        assertFalse(u.isInGroup(supporter));
+    }
+
+    private void assertBehavesTtpGroup(User u) {
+        assertEquals(new HashSet<>(Arrays.asList(ttpGroup)), u.getGroups());
+        assertTrue(u.isInGroup(ttpGroup));
+        assertFalse(u.isInGroup(supporter));
+    }
+
+    @Test
+    public void testListGroup() {
+        Group g = Group.getByString("supporter");
+        User ux = User.getById(createVerifiedUser("fn", "ln", createUniqueName() + "@example.org", TEST_PASSWORD));
+        User ux2 = User.getById(createVerifiedUser("fn", "ln", createUniqueName() + "@example.org", TEST_PASSWORD));
+        assertEquals(0, g.getMembers(0, 10).length);
+        ux.grantGroup(ux, g);
+        assertEquals(1, g.getMembers(0, 10).length);
+        ux2.grantGroup(ux, g);
+        assertEquals(2, g.getMembers(0, 10).length);
+        ux2.revokeGroup(ux, g);
+        assertEquals(1, g.getMembers(0, 10).length);
+        ux.revokeGroup(ux, g);
+        assertEquals(0, g.getMembers(0, 10).length);
+
+    }
+
+    @Test
+    public void testGroupEquals() {
+        assertTrue(ttpGroup.equals(ttpGroup));
+        assertFalse(ttpGroup.equals(null));
+        assertFalse(ttpGroup.equals(""));
+        assertFalse(ttpGroup.equals(supporter));
+    }
+}
diff --git a/tests/org/cacert/gigi/api/IssueCert.java b/tests/org/cacert/gigi/api/IssueCert.java
new file mode 100644 (file)
index 0000000..007edb5
--- /dev/null
@@ -0,0 +1,48 @@
+package org.cacert.gigi.api;
+
+import static org.junit.Assert.*;
+
+import java.io.ByteArrayInputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.net.URLEncoder;
+import java.security.KeyPair;
+import java.security.PrivateKey;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+import java.util.Collection;
+
+import org.cacert.gigi.dbObjects.Certificate;
+import org.cacert.gigi.dbObjects.Certificate.CSRType;
+import org.cacert.gigi.dbObjects.CertificateProfile;
+import org.cacert.gigi.testUtils.ClientTest;
+import org.cacert.gigi.testUtils.IOUtils;
+import org.junit.Test;
+
+import sun.security.x509.X500Name;
+
+public class IssueCert extends ClientTest {
+
+    @Test
+    public void testIssueCert() throws Exception {
+        KeyPair kp = generateKeypair();
+        String key1 = generatePEMCSR(kp, "CN=testmail@example.com");
+        Certificate c = new Certificate(u, Certificate.buildDN("CN", "testmail@example.com"), "sha256", key1, CSRType.CSR, CertificateProfile.getById(1));
+        final PrivateKey pk = kp.getPrivate();
+        c.issue(null, "2y").waitFor(60000);
+        final X509Certificate ce = c.cert();
+        HttpURLConnection connection = (HttpURLConnection) new URL("https://" + getServerName().replaceFirst("^www.", "api.") + "/account/certs/new").openConnection();
+        authenticateClientCert(pk, ce, connection);
+        connection.setDoOutput(true);
+        OutputStream os = connection.getOutputStream();
+        os.write(("csr=" + URLEncoder.encode(generatePEMCSR(kp, "CN=a b"), "UTF-8")).getBytes("UTF-8"));
+        os.flush();
+        assertEquals(connection.getResponseCode(), 200);
+        String cert = IOUtils.readURL(new InputStreamReader(connection.getInputStream(), "UTF-8"));
+        CertificateFactory cf = CertificateFactory.getInstance("X509");
+        Collection<? extends java.security.cert.Certificate> certs = cf.generateCertificates(new ByteArrayInputStream(cert.getBytes("UTF-8")));
+        assertEquals("a b", ((X500Name) ((X509Certificate) certs.iterator().next()).getSubjectDN()).getCommonName());
+    }
+}
diff --git a/tests/org/cacert/gigi/crypto/TestSPKAC.java b/tests/org/cacert/gigi/crypto/TestSPKAC.java
new file mode 100644 (file)
index 0000000..3dd39a2
--- /dev/null
@@ -0,0 +1,72 @@
+package org.cacert.gigi.crypto;
+
+import static org.junit.Assert.*;
+
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.security.GeneralSecurityException;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.Signature;
+import java.security.SignatureException;
+import java.security.interfaces.RSAKey;
+import java.util.Base64;
+
+import org.cacert.gigi.testUtils.IOUtils;
+import org.junit.Test;
+
+import sun.security.x509.X509Key;
+
+public class TestSPKAC {
+
+    @Test
+    public void testParse() throws GeneralSecurityException, IOException {
+        String spkac = IOUtils.readURL(new InputStreamReader(TestSPKAC.class.getResourceAsStream("sampleSPKAC.txt"), "UTF-8"));
+        SPKAC parsed = new SPKAC(Base64.getDecoder().decode(spkac.replaceAll("[\r\n]", "")));
+        assertEquals("i am in the testcase", parsed.getChallenge());
+        RSAKey k = ((RSAKey) parsed.getPubkey());
+        assertEquals("a4004c2addf204fb26ce98f5963cc76def609ec0c50905e091fb84e986e3cb" + //
+                "0d5e14edb9cb8e10524350bd2351589284a4f631ddf9b87f04ea0e58f7d8d816b58" + //
+                "d052ce08b6576c04a7d45daf25b0ac9306f9cbb1f626e4ac301b7a4a3a062252b9a" + //
+                "472b2cde5ec803407b18879a59ccba7716016b1de4537a005b2bd0fd6071", k.getModulus().toString(16));
+    }
+
+    @Test
+    public void testAddData() throws GeneralSecurityException, IOException {
+        String spkac = IOUtils.readURL(new InputStreamReader(TestSPKAC.class.getResourceAsStream("sampleSPKAC.txt"), "UTF-8"));
+        byte[] data = Base64.getDecoder().decode(spkac.replaceAll("[\r\n]", ""));
+        byte[] tampered = new byte[data.length + 1];
+        System.arraycopy(data, 0, tampered, 0, data.length);
+        try {
+            new SPKAC(tampered);
+            fail("Expected illegal arg exception.");
+        } catch (IllegalArgumentException e) {
+            // expected
+        }
+        // change the last byte of the signature.
+        data[data.length - 1]--;
+        try {
+            new SPKAC(data);
+            fail("Expected SignatureException.");
+        } catch (SignatureException e) {
+            // expected
+        }
+    }
+
+    @Test
+    public void testGen() throws GeneralSecurityException, IOException {
+        KeyPairGenerator pkg = KeyPairGenerator.getInstance("RSA");
+        pkg.initialize(1024);
+        KeyPair kp = pkg.generateKeyPair();
+
+        SPKAC s = new SPKAC((X509Key) kp.getPublic(), "this is a even bigger challange");
+        Signature sign = Signature.getInstance("SHA512withRSA");
+        sign.initSign(kp.getPrivate());
+
+        byte[] res = s.getEncoded(sign);
+        SPKAC parsed = new SPKAC(res);
+        assertEquals(s.getChallenge(), parsed.getChallenge());
+        assertEquals(s.getPubkey(), parsed.getPubkey());
+
+    }
+}
diff --git a/tests/org/cacert/gigi/crypto/sampleSPKAC.txt b/tests/org/cacert/gigi/crypto/sampleSPKAC.txt
new file mode 100644 (file)
index 0000000..b3f44b6
--- /dev/null
@@ -0,0 +1,8 @@
+MIIBTjCBuDCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEApABMKt3yBPsmzpj
+1ljzHbe9gnsDFCQXgkfuE6Ybjyw1eFO25y44QUkNQvSNRWJKEpPYx3fm4fwTqDl
+j32NgWtY0FLOCLZXbASn1F2vJbCskwb5y7H2JuSsMBt6SjoGIlK5pHKyzeXsgDQ
+HsYh5pZzLp3FgFrHeRTegBbK9D9YHECAwEAARYUaSBhbSBpbiB0aGUgdGVzdGNh
+c2UwDQYJKoZIhvcNAQEEBQADgYEAamIuLgSIgEfy5VSp+0R2wCtmxVvX9mIEhVg
+kbnNjC1bNJdtM3HPVlYbPRKuSaucgF4A/pPX55l6JECsIu6yGIyAszwGc1+rspg
+zEztsuU38IOl0sdxZBujyv2v1eoMB6TzBlsJ2hb/pC8XlmrCa5g6n1hI6XLgdu6
+3tyT4lBQ6E=
diff --git a/tests/org/cacert/gigi/email/TestEmailProviderClass.java b/tests/org/cacert/gigi/email/TestEmailProviderClass.java
new file mode 100644 (file)
index 0000000..1098ac3
--- /dev/null
@@ -0,0 +1,45 @@
+package org.cacert.gigi.email;
+
+import static org.junit.Assert.*;
+import static org.junit.Assume.*;
+
+import java.io.IOException;
+import java.util.Properties;
+
+import org.cacert.gigi.testUtils.ConfiguredTest;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+public class TestEmailProviderClass extends ConfiguredTest {
+
+    @Test
+    public void testNonmail() throws IOException {
+        String result = EmailProvider.getInstance().checkEmailServer(0, "nomail");
+        assertNotEquals(EmailProvider.OK, result);
+    }
+
+    @Test
+    public void testFastcheckSucceed() throws IOException {
+        String succmail = getTestProps().getProperty("email.address");
+        assumeNotNull(succmail);
+
+        String result = EmailProvider.getInstance().checkEmailServer(0, succmail);
+        assertEquals(EmailProvider.OK, result);
+    }
+
+    @Test
+    public void testFastcheckFail() throws IOException {
+        String failmail = getTestProps().getProperty("email.non-address");
+        assumeNotNull(failmail);
+
+        String result = EmailProvider.getInstance().checkEmailServer(0, failmail);
+        assertNotEquals(EmailProvider.OK, result);
+    }
+
+    @BeforeClass
+    public static void initMailsystem() {
+        Properties prop = new Properties();
+        prop.setProperty("emailProvider", "org.cacert.gigi.email.Sendmail");
+        EmailProvider.initSystem(prop, null, null);
+    }
+}
diff --git a/tests/org/cacert/gigi/email/TestSendmail.java b/tests/org/cacert/gigi/email/TestSendmail.java
new file mode 100644 (file)
index 0000000..83cb9aa
--- /dev/null
@@ -0,0 +1,127 @@
+package org.cacert.gigi.email;
+
+import static org.hamcrest.CoreMatchers.*;
+import static org.junit.Assert.*;
+import static org.junit.Assume.*;
+
+import java.io.BufferedReader;
+import java.io.EOFException;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.OutputStreamWriter;
+import java.io.PrintWriter;
+import java.net.Socket;
+import java.security.GeneralSecurityException;
+import java.security.InvalidKeyException;
+import java.security.KeyPair;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+import java.security.SignatureException;
+import java.security.cert.CertificateException;
+import java.util.Base64;
+import java.util.Date;
+import java.util.Properties;
+import java.util.Random;
+
+import javax.net.ssl.SSLSocketFactory;
+
+import org.cacert.gigi.testUtils.ConfiguredTest;
+import org.junit.Test;
+
+import sun.security.x509.AlgorithmId;
+import sun.security.x509.CertificateAlgorithmId;
+import sun.security.x509.CertificateSerialNumber;
+import sun.security.x509.CertificateValidity;
+import sun.security.x509.CertificateVersion;
+import sun.security.x509.CertificateX509Key;
+import sun.security.x509.X500Name;
+import sun.security.x509.X509CertImpl;
+import sun.security.x509.X509CertInfo;
+
+public class TestSendmail extends ConfiguredTest {
+
+    private static final Random rng = new Random();
+
+    @Test
+    public void testSendmail() throws IOException, GeneralSecurityException {
+        initSelfsign();
+
+        String succmail = getTestProps().getProperty("email.address");
+        String pass = getTestProps().getProperty("email.password");
+        String imap = getTestProps().getProperty("email.imap");
+        String imapuser = getTestProps().getProperty("email.imap.user");
+        assumeNotNull(succmail, pass, imap, imapuser);
+
+        String subj = "subj-" + createUniqueName();
+        String msg = "msg-" + createUniqueName();
+        EmailProvider.getInstance().sendmail(succmail, subj, msg, "system@cacert.org", "system@cacert.org", "Testtarget", "Testsender", null, false);
+
+        try (Socket s = SSLSocketFactory.getDefault().createSocket(imap, 993);//
+                PrintWriter pw = new PrintWriter(new OutputStreamWriter(s.getOutputStream(), "UTF-8"), true);//
+                BufferedReader br = new BufferedReader(new InputStreamReader(s.getInputStream(), "UTF-8"))) {
+            pw.println("a001 login " + imapuser + " " + pass);
+            imapUntil(br, "a001");
+            pw.println("a002 select inbox");
+            String overview = imapUntil(br, "a002");
+            overview = overview.replaceFirst(".*\\* ([0-9]+) EXISTS.*", "$1");
+            int cont = Integer.parseInt(overview);
+
+            int msgid = -1;
+            for (int i = 1; i <= cont; i++) {
+                pw.println("m003" + i + " fetch " + i + " body[header]");
+                String body = imapUntil(br, "m003" + i);
+                if (body.contains(subj)) {
+                    msgid = i;
+                    break;
+                }
+            }
+            assertNotEquals( -1, msgid);
+            pw.println("a003 fetch " + msgid + " body[]");
+            String body = imapUntil(br, "a003");
+            pw.println("delete store " + msgid + " +flags \\deleted");
+            imapUntil(br, "delete");
+            pw.println("exp expunge");
+            imapUntil(br, "exp");
+            pw.println("log logout");
+            imapUntil(br, "log");
+            assertThat(body, containsString("From: support@cacert.local"));
+            assertThat(body, containsString("To: gigi-testuser@dogcraft.de"));
+            assertThat(body, containsString("Subject: " + subj));
+            assertThat(body, containsString(Base64.getEncoder().encodeToString(msg.getBytes("UTF-8"))));
+
+            // TODO maybe verify signature
+        }
+    }
+
+    private String imapUntil(BufferedReader br, String target) throws IOException {
+        StringBuffer response = new StringBuffer();
+        String line = "";
+        while ( !line.startsWith(target)) {
+            line = br.readLine();
+            if (line == null) {
+                throw new EOFException();
+            }
+            response.append(line);
+        }
+        return response.toString();
+    }
+
+    private void initSelfsign() throws GeneralSecurityException, CertificateException, IOException, NoSuchAlgorithmException, InvalidKeyException, NoSuchProviderException, SignatureException {
+        Properties prop = new Properties();
+        prop.setProperty("emailProvider", "org.cacert.gigi.email.Sendmail");
+        KeyPair kp = generateKeypair();
+        X509CertInfo info = new X509CertInfo();
+        // Add all mandatory attributes
+        info.set(X509CertInfo.VERSION, new CertificateVersion(CertificateVersion.V3));
+        info.set(X509CertInfo.SERIAL_NUMBER, new CertificateSerialNumber(rng.nextInt() & 0x7fffffff));
+        AlgorithmId algID = AlgorithmId.get("SHA256WithRSA");
+        info.set(X509CertInfo.ALGORITHM_ID, new CertificateAlgorithmId(algID));
+        info.set(X509CertInfo.SUBJECT, new X500Name("EMAIL=system@cacert.org"));
+        info.set(X509CertInfo.KEY, new CertificateX509Key(kp.getPublic()));
+        info.set(X509CertInfo.VALIDITY, new CertificateValidity(new Date(System.currentTimeMillis()), new Date(System.currentTimeMillis() + 60 * 60 * 1000)));
+        info.set(X509CertInfo.ISSUER, new X500Name("CN=test-issue"));
+        X509CertImpl cert = new X509CertImpl(info);
+        cert.sign(kp.getPrivate(), "SHA256WithRSA");
+        EmailProvider.initSystem(prop, cert, kp.getPrivate());
+    }
+}
diff --git a/tests/org/cacert/gigi/pages/account/TestCertificateAdd.java b/tests/org/cacert/gigi/pages/account/TestCertificateAdd.java
new file mode 100644 (file)
index 0000000..e29dcac
--- /dev/null
@@ -0,0 +1,323 @@
+package org.cacert.gigi.pages.account;
+
+import static org.hamcrest.CoreMatchers.*;
+import static org.junit.Assert.*;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.io.UnsupportedEncodingException;
+import java.net.HttpURLConnection;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.URLConnection;
+import java.net.URLEncoder;
+import java.security.GeneralSecurityException;
+import java.security.KeyPair;
+import java.security.Signature;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+import java.text.SimpleDateFormat;
+import java.util.Arrays;
+import java.util.Base64;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.TimeZone;
+import java.util.Vector;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.cacert.gigi.crypto.SPKAC;
+import org.cacert.gigi.dbObjects.Digest;
+import org.cacert.gigi.pages.account.certs.CertificateAdd;
+import org.cacert.gigi.pages.account.certs.CertificateRequest;
+import org.cacert.gigi.testUtils.ClientTest;
+import org.cacert.gigi.testUtils.IOUtils;
+import org.cacert.gigi.util.PEM;
+import org.junit.Test;
+
+import sun.security.pkcs.PKCS9Attribute;
+import sun.security.pkcs10.PKCS10Attribute;
+import sun.security.pkcs10.PKCS10Attributes;
+import sun.security.util.ObjectIdentifier;
+import sun.security.x509.CertificateExtensions;
+import sun.security.x509.DNSName;
+import sun.security.x509.ExtendedKeyUsageExtension;
+import sun.security.x509.GeneralName;
+import sun.security.x509.GeneralNameInterface;
+import sun.security.x509.GeneralNames;
+import sun.security.x509.RFC822Name;
+import sun.security.x509.SubjectAlternativeNameExtension;
+import sun.security.x509.X509Key;
+
+public class TestCertificateAdd extends ClientTest {
+
+    KeyPair kp = generateKeypair();
+
+    String csrf;
+
+    public TestCertificateAdd() throws GeneralSecurityException, IOException {
+        TestDomain.addDomain(cookie, uniq + ".tld");
+
+    }
+
+    @Test
+    public void testSimpleServer() throws IOException, GeneralSecurityException {
+        PKCS10Attributes atts = buildAtts(new ObjectIdentifier[] {
+            CertificateRequest.OID_KEY_USAGE_SSL_SERVER
+        }, new DNSName(uniq + ".tld"));
+
+        String pem = generatePEMCSR(kp, "CN=a." + uniq + ".tld", atts);
+
+        String[] res = fillOutForm("CSR=" + URLEncoder.encode(pem, "UTF-8"));
+        assertArrayEquals(new String[] {
+                "server", "CAcert WoT User", "dns:a." + uniq + ".tld\ndns:" + uniq + ".tld\n", Digest.SHA256.toString()
+        }, res);
+    }
+
+    @Test
+    public void testSimpleMail() throws IOException, GeneralSecurityException {
+        PKCS10Attributes atts = buildAtts(new ObjectIdentifier[] {
+            CertificateRequest.OID_KEY_USAGE_EMAIL_PROTECTION
+        }, new DNSName("a." + uniq + ".tld"), new DNSName("b." + uniq + ".tld"), new RFC822Name(email));
+
+        String pem = generatePEMCSR(kp, "CN=a b", atts, "SHA384WithRSA");
+
+        String[] res = fillOutForm("CSR=" + URLEncoder.encode(pem, "UTF-8"));
+        assertArrayEquals(new String[] {
+                "mail", "a b", "email:" + email + "\ndns:a." + uniq + ".tld\ndns:b." + uniq + ".tld\n", Digest.SHA384.toString()
+        }, res);
+    }
+
+    @Test
+    public void testSimpleClient() throws IOException, GeneralSecurityException {
+        PKCS10Attributes atts = buildAtts(new ObjectIdentifier[] {
+            CertificateRequest.OID_KEY_USAGE_SSL_CLIENT
+        }, new RFC822Name(email));
+
+        String pem = generatePEMCSR(kp, "CN=a b,email=" + email, atts, "SHA512WithRSA");
+
+        String[] res = fillOutForm("CSR=" + URLEncoder.encode(pem, "UTF-8"));
+        assertArrayEquals(new String[] {
+                "client", "a b", "email:" + email + "\n", Digest.SHA512.toString()
+        }, res);
+    }
+
+    @Test
+    public void testSPKAC() throws GeneralSecurityException, IOException {
+        testSPKAC(false);
+        testSPKAC(true);
+    }
+
+    @Test
+    public void testIssue() throws IOException, GeneralSecurityException {
+        PKCS10Attributes atts = buildAtts(new ObjectIdentifier[] {
+            CertificateRequest.OID_KEY_USAGE_SSL_CLIENT
+        }, new RFC822Name(email));
+
+        String pem = generatePEMCSR(kp, "CN=a b,email=" + email, atts, "SHA512WithRSA");
+
+        String[] res = fillOutForm("CSR=" + URLEncoder.encode(pem, "UTF-8"));
+        assertArrayEquals(new String[] {
+                "client", "a b", "email:" + email + "\n", Digest.SHA512.toString()
+        }, res);
+
+        HttpURLConnection huc = (HttpURLConnection) ncert.openConnection();
+        huc.setRequestProperty("Cookie", cookie);
+        huc.setDoOutput(true);
+        OutputStream out = huc.getOutputStream();
+        out.write(("csrf=" + URLEncoder.encode(csrf, "UTF-8")).getBytes("UTF-8"));
+        out.write(("&profile=client&CN=a+b&SANs=" + URLEncoder.encode("email:" + email + "\n", "UTF-8")).getBytes("UTF-8"));
+        out.write(("&hash_alg=SHA512&CCA=y").getBytes("UTF-8"));
+        URLConnection uc = authenticate(new URL(huc.getHeaderField("Location") + ".crt"));
+        String crt = IOUtils.readURL(new InputStreamReader(uc.getInputStream(), "UTF-8"));
+
+        uc = authenticate(new URL(huc.getHeaderField("Location") + ".cer"));
+        byte[] cer = IOUtils.readURL(uc.getInputStream());
+        assertArrayEquals(cer, PEM.decode("CERTIFICATE", crt));
+
+        uc = authenticate(new URL(huc.getHeaderField("Location") + ".cer?install"));
+        byte[] cer2 = IOUtils.readURL(uc.getInputStream());
+        assertArrayEquals(cer, cer2);
+        assertEquals("application/x-x509-user-cert", uc.getHeaderField("Content-type"));
+
+        uc = authenticate(new URL(huc.getHeaderField("Location")));
+        String gui = IOUtils.readURL(uc);
+        assertThat(gui, containsString("clientAuth"));
+        assertThat(gui, containsString("CN=a b"));
+        assertThat(gui, containsString("SHA512withRSA"));
+        assertThat(gui, containsString("RFC822Name: " + email));
+
+    }
+
+    @Test
+    public void testValidityPeriodCalendar() throws IOException, GeneralSecurityException {
+        testCertificateValidityRelative(Calendar.YEAR, 2, "2y", true);
+        testCertificateValidityRelative(Calendar.YEAR, 1, "1y", true);
+        testCertificateValidityRelative(Calendar.MONTH, 3, "3m", true);
+        testCertificateValidityRelative(Calendar.MONTH, 7, "7m", true);
+        testCertificateValidityRelative(Calendar.MONTH, 13, "13m", true);
+
+        testCertificateValidityRelative(Calendar.MONTH, 13, "-1m", false);
+    }
+
+    @Test
+    public void testValidityPeriodWhishStart() throws IOException, GeneralSecurityException {
+        long now = System.currentTimeMillis();
+        final long MS_PER_DAY = 24 * 60 * 60 * 1000;
+        now -= now % MS_PER_DAY;
+        now += MS_PER_DAY;
+        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
+        sdf.setTimeZone(TimeZone.getTimeZone("UTC"));
+
+        Date start = new Date(now);
+        Date end = new Date(now + MS_PER_DAY * 10);
+        X509Certificate res = createCertWithValidity("&validFrom=" + sdf.format(start) + "&validity=" + sdf.format(end));
+        assertNotNull(res);
+        assertEquals(start, res.getNotBefore());
+        assertEquals(end, res.getNotAfter());
+    }
+
+    private void testCertificateValidityRelative(int field, int amount, String length, boolean shouldsucceed) throws IOException, GeneralSecurityException, UnsupportedEncodingException, MalformedURLException, CertificateException {
+        X509Certificate parsed = createCertWithValidity("&validFrom=now&validity=" + length);
+        if (parsed == null) {
+            assertTrue( !shouldsucceed);
+            return;
+        } else {
+            assertTrue(shouldsucceed);
+        }
+
+        long now = System.currentTimeMillis();
+        Date start = parsed.getNotBefore();
+        Date end = parsed.getNotAfter();
+        Calendar c = Calendar.getInstance();
+        c.setTimeZone(TimeZone.getTimeZone("UTC"));
+        c.setTime(start);
+        c.add(field, amount);
+        assertTrue(Math.abs(start.getTime() - now) < 10000);
+        assertEquals(c.getTime(), end);
+    }
+
+    private X509Certificate createCertWithValidity(String validity) throws IOException, GeneralSecurityException, UnsupportedEncodingException, MalformedURLException, CertificateException {
+        PKCS10Attributes atts = buildAtts(new ObjectIdentifier[] {
+            CertificateRequest.OID_KEY_USAGE_SSL_CLIENT
+        }, new RFC822Name(email));
+
+        String pem = generatePEMCSR(kp, "CN=a b", atts, "SHA512WithRSA");
+        fillOutForm("CSR=" + URLEncoder.encode(pem, "UTF-8"));
+
+        HttpURLConnection huc = (HttpURLConnection) ncert.openConnection();
+        huc.setRequestProperty("Cookie", cookie);
+        huc.setDoOutput(true);
+        OutputStream out = huc.getOutputStream();
+        out.write(("csrf=" + URLEncoder.encode(csrf, "UTF-8")).getBytes("UTF-8"));
+        out.write(("&profile=client&CN=a+b&SANs=" + URLEncoder.encode("email:" + email + "\n", "UTF-8")).getBytes("UTF-8"));
+        out.write(("&hash_alg=SHA512&CCA=y&").getBytes("UTF-8"));
+        out.write(validity.getBytes("UTF-8"));
+
+        String certurl = huc.getHeaderField("Location");
+        if (certurl == null) {
+            return null;
+        }
+        URLConnection uc = authenticate(new URL(certurl + ".crt"));
+        String crt = IOUtils.readURL(new InputStreamReader(uc.getInputStream(), "UTF-8"));
+
+        CertificateFactory cf = CertificateFactory.getInstance("X.509");
+        X509Certificate parsed = (X509Certificate) cf.generateCertificate(new ByteArrayInputStream(crt.getBytes("UTF-8")));
+        return parsed;
+    }
+
+    private URLConnection authenticate(URL url) throws IOException {
+        URLConnection uc = url.openConnection();
+        uc.setRequestProperty("Cookie", cookie);
+        return uc;
+    }
+
+    protected String testSPKAC(boolean correctChallange) throws GeneralSecurityException, IOException {
+        HttpURLConnection uc = (HttpURLConnection) ncert.openConnection();
+        uc.setRequestProperty("Cookie", cookie);
+        String s = IOUtils.readURL(uc);
+
+        csrf = extractPattern(s, Pattern.compile("<input [^>]*name='csrf' [^>]*value='([^']*)'>"));
+        String challenge = extractPattern(s, Pattern.compile("<keygen [^>]*name=\"SPKAC\" [^>]*challenge=\"([^\"]*)\"/>"));
+
+        SPKAC spk = new SPKAC((X509Key) kp.getPublic(), challenge + (correctChallange ? "" : "b"));
+        Signature sign = Signature.getInstance("SHA512WithRSA");
+        sign.initSign(kp.getPrivate());
+        try {
+            String[] res = fillOutFormDirect("SPKAC=" + URLEncoder.encode(Base64.getEncoder().encodeToString(spk.getEncoded(sign)), "UTF-8"));
+            if ( !correctChallange) {
+                fail("Should not succeed with wrong challange.");
+            }
+            assertArrayEquals(new String[] {
+                    "client", CertificateRequest.DEFAULT_CN, "", Digest.SHA512.toString()
+            }, res);
+        } catch (Error e) {
+            assertTrue(e.getMessage().startsWith("<div>Challenge mismatch"));
+        }
+        return csrf;
+    }
+
+    private PKCS10Attributes buildAtts(ObjectIdentifier[] ekuOIDs, GeneralNameInterface... SANs) throws IOException {
+        CertificateExtensions attributeValue = new CertificateExtensions();
+        GeneralNames names = new GeneralNames();
+
+        for (GeneralNameInterface name : SANs) {
+            names.add(new GeneralName(name));
+        }
+        attributeValue.set("SANs", new SubjectAlternativeNameExtension(names));
+        PKCS10Attributes atts = new PKCS10Attributes(new PKCS10Attribute[] {
+            new PKCS10Attribute(PKCS9Attribute.EXTENSION_REQUEST_OID, attributeValue)
+        });
+        ExtendedKeyUsageExtension eku = new ExtendedKeyUsageExtension(//
+                new Vector<>(Arrays.<ObjectIdentifier>asList(ekuOIDs)));
+        attributeValue.set("eku", eku);
+        return atts;
+    }
+
+    private final URL ncert = new URL("https://" + getServerName() + CertificateAdd.PATH);
+
+    private String[] fillOutForm(String pem) throws IOException {
+        HttpURLConnection uc = (HttpURLConnection) ncert.openConnection();
+        uc.setRequestProperty("Cookie", cookie);
+        csrf = getCSRF(uc);
+        return fillOutFormDirect(pem);
+
+    }
+
+    private String[] fillOutFormDirect(String pem) throws IOException {
+
+        HttpURLConnection uc = (HttpURLConnection) ncert.openConnection();
+        uc.setRequestProperty("Cookie", cookie);
+        uc.setDoOutput(true);
+        uc.getOutputStream().write(("csrf=" + URLEncoder.encode(csrf, "UTF-8") + "&" + pem).getBytes("UTF-8"));
+        uc.getOutputStream().flush();
+
+        return extractFormData(uc);
+    }
+
+    private String[] extractFormData(HttpURLConnection uc) throws IOException, Error {
+        String result = IOUtils.readURL(uc);
+        if (result.contains("<div class='formError'>")) {
+            String s = fetchStartErrorMessage(result);
+            throw new Error(s);
+        }
+
+        String profileKey = extractPattern(result, Pattern.compile("<option value=\"([^\"]*)\" selected>"));
+        String resultingCN = extractPattern(result, Pattern.compile("<input [^>]*name='CN' [^>]*value='([^']*)'/>"));
+        String txt = extractPattern(result, Pattern.compile("<textarea [^>]*name='SANs' [^>]*>([^<]*)</textarea>"));
+        String md = extractPattern(result, Pattern.compile("<input type=\"radio\" [^>]*name=\"hash_alg\" value=\"([^\"]*)\" checked='checked'/>"));
+        return new String[] {
+                profileKey, resultingCN, txt, md
+        };
+    }
+
+    private String extractPattern(String result, Pattern p) {
+        Matcher m = p.matcher(result);
+        assertTrue(m.find());
+        String resultingCN = m.group(1);
+        return resultingCN;
+    }
+}
diff --git a/tests/org/cacert/gigi/pages/account/TestCertificateRequest.java b/tests/org/cacert/gigi/pages/account/TestCertificateRequest.java
new file mode 100644 (file)
index 0000000..ecea232
--- /dev/null
@@ -0,0 +1,48 @@
+package org.cacert.gigi.pages.account;
+
+import static org.hamcrest.CoreMatchers.*;
+import static org.junit.Assert.*;
+
+import java.security.GeneralSecurityException;
+import java.security.KeyPair;
+
+import org.cacert.gigi.GigiApiException;
+import org.cacert.gigi.pages.account.certs.CertificateRequest;
+import org.cacert.gigi.testUtils.ClientTest;
+import org.junit.Test;
+
+public class TestCertificateRequest extends ClientTest {
+
+    KeyPair kp = generateKeypair();
+
+    public TestCertificateRequest() throws GeneralSecurityException {}
+
+    @Test
+    public void testIssuingOtherName() throws Exception {
+        try {
+            new CertificateRequest(u, generatePEMCSR(kp, "CN=hansi")).draft();
+        } catch (GigiApiException e) {
+            assertThat(e.getMessage(), containsString("does not match the details"));
+        }
+    }
+
+    @Test
+    public void testIssuingDefault() throws Exception {
+        new CertificateRequest(u, generatePEMCSR(kp, "CN=" + CertificateRequest.DEFAULT_CN)).draft();
+    }
+
+    @Test
+    public void testIssuingRealName() throws Exception {
+        new CertificateRequest(u, generatePEMCSR(kp, "CN=a b")).draft();
+    }
+
+    @Test
+    public void testIssuingModifiedName() throws Exception {
+        try {
+            new CertificateRequest(u, generatePEMCSR(kp, "CN=a ab")).draft();
+        } catch (GigiApiException e) {
+            assertThat(e.getMessage(), containsString("does not match the details"));
+        }
+
+    }
+}
diff --git a/tests/org/cacert/gigi/pages/account/TestChangePassword.java b/tests/org/cacert/gigi/pages/account/TestChangePassword.java
new file mode 100644 (file)
index 0000000..89d318e
--- /dev/null
@@ -0,0 +1,101 @@
+package org.cacert.gigi.pages.account;
+
+import static org.junit.Assert.*;
+
+import java.io.IOException;
+import java.net.URLEncoder;
+
+import org.cacert.gigi.GigiApiException;
+import org.cacert.gigi.testUtils.ClientTest;
+import org.junit.Test;
+
+public class TestChangePassword extends ClientTest {
+
+    String path = ChangePasswordPage.PATH;
+
+    public TestChangePassword() throws IOException {
+        cookie = login(u.getEmail(), TEST_PASSWORD);
+        assertTrue(isLoggedin(cookie));
+    }
+
+    @Test
+    public void testChangePasswordInternal() throws IOException, GigiApiException {
+        try {
+            u.changePassword(TEST_PASSWORD + "wrong", TEST_PASSWORD + "v2");
+            fail("Password change must not succeed if old password is wrong.");
+        } catch (GigiApiException e) {
+            // expected
+        }
+        ;
+        assertTrue(isLoggedin(login(u.getEmail(), TEST_PASSWORD)));
+        u.changePassword(TEST_PASSWORD, TEST_PASSWORD + "v2");
+        assertTrue(isLoggedin(login(u.getEmail(), TEST_PASSWORD + "v2")));
+    }
+
+    @Test
+    public void testChangePasswordWeb() throws IOException {
+        String error = executeBasicWebInteraction(cookie, path, "oldpassword=" + URLEncoder.encode(TEST_PASSWORD, "UTF-8") //
+                + "&pword1=" + URLEncoder.encode(TEST_PASSWORD + "v2", "UTF-8")//
+                + "&pword2=" + URLEncoder.encode(TEST_PASSWORD + "v2", "UTF-8"));
+        assertNull(error);
+        assertTrue(isLoggedin(login(u.getEmail(), TEST_PASSWORD + "v2")));
+        assertFalse(isLoggedin(login(u.getEmail(), TEST_PASSWORD)));
+
+    }
+
+    @Test
+    public void testChangePasswordWebOldWrong() throws IOException {
+        String error = executeBasicWebInteraction(cookie, path, "oldpassword=a" + URLEncoder.encode(TEST_PASSWORD, "UTF-8") //
+                + "&pword1=" + URLEncoder.encode(TEST_PASSWORD + "v2", "UTF-8")//
+                + "&pword2=" + URLEncoder.encode(TEST_PASSWORD + "v2", "UTF-8"));
+        assertNotNull(error);
+        assertFalse(isLoggedin(login(u.getEmail(), TEST_PASSWORD + "v2")));
+        assertTrue(isLoggedin(login(u.getEmail(), TEST_PASSWORD)));
+
+    }
+
+    @Test
+    public void testChangePasswordWebNewWrong() throws IOException {
+        String error = executeBasicWebInteraction(cookie, path, "oldpassword=" + URLEncoder.encode(TEST_PASSWORD, "UTF-8") //
+                + "&pword1=" + URLEncoder.encode(TEST_PASSWORD + "v2", "UTF-8")//
+                + "&pword2=a" + URLEncoder.encode(TEST_PASSWORD + "v2", "UTF-8"));
+        assertNotNull(error);
+        assertFalse(isLoggedin(login(u.getEmail(), TEST_PASSWORD + "v2")));
+        assertTrue(isLoggedin(login(u.getEmail(), TEST_PASSWORD)));
+
+    }
+
+    @Test
+    public void testChangePasswordWebNewEasy() throws IOException {
+        String error = executeBasicWebInteraction(cookie, path, "oldpassword=" + URLEncoder.encode(TEST_PASSWORD, "UTF-8") //
+                + "&pword1=a&pword2=a");
+        assertNotNull(error);
+        assertFalse(isLoggedin(login(u.getEmail(), TEST_PASSWORD + "v2")));
+        assertTrue(isLoggedin(login(u.getEmail(), TEST_PASSWORD)));
+
+    }
+
+    @Test
+    public void testChangePasswordWebMissingFields() throws IOException {
+        String np = URLEncoder.encode(TEST_PASSWORD + "v2", "UTF-8");
+        assertTrue(isLoggedin(login(u.getEmail(), TEST_PASSWORD)));
+        String error = executeBasicWebInteraction(cookie, path, "oldpassword=" + URLEncoder.encode(TEST_PASSWORD, "UTF-8") //
+                + "&pword1=" + np);
+        assertNotNull(error);
+        assertFalse(isLoggedin(login(u.getEmail(), TEST_PASSWORD + "v2")));
+        assertTrue(isLoggedin(login(u.getEmail(), TEST_PASSWORD)));
+
+        error = executeBasicWebInteraction(cookie, path, "oldpassword=" + URLEncoder.encode(TEST_PASSWORD, "UTF-8") //
+                + "&pword2=" + np);
+        assertNotNull(error);
+        assertFalse(isLoggedin(login(u.getEmail(), TEST_PASSWORD + "v2")));
+        assertTrue(isLoggedin(login(u.getEmail(), TEST_PASSWORD)));
+
+        error = executeBasicWebInteraction(cookie, path, "pword1=" + np + "&pword2=" + np);
+        assertNotNull(error);
+        assertFalse(isLoggedin(login(u.getEmail(), TEST_PASSWORD + "v2")));
+        assertTrue(isLoggedin(login(u.getEmail(), TEST_PASSWORD)));
+
+    }
+
+}
diff --git a/tests/org/cacert/gigi/pages/account/TestContactInformation.java b/tests/org/cacert/gigi/pages/account/TestContactInformation.java
new file mode 100644 (file)
index 0000000..c63e36d
--- /dev/null
@@ -0,0 +1,42 @@
+package org.cacert.gigi.pages.account;
+
+import static org.hamcrest.CoreMatchers.*;
+import static org.junit.Assert.*;
+
+import java.io.IOException;
+import java.net.URL;
+import java.net.URLConnection;
+
+import org.cacert.gigi.testUtils.ClientTest;
+import org.cacert.gigi.testUtils.IOUtils;
+import org.junit.Test;
+
+public class TestContactInformation extends ClientTest {
+
+    @Test
+    public void testDirectoryListingToggle() throws IOException {
+        assertNull(executeBasicWebInteraction(cookie, MyDetails.PATH, "listme=1&contactinfo=&processContact", 1));
+        URLConnection url = new URL("https://" + getServerName() + MyDetails.PATH).openConnection();
+        url.setRequestProperty("Cookie", cookie);
+        String res = IOUtils.readURL(url);
+        res = res.split(java.util.regex.Pattern.quote("</table>"))[1];
+        assertThat(res, containsString("value=\"1\" selected"));
+        assertNull(executeBasicWebInteraction(cookie, MyDetails.PATH, "listme=0&contactinfo=&processContact", 1));
+        url = new URL("https://" + getServerName() + MyDetails.PATH).openConnection();
+        url.setRequestProperty("Cookie", cookie);
+        res = IOUtils.readURL(url);
+        res = res.split(java.util.regex.Pattern.quote("</table>"))[1];
+        assertTrue(res.contains("value=\"0\" selected"));
+    }
+
+    @Test
+    public void testContactinfoSet() throws IOException {
+        String text = createUniqueName();
+        assertNull(executeBasicWebInteraction(cookie, MyDetails.PATH, "listme=1&contactinfo=" + text + "&processContact", 1));
+        URLConnection url = new URL("https://" + getServerName() + MyDetails.PATH).openConnection();
+        url.setRequestProperty("Cookie", cookie);
+        String res = IOUtils.readURL(url);
+        res = res.split(java.util.regex.Pattern.quote("</table>"))[1];
+        assertThat(res, containsString(text));
+    }
+}
diff --git a/tests/org/cacert/gigi/pages/account/TestDomain.java b/tests/org/cacert/gigi/pages/account/TestDomain.java
new file mode 100644 (file)
index 0000000..1a3a5a0
--- /dev/null
@@ -0,0 +1,25 @@
+package org.cacert.gigi.pages.account;
+
+import static org.junit.Assert.*;
+
+import java.io.IOException;
+import java.net.URLEncoder;
+
+import org.cacert.gigi.pages.account.domain.DomainOverview;
+import org.cacert.gigi.testUtils.ClientTest;
+import org.junit.Test;
+
+public class TestDomain extends ClientTest {
+
+    public TestDomain() throws IOException {}
+
+    @Test
+    public void testAdd() throws IOException {
+        assertNull(addDomain(cookie, uniq + ".de"));
+        assertNotNull(addDomain(cookie, uniq + ".de"));
+    }
+
+    public static String addDomain(String session, String domain) throws IOException {
+        return executeBasicWebInteraction(session, DomainOverview.PATH, "adddomain&newdomain=" + URLEncoder.encode(domain, "UTF-8"), 1);
+    }
+}
diff --git a/tests/org/cacert/gigi/pages/account/TestMailManagement.java b/tests/org/cacert/gigi/pages/account/TestMailManagement.java
new file mode 100644 (file)
index 0000000..6659a1e
--- /dev/null
@@ -0,0 +1,141 @@
+package org.cacert.gigi.pages.account;
+
+import static org.junit.Assert.*;
+
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.net.MalformedURLException;
+import java.net.URLEncoder;
+import java.util.Locale;
+
+import org.cacert.gigi.GigiApiException;
+import org.cacert.gigi.dbObjects.EmailAddress;
+import org.cacert.gigi.dbObjects.ObjectCache;
+import org.cacert.gigi.dbObjects.User;
+import org.cacert.gigi.localisation.Language;
+import org.cacert.gigi.pages.account.mail.MailOverview;
+import org.cacert.gigi.testUtils.ClientTest;
+import org.junit.Test;
+
+public class TestMailManagement extends ClientTest {
+
+    private String path = MailOverview.DEFAULT_PATH;
+
+    public TestMailManagement() throws IOException {
+        cookie = login(u.getEmail(), TEST_PASSWORD);
+        assertTrue(isLoggedin(cookie));
+    }
+
+    @Test
+    public void testMailAddInternal() throws InterruptedException, GigiApiException {
+        createVerifiedEmail(u);
+    }
+
+    @Test
+    public void testMailAddInternalFaulty() {
+        try {
+            new EmailAddress(u, "kurti ");
+            fail();
+        } catch (IllegalArgumentException e) {
+            // Intended.
+        }
+    }
+
+    @Test
+    public void testMailAddWeb() throws MalformedURLException, UnsupportedEncodingException, IOException {
+        String newMail = createUniqueName() + "uni@example.org";
+        assertNull(executeBasicWebInteraction(cookie, path, "addmail&newemail=" + URLEncoder.encode(newMail, "UTF-8"), 1));
+        EmailAddress[] addrs = u.getEmails();
+        for (int i = 0; i < addrs.length; i++) {
+            if (addrs[i].getAddress().equals(newMail)) {
+                return;
+            }
+        }
+        fail();
+    }
+
+    @Test
+    public void testMailAddWebFaulty() throws MalformedURLException, UnsupportedEncodingException, IOException {
+        String newMail = createUniqueName() + "uniexample.org";
+        assertNotNull(executeBasicWebInteraction(cookie, path, "addmail&newemail=" + URLEncoder.encode(newMail, "UTF-8"), 1));
+        EmailAddress[] addrs = u.getEmails();
+        for (int i = 0; i < addrs.length; i++) {
+            if (addrs[i].getAddress().equals(newMail)) {
+                fail();
+            }
+        }
+    }
+
+    @Test
+    public void testMailSetDefaultWeb() throws MalformedURLException, UnsupportedEncodingException, IOException, InterruptedException, GigiApiException {
+        EmailAddress adrr = createVerifiedEmail(u);
+        assertNull(executeBasicWebInteraction(cookie, path, "makedefault&emailid=" + adrr.getId()));
+        ObjectCache.clearAllCaches();
+        assertEquals(User.getById(u.getId()).getEmail(), adrr.getAddress());
+    }
+
+    @Test
+    public void testMailSetDefaultWebUnverified() throws MalformedURLException, UnsupportedEncodingException, IOException, InterruptedException, GigiApiException {
+        EmailAddress adrr = new EmailAddress(u, createUniqueName() + "test@test.tld");
+        adrr.insert(Language.getInstance(Locale.ENGLISH));
+        assertNotNull(executeBasicWebInteraction(cookie, path, "makedefault&emailid=" + adrr.getId()));
+        assertNotEquals(User.getById(u.getId()).getEmail(), adrr.getAddress());
+        getMailReciever().clearMails();
+    }
+
+    @Test
+    public void testMailSetDefaultWebInvalidID() throws MalformedURLException, UnsupportedEncodingException, IOException, InterruptedException, GigiApiException {
+        User u2 = User.getById(createVerifiedUser("fn", "ln", createUniqueName() + "uni@example.org", TEST_PASSWORD));
+        int id = -1;
+        EmailAddress[] emails = u2.getEmails();
+        for (int i = 0; i < emails.length; i++) {
+            if (emails[i].getAddress().equals(u2.getEmail())) {
+                id = emails[i].getId();
+            }
+        }
+        assertNotEquals(id, -1);
+        assertNotNull(executeBasicWebInteraction(cookie, path, "makedefault&emailid=" + id));
+        assertNotEquals(User.getById(u.getId()).getEmail(), u2.getEmail());
+        getMailReciever().clearMails();
+    }
+
+    @Test
+    public void testMailDeleteWeb() throws InterruptedException, GigiApiException, MalformedURLException, UnsupportedEncodingException, IOException {
+        EmailAddress addr = createVerifiedEmail(u);
+        assertNull(executeBasicWebInteraction(cookie, path, "delete&delid[]=" + addr.getId(), 0));
+        User u = User.getById(this.u.getId());
+        EmailAddress[] addresses = u.getEmails();
+        for (int i = 0; i < addresses.length; i++) {
+            assertNotEquals(addresses[i].getAddress(), addr.getAddress());
+        }
+    }
+
+    @Test
+    public void testMailDeleteWebMulti() throws InterruptedException, GigiApiException, MalformedURLException, UnsupportedEncodingException, IOException {
+        EmailAddress[] addr = new EmailAddress[] {
+                createVerifiedEmail(u), createVerifiedEmail(u)
+        };
+        assertNull(executeBasicWebInteraction(cookie, path, "delete&delid[]=" + addr[0].getId() + "&delid[]=" + addr[1].getId(), 0));
+        User u = User.getById(this.u.getId());
+        EmailAddress[] addresses = u.getEmails();
+        for (int i = 0; i < addresses.length; i++) {
+            assertNotEquals(addresses[i].getAddress(), addr[0].getAddress());
+            assertNotEquals(addresses[i].getAddress(), addr[1].getAddress());
+        }
+    }
+
+    @Test
+    public void testMailDeleteWebFaulty() throws MalformedURLException, UnsupportedEncodingException, IOException {
+        User u2 = User.getById(createVerifiedUser("fn", "ln", createUniqueName() + "uni@test.tld", TEST_PASSWORD));
+        EmailAddress em = u2.getEmails()[0];
+        assertNotNull(executeBasicWebInteraction(cookie, path, "delete&delid[]=" + em.getId(), 0));
+        u2 = User.getById(u2.getId());
+        assertNotEquals(u2.getEmails().length, 0);
+    }
+
+    @Test
+    public void testMailDeleteWebPrimary() throws MalformedURLException, UnsupportedEncodingException, IOException {
+        assertNotNull(executeBasicWebInteraction(cookie, path, "delete&delid[]=" + u.getEmails()[0].getId(), 0));
+        assertNotEquals(u.getEmails().length, 0);
+    }
+}
diff --git a/tests/org/cacert/gigi/pages/account/TestMyDetailsEdit.java b/tests/org/cacert/gigi/pages/account/TestMyDetailsEdit.java
new file mode 100644 (file)
index 0000000..57bb420
--- /dev/null
@@ -0,0 +1,112 @@
+package org.cacert.gigi.pages.account;
+
+import static org.junit.Assert.*;
+
+import java.io.IOException;
+import java.sql.Date;
+import java.util.Calendar;
+import java.util.TimeZone;
+
+import org.cacert.gigi.dbObjects.User;
+import org.cacert.gigi.testUtils.ManagedTest;
+import org.junit.Test;
+
+public class TestMyDetailsEdit extends ManagedTest {
+
+    String email = createUniqueName() + "@e.de";
+
+    int id = createVerifiedUser("Kurti", "Hansel", email, TEST_PASSWORD);
+
+    String cookie = login(email, TEST_PASSWORD);
+
+    public TestMyDetailsEdit() throws IOException {}
+
+    @Test
+    public void testChangeFnameValid() throws IOException {
+        String newName = createUniqueName();
+        assertNull(executeBasicWebInteraction(cookie, MyDetails.PATH, "fname=" + newName + "&lname=Hansel&mname=&suffix=&day=1&month=1&year=2000&processDetails", 0));
+        User u = User.getById(id);
+        assertEquals(newName, u.getFName());
+    }
+
+    @Test
+    public void testChangeLnameValid() throws IOException {
+        String newName = createUniqueName();
+        assertNull(executeBasicWebInteraction(cookie, MyDetails.PATH, "lname=" + newName + "&fname=Kurti&mname=&suffix=&day=1&month=1&year=2000&processDetails", 0));
+        User u = User.getById(id);
+        assertEquals(newName, u.getLName());
+    }
+
+    @Test
+    public void testChangeMnameValid() throws IOException {
+        String newName = createUniqueName();
+        assertNull(executeBasicWebInteraction(cookie, MyDetails.PATH, "mname=" + newName + "&fname=Kurti&lname=Hansel&suffix=&day=1&month=1&year=2000&processDetails", 0));
+        User u = User.getById(id);
+        assertEquals(newName, u.getMName());
+    }
+
+    @Test
+    public void testChangeSuffixValid() throws IOException {
+        String newName = createUniqueName();
+        assertNull(executeBasicWebInteraction(cookie, MyDetails.PATH, "mname=&fname=Kurti&lname=Hansel&suffix=" + newName + "&day=1&month=1&year=2000&processDetails", 0));
+        User u = User.getById(id);
+        assertEquals(newName, u.getSuffix());
+    }
+
+    @Test
+    public void testUnsetSuffix() throws IOException {
+        String newName = createUniqueName();
+        assertNull(executeBasicWebInteraction(cookie, MyDetails.PATH, "mname=&fname=Kurti&lname=Hansel&suffix=" + newName + "&day=1&month=1&year=2000&processDetails", 0));
+        User u = User.getById(id);
+        assertEquals(newName, u.getSuffix());
+        assertNull(executeBasicWebInteraction(cookie, MyDetails.PATH, "mname=&fname=Kurti&lname=Hansel&suffix=&day=1&month=1&year=2000&processDetails", 0));
+        clearCaches();
+        u = User.getById(id);
+        assertEquals("", u.getSuffix());
+    }
+
+    @Test
+    public void testUnsetFname() throws IOException {
+        assertNull(executeBasicWebInteraction(cookie, MyDetails.PATH, "fname=&lname=Hansel&mname=&suffix=&day=1&month=1&year=2000&processDetails", 0));
+        User u = User.getById(id);
+        assertEquals("", u.getFName());
+
+    }
+
+    @Test
+    public void testUnsetLname() throws IOException {
+        assertNotNull(executeBasicWebInteraction(cookie, MyDetails.PATH, "lname=&fname=Kurti&mname=&suffix=&day=1&month=1&year=2000&processDetails", 0));
+        User u = User.getById(id);
+        assertEquals("Hansel", u.getLName());
+    }
+
+    @Test
+    public void testUnsetMname() throws IOException {
+        String newName = createUniqueName();
+        assertNull(executeBasicWebInteraction(cookie, MyDetails.PATH, "mname=" + newName + "&fname=Kurti&lname=Hansel&suffix=&day=1&month=1&year=2000&processDetails", 0));
+        User u = User.getById(id);
+        assertEquals(newName, u.getMName());
+        assertNull(executeBasicWebInteraction(cookie, MyDetails.PATH, "lname=Hansel&fname=Kurti&mname=&suffix=&day=1&month=1&year=2000&processDetails", 0));
+        clearCaches();
+        u = User.getById(id);
+        assertEquals("", u.getMName());
+
+    }
+
+    @Test
+    public void testChangeDOBValid() throws IOException {
+        assertNull(executeBasicWebInteraction(cookie, MyDetails.PATH, "lname=Hansel&fname=Kurti&mname=&suffix=&day=1&month=2&year=2000&processDetails", 0));
+        User u = User.getById(id);
+        Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
+        cal.set(Calendar.YEAR, 2000);
+        cal.set(Calendar.DAY_OF_MONTH, Calendar.FEBRUARY);
+        cal.set(Calendar.MONTH, 1);
+        Date d = new Date(cal.getTimeInMillis());
+        assertEquals(d.toString(), u.getDoB().toString());
+    }
+
+    @Test
+    public void testChangeDOBInvalid() throws IOException {
+        assertNotNull(executeBasicWebInteraction(cookie, MyDetails.PATH, "lname=Hansel&fname=Kurti&mname=&suffix=&day=1&month=1&year=test&processDetails", 0));
+    }
+}
diff --git a/tests/org/cacert/gigi/pages/main/RegisterPageTest.java b/tests/org/cacert/gigi/pages/main/RegisterPageTest.java
new file mode 100644 (file)
index 0000000..c6b2a67
--- /dev/null
@@ -0,0 +1,181 @@
+package org.cacert.gigi.pages.main;
+
+import static org.hamcrest.CoreMatchers.*;
+import static org.junit.Assert.*;
+
+import java.io.IOException;
+import java.net.HttpURLConnection;
+import java.net.URLEncoder;
+import java.util.regex.Pattern;
+
+import org.cacert.gigi.testUtils.InitTruststore;
+import org.cacert.gigi.testUtils.ManagedTest;
+import org.cacert.gigi.testUtils.TestEmailReciever.TestMail;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+public class RegisterPageTest extends ManagedTest {
+
+    static {
+        InitTruststore.run();
+        HttpURLConnection.setFollowRedirects(false);
+    }
+
+    @Before
+    public void setUp() throws Exception {}
+
+    @Test
+    public void testSuccess() throws IOException, InterruptedException {
+        long uniq = System.currentTimeMillis();
+        registerUser("ab", "b", "correct" + uniq + "@email.de", TEST_PASSWORD);
+        assertSuccessfullRegMail();
+
+        String defaultSignup = "fname=" + URLEncoder.encode("ab", "UTF-8") + "&lname=" + URLEncoder.encode("b", "UTF-8") + "&pword1=" + URLEncoder.encode(TEST_PASSWORD, "UTF-8") + "&pword2=" + URLEncoder.encode(TEST_PASSWORD, "UTF-8") + "&day=1&month=1&year=1910&cca_agree=1&mname=mn&suffix=sf&email=";
+
+        String query = defaultSignup + URLEncoder.encode("correct3_" + uniq + "@email.de", "UTF-8") + "&general=1&country=1&regional=1&radius=1";
+        String data = fetchStartErrorMessage(runRegister(query));
+        assertNull(data);
+        assertSuccessfullRegMail();
+
+        getMailReciever().setEmailCheckError("400 Greylisted");
+        getMailReciever().setApproveRegex(Pattern.compile("a"));
+        query = defaultSignup + URLEncoder.encode("correct4_" + uniq + "@email.de", "UTF-8") + "&general=1&country=1&regional=1&radius=1";
+        data = fetchStartErrorMessage(runRegister(query));
+        assertNotNull(data);
+
+        assertNull(getMailReciever().recieve());
+
+    }
+
+    private void assertSuccessfullRegMail() {
+        TestMail tm = waitForMail();
+        Assert.assertNotNull(tm);
+        String link = tm.extractLink();
+        assertTrue(link, link.startsWith("https://"));
+    }
+
+    @Test
+    public void testNoFname() throws IOException {
+        testFailedForm("lname=b&email=e&pword1=ap&pword2=ap&day=1&month=1&year=1910&cca_agree=1");
+    }
+
+    @Test
+    public void testNoLname() throws IOException {
+        testFailedForm("fname=a&email=e&pword1=ap&pword2=ap&day=1&month=1&year=1910&cca_agree=1");
+    }
+
+    @Test
+    public void testNoEmail() throws IOException {
+        testFailedForm("fname=a&lname=b&pword1=ap&pword2=ap&day=1&month=1&year=1910&cca_agree=1");
+    }
+
+    @Test
+    public void testNoPword() throws IOException {
+        testFailedForm("fname=a&lname=b&email=e&pword2=ap&day=1&month=1&year=1910&cca_agree=1");
+    }
+
+    @Test
+    public void testDiffPword() throws IOException {
+        testFailedForm("fname=a&lname=b&email=e&pword1=ap&pword2=ap2&day=1&month=1&year=1910&cca_agree=1");
+    }
+
+    @Test
+    public void testNoDay() throws IOException {
+        testFailedForm("fname=a&lname=b&email=e&pword1=ap&pword2=ap&month=1&year=1910&cca_agree=1");
+    }
+
+    @Test
+    public void testNoMonth() throws IOException {
+        testFailedForm("fname=a&lname=b&email=e&pword1=ap&pword2=ap&day=1&year=1910&cca_agree=1");
+    }
+
+    @Test
+    public void testNoYear() throws IOException {
+        testFailedForm("fname=a&lname=b&email=e&pword1=ap&pword2=ap&day=1&month=1&cca_agree=1");
+    }
+
+    @Test
+    public void testInvDay() throws IOException {
+        testFailedForm("fname=a&lname=b&email=e&pword1=ap&pword2=ap&day=40&month=1&year=1910&cca_agree=1");
+        testFailedForm("fname=a&lname=b&email=e&pword1=ap&pword2=ap&day=0&month=1&year=1910&cca_agree=1");
+        testFailedForm("fname=a&lname=b&email=e&pword1=ap&pword2=ap&day=a&month=1&year=1910&cca_agree=1");
+    }
+
+    @Test
+    public void testInvMonth() throws IOException {
+        testFailedForm("fname=a&lname=b&email=e&pword1=ap&pword2=ap&day=1&month=20&year=1910&cca_agree=1");
+        testFailedForm("fname=a&lname=b&email=e&pword1=ap&pword2=ap&day=1&month=0&year=1910&cca_agree=1");
+        testFailedForm("fname=a&lname=b&email=e&pword1=ap&pword2=ap&day=1&month=-1&year=1910&cca_agree=1");
+        testFailedForm("fname=a&lname=b&email=e&pword1=ap&pword2=ap&day=1&month=a&year=1910&cca_agree=1");
+    }
+
+    @Test
+    public void testInvYear() throws IOException {
+        testFailedForm("fname=a&lname=b&email=e&pword1=ap&pword2=ap&day=1&month=1&year=0&cca_agree=1");
+        testFailedForm("fname=a&lname=b&email=e&pword1=ap&pword2=ap&day=1&month=1&year=100&cca_agree=1");
+        testFailedForm("fname=a&lname=b&email=e&pword1=ap&pword2=ap&day=1&month=1&year=a&cca_agree=1");
+        testFailedForm("fname=a&lname=b&email=e&pword1=ap&pword2=ap&day=1&month=1&year=-1&cca_agree=1");
+    }
+
+    @Test
+    public void testNoAgree() throws IOException {
+        testFailedForm("fname=a&lname=b&email=e&pword1=ap&pword2=ap&day=1&month=1&year=1910&cca_agree=a");
+    }
+
+    @Test
+    public void testDataStays() throws IOException {
+        long uniq = System.currentTimeMillis();
+        String run = runRegister("fname=fn" + uniq + "&lname=ln" + uniq + "&email=ma" + uniq + "@cacert.org&pword1=pas" + uniq + "&pword2=pas2" + uniq + "&day=1&month=1&year=0");
+        assertThat(run, containsString("fn" + uniq));
+        assertThat(run, containsString("ln" + uniq));
+        assertThat(run, containsString("ma" + uniq + "@cacert.org"));
+        assertThat(run, not(containsString("pas" + uniq)));
+        assertThat(run, not(containsString("pas2" + uniq)));
+
+    }
+
+    @Test
+    public void testCheckboxesStay() throws IOException {
+        String run2 = runRegister("general=1&country=a&regional=1&radius=0");
+        assertThat(run2, containsString("name=\"general\" value=\"1\" checked=\"checked\">"));
+        assertThat(run2, containsString("name=\"country\" value=\"1\">"));
+        assertThat(run2, containsString("name=\"regional\" value=\"1\" checked=\"checked\">"));
+        assertThat(run2, containsString("name=\"radius\" value=\"1\">"));
+        run2 = runRegister("general=0&country=1&radius=1");
+        assertThat(run2, containsString("name=\"general\" value=\"1\">"));
+        assertThat(run2, containsString("name=\"country\" value=\"1\" checked=\"checked\">"));
+        assertThat(run2, containsString("name=\"regional\" value=\"1\">"));
+        assertThat(run2, containsString("name=\"radius\" value=\"1\" checked=\"checked\">"));
+    }
+
+    @Test
+    public void testDoubleMail() throws IOException {
+        long uniq = System.currentTimeMillis();
+        registerUser("RegisterTest", "User", "testmail" + uniq + "@cacert.org", TEST_PASSWORD);
+        try {
+            registerUser("RegisterTest", "User", "testmail" + uniq + "@cacert.org", TEST_PASSWORD);
+            throw new Error("Registering a user with the same email needs to fail.");
+        } catch (AssertionError e) {
+
+        }
+    }
+
+    @Test
+    public void testInvalidMailbox() {
+        getMailReciever().setApproveRegex(Pattern.compile("a"));
+        long uniq = System.currentTimeMillis();
+        try {
+            registerUser("RegisterTest", "User", "testInvalidMailbox" + uniq + "@cacert.org", TEST_PASSWORD);
+            throw new Error("Registering a user with invalid mailbox must fail.");
+        } catch (AssertionError e) {
+
+        }
+    }
+
+    private void testFailedForm(String query) throws IOException {
+        String startError = fetchStartErrorMessage(runRegister(query));
+        assertTrue(startError, !startError.startsWith("</div>"));
+    }
+
+}
diff --git a/tests/org/cacert/gigi/pages/orga/TestOrgaManagement.java b/tests/org/cacert/gigi/pages/orga/TestOrgaManagement.java
new file mode 100644 (file)
index 0000000..f0a07a8
--- /dev/null
@@ -0,0 +1,99 @@
+package org.cacert.gigi.pages.orga;
+
+import static org.hamcrest.CoreMatchers.*;
+import static org.junit.Assert.*;
+
+import java.io.IOException;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.net.URLConnection;
+import java.net.URLEncoder;
+import java.util.List;
+
+import org.cacert.gigi.dbObjects.Group;
+import org.cacert.gigi.dbObjects.Organisation;
+import org.cacert.gigi.dbObjects.Organisation.Affiliation;
+import org.cacert.gigi.dbObjects.User;
+import org.cacert.gigi.testUtils.ClientTest;
+import org.cacert.gigi.testUtils.IOUtils;
+import org.junit.Test;
+
+public class TestOrgaManagement extends ClientTest {
+
+    public TestOrgaManagement() throws IOException {
+        u.grantGroup(u, Group.getByString("orgassurer"));
+        clearCaches();
+        cookie = login(email, TEST_PASSWORD);
+    }
+
+    @Test
+    public void testAdd() throws IOException {
+        executeBasicWebInteraction(cookie, CreateOrgPage.DEFAULT_PATH, "O=name&contact=mail&L=K%C3%B6ln&ST=%C3%9C%C3%96%C3%84%C3%9F&C=DE&comments=jkl%C3%B6loiuzfdfgjlh%C3%B6", 0);
+        Organisation[] orgs = Organisation.getOrganisations(0, 30);
+        assertEquals(1, orgs.length);
+        assertEquals("mail", orgs[0].getContactEmail());
+        assertEquals("name", orgs[0].getName());
+        assertEquals("Köln", orgs[0].getCity());
+        assertEquals("ÜÖÄß", orgs[0].getProvince());
+
+        User u2 = User.getById(createVerifiedUser("testworker", "testname", createUniqueName() + "@testdom.com", TEST_PASSWORD));
+        executeBasicWebInteraction(cookie, ViewOrgPage.DEFAULT_PATH + "/" + orgs[0].getId(), "email=" + URLEncoder.encode(u2.getEmail(), "UTF-8") + "&do_affiliate=y&master=y", 1);
+        List<Affiliation> allAdmins = orgs[0].getAllAdmins();
+        assertEquals(1, allAdmins.size());
+        Affiliation affiliation = allAdmins.get(0);
+        assertSame(u2, affiliation.getTarget());
+        assertTrue(affiliation.isMaster());
+
+        executeBasicWebInteraction(cookie, ViewOrgPage.DEFAULT_PATH + "/" + orgs[0].getId(), "email=" + URLEncoder.encode(u.getEmail(), "UTF-8") + "&do_affiliate=y", 1);
+        allAdmins = orgs[0].getAllAdmins();
+        assertEquals(2, allAdmins.size());
+        Affiliation affiliation2 = allAdmins.get(0);
+        if (affiliation2.getTarget().getId() == u2.getId()) {
+            affiliation2 = allAdmins.get(1);
+        }
+        assertSame(u.getId(), affiliation2.getTarget().getId());
+        assertFalse(affiliation2.isMaster());
+
+        executeBasicWebInteraction(cookie, ViewOrgPage.DEFAULT_PATH + "/" + orgs[0].getId(), "del=" + URLEncoder.encode(u.getEmail(), "UTF-8") + "&email=&do_affiliate=y", 1);
+        assertEquals(1, orgs[0].getAllAdmins().size());
+
+        executeBasicWebInteraction(cookie, ViewOrgPage.DEFAULT_PATH + "/" + orgs[0].getId(), "del=" + URLEncoder.encode(u2.getEmail(), "UTF-8") + "&email=&do_affiliate=y", 1);
+        assertEquals(0, orgs[0].getAllAdmins().size());
+
+        executeBasicWebInteraction(cookie, ViewOrgPage.DEFAULT_PATH + "/" + orgs[0].getId(), "O=name1&contact=&L=K%C3%B6ln&ST=%C3%9C%C3%96%C3%84%C3%9F&C=DE&comments=jkl%C3%B6loiuzfdfgjlh%C3%B6", 0);
+        clearCaches();
+        orgs = Organisation.getOrganisations(0, 30);
+        assertEquals("name1", orgs[0].getName());
+    }
+
+    @Test
+    public void testNonAssurerSeeOnlyOwn() throws IOException {
+        User u2 = User.getById(createVerifiedUser("testworker", "testname", createUniqueName() + "@testdom.com", TEST_PASSWORD));
+        Organisation o1 = new Organisation("name21", "DE", "sder", "Rostov", "email", u);
+        Organisation o2 = new Organisation("name12", "DE", "sder", "Rostov", "email", u);
+        o1.addAdmin(u2, u2, false);
+        String session2 = login(u2.getEmail(), TEST_PASSWORD);
+
+        URLConnection uc = new URL("https://" + getServerName() + ViewOrgPage.DEFAULT_PATH).openConnection();
+        uc.addRequestProperty("Cookie", session2);
+        String content = IOUtils.readURL(uc);
+        assertThat(content, containsString("name21"));
+        assertThat(content, not(containsString("name12")));
+        uc = cookie(new URL("https://" + getServerName() + ViewOrgPage.DEFAULT_PATH + "/" + o1.getId()).openConnection(), session2);
+        assertEquals(200, ((HttpURLConnection) uc).getResponseCode());
+        uc = cookie(new URL("https://" + getServerName() + ViewOrgPage.DEFAULT_PATH + "/" + o2.getId()).openConnection(), session2);
+        assertEquals(404, ((HttpURLConnection) uc).getResponseCode());
+
+        uc = new URL("https://" + getServerName() + ViewOrgPage.DEFAULT_PATH).openConnection();
+        uc.addRequestProperty("Cookie", cookie);
+        content = IOUtils.readURL(uc);
+        assertThat(content, containsString("name21"));
+        assertThat(content, containsString("name12"));
+        uc = cookie(new URL("https://" + getServerName() + ViewOrgPage.DEFAULT_PATH + "/" + o1.getId()).openConnection(), cookie);
+        assertEquals(200, ((HttpURLConnection) uc).getResponseCode());
+        uc = cookie(new URL("https://" + getServerName() + ViewOrgPage.DEFAULT_PATH + "/" + o2.getId()).openConnection(), cookie);
+        assertEquals(200, ((HttpURLConnection) uc).getResponseCode());
+        o1.delete();
+        o2.delete();
+    }
+}
diff --git a/tests/org/cacert/gigi/pages/wot/TestAssurance.java b/tests/org/cacert/gigi/pages/wot/TestAssurance.java
new file mode 100644 (file)
index 0000000..9fae5dd
--- /dev/null
@@ -0,0 +1,212 @@
+package org.cacert.gigi.pages.wot;
+
+import static org.hamcrest.CoreMatchers.*;
+import static org.junit.Assert.*;
+
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.net.HttpURLConnection;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.URLConnection;
+import java.net.URLEncoder;
+import java.sql.SQLException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.regex.Pattern;
+
+import org.cacert.gigi.pages.account.MyDetails;
+import org.cacert.gigi.testUtils.IOUtils;
+import org.cacert.gigi.testUtils.ManagedTest;
+import org.junit.Before;
+import org.junit.Test;
+
+public class TestAssurance extends ManagedTest {
+
+    private String assurerM;
+
+    private String assureeM;
+
+    private String cookie;
+
+    @Before
+    public void setup() throws IOException {
+        assurerM = createUniqueName() + "@cacert-test.org";
+        assureeM = createUniqueName() + "@cacert-test.org";
+
+        createAssuranceUser("a", "b", assurerM, TEST_PASSWORD);
+        createVerifiedUser("a", "c", assureeM, TEST_PASSWORD);
+
+        cookie = login(assurerM, TEST_PASSWORD);
+    }
+
+    @Test
+    public void testAssureSearch() throws IOException {
+        String loc = search("email=" + URLEncoder.encode(assureeM, "UTF-8") + "&day=1&month=1&year=1910");
+        assertTrue(loc, loc.contains("type=\"checkbox\" name=\"CCAAgreed\""));
+    }
+
+    @Test
+    public void testAssureSearchEmail() throws IOException {
+        String loc = search("email=1" + URLEncoder.encode(assureeM, "UTF-8") + "&day=1&month=1&year=1910");
+        assertTrue(loc, !loc.contains("type=\"checkbox\" name=\"CCAAgreed\""));
+    }
+
+    @Test
+    public void testAssureSearchDob() throws IOException {
+        String loc = search("email=" + URLEncoder.encode(assureeM, "UTF-8") + "&day=2&month=1&year=1910");
+        assertTrue(loc, !loc.contains("type=\"checkbox\" name=\"CCAAgreed\""));
+        loc = search("email=" + URLEncoder.encode(assureeM, "UTF-8") + "&day=1&month=2&year=1910");
+        assertTrue(loc, !loc.contains("type=\"checkbox\" name=\"CCAAgreed\""));
+        loc = search("email=" + URLEncoder.encode(assureeM, "UTF-8") + "&day=1&month=1&year=1911");
+        assertTrue(loc, !loc.contains("type=\"checkbox\" name=\"CCAAgreed\""));
+    }
+
+    private String search(String query) throws MalformedURLException, IOException, UnsupportedEncodingException {
+        URL u = new URL("https://" + getServerName() + AssurePage.PATH);
+        URLConnection uc = u.openConnection();
+        uc.setDoOutput(true);
+        uc.addRequestProperty("Cookie", cookie);
+        uc.getOutputStream().write(("search&" + query).getBytes("UTF-8"));
+        uc.getOutputStream().flush();
+
+        return IOUtils.readURL(uc);
+    }
+
+    @Test
+    public void testAssureForm() throws IOException {
+        String error = getError("date=2000-01-01&location=testcase&certify=1&rules=1&CCAAgreed=1&assertion=1&points=10");
+        assertNull(error);
+    }
+
+    @Test
+    public void testAssureFormNoCSRF() throws IOException {
+        // override csrf
+        HttpURLConnection uc = (HttpURLConnection) buildupAssureFormConnection(false);
+        uc.getOutputStream().write(("date=2000-01-01&location=testcase&certify=1&rules=1&CCAAgreed=1&assertion=1&points=10").getBytes("UTF-8"));
+        uc.getOutputStream().flush();
+        assertEquals(500, uc.getResponseCode());
+    }
+
+    @Test
+    public void testAssureFormWrongCSRF() throws IOException {
+        // override csrf
+        HttpURLConnection uc = (HttpURLConnection) buildupAssureFormConnection(false);
+        uc.getOutputStream().write(("date=2000-01-01&location=testcase&certify=1&rules=1&CCAAgreed=1&assertion=1&points=10&csrf=aragc").getBytes("UTF-8"));
+        uc.getOutputStream().flush();
+        assertEquals(500, uc.getResponseCode());
+    }
+
+    @Test
+    public void testAssureFormRaceName() throws IOException, SQLException {
+        testAssureFormRace(true);
+    }
+
+    @Test
+    public void testAssureFormRaceDoB() throws IOException, SQLException {
+        testAssureFormRace(false);
+    }
+
+    public void testAssureFormRace(boolean name) throws IOException, SQLException {
+        URLConnection uc = buildupAssureFormConnection(true);
+
+        String assureeCookie = login(assureeM, TEST_PASSWORD);
+        String newName = "lname=" + (name ? "c" : "a") + "&fname=a&mname=&suffix=";
+        String newDob = "day=1&month=1&year=" + (name ? 1910 : 1911);
+
+        assertNull(executeBasicWebInteraction(assureeCookie, MyDetails.PATH, newName + "&" + newDob + "&processDetails", 0));
+
+        uc.getOutputStream().write(("date=2000-01-01&location=testcase&certify=1&rules=1&CCAAgreed=1&assertion=1&points=10").getBytes("UTF-8"));
+        uc.getOutputStream().flush();
+        String error = fetchStartErrorMessage(IOUtils.readURL(uc));
+        assertTrue(error, !error.startsWith("</div>"));
+    }
+
+    @Test
+    public void testAssureFormFuture() throws IOException {
+        SimpleDateFormat sdf = new SimpleDateFormat("yyyy");
+        int year = Integer.parseInt(sdf.format(new Date(System.currentTimeMillis()))) + 2;
+        String error = getError("date=" + year + "-01-01&location=testcase&certify=1&rules=1&CCAAgreed=1&assertion=1&points=10");
+        assertTrue(error, !error.startsWith("</div>"));
+    }
+
+    @Test
+    public void testAssureFormNoLoc() throws IOException {
+        String error = getError("date=2000-01-01&location=a&certify=1&rules=1&CCAAgreed=1&assertion=1&points=10");
+        assertTrue(error, !error.startsWith("</div>"));
+        error = getError("date=2000-01-01&location=&certify=1&rules=1&CCAAgreed=1&assertion=1&points=10");
+        assertTrue(error, !error.startsWith("</div>"));
+    }
+
+    @Test
+    public void testAssureFormInvalDate() throws IOException {
+        String error = getError("date=20000101&location=testcase&certify=1&rules=1&CCAAgreed=1&assertion=1&points=10");
+        assertTrue(error, !error.startsWith("</div>"));
+        error = getError("date=&location=testcase&certify=1&rules=1&CCAAgreed=1&assertion=1&points=10");
+        assertTrue(error, !error.startsWith("</div>"));
+    }
+
+    @Test
+    public void testAssureFormBoxes() throws IOException {
+        String error = getError("date=2000-01-01&location=testcase&certify=0&rules=1&CCAAgreed=1&assertion=1&points=10");
+        assertTrue(error, !error.startsWith("</div>"));
+        error = getError("date=2000-01-01&location=testcase&certify=1&rules=&CCAAgreed=1&assertion=1&points=10");
+        assertTrue(error, !error.startsWith("</div>"));
+        error = getError("date=2000-01-01&location=testcase&certify=1&rules=1&CCAAgreed=a&assertion=1&points=10");
+        assertTrue(error, !error.startsWith("</div>"));
+        error = getError("date=2000-01-01&location=testcase&certify=1&rules=1&CCAAgreed=1&assertion=z&points=10");
+        assertTrue(error, !error.startsWith("</div>"));
+    }
+
+    @Test
+    public void testAssureListingValid() throws IOException {
+        String uniqueLoc = createUniqueName();
+        String error = getError("date=2000-01-01&location=" + uniqueLoc + "&certify=1&rules=1&CCAAgreed=1&assertion=1&points=10");
+        assertNull(error);
+        String cookie = login(assureeM, TEST_PASSWORD);
+        URLConnection url = new URL("https://" + getServerName() + MyPoints.PATH).openConnection();
+        url.setRequestProperty("Cookie", cookie);
+        String resp = IOUtils.readURL(url);
+        resp = resp.split(Pattern.quote("</table>"))[0];
+        assertThat(resp, containsString(uniqueLoc));
+    }
+
+    @Test
+    public void testAssurerListingValid() throws IOException {
+        String uniqueLoc = createUniqueName();
+        String error = getError("date=2000-01-01&location=" + uniqueLoc + "&certify=1&rules=1&CCAAgreed=1&assertion=1&points=10");
+        assertNull(error);
+        String cookie = login(assurerM, TEST_PASSWORD);
+        URLConnection url = new URL("https://" + getServerName() + MyPoints.PATH).openConnection();
+        url.setRequestProperty("Cookie", cookie);
+        String resp = IOUtils.readURL(url);
+        resp = resp.split(Pattern.quote("</table>"))[1];
+        assertThat(resp, containsString(uniqueLoc));
+    }
+
+    private String getError(String query) throws MalformedURLException, IOException {
+        URLConnection uc = buildupAssureFormConnection(true);
+        uc.getOutputStream().write((query).getBytes("UTF-8"));
+        uc.getOutputStream().flush();
+        String error = fetchStartErrorMessage(IOUtils.readURL(uc));
+        return error;
+    }
+
+    private URLConnection buildupAssureFormConnection(boolean doCSRF) throws MalformedURLException, IOException {
+        URL u = new URL("https://" + getServerName() + AssurePage.PATH);
+        URLConnection uc = u.openConnection();
+        uc.addRequestProperty("Cookie", cookie);
+        uc.setDoOutput(true);
+        uc.getOutputStream().write(("email=" + URLEncoder.encode(assureeM, "UTF-8") + "&day=1&month=1&year=1910&search").getBytes("UTF-8"));
+
+        String csrf = getCSRF(uc);
+        uc = u.openConnection();
+        uc.addRequestProperty("Cookie", cookie);
+        uc.setDoOutput(true);
+        if (doCSRF) {
+            uc.getOutputStream().write(("csrf=" + csrf + "&").getBytes("UTF-8"));
+        }
+        return uc;
+    }
+
+}
diff --git a/tests/org/cacert/gigi/pages/wot/TestTTP.java b/tests/org/cacert/gigi/pages/wot/TestTTP.java
new file mode 100644 (file)
index 0000000..7c8884b
--- /dev/null
@@ -0,0 +1,44 @@
+package org.cacert.gigi.pages.wot;
+
+import static org.hamcrest.CoreMatchers.*;
+import static org.junit.Assert.*;
+
+import java.io.IOException;
+import java.net.URL;
+
+import org.cacert.gigi.GigiApiException;
+import org.cacert.gigi.dbObjects.Group;
+import org.cacert.gigi.dbObjects.ObjectCache;
+import org.cacert.gigi.dbObjects.User;
+import org.cacert.gigi.testUtils.ClientTest;
+import org.cacert.gigi.testUtils.IOUtils;
+import org.junit.Test;
+
+public class TestTTP extends ClientTest {
+
+    URL ttpPage = new URL("https://" + getServerName() + RequestTTPPage.PATH);
+
+    public TestTTP() throws IOException {}
+
+    @Test
+    public void testTTPApply() throws IOException {
+        String ttp = IOUtils.readURL(cookie(ttpPage.openConnection(), cookie));
+        assertThat(ttp, containsString("<form"));
+        executeBasicWebInteraction(cookie, RequestTTPPage.PATH, "country=0");
+
+        ttp = IOUtils.readURL(cookie(new URL("https://" + getServerName() + RequestTTPPage.PATH).openConnection(), cookie));
+        assertThat(ttp, not(containsString("<form")));
+        ObjectCache.clearAllCaches();
+        u = User.getById(u.getId());
+        assertTrue(u.isInGroup(Group.getByString("ttp-applicant")));
+    }
+
+    @Test
+    public void testTTPEnoughPoints() throws IOException, GigiApiException {
+        User u = User.getById(createAssuranceUser("fn", "ln", createUniqueName() + "@example.org", TEST_PASSWORD));
+        cookie = login(u.getEmail(), TEST_PASSWORD);
+
+        String ttp = IOUtils.readURL(cookie(new URL("https://" + getServerName() + RequestTTPPage.PATH).openConnection(), cookie));
+        assertThat(ttp, not(containsString("<form")));
+    }
+}
diff --git a/tests/org/cacert/gigi/pages/wot/TestTTPAdmin.java b/tests/org/cacert/gigi/pages/wot/TestTTPAdmin.java
new file mode 100644 (file)
index 0000000..32c4268
--- /dev/null
@@ -0,0 +1,52 @@
+package org.cacert.gigi.pages.wot;
+
+import static org.junit.Assert.*;
+
+import java.io.IOException;
+import java.net.HttpURLConnection;
+import java.net.MalformedURLException;
+import java.net.URL;
+
+import org.cacert.gigi.dbObjects.Group;
+import org.cacert.gigi.dbObjects.User;
+import org.cacert.gigi.pages.admin.TTPAdminPage;
+import org.cacert.gigi.testUtils.ClientTest;
+import org.junit.Test;
+
+public class TestTTPAdmin extends ClientTest {
+
+    User us2;
+
+    public TestTTPAdmin() throws IOException {
+        us2 = User.getById(createVerifiedUser("fn", "ln", createUniqueName() + "@example.com", TEST_PASSWORD));
+    }
+
+    @Test
+    public void testHasRight() throws IOException {
+        testTTPAdmin(true);
+    }
+
+    @Test
+    public void testHasNoRight() throws IOException {
+        testTTPAdmin(false);
+    }
+
+    public void testTTPAdmin(boolean hasRight) throws IOException {
+        if (hasRight) {
+            grant(email, Group.getByString("ttp-assurer"));
+        }
+        grant(u.getEmail(), TTPAdminPage.TTP_APPLICANT);
+        cookie = login(u.getEmail(), TEST_PASSWORD);
+
+        assertEquals( !hasRight ? 403 : 200, fetchStatusCode("https://" + getServerName() + TTPAdminPage.PATH));
+        assertEquals( !hasRight ? 403 : 200, fetchStatusCode("https://" + getServerName() + TTPAdminPage.PATH + "/"));
+        assertEquals( !hasRight ? 403 : 200, fetchStatusCode("https://" + getServerName() + TTPAdminPage.PATH + "/" + u.getId()));
+        assertEquals( !hasRight ? 403 : 404, fetchStatusCode("https://" + getServerName() + TTPAdminPage.PATH + "/" + us2.getId()));
+        assertEquals( !hasRight ? 403 : 404, fetchStatusCode("https://" + getServerName() + TTPAdminPage.PATH + "/" + 100));
+    }
+
+    private int fetchStatusCode(String path) throws MalformedURLException, IOException {
+        URL u = new URL(path);
+        return ((HttpURLConnection) cookie(u.openConnection(), cookie)).getResponseCode();
+    }
+}
diff --git a/tests/org/cacert/gigi/ping/TestDNS.java b/tests/org/cacert/gigi/ping/TestDNS.java
new file mode 100644 (file)
index 0000000..021012a
--- /dev/null
@@ -0,0 +1,106 @@
+package org.cacert.gigi.ping;
+
+import static org.junit.Assert.*;
+import static org.junit.Assume.*;
+
+import java.io.IOException;
+import java.net.URL;
+import java.net.URLEncoder;
+import java.sql.SQLException;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import javax.naming.NamingException;
+
+import org.cacert.gigi.pages.account.domain.DomainOverview;
+import org.cacert.gigi.testUtils.IOUtils;
+import org.cacert.gigi.testUtils.PingTest;
+import org.cacert.gigi.testUtils.TestEmailReciever.TestMail;
+import org.cacert.gigi.util.DNSUtil;
+import org.cacert.gigi.util.RandomToken;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class TestDNS extends PingTest {
+
+    @Test
+    public void dnsSanity() throws IOException, NamingException {
+
+        String token = RandomToken.generateToken(16);
+        String value = RandomToken.generateToken(16);
+
+        updateService(token, value, "dns");
+        assertEquals(value, readDNS(token));
+
+    }
+
+    @Test
+    public void emailAndDNSSuccess() throws IOException, InterruptedException, SQLException, NamingException {
+        testEmailAndDNS(0, 0, true, true);
+    }
+
+    @Test
+    public void dnsFail() throws IOException, InterruptedException, SQLException, NamingException {
+        testEmailAndDNS(1, 0, false, true);
+    }
+
+    @Test
+    public void dnsContentFail() throws IOException, InterruptedException, SQLException, NamingException {
+        testEmailAndDNS(2, 0, false, true);
+    }
+
+    @Test
+    public void emailFail() throws IOException, InterruptedException, SQLException, NamingException {
+        testEmailAndDNS(0, 1, true, false);
+    }
+
+    @Test
+    public void emailAndDNSFail() throws IOException, InterruptedException, SQLException, NamingException {
+        testEmailAndDNS(2, 1, false, false);
+    }
+
+    public void testEmailAndDNS(int dnsVariant, int emailVariant, boolean successDNS, boolean successMail) throws IOException, InterruptedException, SQLException, NamingException {
+
+        String test = getTestProps().getProperty("domain.dnstest");
+        assumeNotNull(test);
+
+        URL u = new URL("https://" + getServerName() + DomainOverview.PATH);
+        Matcher m = initailizeDomainForm(u);
+        updateService(m.group(1) + (dnsVariant == 1 ? "a" : ""), m.group(2) + (dnsVariant == 2 ? "a" : ""), "dns");
+
+        String content = "newdomain=" + URLEncoder.encode(test, "UTF-8") + //
+                "&emailType=y&email=2&DNSType=y" + //
+                "&ssl-type-0=direct&ssl-port-0=" + //
+                "&ssl-type-1=direct&ssl-port-1=" + //
+                "&ssl-type-2=direct&ssl-port-2=" + //
+                "&ssl-type-3=direct&ssl-port-3=" + //
+                "&adddomain&csrf=" + csrf;
+        URL u2 = sendDomainForm(u, content);
+
+        TestMail mail = getMailReciever().recieve();
+        if (emailVariant == 0) {
+            Assert.assertNotNull(mail);
+            mail.verify();
+        }
+
+        waitForPings(2);
+
+        String newcontent = IOUtils.readURL(cookie(u2.openConnection(), cookie));
+        Pattern pat = Pattern.compile("<td>dns</td>\\s*<td>success</td>");
+        assertTrue(newcontent, !successDNS ^ pat.matcher(newcontent).find());
+        pat = Pattern.compile("<td>email</td>\\s*<td>success</td>");
+        assertTrue(newcontent, !successMail ^ pat.matcher(newcontent).find());
+    }
+
+    private String readDNS(String token) throws NamingException {
+        String test = getTestProps().getProperty("domain.dnstest");
+        assumeNotNull(test);
+        String targetDomain = token + "._cacert._auth." + test;
+        String testns = getTestProps().getProperty("domain.testns");
+        assumeNotNull(testns);
+        String[] data = DNSUtil.getTXTEntries(targetDomain, testns);
+        assertEquals(1, data.length);
+        return data[0];
+
+    }
+}
diff --git a/tests/org/cacert/gigi/ping/TestHTTP.java b/tests/org/cacert/gigi/ping/TestHTTP.java
new file mode 100644 (file)
index 0000000..4daa0d7
--- /dev/null
@@ -0,0 +1,113 @@
+package org.cacert.gigi.ping;
+
+import static org.hamcrest.CoreMatchers.*;
+import static org.junit.Assert.*;
+import static org.junit.Assume.*;
+
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.net.URL;
+import java.net.URLEncoder;
+import java.sql.SQLException;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import javax.naming.NamingException;
+
+import org.cacert.gigi.GigiApiException;
+import org.cacert.gigi.dbObjects.Domain;
+import org.cacert.gigi.dbObjects.DomainPingConfiguration;
+import org.cacert.gigi.dbObjects.DomainPingConfiguration.PingType;
+import org.cacert.gigi.pages.account.domain.DomainOverview;
+import org.cacert.gigi.testUtils.IOUtils;
+import org.cacert.gigi.testUtils.PingTest;
+import org.cacert.gigi.testUtils.TestEmailReciever.TestMail;
+import org.cacert.gigi.util.RandomToken;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class TestHTTP extends PingTest {
+
+    @Test
+    public void httpSanity() throws IOException, NamingException {
+
+        String token = RandomToken.generateToken(16);
+        String value = RandomToken.generateToken(16);
+
+        TestDNS.updateService(token, value, "http");
+        assertEquals(value, readHTTP(token));
+
+    }
+
+    @Test
+    public void httpAndMailSuccess() throws Exception {
+        testEmailAndHTTP(0, 0, true, true);
+    }
+
+    @Test
+    public void httpFailKeyAndMailSuccess() throws Exception {
+        testEmailAndHTTP(1, 0, false, true);
+    }
+
+    @Test
+    public void httpFailValAndMailFail() throws Exception {
+        testEmailAndHTTP(2, 1, false, false);
+    }
+
+    public void testEmailAndHTTP(int httpVariant, int emailVariant, boolean successHTTP, boolean successMail) throws IOException, InterruptedException, SQLException, GigiApiException {
+
+        String test = getTestProps().getProperty("domain.http");
+        assumeNotNull(test);
+
+        URL u = new URL("https://" + getServerName() + DomainOverview.PATH);
+        Matcher m = initailizeDomainForm(u);
+        updateService(m.group(1) + (httpVariant == 1 ? "a" : ""), m.group(2) + (httpVariant == 2 ? "a" : ""), "http");
+
+        String content = "newdomain=" + URLEncoder.encode(test, "UTF-8") + //
+                "&emailType=y&email=2&HTTPType=y" + //
+                "&ssl-type-0=direct&ssl-port-0=" + //
+                "&ssl-type-1=direct&ssl-port-1=" + //
+                "&ssl-type-2=direct&ssl-port-2=" + //
+                "&ssl-type-3=direct&ssl-port-3=" + //
+                "&adddomain&csrf=" + csrf;
+        URL u2 = sendDomainForm(u, content);
+
+        TestMail mail = getMailReciever().recieve();
+        if (emailVariant == 0) {
+            Assert.assertNotNull(mail);
+            mail.verify();
+        }
+        waitForPings(2);
+
+        String newcontent = IOUtils.readURL(cookie(u2.openConnection(), cookie));
+        Pattern pat = Pattern.compile("<td>http</td>\\s*<td>success</td>");
+        assertTrue(newcontent, !successHTTP ^ pat.matcher(newcontent).find());
+        pat = Pattern.compile("<td>email</td>\\s*<td>success</td>");
+        assertTrue(newcontent, !successMail ^ pat.matcher(newcontent).find());
+
+        if (successHTTP) { // give it a second try
+            int id = Integer.parseInt(u2.toString().replaceFirst("^.*/([0-9]+)$", "$1"));
+            Domain d = Domain.getById(id);
+            DomainPingConfiguration dpc = null;
+            for (DomainPingConfiguration conf : d.getConfiguredPings()) {
+                if (conf.getType() == PingType.HTTP) {
+                    dpc = conf;
+                    break;
+                }
+            }
+            if (dpc == null) {
+                fail("Http config not found");
+            }
+            String res = executeBasicWebInteraction(cookie, u2.getPath(), "configId=" + dpc.getId());
+            assertThat(res, containsString("only allowed after"));
+        }
+    }
+
+    private String readHTTP(String token) throws IOException {
+        String httpDom = getTestProps().getProperty("domain.http");
+        assumeNotNull(httpDom);
+        URL u = new URL("http://" + httpDom + "/cacert-" + token + ".txt");
+        return IOUtils.readURL(new InputStreamReader(u.openStream(), "UTF-8")).trim();
+
+    }
+}
diff --git a/tests/org/cacert/gigi/ping/TestSSL.java b/tests/org/cacert/gigi/ping/TestSSL.java
new file mode 100644 (file)
index 0000000..a9aa73b
--- /dev/null
@@ -0,0 +1,226 @@
+package org.cacert.gigi.ping;
+
+import static org.junit.Assert.*;
+import static org.junit.Assume.*;
+
+import java.io.IOException;
+import java.net.Socket;
+import java.net.URL;
+import java.net.URLEncoder;
+import java.security.GeneralSecurityException;
+import java.security.KeyManagementException;
+import java.security.KeyPair;
+import java.security.NoSuchAlgorithmException;
+import java.security.Principal;
+import java.security.PrivateKey;
+import java.security.SecureRandom;
+import java.security.cert.CertificateException;
+import java.security.cert.X509Certificate;
+import java.sql.SQLException;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import javax.net.ssl.KeyManager;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLServerSocket;
+import javax.net.ssl.SSLServerSocketFactory;
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.X509KeyManager;
+import javax.net.ssl.X509TrustManager;
+
+import org.cacert.gigi.GigiApiException;
+import org.cacert.gigi.dbObjects.Certificate;
+import org.cacert.gigi.dbObjects.Certificate.CSRType;
+import org.cacert.gigi.dbObjects.CertificateProfile;
+import org.cacert.gigi.dbObjects.User;
+import org.cacert.gigi.pages.account.domain.DomainOverview;
+import org.cacert.gigi.testUtils.IOUtils;
+import org.cacert.gigi.testUtils.PingTest;
+import org.cacert.gigi.testUtils.TestEmailReciever.TestMail;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class TestSSL extends PingTest {
+
+    private KeyPair kp;
+
+    private Certificate c;
+
+    @Test(timeout = 70000)
+    public void sslAndMailSuccess() throws IOException, InterruptedException, SQLException, GeneralSecurityException, GigiApiException {
+        testEmailAndSSL(0, 0, true);
+    }
+
+    @Test(timeout = 70000)
+    public void sslWongTypeAndMailSuccess() throws IOException, InterruptedException, SQLException, GeneralSecurityException, GigiApiException {
+        testEmailAndSSL(1, 0, true);
+    }
+
+    @Test(timeout = 70000)
+    public void sslOneMissingAndMailSuccess() throws IOException, InterruptedException, SQLException, GeneralSecurityException, GigiApiException {
+        testEmailAndSSL(2, 0, true);
+    }
+
+    @Test(timeout = 70000)
+    public void sslBothMissingAndMailSuccess() throws IOException, InterruptedException, SQLException, GeneralSecurityException, GigiApiException {
+        testEmailAndSSL(3, 0, true);
+    }
+
+    @Test(timeout = 70000)
+    public void sslWrongTypeAndMailFail() throws IOException, InterruptedException, SQLException, GeneralSecurityException, GigiApiException {
+        testEmailAndSSL(1, 1, false);
+    }
+
+    /**
+     * @param sslVariant
+     *            <ul>
+     *            <li>0= all valid</li>
+     *            <li>1= wrong type</li>
+     *            <li>2= one server missing</li>
+     *            <li>3= both servers missing</li>
+     *            </ul>
+     * @param emailVariant
+     * @param successSSL
+     * @param successMail
+     * @throws IOException
+     * @throws InterruptedException
+     * @throws SQLException
+     * @throws GeneralSecurityException
+     * @throws GigiApiException
+     */
+
+    private void testEmailAndSSL(int sslVariant, int emailVariant, boolean successMail) throws IOException, InterruptedException, SQLException, GeneralSecurityException, GigiApiException {
+        String test = getTestProps().getProperty("domain.local");
+        assumeNotNull(test);
+        URL u = new URL("https://" + getServerName() + DomainOverview.PATH);
+
+        initailizeDomainForm(u);
+
+        createCertificate(test, CertificateProfile.getByName(sslVariant == 1 ? "client" : "server"));
+        SSLServerSocket sss = createSSLServer(kp.getPrivate(), c.cert());
+        int port = sss.getLocalPort();
+        SSLServerSocket sss2 = createSSLServer(kp.getPrivate(), c.cert());
+        int port2 = sss2.getLocalPort();
+        if (sslVariant == 3 || sslVariant == 2) {
+            sss2.close();
+            if (sslVariant == 3) {
+                sss.close();
+            }
+        }
+        String content = "newdomain=" + URLEncoder.encode(test, "UTF-8") + //
+                "&emailType=y&email=2&SSLType=y" + //
+                "&ssl-type-0=direct&ssl-port-0=" + port + //
+                "&ssl-type-1=direct&ssl-port-1=" + port2 + //
+                "&ssl-type-2=direct&ssl-port-2=" + //
+                "&ssl-type-3=direct&ssl-port-3=" + //
+                "&adddomain&csrf=" + csrf;
+        URL u2 = sendDomainForm(u, content);
+        boolean firstSucceeds = sslVariant != 0 && sslVariant != 2;
+        assertTrue(firstSucceeds ^ acceptSSLServer(sss));
+        boolean secondsSucceeds = sslVariant != 0;
+        assertTrue(secondsSucceeds ^ acceptSSLServer(sss2));
+
+        TestMail mail = getMailReciever().recieve();
+        if (emailVariant == 0) {
+            Assert.assertNotNull(mail);
+            mail.verify();
+        }
+        waitForPings(3);
+
+        String newcontent = IOUtils.readURL(cookie(u2.openConnection(), cookie));
+        Pattern pat = Pattern.compile("<td>ssl</td>\\s*<td>success</td>");
+        Matcher matcher = pat.matcher(newcontent);
+        assertTrue(newcontent, firstSucceeds ^ matcher.find());
+        assertTrue(newcontent, secondsSucceeds ^ matcher.find());
+        assertFalse(newcontent, matcher.find());
+        pat = Pattern.compile("<td>email</td>\\s*<td>success</td>");
+        assertTrue(newcontent, !successMail ^ pat.matcher(newcontent).find());
+    }
+
+    private void createCertificate(String test, CertificateProfile profile) throws GeneralSecurityException, IOException, SQLException, InterruptedException, GigiApiException {
+        kp = generateKeypair();
+        String csr = generatePEMCSR(kp, "CN=" + test);
+        c = new Certificate(User.getById(id), Certificate.buildDN("CN", test), "sha256", csr, CSRType.CSR, profile);
+        c.issue(null, "2y").waitFor(60000);
+    }
+
+    private boolean acceptSSLServer(SSLServerSocket sss) throws IOException {
+        try (Socket s = sss.accept()) {
+            s.getOutputStream().write('b');
+            s.getOutputStream().close();
+            return true;
+        } catch (IOException e) {
+            return false;
+        }
+    }
+
+    private SSLServerSocket createSSLServer(final PrivateKey priv, final X509Certificate cert) throws Error, IOException {
+        SSLContext sc;
+        try {
+            sc = SSLContext.getInstance("SSL");
+            sc.init(new KeyManager[] {
+                new X509KeyManager() {
+
+                    @Override
+                    public String[] getServerAliases(String keyType, Principal[] issuers) {
+                        return new String[] {
+                            "server"
+                        };
+                    }
+
+                    @Override
+                    public PrivateKey getPrivateKey(String alias) {
+                        return priv;
+                    }
+
+                    @Override
+                    public String[] getClientAliases(String keyType, Principal[] issuers) {
+                        throw new Error();
+                    }
+
+                    @Override
+                    public X509Certificate[] getCertificateChain(String alias) {
+                        return new X509Certificate[] {
+                            cert
+                        };
+                    }
+
+                    @Override
+                    public String chooseClientAlias(String[] keyType, Principal[] issuers, Socket socket) {
+                        throw new Error();
+                    }
+
+                    @Override
+                    public String chooseServerAlias(String keyType, Principal[] issuers, Socket socket) {
+                        return "server";
+                    }
+
+                }
+            }, new TrustManager[] {
+                new X509TrustManager() {
+
+                    @Override
+                    public X509Certificate[] getAcceptedIssuers() {
+                        return null;
+                    }
+
+                    @Override
+                    public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {}
+
+                    @Override
+                    public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {}
+                }
+            }, new SecureRandom());
+        } catch (NoSuchAlgorithmException e) {
+            e.printStackTrace();
+            throw new Error(e);
+        } catch (KeyManagementException e) {
+            e.printStackTrace();
+            throw new Error(e);
+        }
+
+        SSLServerSocketFactory sssf = sc.getServerSocketFactory();
+        return (SSLServerSocket) sssf.createServerSocket(0);
+    }
+
+}
diff --git a/tests/org/cacert/gigi/template/TestTemplate.java b/tests/org/cacert/gigi/template/TestTemplate.java
new file mode 100644 (file)
index 0000000..7dca58e
--- /dev/null
@@ -0,0 +1,140 @@
+package org.cacert.gigi.template;
+
+import static org.junit.Assert.*;
+
+import java.io.CharArrayWriter;
+import java.io.PrintWriter;
+import java.io.StringReader;
+import java.util.HashMap;
+import java.util.Locale;
+import java.util.Map;
+
+import org.cacert.gigi.dbObjects.Digest;
+import org.cacert.gigi.localisation.Language;
+import org.cacert.gigi.output.HashAlgorithms;
+import org.cacert.gigi.output.template.IterableDataset;
+import org.cacert.gigi.output.template.Outputable;
+import org.cacert.gigi.output.template.OutputableArrayIterable;
+import org.cacert.gigi.output.template.Template;
+import org.junit.Test;
+
+public class TestTemplate {
+
+    private String testExecute(Language l, HashMap<String, Object> vars, String input) {
+        Template t = new Template(new StringReader(input));
+        CharArrayWriter caw = new CharArrayWriter();
+        PrintWriter pw = new PrintWriter(caw);
+        t.output(pw, l, vars);
+        pw.flush();
+        return new String(caw.toCharArray());
+    }
+
+    HashMap<String, Object> vars = new HashMap<>();
+
+    @Test
+    public void testVarEscape() {
+        vars.put("var", "val");
+        assertEquals("vall", testExecute(Language.getInstance(Locale.ENGLISH), vars, "<?=$var?>l"));
+        vars.put("var", "val<");
+        assertEquals("val&lt;l", testExecute(Language.getInstance(Locale.ENGLISH), vars, "<?=$var?>l"));
+        assertEquals("val<l", testExecute(Language.getInstance(Locale.ENGLISH), vars, "<?=$!var?>l"));
+        vars.put("var", "val\">");
+        assertEquals("val&quot;&gt;l", testExecute(Language.getInstance(Locale.ENGLISH), vars, "<?=$var?>l"));
+        assertEquals("val\">l", testExecute(Language.getInstance(Locale.ENGLISH), vars, "<?=$!var?>l"));
+
+    }
+
+    @Test
+    public void testVarSprintf() {
+        vars.put("var", "val\">");
+        vars.put("var2", "val3<\"");
+        vars.put("var3", "val4>");
+        assertEquals("This val&quot;&gt; textl", testExecute(Language.getInstance(Locale.ENGLISH), vars, "<?=_This ${var} text?>l"));
+        assertEquals("This val\"> textl", testExecute(Language.getInstance(Locale.ENGLISH), vars, "<?=_This $!{var} text?>l"));
+
+        assertEquals("This val&quot;&gt; val3&lt;&quot; the val4&gt; textl", testExecute(Language.getInstance(Locale.ENGLISH), vars, "<?=_This ${var} ${var2} the ${var3} text?>l"));
+        assertEquals("This val\"> val3<\" the val4> textl", testExecute(Language.getInstance(Locale.ENGLISH), vars, "<?=_This $!{var} $!{var2} the $!{var3} text?>l"));
+
+        assertEquals("This blargh&lt;&gt;!, <>! textl", testExecute(Language.getInstance(Locale.ENGLISH), vars, "<?=_This !'blargh&lt;&gt;!', !'<>! 'text?>l"));
+        assertEquals("This blargh&lt;&gt;!, <>!l", testExecute(Language.getInstance(Locale.ENGLISH), vars, "<?=_This !'blargh&lt;&gt;!', !'<>!'?>l"));
+    }
+
+    @Test
+    public void testIf() {
+        vars.put("existent", "val");
+        assertEquals(">existent<", testExecute(Language.getInstance(Locale.ENGLISH), vars, "<? if($existent) { ?>>existent<? } ?><<? if($nonexistent) { ?>nonexistent<? } ?>"));
+    }
+
+    @Test
+    public void testForeach() {
+        vars.put("it", new IterableDataset() {
+
+            int i = 0;
+
+            @Override
+            public boolean next(Language l, Map<String, Object> vars) {
+                vars.put("e", Integer.toString(i++));
+                return i < 10;
+            }
+        });
+        assertEquals("012345678<", testExecute(Language.getInstance(Locale.ENGLISH), vars, "<? foreach($it) { ?><?=$e?><? } ?><<? foreach($nonexistent) { ?>nonexistent<? } ?>"));
+    }
+
+    @Test
+    public void testNullContent() {
+        assertEquals("<null>", testExecute(Language.getInstance(Locale.ENGLISH), vars, "<<?=$undef?>>"));
+
+    }
+
+    @Test
+    public void testTranslate() {
+        assertEquals("&lt;null&gt;", testExecute(Language.getInstance(Locale.ENGLISH), vars, "<?=_<null>?>"));
+
+    }
+
+    @Test
+    public void testOutputable() {
+        Outputable o = new Outputable() {
+
+            @Override
+            public void output(PrintWriter out, Language l, Map<String, Object> vars) {
+                out.print("bl");
+            }
+        };
+        vars.put("v", new OutputableArrayIterable(new Object[] {
+                o, o, o, o, o
+        }, "e"));
+        assertEquals("[0]bl[1]bl[2]bl[3]bl[4]bl", testExecute(Language.getInstance(Locale.ENGLISH), vars, "<? foreach($v) { ?>[<?=$i?>]<?=$e?><? } ?>"));
+
+    }
+
+    @Test
+    public void testHashalgs() {
+        vars.put("v", new HashAlgorithms(Digest.SHA256));
+        assertEquals("SHA256[ checked='checked']SHA384[]SHA512[]", testExecute(Language.getInstance(Locale.ENGLISH), vars, "<? foreach($v) { ?><?=$algorithm?>[<?=$!checked?>]<? } ?>"));
+
+    }
+
+    @Test
+    public void testInvalidBracketContent() {
+        try {
+            assertEquals("", testExecute(Language.getInstance(Locale.ENGLISH), vars, "<? } ?>"));
+            fail("should throw an error");
+        } catch (Error e) {
+
+        }
+    }
+
+    @Test
+    public void testIfElse() {
+        vars.put("b", Boolean.TRUE);
+        assertEquals("true", testExecute(Language.getInstance(Locale.ENGLISH), vars, "<? if($b){ ?>true<? } else{?>false<?}?>"));
+        vars.put("b", Boolean.FALSE);
+        assertEquals("false", testExecute(Language.getInstance(Locale.ENGLISH), vars, "<? if($b){ ?>true<? } else{?>false<?}?>"));
+
+        vars.put("b", new Object());
+        assertEquals("true", testExecute(Language.getInstance(Locale.ENGLISH), vars, "<? if($b){ ?>true<? } else{?>false<?}?>"));
+        vars.put("b", null);
+        assertEquals("false", testExecute(Language.getInstance(Locale.ENGLISH), vars, "<? if($b){ ?>true<? } else{?>false<?}?>"));
+    }
+}
diff --git a/tests/org/cacert/gigi/testUtils/ClientTest.java b/tests/org/cacert/gigi/testUtils/ClientTest.java
new file mode 100644 (file)
index 0000000..fc22e6d
--- /dev/null
@@ -0,0 +1,40 @@
+package org.cacert.gigi.testUtils;
+
+import java.io.IOException;
+
+import org.cacert.gigi.dbObjects.User;
+
+/**
+ * Superclass for testsuites in a scenario where there is an registered member,
+ * who is already logged on.
+ */
+public abstract class ClientTest extends ManagedTest {
+
+    /**
+     * Email of the member.
+     */
+    protected String email = createUniqueName() + "@example.org";
+
+    /**
+     * Id of the member
+     */
+    protected int id = createVerifiedUser("a", "b", email, TEST_PASSWORD);
+
+    /**
+     * {@link User} object of the member
+     */
+    protected User u = User.getById(id);
+
+    /**
+     * Session cookie of the member.
+     */
+    protected String cookie;
+
+    public ClientTest() {
+        try {
+            cookie = login(email, TEST_PASSWORD);
+        } catch (IOException e) {
+            throw new Error(e);
+        }
+    }
+}
diff --git a/tests/org/cacert/gigi/testUtils/ConfiguredTest.java b/tests/org/cacert/gigi/testUtils/ConfiguredTest.java
new file mode 100644 (file)
index 0000000..f8098ed
--- /dev/null
@@ -0,0 +1,100 @@
+package org.cacert.gigi.testUtils;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.security.GeneralSecurityException;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.Signature;
+import java.util.Properties;
+import java.util.TimeZone;
+
+import org.cacert.gigi.database.DatabaseConnection;
+import org.cacert.gigi.util.PEM;
+import org.junit.BeforeClass;
+
+import sun.security.pkcs10.PKCS10;
+import sun.security.pkcs10.PKCS10Attributes;
+import sun.security.x509.X500Name;
+
+/**
+ * Base class for a Testsuite that makes use of the config variables that define
+ * the environment.
+ */
+public abstract class ConfiguredTest {
+
+    static Properties testProps = new Properties();
+
+    public static Properties getTestProps() {
+        return testProps;
+    }
+
+    private static boolean envInited = false;
+
+    @BeforeClass
+    public static void initEnvironment() throws IOException {
+        TimeZone.setDefault(TimeZone.getTimeZone("UTC"));
+        if (envInited) {
+            return;
+        }
+        envInited = true;
+        try (FileInputStream inStream = new FileInputStream("config/test.properties")) {
+            testProps.load(inStream);
+        }
+        if ( !DatabaseConnection.isInited()) {
+            DatabaseConnection.init(testProps);
+        }
+    }
+
+    public static KeyPair generateKeypair() throws GeneralSecurityException {
+        KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA");
+        kpg.initialize(4096);
+        KeyPair keyPair = null;
+        File f = new File("testKeypair");
+        if (f.exists()) {
+            try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream(f))) {
+                keyPair = (KeyPair) ois.readObject();
+            } catch (ClassNotFoundException e) {
+                e.printStackTrace();
+            } catch (IOException e) {
+                e.printStackTrace();
+            }
+        } else {
+            keyPair = kpg.generateKeyPair();
+            try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(f))) {
+                oos.writeObject(keyPair);
+                oos.close();
+            } catch (IOException e) {
+                e.printStackTrace();
+            }
+        }
+        return keyPair;
+    }
+
+    public static String generatePEMCSR(KeyPair kp, String dn) throws GeneralSecurityException, IOException {
+        return generatePEMCSR(kp, dn, new PKCS10Attributes());
+    }
+
+    public static String generatePEMCSR(KeyPair kp, String dn, PKCS10Attributes atts) throws GeneralSecurityException, IOException {
+        return generatePEMCSR(kp, dn, atts, "SHA256WithRSA");
+    }
+
+    public static String generatePEMCSR(KeyPair kp, String dn, PKCS10Attributes atts, String signature) throws GeneralSecurityException, IOException {
+        PKCS10 p10 = new PKCS10(kp.getPublic(), atts);
+        Signature s = Signature.getInstance(signature);
+        s.initSign(kp.getPrivate());
+        p10.encodeAndSign(new X500Name(dn), s);
+        return PEM.encode("CERTIFICATE REQUEST", p10.getEncoded());
+    }
+
+    static int count = 0;
+
+    public static String createUniqueName() {
+        return "test" + System.currentTimeMillis() + "a" + (count++) + "u";
+    }
+
+}
diff --git a/tests/org/cacert/gigi/testUtils/IOUtils.java b/tests/org/cacert/gigi/testUtils/IOUtils.java
new file mode 100644 (file)
index 0000000..3e91d7e
--- /dev/null
@@ -0,0 +1,63 @@
+package org.cacert.gigi.testUtils;
+
+import java.io.ByteArrayOutputStream;
+import java.io.CharArrayWriter;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.net.HttpURLConnection;
+import java.net.URLConnection;
+
+public class IOUtils {
+
+    private IOUtils() {
+
+    }
+
+    public static String readURL(URLConnection in) {
+        try {
+            if ( !in.getContentType().equals("text/html; charset=UTF-8")) {
+                if (in instanceof HttpURLConnection && ((HttpURLConnection) in).getResponseCode() != 200) {
+                    System.err.println(readURL(new InputStreamReader(((HttpURLConnection) in).getErrorStream(), "UTF-8")));
+                }
+                throw new Error("Unrecognized content-type: " + in.getContentType());
+            }
+            return readURL(new InputStreamReader(in.getInputStream(), "UTF-8"));
+        } catch (IOException e) {
+            throw new Error(e);
+        }
+
+    }
+
+    public static String readURL(Reader in) {
+        CharArrayWriter caw = new CharArrayWriter();
+        char[] buffer = new char[1024];
+        int len = 0;
+        try {
+            while ((len = in.read(buffer)) > 0) {
+                caw.write(buffer, 0, len);
+            }
+            in.close();
+            return new String(caw.toCharArray());
+        } catch (IOException e) {
+            throw new Error(e);
+        }
+
+    }
+
+    public static byte[] readURL(InputStream in) {
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        byte[] buffer = new byte[1024];
+        int len = 0;
+        try {
+            while ((len = in.read(buffer)) > 0) {
+                baos.write(buffer, 0, len);
+            }
+            in.close();
+            return baos.toByteArray();
+        } catch (IOException e) {
+            throw new Error(e);
+        }
+    }
+}
diff --git a/tests/org/cacert/gigi/testUtils/InitTruststore.java b/tests/org/cacert/gigi/testUtils/InitTruststore.java
new file mode 100644 (file)
index 0000000..1207df9
--- /dev/null
@@ -0,0 +1,15 @@
+package org.cacert.gigi.testUtils;
+
+public class InitTruststore {
+
+    private InitTruststore() {}
+
+    static {
+        System.setProperty("javax.net.ssl.trustStorePassword", "changeit");
+        System.setProperty("javax.net.ssl.trustStore", "config/cacerts.jks");
+    }
+
+    public static void run() {
+
+    }
+}
diff --git a/tests/org/cacert/gigi/testUtils/ManagedTest.java b/tests/org/cacert/gigi/testUtils/ManagedTest.java
new file mode 100644 (file)
index 0000000..f5354eb
--- /dev/null
@@ -0,0 +1,499 @@
+package org.cacert.gigi.testUtils;
+
+import static org.junit.Assert.*;
+
+import java.io.BufferedReader;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.io.UnsupportedEncodingException;
+import java.net.HttpURLConnection;
+import java.net.InetSocketAddress;
+import java.net.MalformedURLException;
+import java.net.Socket;
+import java.net.URL;
+import java.net.URLConnection;
+import java.net.URLEncoder;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.security.KeyManagementException;
+import java.security.NoSuchAlgorithmException;
+import java.security.Principal;
+import java.security.PrivateKey;
+import java.security.cert.X509Certificate;
+import java.sql.SQLException;
+import java.util.Locale;
+import java.util.Properties;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import javax.net.ssl.HttpsURLConnection;
+import javax.net.ssl.KeyManager;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.X509KeyManager;
+
+import org.cacert.gigi.DevelLauncher;
+import org.cacert.gigi.GigiApiException;
+import org.cacert.gigi.database.DatabaseConnection;
+import org.cacert.gigi.database.GigiPreparedStatement;
+import org.cacert.gigi.database.GigiResultSet;
+import org.cacert.gigi.database.SQLFileManager.ImportType;
+import org.cacert.gigi.dbObjects.EmailAddress;
+import org.cacert.gigi.dbObjects.Group;
+import org.cacert.gigi.dbObjects.ObjectCache;
+import org.cacert.gigi.dbObjects.User;
+import org.cacert.gigi.localisation.Language;
+import org.cacert.gigi.pages.Manager;
+import org.cacert.gigi.pages.account.MyDetails;
+import org.cacert.gigi.pages.main.RegisterPage;
+import org.cacert.gigi.testUtils.TestEmailReciever.TestMail;
+import org.cacert.gigi.util.DatabaseManager;
+import org.cacert.gigi.util.ServerConstants;
+import org.cacert.gigi.util.SimpleSigner;
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.Assert;
+import org.junit.BeforeClass;
+
+/**
+ * Base class for test suites who require a launched Gigi instance. The instance
+ * is cleared once per test suite.
+ */
+public class ManagedTest extends ConfiguredTest {
+
+    static {
+        System.setProperty("sun.net.http.allowRestrictedHeaders", "true");
+    }
+
+    /**
+     * Some password that fulfills the password criteria.
+     */
+    protected static final String TEST_PASSWORD = "xvXV12°§";
+
+    private static TestEmailReciever ter;
+
+    private static Process gigi;
+
+    private static String url = "localhost:4443";
+
+    private static String acceptLanguage = null;
+
+    public static void setAcceptLanguage(String acceptLanguage) {
+        ManagedTest.acceptLanguage = acceptLanguage;
+    }
+
+    public static String getServerName() {
+        return url;
+    }
+
+    static {
+        InitTruststore.run();
+        HttpURLConnection.setFollowRedirects(false);
+    }
+
+    @BeforeClass
+    public static void initEnvironment() {
+        try {
+            ConfiguredTest.initEnvironment();
+
+            purgeDatabase();
+            String type = testProps.getProperty("type");
+            Properties mainProps = generateMainProps();
+            ServerConstants.init(mainProps);
+            if (type.equals("local")) {
+                url = testProps.getProperty("name.www") + ":" + testProps.getProperty("serverPort.https");
+                String[] parts = testProps.getProperty("mail").split(":", 2);
+                ter = new TestEmailReciever(new InetSocketAddress(parts[0], Integer.parseInt(parts[1])));
+                ter.start();
+                return;
+            }
+            url = testProps.getProperty("name.www") + ":" + testProps.getProperty("serverPort.https");
+            gigi = Runtime.getRuntime().exec(testProps.getProperty("java"));
+            DataOutputStream toGigi = new DataOutputStream(gigi.getOutputStream());
+            System.out.println("... starting server");
+
+            byte[] cacerts = Files.readAllBytes(Paths.get("config/cacerts.jks"));
+            byte[] keystore = Files.readAllBytes(Paths.get("config/keystore.pkcs12"));
+
+            DevelLauncher.writeGigiConfig(toGigi, "changeit".getBytes("UTF-8"), "changeit".getBytes("UTF-8"), mainProps, cacerts, keystore);
+            toGigi.flush();
+
+            final BufferedReader br = new BufferedReader(new InputStreamReader(gigi.getErrorStream(), "UTF-8"));
+            String line;
+            while ((line = br.readLine()) != null && !line.contains("Server:main: Started")) {
+            }
+            new Thread() {
+
+                @Override
+                public void run() {
+                    String line;
+                    try {
+                        while ((line = br.readLine()) != null) {
+                            System.err.println(line);
+                        }
+                    } catch (IOException e) {
+                        e.printStackTrace();
+                    }
+                }
+            }.start();
+            if (line == null) {
+                throw new Error("Server startup failed");
+            }
+            ter = new TestEmailReciever(new InetSocketAddress("localhost", 8473));
+            ter.start();
+            SimpleSigner.runSigner();
+        } catch (IOException e) {
+            throw new Error(e);
+        } catch (SQLException e1) {
+            e1.printStackTrace();
+        } catch (InterruptedException e) {
+            e.printStackTrace();
+        }
+
+    }
+
+    public static void purgeDatabase() throws SQLException, IOException {
+        System.out.print("... resetting Database");
+        long ms = System.currentTimeMillis();
+        try {
+            DatabaseManager.run(new String[] {
+                    testProps.getProperty("sql.driver"), testProps.getProperty("sql.url"), testProps.getProperty("sql.user"), testProps.getProperty("sql.password")
+            }, ImportType.TRUNCATE);
+        } catch (ClassNotFoundException e) {
+            e.printStackTrace();
+        }
+        System.out.println(" in " + (System.currentTimeMillis() - ms) + " ms");
+        clearCaches();
+    }
+
+    public static void clearCaches() throws IOException {
+        ObjectCache.clearAllCaches();
+        // String type = testProps.getProperty("type");
+        URL u = new URL("https://" + getServerName() + "/manage");
+        u.openConnection().getHeaderField("Location");
+    }
+
+    private static Properties generateMainProps() {
+        Properties mainProps = new Properties();
+        mainProps.setProperty("testrunner", "true");
+        mainProps.setProperty("host", "127.0.0.1");
+        mainProps.setProperty("name.secure", testProps.getProperty("name.secure"));
+        mainProps.setProperty("name.www", testProps.getProperty("name.www"));
+        mainProps.setProperty("name.static", testProps.getProperty("name.static"));
+        mainProps.setProperty("name.api", testProps.getProperty("name.api"));
+
+        mainProps.setProperty("https.port", testProps.getProperty("serverPort.https"));
+        mainProps.setProperty("http.port", testProps.getProperty("serverPort.http"));
+        mainProps.setProperty("emailProvider", "org.cacert.gigi.email.TestEmailProvider");
+        mainProps.setProperty("emailProvider.port", "8473");
+        mainProps.setProperty("sql.driver", testProps.getProperty("sql.driver"));
+        mainProps.setProperty("sql.url", testProps.getProperty("sql.url"));
+        mainProps.setProperty("sql.user", testProps.getProperty("sql.user"));
+        mainProps.setProperty("sql.password", testProps.getProperty("sql.password"));
+        mainProps.setProperty("testing", "true");
+        return mainProps;
+    }
+
+    @AfterClass
+    public static void tearDownServer() {
+        String type = testProps.getProperty("type");
+        ter.destroy();
+        if (type.equals("local")) {
+            return;
+        }
+        gigi.destroy();
+        try {
+            SimpleSigner.stopSigner();
+        } catch (InterruptedException e) {
+            e.printStackTrace();
+        }
+    }
+
+    public final String uniq = createUniqueName();
+
+    @After
+    public void removeMails() {
+        ter.reset();
+    }
+
+    @After
+    public void clearAcceptLanguage() {
+        ManagedTest.setAcceptLanguage(null);
+    }
+
+    public TestMail waitForMail() {
+        TestMail mail = null;
+        while (null == mail) {
+            try {
+                mail = ter.recieve();
+            } catch (InterruptedException e) {
+                throw new Error(e);
+            }
+        }
+        return mail;
+    }
+
+    public static TestEmailReciever getMailReciever() {
+        return ter;
+    }
+
+    public static String runRegister(String param) throws IOException {
+        URL regist = new URL("https://" + getServerName() + RegisterPage.PATH);
+        HttpURLConnection uc = (HttpURLConnection) regist.openConnection();
+        HttpURLConnection csrfConn = (HttpURLConnection) regist.openConnection();
+        if (acceptLanguage != null) {
+            csrfConn.setRequestProperty("Accept-Language", acceptLanguage);
+            uc.setRequestProperty("Accept-Language", acceptLanguage);
+        }
+
+        String headerField = csrfConn.getHeaderField("Set-Cookie");
+        headerField = stripCookie(headerField);
+
+        String csrf = getCSRF(csrfConn);
+        uc.addRequestProperty("Cookie", headerField);
+        uc.setDoOutput(true);
+        uc.getOutputStream().write((param + "&csrf=" + csrf).getBytes("UTF-8"));
+        String d = IOUtils.readURL(uc);
+        return d;
+    }
+
+    public static String fetchStartErrorMessage(String d) throws IOException {
+        String formFail = "<div class='formError'>";
+        int idx = d.indexOf(formFail);
+        if (idx == -1) {
+            return null;
+        }
+        String startError = d.substring(idx + formFail.length(), idx + 100).trim();
+        return startError;
+    }
+
+    public static void registerUser(String firstName, String lastName, String email, String password) {
+        try {
+            String query = "fname=" + URLEncoder.encode(firstName, "UTF-8") + "&lname=" + URLEncoder.encode(lastName, "UTF-8") + "&email=" + URLEncoder.encode(email, "UTF-8") + "&pword1=" + URLEncoder.encode(password, "UTF-8") + "&pword2=" + URLEncoder.encode(password, "UTF-8") + "&day=1&month=1&year=1910&cca_agree=1";
+            String data = fetchStartErrorMessage(runRegister(query));
+            assertNull(data);
+        } catch (UnsupportedEncodingException e) {
+            throw new Error(e);
+        } catch (IOException e) {
+            throw new Error(e);
+        }
+    }
+
+    public static int createVerifiedUser(String firstName, String lastName, String email, String password) {
+        registerUser(firstName, lastName, email, password);
+        try {
+            TestMail tm = ter.recieve();
+            Assert.assertNotNull(tm);
+            tm.verify();
+
+            GigiPreparedStatement ps = DatabaseConnection.getInstance().prepare("SELECT id FROM users where email=?");
+            ps.setString(1, email);
+
+            try (GigiResultSet rs = ps.executeQuery()) {
+                if (rs.next()) {
+                    return rs.getInt(1);
+                }
+            }
+
+            throw new Error();
+        } catch (InterruptedException e) {
+            throw new Error(e);
+        } catch (IOException e) {
+            throw new Error(e);
+        }
+    }
+
+    public static void grant(String email, Group g) throws IOException {
+        HttpURLConnection huc = (HttpURLConnection) new URL("https://" + getServerName() + Manager.PATH).openConnection();
+        huc.setDoOutput(true);
+        huc.getOutputStream().write(("addpriv=y&priv=" + URLEncoder.encode(g.getDatabaseName(), "UTF-8") + "&email=" + URLEncoder.encode(email, "UTF-8")).getBytes("UTF-8"));
+        assertEquals(200, huc.getResponseCode());
+    }
+
+    /**
+     * Creates a new user with 100 Assurance points given by an (invalid)
+     * assurance.
+     * 
+     * @param firstName
+     *            the first name
+     * @param lastName
+     *            the last name
+     * @param email
+     *            the email
+     * @param password
+     *            the password
+     * @return a new userid.
+     */
+    public static int createAssuranceUser(String firstName, String lastName, String email, String password) {
+        int uid = createVerifiedUser(firstName, lastName, email, password);
+
+        GigiPreparedStatement ps1 = DatabaseConnection.getInstance().prepare("INSERT INTO `cats_passed` SET `user_id`=?, `variant_id`=?");
+        ps1.setInt(1, uid);
+        ps1.setInt(2, 0);
+        ps1.execute();
+
+        GigiPreparedStatement ps2 = DatabaseConnection.getInstance().prepare("INSERT INTO `notary` SET `from`=?, `to`=?, points='100'");
+        ps2.setInt(1, uid);
+        ps2.setInt(2, uid);
+        ps2.execute();
+
+        return uid;
+    }
+
+    static String stripCookie(String headerField) {
+        return headerField.substring(0, headerField.indexOf(';'));
+    }
+
+    public static final String SECURE_REFERENCE = MyDetails.PATH;
+
+    public static boolean isLoggedin(String cookie) throws IOException {
+        URL u = new URL("https://" + getServerName() + SECURE_REFERENCE);
+        HttpURLConnection huc = (HttpURLConnection) u.openConnection();
+        huc.addRequestProperty("Cookie", cookie);
+        return huc.getResponseCode() == 200;
+    }
+
+    public static String login(String email, String pw) throws IOException {
+        URL u = new URL("https://" + getServerName() + "/login");
+        HttpURLConnection huc = (HttpURLConnection) u.openConnection();
+
+        String csrf = getCSRF(huc);
+        String headerField = stripCookie(huc.getHeaderField("Set-Cookie"));
+
+        huc = (HttpURLConnection) u.openConnection();
+        cookie(huc, headerField);
+        huc.setDoOutput(true);
+        OutputStream os = huc.getOutputStream();
+        String data = "username=" + URLEncoder.encode(email, "UTF-8") + "&password=" + URLEncoder.encode(pw, "UTF-8") + "&csrf=" + URLEncoder.encode(csrf, "UTF-8");
+        os.write(data.getBytes("UTF-8"));
+        os.flush();
+        headerField = huc.getHeaderField("Set-Cookie");
+        if (headerField == null) {
+            return "";
+        }
+        return stripCookie(headerField);
+    }
+
+    public static String login(final PrivateKey pk, final X509Certificate ce) throws NoSuchAlgorithmException, KeyManagementException, IOException, MalformedURLException {
+
+        HttpURLConnection connection = (HttpURLConnection) new URL("https://" + getServerName().replaceFirst("^www.", "secure.") + "/login").openConnection();
+        authenticateClientCert(pk, ce, connection);
+        if (connection.getResponseCode() == 302) {
+            assertEquals("https://" + getServerName().replaceFirst("^www.", "secure.").replaceFirst(":443$", "") + "/", connection.getHeaderField("Location").replaceFirst(":443$", ""));
+            return stripCookie(connection.getHeaderField("Set-Cookie"));
+        } else {
+            return null;
+        }
+    }
+
+    public static void authenticateClientCert(final PrivateKey pk, final X509Certificate ce, HttpURLConnection connection) throws NoSuchAlgorithmException, KeyManagementException {
+        KeyManager km = new X509KeyManager() {
+
+            @Override
+            public String chooseClientAlias(String[] arg0, Principal[] arg1, Socket arg2) {
+                return "client";
+            }
+
+            @Override
+            public String chooseServerAlias(String arg0, Principal[] arg1, Socket arg2) {
+                return null;
+            }
+
+            @Override
+            public X509Certificate[] getCertificateChain(String arg0) {
+                return new X509Certificate[] {
+                    ce
+                };
+            }
+
+            @Override
+            public String[] getClientAliases(String arg0, Principal[] arg1) {
+                return new String[] {
+                    "client"
+                };
+            }
+
+            @Override
+            public PrivateKey getPrivateKey(String arg0) {
+                if (arg0.equals("client")) {
+                    return pk;
+                }
+                return null;
+            }
+
+            @Override
+            public String[] getServerAliases(String arg0, Principal[] arg1) {
+                return new String[] {
+                    "client"
+                };
+            }
+        };
+        SSLContext sc = SSLContext.getInstance("TLS");
+        sc.init(new KeyManager[] {
+            km
+        }, null, null);
+        if (connection instanceof HttpsURLConnection) {
+            ((HttpsURLConnection) connection).setSSLSocketFactory(sc.getSocketFactory());
+        }
+    }
+
+    public static String getCSRF(URLConnection u) throws IOException {
+        return getCSRF(u, 0);
+    }
+
+    public static String getCSRF(URLConnection u, int formIndex) throws IOException {
+        String content = IOUtils.readURL(u);
+        return getCSRF(formIndex, content);
+    }
+
+    public static String getCSRF(int formIndex, String content) throws Error {
+        Pattern p = Pattern.compile("<input type='hidden' name='csrf' value='([^']+)'>");
+        Matcher m = p.matcher(content);
+        for (int i = 0; i < formIndex + 1; i++) {
+            if ( !m.find()) {
+                throw new Error("No CSRF Token");
+            }
+        }
+        return m.group(1);
+    }
+
+    public static String executeBasicWebInteraction(String cookie, String path, String query) throws MalformedURLException, UnsupportedEncodingException, IOException {
+        return executeBasicWebInteraction(cookie, path, query, 0);
+    }
+
+    public static String executeBasicWebInteraction(String cookie, String path, String query, int formIndex) throws IOException, MalformedURLException, UnsupportedEncodingException {
+        URLConnection uc = new URL("https://" + getServerName() + path).openConnection();
+        uc.addRequestProperty("Cookie", cookie);
+        String csrf = getCSRF(uc, formIndex);
+
+        uc = new URL("https://" + getServerName() + path).openConnection();
+        uc.addRequestProperty("Cookie", cookie);
+        uc.setDoOutput(true);
+        OutputStream os = uc.getOutputStream();
+        os.write(("csrf=" + URLEncoder.encode(csrf, "UTF-8") + "&" //
+        + query//
+        ).getBytes("UTF-8"));
+        os.flush();
+        String error = fetchStartErrorMessage(IOUtils.readURL(uc));
+        return error;
+    }
+
+    public static EmailAddress createVerifiedEmail(User u) throws InterruptedException, GigiApiException {
+        EmailAddress adrr = new EmailAddress(u, createUniqueName() + "test@test.tld");
+        adrr.insert(Language.getInstance(Locale.ENGLISH));
+        TestMail testMail = getMailReciever().recieve();
+        Assert.assertNotNull(testMail);
+        assertEquals(adrr.getAddress(), testMail.getTo());
+        String hash = testMail.extractLink().substring(testMail.extractLink().lastIndexOf('=') + 1);
+        adrr.verify(hash);
+        getMailReciever().clearMails();
+        return adrr;
+    }
+
+    public static URLConnection cookie(URLConnection openConnection, String cookie) {
+        openConnection.setRequestProperty("Cookie", cookie);
+        return openConnection;
+    }
+
+}
diff --git a/tests/org/cacert/gigi/testUtils/PingTest.java b/tests/org/cacert/gigi/testUtils/PingTest.java
new file mode 100644 (file)
index 0000000..2039587
--- /dev/null
@@ -0,0 +1,83 @@
+package org.cacert.gigi.testUtils;
+
+import static org.junit.Assert.*;
+import static org.junit.Assume.*;
+
+import java.io.IOException;
+import java.net.HttpURLConnection;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.URLConnection;
+import java.sql.SQLException;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.cacert.gigi.database.DatabaseConnection;
+import org.cacert.gigi.database.GigiPreparedStatement;
+import org.cacert.gigi.database.GigiResultSet;
+import org.cacert.gigi.pages.account.domain.DomainOverview;
+import org.junit.After;
+
+/**
+ * Base class for test suites that check extensively if the domain-ping
+ * functionality wroks as expected.
+ */
+public abstract class PingTest extends ClientTest {
+
+    protected String csrf;
+
+    protected static void updateService(String token, String value, String action) throws IOException, MalformedURLException {
+        String manage = getTestProps().getProperty("domain.manage");
+        assumeNotNull(manage);
+        String url = manage + "t1=" + token + "&t2=" + value + "&action=" + action;
+        assertEquals(200, ((HttpURLConnection) new URL(url).openConnection()).getResponseCode());
+    }
+
+    protected void waitForPings(int count) throws SQLException, InterruptedException {
+        GigiPreparedStatement ps = DatabaseConnection.getInstance().prepare("SELECT COUNT(*) FROM domainPinglog");
+        long start = System.currentTimeMillis();
+        while (System.currentTimeMillis() - start < 10000) {
+            GigiResultSet rs = ps.executeQuery();
+            rs.next();
+            if (rs.getInt(1) >= count) {
+                break;
+            }
+            Thread.sleep(200);
+        }
+    }
+
+    protected URL sendDomainForm(URL u, String content) throws IOException, MalformedURLException {
+        URLConnection openConnection = u.openConnection();
+        openConnection.setRequestProperty("Cookie", cookie);
+        openConnection.setDoOutput(true);
+        openConnection.getOutputStream().write(content.getBytes("UTF-8"));
+        openConnection.getHeaderField("Location");
+
+        String newcontent = IOUtils.readURL(cookie(u.openConnection(), cookie));
+        Pattern dlink = Pattern.compile(DomainOverview.PATH + "([0-9]+)'>");
+        Matcher m1 = dlink.matcher(newcontent);
+        if ( !m1.find()) {
+            throw new Error(newcontent);
+        }
+        URL u2 = new URL(u.toString() + m1.group(1));
+        return u2;
+    }
+
+    protected Matcher initailizeDomainForm(URL u) throws IOException, Error {
+        URLConnection openConnection = u.openConnection();
+        openConnection.setRequestProperty("Cookie", cookie);
+        String content1 = IOUtils.readURL(openConnection);
+        csrf = getCSRF(1, content1);
+
+        Pattern p = Pattern.compile("([A-Za-z0-9]+)._cacert._auth IN TXT ([A-Za-z0-9]+)");
+        Matcher m = p.matcher(content1);
+        m.find();
+        return m;
+    }
+
+    @After
+    public void purgeDbAfterTest() throws SQLException, IOException {
+        purgeDatabase();
+    }
+
+}
diff --git a/tests/org/cacert/gigi/testUtils/RegisteredUser.java b/tests/org/cacert/gigi/testUtils/RegisteredUser.java
new file mode 100644 (file)
index 0000000..a5b5172
--- /dev/null
@@ -0,0 +1,32 @@
+package org.cacert.gigi.testUtils;
+
+import org.cacert.gigi.dbObjects.User;
+import org.junit.rules.TestRule;
+import org.junit.runner.Description;
+import org.junit.runners.model.Statement;
+
+public class RegisteredUser implements TestRule {
+
+    User u;
+
+    @Override
+    public Statement apply(final Statement base, Description description) {
+        return new Statement() {
+
+            @Override
+            public void evaluate() throws Throwable {
+                u = User.getById(ManagedTest.createVerifiedUser("fn", "ln", ManagedTest.createUniqueName() + "@example.org", ManagedTest.TEST_PASSWORD));
+                try {
+                    base.evaluate();
+                } finally {
+
+                }
+            }
+        };
+    }
+
+    public User getUser() {
+        return u;
+    }
+
+}
diff --git a/tests/org/cacert/gigi/testUtils/TestEmailReciever.java b/tests/org/cacert/gigi/testUtils/TestEmailReciever.java
new file mode 100644 (file)
index 0000000..dc90acd
--- /dev/null
@@ -0,0 +1,191 @@
+package org.cacert.gigi.testUtils;
+
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.net.Socket;
+import java.net.SocketAddress;
+import java.net.URL;
+import java.net.URLConnection;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.TimeUnit;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.cacert.gigi.email.EmailProvider;
+
+public final class TestEmailReciever extends EmailProvider implements Runnable {
+
+    public class TestMail {
+
+        String to;
+
+        String subject;
+
+        String message;
+
+        String from;
+
+        String replyto;
+
+        public TestMail(String to, String subject, String message, String from, String replyto) {
+            this.to = to;
+            this.subject = subject;
+            this.message = message;
+            this.from = from;
+            this.replyto = replyto;
+        }
+
+        public String getTo() {
+            return to;
+        }
+
+        public String getSubject() {
+            return subject;
+        }
+
+        public String getMessage() {
+            return message;
+        }
+
+        public String getFrom() {
+            return from;
+        }
+
+        public String getReplyto() {
+            return replyto;
+        }
+
+        public String extractLink() {
+            Pattern link = Pattern.compile("https?://[^\\s]+(?=\\s)");
+            Matcher m = link.matcher(getMessage());
+            m.find();
+            return m.group(0);
+        }
+
+        public void verify() throws IOException {
+            String[] parts = extractLink().split("\\?");
+            URL u = new URL("https://" + ManagedTest.getServerName() + "/verify?" + parts[1]);
+
+            URLConnection csrfConn = u.openConnection();
+            String csrf = ManagedTest.getCSRF(csrfConn, 0);
+
+            u = new URL("https://" + ManagedTest.getServerName() + "/verify");
+            URLConnection uc = u.openConnection();
+            ManagedTest.cookie(uc, ManagedTest.stripCookie(csrfConn.getHeaderField("Set-Cookie")));
+            uc.setDoOutput(true);
+            uc.getOutputStream().write((parts[1] + "&csrf=" + csrf).getBytes("UTF-8"));
+            uc.connect();
+            uc.getInputStream().close();
+        }
+
+    }
+
+    private Socket s;
+
+    private DataInputStream dis;
+
+    private DataOutputStream dos;
+
+    public TestEmailReciever(SocketAddress target) throws IOException {
+        s = new Socket();
+        s.connect(target);
+        s.setKeepAlive(true);
+        s.setSoTimeout(1000 * 60 * 60);
+        dis = new DataInputStream(s.getInputStream());
+        dos = new DataOutputStream(s.getOutputStream());
+        setInstance(this);
+    }
+
+    public void start() {
+        new Thread(this, "Mail reciever").start();
+    }
+
+    LinkedBlockingQueue<TestMail> mails = new LinkedBlockingQueue<TestEmailReciever.TestMail>();
+
+    public TestMail recieve() throws InterruptedException {
+        TestMail poll = mails.poll(5, TimeUnit.SECONDS);
+        return poll;
+    }
+
+    @Override
+    public void run() {
+        try {
+            while (true) {
+                String type = dis.readUTF();
+                if (type.equals("mail")) {
+                    String to = dis.readUTF();
+                    String subject = dis.readUTF();
+                    String message = dis.readUTF();
+                    String from = dis.readUTF();
+                    String replyto = dis.readUTF();
+                    mails.add(new TestMail(to, subject, message, from, replyto));
+                } else if (type.equals("challengeAddrBox")) {
+                    String email = dis.readUTF();
+                    dos.writeUTF(quickEmailCheck(email));
+                    dos.flush();
+                } else if (type.equals("ping")) {
+                } else {
+                    System.err.println("Unknown type: " + type);
+                }
+            }
+        } catch (IOException e) {
+            if ( !closed) {
+                e.printStackTrace();
+            }
+        }
+
+    }
+
+    private String quickEmailCheck(String email) throws IOException {
+        if (approveRegex.matcher(email).matches()) {
+            return "OK";
+        } else {
+            return error;
+        }
+    }
+
+    String error = "FAIL";
+
+    public void setEmailCheckError(String error) {
+        this.error = error;
+    }
+
+    Pattern approveRegex = Pattern.compile(".*");
+
+    public void setApproveRegex(Pattern approveRegex) {
+        this.approveRegex = approveRegex;
+    }
+
+    public void clearMails() {
+        mails.clear();
+    }
+
+    public void reset() {
+        clearMails();
+        error = "FAIL";
+        approveRegex = Pattern.compile(".*");
+    }
+
+    boolean closed = false;
+
+    public void destroy() {
+        try {
+            closed = true;
+            s.close();
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+    }
+
+    @Override
+    public String checkEmailServer(int forUid, String address) throws IOException {
+        return quickEmailCheck(address);
+    }
+
+    @Override
+    public void sendmail(String to, String subject, String message, String from, String replyto, String toname, String fromname, String errorsto, boolean extra) throws IOException {
+        mails.add(new TestMail(to, subject, message, from, replyto));
+    }
+
+}
diff --git a/tests/org/cacert/gigi/util/TestHTMLEncoder.java b/tests/org/cacert/gigi/util/TestHTMLEncoder.java
new file mode 100644 (file)
index 0000000..5d3a2ae
--- /dev/null
@@ -0,0 +1,28 @@
+package org.cacert.gigi.util;
+
+import static org.junit.Assert.*;
+
+import org.junit.Test;
+
+public class TestHTMLEncoder {
+
+    @Test
+    public void testEncodeSimpleString() {
+        assertEquals("1234_ä", HTMLEncoder.encodeHTML("1234_ä"));
+    }
+
+    @Test
+    public void testEncodeQuotes() {
+        assertEquals("\\&quot;_ä.", HTMLEncoder.encodeHTML("\\\"_ä."));
+    }
+
+    @Test
+    public void testEncodeTagString() {
+        assertEquals("&lt;td class=&quot;&amp;amp;&quot;&gt;", HTMLEncoder.encodeHTML("<td class=\"&amp;\">"));
+    }
+
+    @Test
+    public void testEncodeSingleQuoteString() {
+        assertEquals("&#39;&amp;#39;", HTMLEncoder.encodeHTML("'&#39;"));
+    }
+}
diff --git a/tests/org/cacert/gigi/util/TestNotary.java b/tests/org/cacert/gigi/util/TestNotary.java
new file mode 100644 (file)
index 0000000..4bfb26d
--- /dev/null
@@ -0,0 +1,115 @@
+package org.cacert.gigi.util;
+
+import static org.junit.Assert.*;
+
+import java.sql.SQLException;
+import java.util.Date;
+
+import org.cacert.gigi.GigiApiException;
+import org.cacert.gigi.database.DatabaseConnection;
+import org.cacert.gigi.database.GigiPreparedStatement;
+import org.cacert.gigi.dbObjects.User;
+import org.cacert.gigi.output.DateSelector;
+import org.cacert.gigi.testUtils.ManagedTest;
+import org.junit.Test;
+
+public class TestNotary extends ManagedTest {
+
+    @Test
+    public void testNormalAssurance() throws SQLException, GigiApiException {
+        User[] users = new User[30];
+        for (int i = 0; i < users.length; i++) {
+            int id = createVerifiedUser("fn" + i, "ln" + i, createUniqueName() + "@email.org", TEST_PASSWORD);
+            users[i] = User.getById(id);
+        }
+        User assurer = User.getById(createAssuranceUser("fn", "ln", createUniqueName() + "@email.org", TEST_PASSWORD));
+        int[] result = new int[] {
+                10, 10, 10, 10, 15, 15, 15, 15, 15, 20, 20, 20, 20, 20, 25, 25, 25, 25, 25, 30, 30, 30, 30, 30, 35, 35, 35, 35, 35, 35
+        };
+
+        try {
+            Notary.assure(assurer, users[0], users[0].getName(), users[0].getDoB(), -1, "test-notary", "2014-01-01");
+            fail("This shouldn't have passed");
+        } catch (GigiApiException e) {
+            // expected
+        }
+        for (int i = 0; i < result.length; i++) {
+            assertEquals(result[i], assurer.getMaxAssurePoints());
+
+            assuranceFail(assurer, users[i], result[i] + 1, "test-notary", "2014-01-01");
+            Notary.assure(assurer, users[i], users[i].getName(), users[i].getDoB(), result[i], "test-notary", "2014-01-01");
+            assuranceFail(assurer, users[i], result[i], "test-notary", "2014-01-01");
+        }
+
+        assertEquals(35, assurer.getMaxAssurePoints());
+
+        assertEquals(2 + 60, assurer.getExperiencePoints());
+
+    }
+
+    private void assuranceFail(User assurer, User user, int i, String location, String date) throws SQLException {
+        try {
+            Notary.assure(assurer, user, user.getName(), user.getDoB(), i, location, date);
+            fail("This shouldn't have passed");
+        } catch (GigiApiException e) {
+            // expected
+        }
+    }
+
+    @Test
+    public void testPoJam() throws SQLException, GigiApiException {
+        User[] users = new User[30];
+        for (int i = 0; i < users.length; i++) {
+            int id = createVerifiedUser("fn" + i, "ln" + i, createUniqueName() + "@email.org", TEST_PASSWORD);
+            users[i] = User.getById(id);
+        }
+        int id = createAssuranceUser("fn", "ln", createUniqueName() + "@email.org", TEST_PASSWORD);
+        GigiPreparedStatement ps = DatabaseConnection.getInstance().prepare("UPDATE users SET dob=TIMESTAMPADD(YEAR,-15,NOW()) WHERE id=?");
+        ps.setInt(1, id);
+        ps.execute();
+        User assurer = User.getById(id);
+        for (int i = 0; i < users.length; i++) {
+            assuranceFail(assurer, users[i], -1, "test-notary", "2014-01-01");
+            assuranceFail(assurer, users[i], 11, "test-notary", "2014-01-01");
+            Notary.assure(assurer, users[i], users[i].getName(), users[i].getDoB(), 10, "test-notary", "2014-01-01");
+            assuranceFail(assurer, users[i], 10, "test-notary", "2014-01-01");
+        }
+    }
+
+    @Test
+    public void testFail() throws SQLException, GigiApiException {
+        User assuranceUser = User.getById(createAssuranceUser("fn", "ln", createUniqueName() + "@example.org", TEST_PASSWORD));
+        User assuree = User.getById(createVerifiedUser("fn", "ln", createUniqueName() + "@example.org", TEST_PASSWORD));
+
+        // invalid date format
+        assuranceFail(assuranceUser, assuree, 10, "notary-junit-test", "2014-01-blah");
+        // empty date
+        assuranceFail(assuranceUser, assuree, 10, "notary-junit-test", "");
+        // null date
+        assuranceFail(assuranceUser, assuree, 10, "notary-junit-test", null);
+        // null location
+        assuranceFail(assuranceUser, assuree, 10, null, "2014-01-01");
+        // empty location
+        assuranceFail(assuranceUser, assuree, 10, "", "2014-01-01");
+        // date in the future
+        assuranceFail(assuranceUser, assuree, 10, "notary-junit-test", DateSelector.getDateFormat().format(new Date(System.currentTimeMillis() + 2 * 24 * 60 * 60 * 1000)));
+        // location too short
+        assuranceFail(assuranceUser, assuree, 10, "n", "2014-01-01");
+        // points too low
+        assuranceFail(assuranceUser, assuree, -1, "notary-junit-test", "2014-01-01");
+        // points too high
+        assuranceFail(assuranceUser, assuree, 11, "notary-junit-test", "2014-01-01");
+
+        // assure oneself
+        assuranceFail(assuranceUser, assuranceUser, 10, "notary-junit-test", "2014-01-01");
+        // not an assurer
+        assuranceFail(assuree, assuranceUser, 10, "notary-junit-test", "2014-01-01");
+
+        // valid
+        Notary.assure(assuranceUser, assuree, assuree.getName(), assuree.getDoB(), 10, "notary-junit-test", "2014-01-01");
+
+        // assure double
+        assuranceFail(assuranceUser, assuree, 10, "notary-junit-test", "2014-01-01");
+
+    }
+}
diff --git a/tests/org/cacert/gigi/util/TestPasswordHash.java b/tests/org/cacert/gigi/util/TestPasswordHash.java
new file mode 100644 (file)
index 0000000..6e38a2b
--- /dev/null
@@ -0,0 +1,21 @@
+package org.cacert.gigi.util;
+
+import static org.junit.Assert.*;
+
+import org.junit.Test;
+
+public class TestPasswordHash {
+
+    @Test
+    public void testVerify() {
+        assertTrue(PasswordHash.verifyHash("a", PasswordHash.hash("a")) != null);
+        assertTrue(PasswordHash.verifyHash("a1234", PasswordHash.hash("a1234")) != null);
+        assertTrue(PasswordHash.verifyHash("auhlcb4 9x,IUQẞ&lvrvä", PasswordHash.hash("auhlcb4 9x,IUQẞ&lvrvä")) != null);
+    }
+
+    @Test
+    public void testVerifyNegative() {
+        assertFalse(PasswordHash.verifyHash("b", PasswordHash.hash("a")) != null);
+        assertFalse(PasswordHash.verifyHash("ae", PasswordHash.hash("auhlcb4 9x,IUQẞ&lvrvä")) != null);
+    }
+}
diff --git a/tests/org/cacert/gigi/util/TestPasswordMigration.java b/tests/org/cacert/gigi/util/TestPasswordMigration.java
new file mode 100644 (file)
index 0000000..030794c
--- /dev/null
@@ -0,0 +1,37 @@
+package org.cacert.gigi.util;
+
+import static org.hamcrest.CoreMatchers.*;
+import static org.junit.Assert.*;
+
+import java.io.IOException;
+
+import org.cacert.gigi.database.DatabaseConnection;
+import org.cacert.gigi.database.GigiPreparedStatement;
+import org.cacert.gigi.database.GigiResultSet;
+import org.cacert.gigi.testUtils.ManagedTest;
+import org.cacert.gigi.testUtils.RegisteredUser;
+import org.junit.Rule;
+import org.junit.Test;
+
+public class TestPasswordMigration extends ManagedTest {
+
+    @Rule
+    public RegisteredUser ru = new RegisteredUser();
+
+    @Test
+    public void testPasswordMigration() throws IOException {
+        GigiPreparedStatement stmt = DatabaseConnection.getInstance().prepare("UPDATE users SET `password`=SHA1(?) WHERE id=?");
+        stmt.setString(1, "a");
+        stmt.setInt(2, ru.getUser().getId());
+        stmt.execute();
+        String cookie = login(ru.getUser().getEmail(), "a");
+        assertTrue(isLoggedin(cookie));
+
+        stmt = DatabaseConnection.getInstance().prepare("SELECT `password` FROM users WHERE id=?");
+        stmt.setInt(1, ru.getUser().getId());
+        GigiResultSet res = stmt.executeQuery();
+        assertTrue(res.next());
+        String newHash = res.getString(1);
+        assertThat(newHash, containsString("$"));
+    }
+}
diff --git a/tests/org/cacert/gigi/util/TestPasswordStrengthChecker.java b/tests/org/cacert/gigi/util/TestPasswordStrengthChecker.java
new file mode 100644 (file)
index 0000000..eabb042
--- /dev/null
@@ -0,0 +1,75 @@
+package org.cacert.gigi.util;
+
+import static org.junit.Assert.*;
+
+import org.cacert.gigi.dbObjects.User;
+import org.junit.Test;
+
+public class TestPasswordStrengthChecker {
+
+    User u;
+
+    public TestPasswordStrengthChecker() {
+        u = new User();
+        u.setFName("fname");
+        u.setLName("lname");
+        u.setMName("mname");
+        u.setEmail("email");
+        u.setSuffix("suffix");
+    }
+
+    @Test
+    public void testPasswordLength() {
+        assertEquals(1, PasswordStrengthChecker.checkpw("01234", u));
+        assertEquals(2, PasswordStrengthChecker.checkpw("0123456789012345", u));
+        assertEquals(3, PasswordStrengthChecker.checkpw("012345678901234567890", u));
+        assertEquals(4, PasswordStrengthChecker.checkpw("01234567890123456789012345", u));
+        assertEquals(5, PasswordStrengthChecker.checkpw("0123456789012345678901234567890", u));
+    }
+
+    @Test
+    public void testPasswordNonASCII() {
+        assertEquals(2, PasswordStrengthChecker.checkpw("0ä", u));
+        assertEquals(3, PasswordStrengthChecker.checkpw("0aä", u));
+        assertEquals(3, PasswordStrengthChecker.checkpw("0azä", u));
+        assertEquals(3, PasswordStrengthChecker.checkpw("0az.ä", u));
+    }
+
+    @Test
+    public void testPasswordCharTypes() {
+        assertEquals(1, PasswordStrengthChecker.checkpw("0", u));
+        assertEquals(2, PasswordStrengthChecker.checkpw("0a", u));
+        assertEquals(2, PasswordStrengthChecker.checkpw("0az", u));
+        assertEquals(3, PasswordStrengthChecker.checkpw("0azZ", u));
+        assertEquals(4, PasswordStrengthChecker.checkpw("0a zZ", u));
+        assertEquals(5, PasswordStrengthChecker.checkpw("0a. zZ", u));
+
+        assertEquals(1, PasswordStrengthChecker.checkpw(".", u));
+        assertEquals(1, PasswordStrengthChecker.checkpw(" ", u));
+        assertEquals(1, PasswordStrengthChecker.checkpw("b", u));
+        assertEquals(1, PasswordStrengthChecker.checkpw("Z", u));
+
+        assertEquals(2, PasswordStrengthChecker.checkpw("0.", u));
+        assertEquals(2, PasswordStrengthChecker.checkpw("0 ", u));
+        assertEquals(2, PasswordStrengthChecker.checkpw("0a", u));
+        assertEquals(2, PasswordStrengthChecker.checkpw("0Z", u));
+
+        assertEquals(2, PasswordStrengthChecker.checkpw(" .", u));
+        assertEquals(2, PasswordStrengthChecker.checkpw(" a", u));
+        assertEquals(2, PasswordStrengthChecker.checkpw(" Z", u));
+
+    }
+
+    @Test
+    public void testPasswordContains() {
+        assertEquals( -1, PasswordStrengthChecker.checkpw("fnamea", u));
+        assertEquals( -5, PasswordStrengthChecker.checkpw("na", u));
+        assertEquals(0, PasswordStrengthChecker.checkpw("1lname", u));
+        assertEquals(0, PasswordStrengthChecker.checkpw("1email", u));
+        assertEquals( -1, PasswordStrengthChecker.checkpw("mai", u));
+        assertEquals( -1, PasswordStrengthChecker.checkpw("suff", u));
+        assertEquals(0, PasswordStrengthChecker.checkpw("1suffix", u));
+
+    }
+
+}
diff --git a/tests/org/cacert/gigi/util/TestPublicSuffixes.java b/tests/org/cacert/gigi/util/TestPublicSuffixes.java
new file mode 100644 (file)
index 0000000..9502241
--- /dev/null
@@ -0,0 +1,82 @@
+package org.cacert.gigi.util;
+
+import static org.junit.Assert.*;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.net.IDN;
+import java.util.ArrayList;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class TestPublicSuffixes {
+
+    /**
+     * Taken from
+     * http://mxr.mozilla.org/mozilla-central/source/netwerk/test/unit
+     * /data/test_psl.txt?raw=1
+     */
+    @Parameters(name = "publicSuffix({0}) = {1}")
+    public static Iterable<String[]> genParams() throws IOException {
+        BufferedReader br = null;
+        try {
+            br = new BufferedReader(new InputStreamReader(TestPublicSuffixes.class.getResourceAsStream("TestPublicSuffixes.txt"), "UTF-8"));
+            ArrayList<String[]> result = new ArrayList<>();
+            String line;
+            while ((line = br.readLine()) != null) {
+                if (line.startsWith("//") || line.isEmpty()) {
+                    continue;
+                }
+                String parseSuffix = "checkPublicSuffix(";
+                if (line.startsWith(parseSuffix)) {
+                    String data = line.substring(parseSuffix.length(), line.length() - 2);
+                    String[] parts = data.split(", ");
+                    if (parts.length != 2) {
+                        throw new Error("Syntax error in public suffix test data file: " + line);
+                    }
+                    result.add(new String[] {
+                            parse(parts[0]), parse(parts[1])
+                    });
+                } else {
+                    throw new Error("Unparsable line: " + line);
+                }
+            }
+            return result;
+        } finally {
+            if (br != null) {
+                br.close();
+            }
+        }
+    }
+
+    private static String parse(String data) {
+        if (data.equals("null")) {
+            return null;
+        }
+        if (data.startsWith("'") && data.endsWith("'")) {
+            return data.substring(1, data.length() - 1);
+        }
+        throw new Error("Syntax error with literal: " + data);
+    }
+
+    @Parameter(0)
+    public String domain;
+
+    @Parameter(1)
+    public String suffix;
+
+    @Test
+    public void testPublicSuffix() {
+        if (domain != null) {
+            domain = domain.toLowerCase();
+        }
+        String publicSuffix = PublicSuffixes.getInstance().getRegistrablePart(domain == null ? null : IDN.toASCII(domain));
+        assertEquals(suffix == null ? null : IDN.toASCII(suffix), publicSuffix);
+    }
+}
diff --git a/tests/org/cacert/gigi/util/TestPublicSuffixes.txt b/tests/org/cacert/gigi/util/TestPublicSuffixes.txt
new file mode 100644 (file)
index 0000000..9c7a4aa
--- /dev/null
@@ -0,0 +1,98 @@
+// Any copyright is dedicated to the Public Domain.
+// http://creativecommons.org/publicdomain/zero/1.0/
+
+// null input.
+checkPublicSuffix(null, null);
+// Mixed case.
+checkPublicSuffix('COM', null);
+checkPublicSuffix('example.COM', 'example.com');
+checkPublicSuffix('WwW.example.COM', 'example.com');
+// Leading dot.
+//checkPublicSuffix('.com', null);
+//checkPublicSuffix('.example', null);
+//checkPublicSuffix('.example.com', null);
+//checkPublicSuffix('.example.example', null);
+// Unlisted TLD.
+//checkPublicSuffix('example', null);
+//checkPublicSuffix('example.example', 'example.example');
+//checkPublicSuffix('b.example.example', 'example.example');
+//checkPublicSuffix('a.b.example.example', 'example.example');
+// Listed, but non-Internet, TLD.
+checkPublicSuffix('local', null);
+checkPublicSuffix('example.local', null);
+checkPublicSuffix('b.example.local', null);
+checkPublicSuffix('a.b.example.local', null);
+// TLD with only 1 rule.
+checkPublicSuffix('biz', null);
+checkPublicSuffix('domain.biz', 'domain.biz');
+checkPublicSuffix('b.domain.biz', 'domain.biz');
+checkPublicSuffix('a.b.domain.biz', 'domain.biz');
+// TLD with some 2-level rules.
+checkPublicSuffix('com', null);
+checkPublicSuffix('example.com', 'example.com');
+checkPublicSuffix('b.example.com', 'example.com');
+checkPublicSuffix('a.b.example.com', 'example.com');
+checkPublicSuffix('uk.com', null);
+checkPublicSuffix('example.uk.com', 'example.uk.com');
+checkPublicSuffix('b.example.uk.com', 'example.uk.com');
+checkPublicSuffix('a.b.example.uk.com', 'example.uk.com');
+checkPublicSuffix('test.ac', 'test.ac');
+// TLD with only 1 (wildcard) rule.
+checkPublicSuffix('cy', null);
+checkPublicSuffix('c.cy', null);
+checkPublicSuffix('b.c.cy', 'b.c.cy');
+checkPublicSuffix('a.b.c.cy', 'b.c.cy');
+// More complex TLD.
+checkPublicSuffix('jp', null);
+checkPublicSuffix('test.jp', 'test.jp');
+checkPublicSuffix('www.test.jp', 'test.jp');
+checkPublicSuffix('ac.jp', null);
+checkPublicSuffix('test.ac.jp', 'test.ac.jp');
+checkPublicSuffix('www.test.ac.jp', 'test.ac.jp');
+checkPublicSuffix('kyoto.jp', null);
+checkPublicSuffix('test.kyoto.jp', 'test.kyoto.jp');
+checkPublicSuffix('ide.kyoto.jp', null);
+checkPublicSuffix('b.ide.kyoto.jp', 'b.ide.kyoto.jp');
+checkPublicSuffix('a.b.ide.kyoto.jp', 'b.ide.kyoto.jp');
+checkPublicSuffix('c.kobe.jp', null);
+checkPublicSuffix('b.c.kobe.jp', 'b.c.kobe.jp');
+checkPublicSuffix('a.b.c.kobe.jp', 'b.c.kobe.jp');
+checkPublicSuffix('city.kobe.jp', 'city.kobe.jp');
+checkPublicSuffix('www.city.kobe.jp', 'city.kobe.jp');
+// TLD with a wildcard rule and exceptions.
+checkPublicSuffix('ck', null);
+checkPublicSuffix('test.ck', null);
+checkPublicSuffix('b.test.ck', 'b.test.ck');
+checkPublicSuffix('a.b.test.ck', 'b.test.ck');
+checkPublicSuffix('www.ck', 'www.ck');
+checkPublicSuffix('www.www.ck', 'www.ck');
+// US K12.
+checkPublicSuffix('us', null);
+checkPublicSuffix('test.us', 'test.us');
+checkPublicSuffix('www.test.us', 'test.us');
+checkPublicSuffix('ak.us', null);
+checkPublicSuffix('test.ak.us', 'test.ak.us');
+checkPublicSuffix('www.test.ak.us', 'test.ak.us');
+checkPublicSuffix('k12.ak.us', null);
+checkPublicSuffix('test.k12.ak.us', 'test.k12.ak.us');
+checkPublicSuffix('www.test.k12.ak.us', 'test.k12.ak.us');
+// IDN labels.
+checkPublicSuffix('食狮.com.cn', '食狮.com.cn');
+checkPublicSuffix('食狮.公司.cn', '食狮.公司.cn');
+checkPublicSuffix('www.食狮.公司.cn', '食狮.公司.cn');
+checkPublicSuffix('shishi.公司.cn', 'shishi.公司.cn');
+checkPublicSuffix('公司.cn', null);
+checkPublicSuffix('食狮.中国', '食狮.中国');
+checkPublicSuffix('www.食狮.中国', '食狮.中国');
+checkPublicSuffix('shishi.中国', 'shishi.中国');
+checkPublicSuffix('中国', null);
+// Same as above, but punycoded.
+checkPublicSuffix('xn--85x722f.com.cn', 'xn--85x722f.com.cn');
+checkPublicSuffix('xn--85x722f.xn--55qx5d.cn', 'xn--85x722f.xn--55qx5d.cn');
+checkPublicSuffix('www.xn--85x722f.xn--55qx5d.cn', 'xn--85x722f.xn--55qx5d.cn');
+checkPublicSuffix('shishi.xn--55qx5d.cn', 'shishi.xn--55qx5d.cn');
+checkPublicSuffix('xn--55qx5d.cn', null);
+checkPublicSuffix('xn--85x722f.xn--fiqs8s', 'xn--85x722f.xn--fiqs8s');
+checkPublicSuffix('www.xn--85x722f.xn--fiqs8s', 'xn--85x722f.xn--fiqs8s');
+checkPublicSuffix('shishi.xn--fiqs8s', 'shishi.xn--fiqs8s');
+checkPublicSuffix('xn--fiqs8s', null);
diff --git a/util-testing/org/cacert/gigi/email/CommandlineEmailProvider.java b/util-testing/org/cacert/gigi/email/CommandlineEmailProvider.java
new file mode 100644 (file)
index 0000000..108dd64
--- /dev/null
@@ -0,0 +1,30 @@
+package org.cacert.gigi.email;
+
+import java.io.IOException;
+import java.util.Properties;
+
+public class CommandlineEmailProvider extends EmailProvider {
+
+    public CommandlineEmailProvider(Properties p) {}
+
+    @Override
+    public void sendmail(String to, String subject, String message, String from, String replyto, String toname, String fromname, String errorsto, boolean extra) throws IOException {
+        synchronized (System.out) {
+            System.out.println("== MAIL ==");
+            System.out.println("To: " + to);
+            System.out.println("Subject: " + subject);
+            System.out.println("From: " + from);
+            System.out.println("Errors-To: " + errorsto);
+            System.out.println("Extra: " + extra);
+            System.out.println(message);
+        }
+
+    }
+
+    @Override
+    public String checkEmailServer(int forUid, String address) throws IOException {
+        System.out.println("checkMailBox: " + address);
+        return OK;
+    }
+
+}
diff --git a/util-testing/org/cacert/gigi/email/TestEmailProvider.java b/util-testing/org/cacert/gigi/email/TestEmailProvider.java
new file mode 100644 (file)
index 0000000..7beaa9a
--- /dev/null
@@ -0,0 +1,85 @@
+package org.cacert.gigi.email;
+
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.net.InetAddress;
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.util.Properties;
+
+class TestEmailProvider extends EmailProvider {
+
+    private ServerSocket servs;
+
+    private Socket client;
+
+    private DataOutputStream out;
+
+    private DataInputStream in;
+
+    protected TestEmailProvider(Properties props) {
+        try {
+            servs = new ServerSocket(Integer.parseInt(props.getProperty("emailProvider.port")), 10, InetAddress.getByName("127.0.0.1"));
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+    }
+
+    @Override
+    public synchronized void sendmail(String to, String subject, String message, String from, String replyto, String toname, String fromname, String errorsto, boolean extra) throws IOException {
+        while (true) {
+            assureLocalConnection();
+            try {
+                out.writeUTF("mail");
+                write(to);
+                write(subject);
+                write(message);
+                write(from);
+                write(replyto);
+                out.flush();
+                return;
+            } catch (IOException e) {
+                client = null;
+            }
+        }
+    }
+
+    private void assureLocalConnection() throws IOException {
+        if (out != null) {
+            try {
+                out.writeUTF("ping");
+            } catch (IOException e) {
+                client = null;
+            }
+        }
+        if (client == null || client.isClosed()) {
+            client = servs.accept();
+            out = new DataOutputStream(client.getOutputStream());
+            in = new DataInputStream(client.getInputStream());
+        }
+    }
+
+    @Override
+    public synchronized String checkEmailServer(int forUid, String address) throws IOException {
+        while (true) {
+            assureLocalConnection();
+            try {
+                out.writeUTF("challengeAddrBox");
+                out.writeUTF(address);
+                return in.readUTF();
+            } catch (IOException e) {
+                client = null;
+            }
+        }
+    }
+
+    private void write(String to) throws IOException {
+        if (to == null) {
+            out.writeUTF("<null>");
+        } else {
+            out.writeUTF(to);
+        }
+    }
+
+}
diff --git a/util-testing/org/cacert/gigi/pages/Manager.java b/util-testing/org/cacert/gigi/pages/Manager.java
new file mode 100644 (file)
index 0000000..9f4567e
--- /dev/null
@@ -0,0 +1,268 @@
+package org.cacert.gigi.pages;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.lang.reflect.Field;
+import java.sql.Date;
+import java.util.Calendar;
+import java.util.GregorianCalendar;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Properties;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.cacert.gigi.GigiApiException;
+import org.cacert.gigi.database.DatabaseConnection;
+import org.cacert.gigi.database.GigiPreparedStatement;
+import org.cacert.gigi.dbObjects.EmailAddress;
+import org.cacert.gigi.dbObjects.Group;
+import org.cacert.gigi.dbObjects.User;
+import org.cacert.gigi.email.EmailProvider;
+import org.cacert.gigi.localisation.Language;
+import org.cacert.gigi.output.template.Form;
+import org.cacert.gigi.output.template.IterableDataset;
+import org.cacert.gigi.output.template.Template;
+import org.cacert.gigi.util.Notary;
+
+public class Manager extends Page {
+
+    public static final String PATH = "/manager";
+
+    Field f;
+
+    private Manager() {
+        super("Test Manager");
+        try {
+            f = EmailAddress.class.getDeclaredField("hash");
+            f.setAccessible(true);
+        } catch (ReflectiveOperationException e) {
+            throw new Error(e);
+        }
+    }
+
+    public User[] getAssurers() {
+        if (assurers != null) {
+            return assurers;
+        }
+        assurers = new User[10];
+        try {
+            GigiPreparedStatement ps = DatabaseConnection.getInstance().prepare("INSERT INTO `notary` SET `from`=?, `to`=?, `points`=?, `location`=?, `date`=?");
+            for (int i = 0; i < assurers.length; i++) {
+                String mail = "test-assurer" + i + "@example.com";
+                User u = User.getByEmail(mail);
+                if (u == null) {
+                    createUser(mail);
+                    u = User.getByEmail(mail);
+                    passCATS(u);
+                    ps.setInt(1, u.getId());
+                    ps.setInt(2, u.getId());
+                    ps.setInt(3, 100);
+                    ps.setString(4, "Manager init code");
+                    ps.setString(5, "1990-01-01");
+                    ps.execute();
+                }
+                assurers[i] = u;
+
+            }
+        } catch (ReflectiveOperationException | GigiApiException e) {
+            e.printStackTrace();
+        }
+        return assurers;
+    }
+
+    private void passCATS(User u) {
+        GigiPreparedStatement ps = DatabaseConnection.getInstance().prepare("INSERT INTO cats_passed SET user_id=?, variant_id=3");
+        ps.setInt(1, u.getId());
+        ps.execute();
+    }
+
+    private static Manager instance;
+
+    Template t = new Template(Manager.class.getResource("ManagerMails.templ"));
+
+    HashMap<String, LinkedList<String>> emails = new HashMap<>();
+
+    public static Manager getInstance() {
+        if (instance == null) {
+            instance = new Manager();
+        }
+        return instance;
+    }
+
+    public static class MailFetcher extends EmailProvider {
+
+        public MailFetcher(Properties p) {}
+
+        @Override
+        public String checkEmailServer(int forUid, String address) throws IOException {
+            return OK;
+        }
+
+        @Override
+        public synchronized void sendmail(String to, String subject, String message, String from, String replyto, String toname, String fromname, String errorsto, boolean extra) throws IOException {
+            HashMap<String, LinkedList<String>> mails = Manager.getInstance().emails;
+            LinkedList<String> hismails = mails.get(to);
+            if (hismails == null) {
+                mails.put(to, hismails = new LinkedList<>());
+            }
+            hismails.addFirst(subject + "\n" + message);
+        }
+
+    }
+
+    public class ManagementForm extends Form {
+
+        public ManagementForm(HttpServletRequest hsr) {
+            super(hsr);
+        }
+
+        @Override
+        public boolean submit(PrintWriter out, HttpServletRequest req) throws GigiApiException {
+            return false;
+        }
+
+        @Override
+        protected void outputContent(PrintWriter out, Language l, Map<String, Object> vars) {
+            getDefaultTemplate().output(out, l, vars);
+        }
+
+    }
+
+    public void batchCreateUsers(String mailPrefix, String domain, int amount, PrintWriter out) {
+
+        try {
+            if (amount > 100) {
+                out.print("100 at most, please.");
+                return;
+            }
+            for (int i = 0; i < amount; i++) {
+                String email = mailPrefix + i + "@" + domain;
+                createUser(email);
+            }
+        } catch (ReflectiveOperationException e) {
+            out.println("failed");
+            e.printStackTrace();
+        } catch (GigiApiException e) {
+            out.println("failed: " + e.getMessage());
+            e.printStackTrace();
+        }
+    }
+
+    private void createUser(String email) throws GigiApiException, IllegalAccessException {
+        User u = new User();
+        u.setFName("Först");
+        u.setMName("Müddle");
+        u.setLName("Läst");
+        u.setSuffix("Süffix");
+        u.setEmail(email);
+        Calendar gc = GregorianCalendar.getInstance();
+        gc.set(1990, 0, 1);
+        u.setDoB(new Date(gc.getTime().getTime()));
+        u.setPreferredLocale(Locale.ENGLISH);
+        u.insert("xvXV12°§");
+        EmailAddress ea = new EmailAddress(u, email);
+        ea.insert(Language.getInstance(Locale.ENGLISH));
+        String hash = (String) f.get(ea);
+
+        ea.verify(hash);
+    }
+
+    User[] assurers;
+
+    @Override
+    public void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException {
+        if (req.getParameter("create") != null) {
+            batchCreateUsers(req.getParameter("prefix"), req.getParameter("suffix"), Integer.parseInt(req.getParameter("amount")), resp.getWriter());
+            resp.getWriter().println("User batch created.");
+        } else if (req.getParameter("addpriv") != null || req.getParameter("delpriv") != null) {
+            User u = User.getByEmail(req.getParameter("email"));
+            if (u == null) {
+                resp.getWriter().println("User not found.");
+                return;
+            }
+            if (req.getParameter("addpriv") != null) {
+                u.grantGroup(u, Group.getByString(req.getParameter("priv")));
+                resp.getWriter().println("Privilege granted");
+            } else {
+                u.revokeGroup(u, Group.getByString(req.getParameter("priv")));
+                resp.getWriter().println("Privilege revoked");
+            }
+        } else if (req.getParameter("fetch") != null) {
+            String mail = req.getParameter("femail");
+            fetchMails(req, resp, mail);
+        } else if (req.getParameter("cats") != null) {
+            String mail = req.getParameter("catsEmail");
+            User byEmail = User.getByEmail(mail);
+            if (byEmail == null) {
+                resp.getWriter().println("User not found.");
+                return;
+            }
+            passCATS(byEmail);
+            resp.getWriter().println("User has been passed CATS");
+        } else if (req.getParameter("assure") != null) {
+            String mail = req.getParameter("assureEmail");
+            User byEmail = User.getByEmail(mail);
+            if (byEmail == null) {
+                resp.getWriter().println("User not found.");
+                return;
+            }
+            try {
+                for (int i = 0; i < getAssurers().length; i++) {
+                    Notary.assure(getAssurers()[i], byEmail, byEmail.getName(), byEmail.getDoB(), 10, "Testmanager Assure up code", "2014-11-06");
+                }
+            } catch (GigiApiException e) {
+                throw new Error(e);
+            }
+            resp.getWriter().println("User has been assured.");
+        }
+    }
+
+    private void fetchMails(HttpServletRequest req, HttpServletResponse resp, String mail) throws IOException {
+        final LinkedList<String> mails = emails.get(mail);
+        HashMap<String, Object> vars = new HashMap<>();
+        vars.put("mail", mail);
+        if (mails != null) {
+            vars.put("mails", new IterableDataset() {
+
+                Iterator<String> s = mails.iterator();
+
+                @Override
+                public boolean next(Language l, Map<String, Object> vars) {
+                    if ( !s.hasNext()) {
+                        return false;
+                    }
+                    vars.put("body", s.next().replaceAll("(https?://\\S+)", "<a href=\"$1\">$1</a>"));
+                    return true;
+                }
+            });
+        }
+        t.output(resp.getWriter(), getLanguage(req), vars);
+        if (mails == null) {
+            resp.getWriter().println("No mails");
+
+        }
+    }
+
+    @Override
+    public boolean needsLogin() {
+        return false;
+    }
+
+    @Override
+    public void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
+        getAssurers();
+        String pi = req.getPathInfo().substring(PATH.length());
+        if (pi.length() > 1 && pi.startsWith("/fetch-")) {
+            String mail = pi.substring(pi.indexOf('-', 2) + 1);
+            fetchMails(req, resp, mail);
+            return;
+        }
+
+        new ManagementForm(req).output(resp.getWriter(), getLanguage(req), new HashMap<String, Object>());
+    }
+}
diff --git a/util-testing/org/cacert/gigi/pages/Manager.templ b/util-testing/org/cacert/gigi/pages/Manager.templ
new file mode 100644 (file)
index 0000000..31bedcf
--- /dev/null
@@ -0,0 +1,45 @@
+<div>
+Batch create users:
+<div>
+  Email: 
+  <input type="text" name="prefix"/> NNN@
+  <input type="text" name="suffix"/>
+</div>
+Amount:  <input type="slider" name="amount"/> <input type="submit" name="create" value="Create Users"/>
+</div>
+<div>
+Add privillege:
+Email: <input type="text" name="email"/> 
+<select name="priv">
+<option>supporter</option>
+<option>arbitrator</option>
+<option>blockedassuree</option>
+<option>blockedassurer</option>
+<option>blockedlogin</option>
+<option>ttp-assurer</option>
+<option>ttp-applicant</option>
+<option>codesigning</option>
+<option>orgassurer</option>
+</select>
+<input type="submit" name="addpriv" value="Grant Privillege"/>
+<input type="submit" name="delpriv" value="Revoke Privillege"/>
+</div>
+
+<div>
+Recive Mails:
+Email: <input type="text" name="femail"/>
+<input type="submit" value="Recieve Mails" name="fetch"/>
+</div>
+
+<div>
+Add CATs entry:
+Email: <input type="text" name="catsEmail"/>
+<input type="submit" value="Add CATs" name="cats"/>
+</div>
+
+
+<div>
+Add 100 Assurance points:
+Email: <input type="text" name="assureEmail"/>
+<input type="submit" value="Assure 100 Points" name="assure"/>
+</div>
diff --git a/util-testing/org/cacert/gigi/pages/ManagerMails.templ b/util-testing/org/cacert/gigi/pages/ManagerMails.templ
new file mode 100644 (file)
index 0000000..6298803
--- /dev/null
@@ -0,0 +1,5 @@
+<a href="/manager/fetch-<?=$mail?>"> Quick Link </a><br/>
+
+<? foreach($mails) { ?>
+<pre><?=$!body?></pre>
+<? } ?>
diff --git a/util-testing/org/cacert/gigi/util/SimpleSigner.java b/util-testing/org/cacert/gigi/util/SimpleSigner.java
new file mode 100644 (file)
index 0000000..a9ed464
--- /dev/null
@@ -0,0 +1,355 @@
+package org.cacert.gigi.util;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStreamWriter;
+import java.io.PrintWriter;
+import java.io.Reader;
+import java.math.BigInteger;
+import java.security.GeneralSecurityException;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+import java.sql.SQLException;
+import java.sql.Timestamp;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Properties;
+import java.util.TimeZone;
+
+import org.cacert.gigi.database.DatabaseConnection;
+import org.cacert.gigi.database.GigiPreparedStatement;
+import org.cacert.gigi.database.GigiResultSet;
+import org.cacert.gigi.dbObjects.Certificate;
+import org.cacert.gigi.dbObjects.Certificate.CSRType;
+import org.cacert.gigi.output.DateSelector;
+
+public class SimpleSigner {
+
+    private static GigiPreparedStatement warnMail;
+
+    private static GigiPreparedStatement updateMail;
+
+    private static GigiPreparedStatement readyCerts;
+
+    private static GigiPreparedStatement getSANSs;
+
+    private static GigiPreparedStatement revoke;
+
+    private static GigiPreparedStatement revokeCompleted;
+
+    private static GigiPreparedStatement finishJob;
+
+    private static boolean running = true;
+
+    private static Thread runner;
+
+    private static SimpleDateFormat sdf = new SimpleDateFormat("yyMMddHHmmss'Z'");
+
+    static {
+        TimeZone.setDefault(TimeZone.getTimeZone("UTC"));
+        sdf.setTimeZone(TimeZone.getTimeZone("UTC"));
+    }
+
+    public static void main(String[] args) throws IOException, SQLException, InterruptedException {
+        Properties p = new Properties();
+        try (Reader reader = new InputStreamReader(new FileInputStream("config/gigi.properties"), "UTF-8")) {
+            p.load(reader);
+        }
+        DatabaseConnection.init(p);
+
+        runSigner();
+    }
+
+    public synchronized static void stopSigner() throws InterruptedException {
+        if (runner == null) {
+            throw new IllegalStateException("already stopped");
+        }
+        running = false;
+        runner.interrupt();
+        runner.join();
+        runner = null;
+    }
+
+    public synchronized static void runSigner() throws SQLException, IOException, InterruptedException {
+        if (runner != null) {
+            throw new IllegalStateException("already running");
+        }
+        running = true;
+        readyCerts = DatabaseConnection.getInstance().prepare("SELECT certs.id AS id, certs.csr_name, jobs.id AS jobid, csr_type, md, keyUsage, extendedKeyUsage, executeFrom, executeTo, rootcert FROM jobs " + //
+                "INNER JOIN certs ON certs.id=jobs.targetId " + //
+                "INNER JOIN profiles ON profiles.id=certs.profile " + //
+                "WHERE jobs.state='open' "//
+                + "AND task='sign'");
+
+        getSANSs = DatabaseConnection.getInstance().prepare("SELECT contents, type FROM subjectAlternativeNames " + //
+                "WHERE certId=?");
+
+        updateMail = DatabaseConnection.getInstance().prepare("UPDATE certs SET crt_name=?," + " created=NOW(), serial=? WHERE id=?");
+        warnMail = DatabaseConnection.getInstance().prepare("UPDATE jobs SET warning=warning+1, state=IF(warning<3, 'open','error') WHERE id=?");
+
+        revoke = DatabaseConnection.getInstance().prepare("SELECT certs.id, certs.csr_name,jobs.id FROM jobs INNER JOIN certs ON jobs.targetId=certs.id" + " WHERE jobs.state='open' AND task='revoke'");
+        revokeCompleted = DatabaseConnection.getInstance().prepare("UPDATE certs SET revoked=NOW() WHERE id=?");
+
+        finishJob = DatabaseConnection.getInstance().prepare("UPDATE jobs SET state='done' WHERE id=?");
+
+        runner = new Thread() {
+
+            @Override
+            public void run() {
+                work();
+            }
+
+        };
+        runner.start();
+    }
+
+    private static void work() {
+        try {
+            gencrl();
+        } catch (IOException e2) {
+            e2.printStackTrace();
+        } catch (InterruptedException e2) {
+            e2.printStackTrace();
+        }
+        while (running) {
+            try {
+                signCertificates();
+                revokeCertificates();
+                Thread.sleep(5000);
+            } catch (IOException e) {
+                e.printStackTrace();
+            } catch (SQLException e) {
+                e.printStackTrace();
+            } catch (InterruptedException e1) {
+            }
+        }
+    }
+
+    private static void revokeCertificates() throws SQLException, IOException, InterruptedException {
+        GigiResultSet rs = revoke.executeQuery();
+        boolean worked = false;
+        while (rs.next()) {
+            int id = rs.getInt(1);
+            File crt = KeyStorage.locateCrt(id);
+            String[] call = new String[] {
+                    "openssl", "ca",//
+                    "-cert",
+                    "../unassured.crt",//
+                    "-keyfile",
+                    "../unassured.key",//
+                    "-revoke",
+                    "../../" + crt.getPath(),//
+                    "-batch",//
+                    "-config",
+                    "../selfsign.config"
+
+            };
+            Process p1 = Runtime.getRuntime().exec(call, null, new File("keys/unassured.ca"));
+            System.out.println("revoking: " + crt.getPath());
+            if (p1.waitFor() == 0) {
+                worked = true;
+                revokeCompleted.setInt(1, id);
+                revokeCompleted.execute();
+                finishJob.setInt(1, rs.getInt(3));
+                finishJob.execute();
+            } else {
+                System.out.println("Failed");
+            }
+        }
+        if (worked) {
+            gencrl();
+        }
+    }
+
+    private static void gencrl() throws IOException, InterruptedException {
+        String[] call = new String[] {
+                "openssl", "ca",//
+                "-cert",
+                "../unassured.crt",//
+                "-keyfile",
+                "../unassured.key",//
+                "-gencrl",//
+                "-crlhours",//
+                "12",//
+                "-out",
+                "../unassured.crl",//
+                "-config",
+                "../selfsign.config"
+
+        };
+        Process p1 = Runtime.getRuntime().exec(call, null, new File("keys/unassured.ca"));
+        if (p1.waitFor() != 0) {
+            System.out.println("Error while generating crl.");
+        }
+    }
+
+    private static int counter = 0;
+
+    private static void signCertificates() throws SQLException {
+        GigiResultSet rs = readyCerts.executeQuery();
+
+        Calendar c = Calendar.getInstance();
+        c.setTimeZone(TimeZone.getTimeZone("UTC"));
+
+        while (rs.next()) {
+            String csrname = rs.getString("csr_name");
+            int id = rs.getInt("id");
+            System.out.println("sign: " + csrname);
+            try {
+                String csrType = rs.getString("csr_type");
+                CSRType ct = CSRType.valueOf(csrType);
+                File crt = KeyStorage.locateCrt(id);
+
+                String keyUsage = rs.getString("keyUsage");
+                String ekeyUsage = rs.getString("extendedKeyUsage");
+
+                Timestamp from = rs.getTimestamp("executeFrom");
+                String length = rs.getString("executeTo");
+                Date fromDate;
+                Date toDate;
+                if (from == null) {
+                    fromDate = new Date(System.currentTimeMillis());
+                } else {
+                    fromDate = new Date(from.getTime());
+                }
+                if (length.endsWith("m") || length.endsWith("y")) {
+                    String num = length.substring(0, length.length() - 1);
+                    int inter = Integer.parseInt(num);
+                    c.setTime(fromDate);
+                    if (length.endsWith("m")) {
+                        c.add(Calendar.MONTH, inter);
+                    } else {
+                        c.add(Calendar.YEAR, inter);
+                    }
+                    toDate = c.getTime();
+                } else {
+                    toDate = DateSelector.getDateFormat().parse(length);
+                }
+
+                getSANSs.setInt(1, id);
+                GigiResultSet san = getSANSs.executeQuery();
+
+                File f = new File("keys", "SANFile" + System.currentTimeMillis() + (counter++) + ".cfg");
+                PrintWriter cfg = new PrintWriter(new OutputStreamWriter(new FileOutputStream(f), "UTF-8"));
+                boolean first = true;
+                while (san.next()) {
+                    if ( !first) {
+                        cfg.print(", ");
+                    } else {
+                        cfg.print("subjectAltName=");
+                    }
+                    first = false;
+                    cfg.print(san.getString("type"));
+                    cfg.print(":");
+                    cfg.print(san.getString("contents"));
+                }
+                cfg.println();
+                cfg.println("keyUsage=critical," + keyUsage);
+                cfg.println("extendedKeyUsage=critical," + ekeyUsage);
+                cfg.close();
+
+                int rootcert = rs.getInt("rootcert");
+                String ca = "unassured";
+                if (rootcert == 0) {
+                    ca = "unassured";
+                } else if (rootcert == 1) {
+                    ca = "assured";
+                }
+                HashMap<String, String> subj = new HashMap<>();
+                GigiPreparedStatement ps = DatabaseConnection.getInstance().prepare("SELECT name, value FROM certAvas WHERE certId=?");
+                ps.setInt(1, rs.getInt("id"));
+                GigiResultSet rs2 = ps.executeQuery();
+                while (rs2.next()) {
+                    subj.put(rs2.getString("name"), rs2.getString("value"));
+                }
+                if (subj.size() == 0) {
+                    subj.put("CN", "<empty>");
+                    System.out.println("WARNING: DN was empty");
+                }
+                String[] call;
+                synchronized (sdf) {
+                    call = new String[] {
+                            "openssl", "ca",//
+                            "-in",
+                            "../../" + csrname,//
+                            "-cert",
+                            "../" + ca + ".crt",//
+                            "-keyfile",
+                            "../" + ca + ".key",//
+                            "-out",
+                            "../../" + crt.getPath(),//
+                            "-utf8",
+                            "-startdate",
+                            sdf.format(fromDate),//
+                            "-enddate",
+                            sdf.format(toDate),//
+                            "-batch",//
+                            "-md",
+                            rs.getString("md"),//
+                            "-extfile",
+                            "../" + f.getName(),//
+
+                            "-subj",
+                            Certificate.stringifyDN(subj),//
+                            "-config",
+                            "../selfsign.config"//
+                    };
+                }
+
+                if (ct == CSRType.SPKAC) {
+                    call[2] = "-spkac";
+                }
+
+                Process p1 = Runtime.getRuntime().exec(call, null, new File("keys/unassured.ca"));
+
+                int waitFor = p1.waitFor();
+                if ( !f.delete()) {
+                    System.err.println("Could not delete SAN-File " + f.getAbsolutePath());
+                }
+                if (waitFor == 0) {
+                    try (InputStream is = new FileInputStream(crt)) {
+                        CertificateFactory cf = CertificateFactory.getInstance("X.509");
+                        X509Certificate crtp = (X509Certificate) cf.generateCertificate(is);
+                        BigInteger serial = crtp.getSerialNumber();
+                        updateMail.setString(1, crt.getPath());
+                        updateMail.setString(2, serial.toString(16));
+                        updateMail.setInt(3, id);
+                        updateMail.execute();
+
+                        finishJob.setInt(1, rs.getInt("jobid"));
+                        finishJob.execute();
+                        System.out.println("signed: " + id);
+                        continue;
+                    }
+                } else {
+                    BufferedReader br = new BufferedReader(new InputStreamReader(p1.getErrorStream(), "UTF-8"));
+                    String s;
+                    while ((s = br.readLine()) != null) {
+                        System.out.println(s);
+                    }
+                }
+            } catch (GeneralSecurityException e) {
+                e.printStackTrace();
+            } catch (IOException e) {
+                e.printStackTrace();
+            } catch (ParseException e) {
+                e.printStackTrace();
+            } catch (InterruptedException e1) {
+                e1.printStackTrace();
+            }
+            System.out.println("Error with: " + id);
+            warnMail.setInt(1, rs.getInt("jobid"));
+            warnMail.execute();
+
+        }
+        rs.close();
+    }
+}
diff --git a/util/org/cacert/gigi/util/DatabaseManager.java b/util/org/cacert/gigi/util/DatabaseManager.java
new file mode 100644 (file)
index 0000000..da164a5
--- /dev/null
@@ -0,0 +1,70 @@
+package org.cacert.gigi.util;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.util.Properties;
+
+import org.cacert.gigi.database.DatabaseConnection;
+import org.cacert.gigi.database.SQLFileManager;
+import org.cacert.gigi.database.SQLFileManager.ImportType;
+
+public class DatabaseManager {
+
+    public static void main(String[] args) throws SQLException, ClassNotFoundException, IOException {
+        boolean test = false;
+        if (args.length >= 1 && args[0].equals("--test")) {
+            test = true;
+            String[] ne = new String[args.length - 1];
+            System.arraycopy(args, 1, ne, 0, ne.length);
+            args = ne;
+        }
+        if (args.length == 0) {
+            Properties p = new Properties();
+            try (Reader reader = new InputStreamReader(new FileInputStream("config/gigi.properties"), "UTF-8")) {
+                p.load(reader);
+            }
+            args = new String[] {
+                    p.getProperty("sql.driver"), p.getProperty("sql.url"), p.getProperty("sql.user"), p.getProperty("sql.password")
+            };
+        }
+        if (args.length < 4) {
+            System.err.println("Usage: com.mysql.jdbc.Driver jdbc:mysql://localhost/cacert user password");
+            return;
+        }
+        run(args, test ? ImportType.TEST : ImportType.PRODUCTION);
+    }
+
+    public static void run(String[] args, ImportType truncate) throws ClassNotFoundException, SQLException, IOException {
+        Class.forName(args[0]);
+        final Connection conn = DriverManager.getConnection(args[1], args[2], args[3]);
+        try {
+            conn.setAutoCommit(false);
+            Statement stmt = conn.createStatement();
+            try {
+                try (InputStream structure = DatabaseConnection.class.getResourceAsStream("tableStructure.sql")) {
+                    SQLFileManager.addFile(stmt, structure, truncate);
+                }
+                File localData = new File("doc/sampleData.sql");
+                if (localData.exists()) {
+                    try (FileInputStream f = new FileInputStream(localData)) {
+                        SQLFileManager.addFile(stmt, f, ImportType.PRODUCTION);
+                    }
+                }
+                stmt.executeBatch();
+                conn.commit();
+            } finally {
+                stmt.close();
+            }
+        } finally {
+            conn.close();
+        }
+    }
+}
diff --git a/util/org/cacert/gigi/util/FetchLocales.java b/util/org/cacert/gigi/util/FetchLocales.java
new file mode 100644 (file)
index 0000000..aed8973
--- /dev/null
@@ -0,0 +1,122 @@
+package org.cacert.gigi.util;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.net.URL;
+import java.net.URLConnection;
+import java.util.Scanner;
+
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.transform.OutputKeys;
+import javax.xml.transform.Transformer;
+import javax.xml.transform.TransformerException;
+import javax.xml.transform.TransformerFactory;
+import javax.xml.transform.dom.DOMSource;
+import javax.xml.transform.stream.StreamResult;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+
+public class FetchLocales {
+
+    public static final String DOWNLOAD_SERVER = "translations.cacert.org";
+
+    public static final String PO_URL_TEMPLATE = "http://" + DOWNLOAD_SERVER + "/export/cacert/%/messages.po";
+
+    public static final String[] AUTO_LANGS = new String[] {
+            "en", "de", "nl", "pt_BR", "fr", "sv", "it", "es", "hu", "fi", "ja", "bg", "pt", "da", "pl", "zh_CN", "ru", "lv", "cs", "zh_TW", "el", "tr", "ar"
+    };
+
+    public static void main(String[] args) throws IOException, ParserConfigurationException, TransformerException {
+        System.out.println("downloading locales ...");
+        File locale = new File("locale");
+        if ( !locale.isDirectory() && !locale.mkdir()) {
+            throw new IOException("Could not create locales directory.");
+        }
+
+        DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
+        DocumentBuilder db = dbf.newDocumentBuilder();
+        for (String lang : AUTO_LANGS) {
+            Document doc = db.newDocument();
+            doc.appendChild(doc.createElement("translations"));
+            URL fetch = new URL(PO_URL_TEMPLATE.replace("%", lang));
+            URLConnection uc = fetch.openConnection();
+            Scanner sc = new Scanner(new InputStreamReader(uc.getInputStream(), "UTF-8"));
+            String s = readLine(sc);
+            StringBuffer contents = new StringBuffer();
+            String id = "";
+            while (s != null) {
+                if (s.startsWith("msgid")) {
+                    contents.delete(0, contents.length());
+                    s = readString(s, sc, contents);
+                    id = contents.toString();
+                    continue;
+                } else if (s.startsWith("msgstr")) {
+                    contents.delete(0, contents.length());
+                    // System.out.println("msgstr");
+                    s = readString(s, sc, contents);
+                    String msg = contents.toString().replace("\\\"", "\"").replace("\\n", "\n");
+                    insertTranslation(doc, id, msg);
+                } else if (s.startsWith("#")) {
+                    // System.out.println(s);
+                } else if (s.equals("") || s.equals("\r")) {
+
+                } else {
+                    System.out.println("unknown line: " + s);
+                }
+                s = readLine(sc);
+            }
+            TransformerFactory tFactory = TransformerFactory.newInstance();
+            Transformer transformer = tFactory.newTransformer();
+
+            DOMSource source = new DOMSource(doc);
+            FileOutputStream fos = new FileOutputStream(new File(locale, lang + ".xml"));
+            StreamResult result = new StreamResult(fos);
+            transformer.setOutputProperty(OutputKeys.INDENT, "yes");
+            transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2");
+            transformer.transform(source, result);
+            fos.close();
+        }
+        System.out.println("Done.");
+    }
+
+    private static String readLine(Scanner sc) {
+        String line = sc.findWithinHorizon("[^\n]*\n", 0);
+        if (line == null) {
+            return null;
+        }
+        return line.substring(0, line.length() - 1);
+    }
+
+    private static void insertTranslation(Document doc, String id, String msg) {
+        Node idN = doc.createTextNode(id);
+        Node textN = doc.createTextNode(msg);
+        Element tr = doc.createElement("translation");
+        Element e = doc.createElement("id");
+        e.appendChild(idN);
+        tr.appendChild(e);
+        e = doc.createElement("msg");
+        e.appendChild(textN);
+        tr.appendChild(e);
+        doc.getDocumentElement().appendChild(tr);
+    }
+
+    private static String readString(String head, Scanner sc, StringBuffer contents) throws IOException {
+        head = head.split(" ", 2)[1];
+        contents.append(head.substring(1, head.length() - 1));
+        String s;
+        while ((s = readLine(sc)) != null) {
+            if ( !s.startsWith("\"")) {
+                break;
+            }
+            contents.append(s.substring(1, s.length() - 1));
+        }
+        return s;
+    }
+
+}