From 73ef54a38e3930a1a789cdc6b5fa23cdd4c9d086 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Felix=20D=C3=B6rre?= Date: Thu, 19 Jun 2014 19:22:50 +0200 Subject: [PATCH] Importing upstream Jetty jetty-9.2.1.v20140609 Imported packages: * http * io * security * server * servlet * util Removed for compilance: * Packages: - org.eclipse.jetty.server.session.jmx - org.eclipse.jetty.server.handler.jmx - org.eclipse.jetty.server.jmx - org.eclipse.jetty.servlet.jmx * Classes: - org.eclipse.jetty.util.log.JettyAwareLogger - org.eclipse.jetty.util.log.Slf4jLog - org.eclipse.jetty.server.Slf4jRequestLog --- .classpath | 1 + .../org/eclipse/jetty/http/DateGenerator.java | 164 + .../org/eclipse/jetty/http/DateParser.java | 109 + .../org/eclipse/jetty/http/HttpContent.java | 172 + .../org/eclipse/jetty/http/HttpCookie.java | 164 + .../org/eclipse/jetty/http/HttpField.java | 89 + .../org/eclipse/jetty/http/HttpFields.java | 784 +++++ .../org/eclipse/jetty/http/HttpGenerator.java | 1104 +++++++ .../org/eclipse/jetty/http/HttpHeader.java | 178 ++ .../eclipse/jetty/http/HttpHeaderValue.java | 104 + .../org/eclipse/jetty/http/HttpMethod.java | 174 + .../org/eclipse/jetty/http/HttpParser.java | 1688 ++++++++++ .../org/eclipse/jetty/http/HttpScheme.java | 79 + .../org/eclipse/jetty/http/HttpStatus.java | 1037 ++++++ .../org/eclipse/jetty/http/HttpTester.java | 367 +++ .../org/eclipse/jetty/http/HttpTokens.java | 38 + lib/jetty/org/eclipse/jetty/http/HttpURI.java | 784 +++++ .../org/eclipse/jetty/http/HttpVersion.java | 172 + .../org/eclipse/jetty/http/MimeTypes.java | 485 +++ lib/jetty/org/eclipse/jetty/http/PathMap.java | 572 ++++ .../eclipse/jetty/http/encoding.properties | 4 + .../org/eclipse/jetty/http/mime.properties | 183 ++ .../org/eclipse/jetty/http/package-info.java | 23 + lib/jetty/org/eclipse/jetty/http/useragents | 0 .../eclipse/jetty/io/AbstractConnection.java | 587 ++++ .../eclipse/jetty/io/AbstractEndPoint.java | 180 ++ .../eclipse/jetty/io/ArrayByteBufferPool.java | 133 + .../eclipse/jetty/io/ByteArrayEndPoint.java | 409 +++ .../org/eclipse/jetty/io/ByteBufferPool.java | 51 + .../org/eclipse/jetty/io/ChannelEndPoint.java | 229 ++ .../jetty/io/ClientConnectionFactory.java | 89 + .../org/eclipse/jetty/io/Connection.java | 89 + lib/jetty/org/eclipse/jetty/io/EndPoint.java | 243 ++ .../org/eclipse/jetty/io/EofException.java | 46 + .../org/eclipse/jetty/io/FillInterest.java | 135 + .../org/eclipse/jetty/io/IdleTimeout.java | 182 ++ .../jetty/io/LeakTrackingByteBufferPool.java | 73 + .../jetty/io/MappedByteBufferPool.java | 111 + .../jetty/io/NegotiatingClientConnection.java | 128 + .../NegotiatingClientConnectionFactory.java | 35 + .../jetty/io/NetworkTrafficListener.java | 100 + .../NetworkTrafficSelectChannelEndPoint.java | 155 + .../eclipse/jetty/io/RuntimeIOException.java | 48 + .../jetty/io/SelectChannelEndPoint.java | 207 ++ .../org/eclipse/jetty/io/SelectorManager.java | 984 ++++++ .../jetty/io/UncheckedPrintWriter.java | 682 ++++ .../org/eclipse/jetty/io/WriteFlusher.java | 504 +++ .../eclipse/jetty/io/WriterOutputStream.java | 101 + .../org/eclipse/jetty/io/package-info.java | 23 + .../io/ssl/SslClientConnectionFactory.java | 73 + .../eclipse/jetty/io/ssl/SslConnection.java | 915 ++++++ .../eclipse/jetty/io/ssl/package-info.java | 23 + .../security/AbstractUserAuthentication.java | 98 + .../eclipse/jetty/security/Authenticator.java | 138 + .../jetty/security/ConstraintAware.java | 70 + .../jetty/security/ConstraintMapping.java | 100 + .../security/ConstraintSecurityHandler.java | 928 ++++++ .../security/CrossContextPsuedoSession.java | 36 + .../security/DefaultAuthenticatorFactory.java | 96 + .../security/DefaultIdentityService.java | 90 + .../jetty/security/DefaultUserIdentity.java | 81 + .../HashCrossContextPsuedoSession.java | 99 + .../jetty/security/HashLoginService.java | 184 ++ .../jetty/security/IdentityService.java | 95 + .../jetty/security/JDBCLoginService.java | 297 ++ .../eclipse/jetty/security/LoginService.java | 72 + .../jetty/security/MappedLoginService.java | 343 ++ .../jetty/security/PropertyUserStore.java | 356 +++ .../org/eclipse/jetty/security/RoleInfo.java | 162 + .../jetty/security/RoleRunAsToken.java | 44 + .../eclipse/jetty/security/RunAsToken.java | 27 + .../jetty/security/SecurityHandler.java | 708 +++++ .../jetty/security/ServerAuthException.java | 47 + .../jetty/security/SpnegoLoginService.java | 191 ++ .../jetty/security/SpnegoUserIdentity.java | 57 + .../jetty/security/SpnegoUserPrincipal.java | 65 + .../jetty/security/UserAuthentication.java | 48 + .../jetty/security/UserDataConstraint.java | 40 + .../authentication/BasicAuthenticator.java | 121 + .../ClientCertAuthenticator.java | 371 +++ .../DeferredAuthentication.java | 395 +++ .../authentication/DigestAuthenticator.java | 421 +++ .../authentication/FormAuthenticator.java | 564 ++++ .../authentication/LoginAuthenticator.java | 133 + .../authentication/LoginCallback.java | 55 + .../authentication/LoginCallbackImpl.java | 109 + .../authentication/SessionAuthentication.java | 131 + .../authentication/SpnegoAuthenticator.java | 116 + .../security/authentication/package-info.java | 23 + .../eclipse/jetty/security/package-info.java | 23 + .../server/AbstractConnectionFactory.java | 92 + .../jetty/server/AbstractConnector.java | 564 ++++ .../jetty/server/AbstractNCSARequestLog.java | 483 +++ .../server/AbstractNetworkConnector.java | 125 + .../jetty/server/AsyncContextEvent.java | 164 + .../jetty/server/AsyncContextState.java | 185 ++ .../jetty/server/AsyncNCSARequestLog.java | 129 + .../eclipse/jetty/server/Authentication.java | 164 + .../server/ByteBufferQueuedHttpInput.java | 53 + .../eclipse/jetty/server/ClassLoaderDump.java | 66 + .../jetty/server/ConnectionFactory.java | 57 + .../org/eclipse/jetty/server/Connector.java | 105 + .../jetty/server/ConnectorStatistics.java | 308 ++ .../eclipse/jetty/server/CookieCutter.java | 328 ++ .../org/eclipse/jetty/server/Dispatcher.java | 456 +++ .../jetty/server/EncodingHttpWriter.java | 70 + .../server/ForwardedRequestCustomizer.java | 291 ++ .../org/eclipse/jetty/server/Handler.java | 79 + .../jetty/server/HandlerContainer.java | 62 + .../jetty/server/HostHeaderCustomizer.java | 73 + .../org/eclipse/jetty/server/HttpChannel.java | 860 +++++ .../jetty/server/HttpChannelState.java | 714 +++++ .../jetty/server/HttpConfiguration.java | 269 ++ .../eclipse/jetty/server/HttpConnection.java | 790 +++++ .../jetty/server/HttpConnectionFactory.java | 64 + .../org/eclipse/jetty/server/HttpInput.java | 503 +++ .../jetty/server/HttpInputOverHTTP.java | 145 + .../org/eclipse/jetty/server/HttpOutput.java | 1096 +++++++ .../eclipse/jetty/server/HttpTransport.java | 40 + .../org/eclipse/jetty/server/HttpWriter.java | 80 + .../jetty/server/InclusiveByteRange.java | 230 ++ .../jetty/server/Iso88591HttpWriter.java | 75 + .../eclipse/jetty/server/LocalConnector.java | 265 ++ .../jetty/server/LowResourceMonitor.java | 350 ++ .../eclipse/jetty/server/NCSARequestLog.java | 281 ++ .../server/NegotiatingServerConnection.java | 163 + .../NegotiatingServerConnectionFactory.java | 103 + .../jetty/server/NetworkConnector.java | 72 + .../server/NetworkTrafficServerConnector.java | 92 + .../eclipse/jetty/server/QueuedHttpInput.java | 143 + .../jetty/server/QuietServletException.java | 53 + .../org/eclipse/jetty/server/Request.java | 2265 +++++++++++++ .../org/eclipse/jetty/server/RequestLog.java | 30 + .../eclipse/jetty/server/ResourceCache.java | 511 +++ .../org/eclipse/jetty/server/Response.java | 1384 ++++++++ .../jetty/server/SecureRequestCustomizer.java | 168 + .../org/eclipse/jetty/server/Server.java | 680 ++++ .../eclipse/jetty/server/ServerConnector.java | 483 +++ .../server/ServletRequestHttpWrapper.java | 238 ++ .../server/ServletResponseHttpWrapper.java | 148 + .../jetty/server/SessionIdManager.java | 94 + .../eclipse/jetty/server/SessionManager.java | 317 ++ .../eclipse/jetty/server/ShutdownMonitor.java | 411 +++ .../jetty/server/SslConnectionFactory.java | 102 + .../eclipse/jetty/server/UserIdentity.java | 117 + .../eclipse/jetty/server/Utf8HttpWriter.java | 188 ++ .../jetty/server/handler/AbstractHandler.java | 108 + .../handler/AbstractHandlerContainer.java | 118 + .../handler/AllowSymLinkAliasChecker.java | 95 + .../jetty/server/handler/ContextHandler.java | 2807 +++++++++++++++++ .../handler/ContextHandlerCollection.java | 267 ++ .../jetty/server/handler/DebugHandler.java | 184 ++ .../jetty/server/handler/DefaultHandler.java | 206 ++ .../jetty/server/handler/ErrorHandler.java | 319 ++ .../server/handler/HandlerCollection.java | 188 ++ .../jetty/server/handler/HandlerList.java | 58 + .../jetty/server/handler/HandlerWrapper.java | 142 + .../jetty/server/handler/HotSwapHandler.java | 160 + .../jetty/server/handler/IPAccessHandler.java | 385 +++ .../server/handler/IdleTimeoutHandler.java | 134 + .../server/handler/MovedContextHandler.java | 154 + .../server/handler/RequestLogHandler.java | 153 + .../jetty/server/handler/ResourceHandler.java | 613 ++++ .../jetty/server/handler/ScopedHandler.java | 200 ++ .../jetty/server/handler/ShutdownHandler.java | 266 ++ .../server/handler/StatisticsHandler.java | 571 ++++ .../jetty/server/handler/package-info.java | 23 + .../NetworkTrafficSelectChannelConnector.java | 60 + .../jetty/server/nio/package-info.java | 23 + .../eclipse/jetty/server/package-info.java | 23 + .../jetty/server/session/AbstractSession.java | 650 ++++ .../session/AbstractSessionIdManager.java | 279 ++ .../session/AbstractSessionManager.java | 1052 ++++++ .../server/session/HashSessionIdManager.java | 231 ++ .../server/session/HashSessionManager.java | 679 ++++ .../jetty/server/session/HashedSession.java | 286 ++ .../server/session/JDBCSessionIdManager.java | 1496 +++++++++ .../server/session/JDBCSessionManager.java | 1201 +++++++ .../jetty/server/session/MemSession.java | 146 + .../jetty/server/session/SessionHandler.java | 346 ++ .../jetty/server/session/package-info.java | 23 + .../org/eclipse/jetty/servlet/BaseHolder.java | 209 ++ .../eclipse/jetty/servlet/DefaultServlet.java | 1100 +++++++ .../jetty/servlet/ErrorPageErrorHandler.java | 229 ++ .../eclipse/jetty/servlet/FilterHolder.java | 289 ++ .../eclipse/jetty/servlet/FilterMapping.java | 294 ++ .../org/eclipse/jetty/servlet/Holder.java | 318 ++ .../org/eclipse/jetty/servlet/Invoker.java | 314 ++ .../servlet/JspPropertyGroupServlet.java | 144 + .../eclipse/jetty/servlet/ListenerHolder.java | 65 + .../eclipse/jetty/servlet/NoJspServlet.java | 45 + .../jetty/servlet/ServletContextHandler.java | 1367 ++++++++ .../eclipse/jetty/servlet/ServletHandler.java | 1807 +++++++++++ .../eclipse/jetty/servlet/ServletHolder.java | 1124 +++++++ .../eclipse/jetty/servlet/ServletMapping.java | 119 + .../eclipse/jetty/servlet/ServletTester.java | 231 ++ .../jetty/servlet/StatisticsServlet.java | 290 ++ .../servlet/listener/ELContextCleaner.java | 126 + .../servlet/listener/IntrospectorCleaner.java | 45 + .../jetty/servlet/listener/package-info.java | 23 + .../eclipse/jetty/servlet/package-info.java | 23 + .../org/eclipse/jetty/util/AbstractTrie.java | 85 + .../org/eclipse/jetty/util/ArrayQueue.java | 408 +++ .../eclipse/jetty/util/ArrayTernaryTrie.java | 473 +++ .../org/eclipse/jetty/util/ArrayTrie.java | 445 +++ .../org/eclipse/jetty/util/ArrayUtil.java | 142 + lib/jetty/org/eclipse/jetty/util/Atomics.java | 73 + .../org/eclipse/jetty/util/Attributes.java | 36 + .../org/eclipse/jetty/util/AttributesMap.java | 151 + lib/jetty/org/eclipse/jetty/util/B64Code.java | 464 +++ .../jetty/util/BlockingArrayQueue.java | 880 ++++++ .../eclipse/jetty/util/BlockingCallback.java | 105 + .../org/eclipse/jetty/util/BufferUtil.java | 1045 ++++++ .../jetty/util/ByteArrayISO8859Writer.java | 269 ++ .../jetty/util/ByteArrayOutputStream2.java | 54 + .../org/eclipse/jetty/util/Callback.java | 79 + .../util/ClassLoadingObjectInputStream.java | 105 + .../jetty/util/CompletableCallback.java | 100 + .../jetty/util/ConcurrentArrayQueue.java | 570 ++++ .../eclipse/jetty/util/ConcurrentHashSet.java | 126 + .../org/eclipse/jetty/util/DateCache.java | 268 ++ lib/jetty/org/eclipse/jetty/util/Fields.java | 336 ++ .../org/eclipse/jetty/util/ForkInvoker.java | 135 + .../eclipse/jetty/util/FutureCallback.java | 157 + .../org/eclipse/jetty/util/FuturePromise.java | 159 + lib/jetty/org/eclipse/jetty/util/HostMap.java | 108 + .../eclipse/jetty/util/HttpCookieStore.java | 114 + lib/jetty/org/eclipse/jetty/util/IO.java | 499 +++ .../org/eclipse/jetty/util/IPAddressMap.java | 364 +++ .../eclipse/jetty/util/IntrospectionUtil.java | 300 ++ .../eclipse/jetty/util/IteratingCallback.java | 374 +++ .../jetty/util/IteratingNestedCallback.java | 68 + lib/jetty/org/eclipse/jetty/util/Jetty.java | 39 + .../org/eclipse/jetty/util/LazyList.java | 451 +++ .../org/eclipse/jetty/util/LeakDetector.java | 201 ++ lib/jetty/org/eclipse/jetty/util/Loader.java | 183 ++ .../org/eclipse/jetty/util/MemoryUtils.java | 71 + .../eclipse/jetty/util/MultiException.java | 214 ++ .../org/eclipse/jetty/util/MultiMap.java | 373 +++ .../util/MultiPartInputStreamParser.java | 835 +++++ .../jetty/util/MultiPartOutputStream.java | 152 + .../eclipse/jetty/util/MultiPartWriter.java | 144 + .../eclipse/jetty/util/PatternMatcher.java | 104 + lib/jetty/org/eclipse/jetty/util/Promise.java | 65 + .../jetty/util/QuotedStringTokenizer.java | 609 ++++ .../jetty/util/ReadLineInputStream.java | 137 + .../jetty/util/RolloverFileOutputStream.java | 340 ++ lib/jetty/org/eclipse/jetty/util/Scanner.java | 736 +++++ .../jetty/util/SharedBlockingCallback.java | 272 ++ .../jetty/util/SocketAddressResolver.java | 175 + .../org/eclipse/jetty/util/StringUtil.java | 735 +++++ .../org/eclipse/jetty/util/TreeTrie.java | 349 ++ lib/jetty/org/eclipse/jetty/util/Trie.java | 124 + .../org/eclipse/jetty/util/TypeUtil.java | 647 ++++ lib/jetty/org/eclipse/jetty/util/URIUtil.java | 694 ++++ .../org/eclipse/jetty/util/UrlEncoded.java | 1064 +++++++ .../eclipse/jetty/util/Utf8Appendable.java | 256 ++ .../eclipse/jetty/util/Utf8LineParser.java | 101 + .../eclipse/jetty/util/Utf8StringBuffer.java | 75 + .../eclipse/jetty/util/Utf8StringBuilder.java | 78 + .../util/annotation/ManagedAttribute.java | 82 + .../jetty/util/annotation/ManagedObject.java | 45 + .../util/annotation/ManagedOperation.java | 60 + .../eclipse/jetty/util/annotation/Name.java | 49 + .../jetty/util/annotation/package-info.java | 23 + .../util/component/AbstractLifeCycle.java | 234 ++ .../jetty/util/component/Container.java | 92 + .../util/component/ContainerLifeCycle.java | 811 +++++ .../jetty/util/component/Destroyable.java | 31 + .../jetty/util/component/Dumpable.java | 33 + .../jetty/util/component/FileDestroyable.java | 95 + .../FileNoticeLifeCycleListener.java | 79 + .../jetty/util/component/Graceful.java | 29 + .../jetty/util/component/LifeCycle.java | 125 + .../jetty/util/component/package-info.java | 23 + .../jetty/util/log/AbstractLogger.java | 84 + .../eclipse/jetty/util/log/JavaUtilLog.java | 172 + lib/jetty/org/eclipse/jetty/util/log/Log.java | 317 ++ .../org/eclipse/jetty/util/log/Logger.java | 122 + .../org/eclipse/jetty/util/log/LoggerLog.java | 229 ++ .../jetty/util/log/StacklessLogging.java | 69 + .../org/eclipse/jetty/util/log/StdErrLog.java | 779 +++++ .../eclipse/jetty/util/log/package-info.java | 23 + .../org/eclipse/jetty/util/package-info.java | 23 + .../util/preventers/AWTLeakPreventer.java | 47 + .../preventers/AbstractLeakPreventer.java | 62 + .../preventers/AppContextLeakPreventer.java | 41 + .../util/preventers/DOMLeakPreventer.java | 56 + .../DriverManagerLeakPreventer.java | 42 + .../preventers/GCThreadLeakPreventer.java | 64 + .../util/preventers/Java2DLeakPreventer.java | 49 + .../util/preventers/LDAPLeakPreventer.java | 51 + .../LoginConfigurationLeakPreventer.java | 49 + .../SecurityProviderLeakPreventer.java | 44 + .../jetty/util/preventers/package-info.java | 23 + .../jetty/util/resource/BadResource.java | 130 + .../jetty/util/resource/EmptyResource.java | 130 + .../jetty/util/resource/FileResource.java | 429 +++ .../jetty/util/resource/JarFileResource.java | 412 +++ .../jetty/util/resource/JarResource.java | 269 ++ .../eclipse/jetty/util/resource/Resource.java | 709 +++++ .../util/resource/ResourceCollection.java | 493 +++ .../jetty/util/resource/ResourceFactory.java | 34 + .../jetty/util/resource/URLResource.java | 316 ++ .../jetty/util/resource/package-info.java | 23 + .../jetty/util/security/CertificateUtils.java | 94 + .../util/security/CertificateValidator.java | 343 ++ .../jetty/util/security/Constraint.java | 254 ++ .../jetty/util/security/Credential.java | 229 ++ .../eclipse/jetty/util/security/Password.java | 267 ++ .../jetty/util/security/UnixCrypt.java | 461 +++ .../jetty/util/security/package-info.java | 23 + .../ssl/AliasedX509ExtendedKeyManager.java | 130 + .../jetty/util/ssl/AliasedX509KeyManager.java | 107 + .../jetty/util/ssl/SslContextFactory.java | 1484 +++++++++ .../eclipse/jetty/util/ssl/package-info.java | 23 + .../util/statistic/CounterStatistic.java | 118 + .../jetty/util/statistic/SampleStatistic.java | 116 + .../jetty/util/statistic/package-info.java | 23 + .../jetty/util/thread/ExecutorThreadPool.java | 192 ++ .../jetty/util/thread/NonBlockingThread.java | 59 + .../jetty/util/thread/QueuedThreadPool.java | 666 ++++ .../thread/ScheduledExecutorScheduler.java | 100 + .../eclipse/jetty/util/thread/Scheduler.java | 33 + .../jetty/util/thread/ShutdownThread.java | 148 + .../eclipse/jetty/util/thread/ThreadPool.java | 74 + .../jetty/util/thread/TimerScheduler.java | 129 + .../jetty/util/thread/package-info.java | 23 + 328 files changed, 91044 insertions(+) create mode 100644 lib/jetty/org/eclipse/jetty/http/DateGenerator.java create mode 100644 lib/jetty/org/eclipse/jetty/http/DateParser.java create mode 100644 lib/jetty/org/eclipse/jetty/http/HttpContent.java create mode 100644 lib/jetty/org/eclipse/jetty/http/HttpCookie.java create mode 100644 lib/jetty/org/eclipse/jetty/http/HttpField.java create mode 100644 lib/jetty/org/eclipse/jetty/http/HttpFields.java create mode 100644 lib/jetty/org/eclipse/jetty/http/HttpGenerator.java create mode 100644 lib/jetty/org/eclipse/jetty/http/HttpHeader.java create mode 100644 lib/jetty/org/eclipse/jetty/http/HttpHeaderValue.java create mode 100644 lib/jetty/org/eclipse/jetty/http/HttpMethod.java create mode 100644 lib/jetty/org/eclipse/jetty/http/HttpParser.java create mode 100644 lib/jetty/org/eclipse/jetty/http/HttpScheme.java create mode 100644 lib/jetty/org/eclipse/jetty/http/HttpStatus.java create mode 100644 lib/jetty/org/eclipse/jetty/http/HttpTester.java create mode 100644 lib/jetty/org/eclipse/jetty/http/HttpTokens.java create mode 100644 lib/jetty/org/eclipse/jetty/http/HttpURI.java create mode 100644 lib/jetty/org/eclipse/jetty/http/HttpVersion.java create mode 100644 lib/jetty/org/eclipse/jetty/http/MimeTypes.java create mode 100644 lib/jetty/org/eclipse/jetty/http/PathMap.java create mode 100644 lib/jetty/org/eclipse/jetty/http/encoding.properties create mode 100644 lib/jetty/org/eclipse/jetty/http/mime.properties create mode 100644 lib/jetty/org/eclipse/jetty/http/package-info.java create mode 100644 lib/jetty/org/eclipse/jetty/http/useragents create mode 100644 lib/jetty/org/eclipse/jetty/io/AbstractConnection.java create mode 100644 lib/jetty/org/eclipse/jetty/io/AbstractEndPoint.java create mode 100644 lib/jetty/org/eclipse/jetty/io/ArrayByteBufferPool.java create mode 100644 lib/jetty/org/eclipse/jetty/io/ByteArrayEndPoint.java create mode 100644 lib/jetty/org/eclipse/jetty/io/ByteBufferPool.java create mode 100644 lib/jetty/org/eclipse/jetty/io/ChannelEndPoint.java create mode 100644 lib/jetty/org/eclipse/jetty/io/ClientConnectionFactory.java create mode 100644 lib/jetty/org/eclipse/jetty/io/Connection.java create mode 100644 lib/jetty/org/eclipse/jetty/io/EndPoint.java create mode 100644 lib/jetty/org/eclipse/jetty/io/EofException.java create mode 100644 lib/jetty/org/eclipse/jetty/io/FillInterest.java create mode 100644 lib/jetty/org/eclipse/jetty/io/IdleTimeout.java create mode 100644 lib/jetty/org/eclipse/jetty/io/LeakTrackingByteBufferPool.java create mode 100644 lib/jetty/org/eclipse/jetty/io/MappedByteBufferPool.java create mode 100644 lib/jetty/org/eclipse/jetty/io/NegotiatingClientConnection.java create mode 100644 lib/jetty/org/eclipse/jetty/io/NegotiatingClientConnectionFactory.java create mode 100644 lib/jetty/org/eclipse/jetty/io/NetworkTrafficListener.java create mode 100644 lib/jetty/org/eclipse/jetty/io/NetworkTrafficSelectChannelEndPoint.java create mode 100644 lib/jetty/org/eclipse/jetty/io/RuntimeIOException.java create mode 100644 lib/jetty/org/eclipse/jetty/io/SelectChannelEndPoint.java create mode 100644 lib/jetty/org/eclipse/jetty/io/SelectorManager.java create mode 100644 lib/jetty/org/eclipse/jetty/io/UncheckedPrintWriter.java create mode 100644 lib/jetty/org/eclipse/jetty/io/WriteFlusher.java create mode 100644 lib/jetty/org/eclipse/jetty/io/WriterOutputStream.java create mode 100644 lib/jetty/org/eclipse/jetty/io/package-info.java create mode 100644 lib/jetty/org/eclipse/jetty/io/ssl/SslClientConnectionFactory.java create mode 100644 lib/jetty/org/eclipse/jetty/io/ssl/SslConnection.java create mode 100644 lib/jetty/org/eclipse/jetty/io/ssl/package-info.java create mode 100644 lib/jetty/org/eclipse/jetty/security/AbstractUserAuthentication.java create mode 100644 lib/jetty/org/eclipse/jetty/security/Authenticator.java create mode 100644 lib/jetty/org/eclipse/jetty/security/ConstraintAware.java create mode 100644 lib/jetty/org/eclipse/jetty/security/ConstraintMapping.java create mode 100644 lib/jetty/org/eclipse/jetty/security/ConstraintSecurityHandler.java create mode 100644 lib/jetty/org/eclipse/jetty/security/CrossContextPsuedoSession.java create mode 100644 lib/jetty/org/eclipse/jetty/security/DefaultAuthenticatorFactory.java create mode 100644 lib/jetty/org/eclipse/jetty/security/DefaultIdentityService.java create mode 100644 lib/jetty/org/eclipse/jetty/security/DefaultUserIdentity.java create mode 100644 lib/jetty/org/eclipse/jetty/security/HashCrossContextPsuedoSession.java create mode 100644 lib/jetty/org/eclipse/jetty/security/HashLoginService.java create mode 100644 lib/jetty/org/eclipse/jetty/security/IdentityService.java create mode 100644 lib/jetty/org/eclipse/jetty/security/JDBCLoginService.java create mode 100644 lib/jetty/org/eclipse/jetty/security/LoginService.java create mode 100644 lib/jetty/org/eclipse/jetty/security/MappedLoginService.java create mode 100644 lib/jetty/org/eclipse/jetty/security/PropertyUserStore.java create mode 100644 lib/jetty/org/eclipse/jetty/security/RoleInfo.java create mode 100644 lib/jetty/org/eclipse/jetty/security/RoleRunAsToken.java create mode 100644 lib/jetty/org/eclipse/jetty/security/RunAsToken.java create mode 100644 lib/jetty/org/eclipse/jetty/security/SecurityHandler.java create mode 100644 lib/jetty/org/eclipse/jetty/security/ServerAuthException.java create mode 100644 lib/jetty/org/eclipse/jetty/security/SpnegoLoginService.java create mode 100644 lib/jetty/org/eclipse/jetty/security/SpnegoUserIdentity.java create mode 100644 lib/jetty/org/eclipse/jetty/security/SpnegoUserPrincipal.java create mode 100644 lib/jetty/org/eclipse/jetty/security/UserAuthentication.java create mode 100644 lib/jetty/org/eclipse/jetty/security/UserDataConstraint.java create mode 100644 lib/jetty/org/eclipse/jetty/security/authentication/BasicAuthenticator.java create mode 100644 lib/jetty/org/eclipse/jetty/security/authentication/ClientCertAuthenticator.java create mode 100644 lib/jetty/org/eclipse/jetty/security/authentication/DeferredAuthentication.java create mode 100644 lib/jetty/org/eclipse/jetty/security/authentication/DigestAuthenticator.java create mode 100644 lib/jetty/org/eclipse/jetty/security/authentication/FormAuthenticator.java create mode 100644 lib/jetty/org/eclipse/jetty/security/authentication/LoginAuthenticator.java create mode 100644 lib/jetty/org/eclipse/jetty/security/authentication/LoginCallback.java create mode 100644 lib/jetty/org/eclipse/jetty/security/authentication/LoginCallbackImpl.java create mode 100644 lib/jetty/org/eclipse/jetty/security/authentication/SessionAuthentication.java create mode 100644 lib/jetty/org/eclipse/jetty/security/authentication/SpnegoAuthenticator.java create mode 100644 lib/jetty/org/eclipse/jetty/security/authentication/package-info.java create mode 100644 lib/jetty/org/eclipse/jetty/security/package-info.java create mode 100644 lib/jetty/org/eclipse/jetty/server/AbstractConnectionFactory.java create mode 100644 lib/jetty/org/eclipse/jetty/server/AbstractConnector.java create mode 100644 lib/jetty/org/eclipse/jetty/server/AbstractNCSARequestLog.java create mode 100644 lib/jetty/org/eclipse/jetty/server/AbstractNetworkConnector.java create mode 100644 lib/jetty/org/eclipse/jetty/server/AsyncContextEvent.java create mode 100644 lib/jetty/org/eclipse/jetty/server/AsyncContextState.java create mode 100644 lib/jetty/org/eclipse/jetty/server/AsyncNCSARequestLog.java create mode 100644 lib/jetty/org/eclipse/jetty/server/Authentication.java create mode 100644 lib/jetty/org/eclipse/jetty/server/ByteBufferQueuedHttpInput.java create mode 100644 lib/jetty/org/eclipse/jetty/server/ClassLoaderDump.java create mode 100644 lib/jetty/org/eclipse/jetty/server/ConnectionFactory.java create mode 100644 lib/jetty/org/eclipse/jetty/server/Connector.java create mode 100644 lib/jetty/org/eclipse/jetty/server/ConnectorStatistics.java create mode 100644 lib/jetty/org/eclipse/jetty/server/CookieCutter.java create mode 100644 lib/jetty/org/eclipse/jetty/server/Dispatcher.java create mode 100644 lib/jetty/org/eclipse/jetty/server/EncodingHttpWriter.java create mode 100644 lib/jetty/org/eclipse/jetty/server/ForwardedRequestCustomizer.java create mode 100644 lib/jetty/org/eclipse/jetty/server/Handler.java create mode 100644 lib/jetty/org/eclipse/jetty/server/HandlerContainer.java create mode 100644 lib/jetty/org/eclipse/jetty/server/HostHeaderCustomizer.java create mode 100644 lib/jetty/org/eclipse/jetty/server/HttpChannel.java create mode 100644 lib/jetty/org/eclipse/jetty/server/HttpChannelState.java create mode 100644 lib/jetty/org/eclipse/jetty/server/HttpConfiguration.java create mode 100644 lib/jetty/org/eclipse/jetty/server/HttpConnection.java create mode 100644 lib/jetty/org/eclipse/jetty/server/HttpConnectionFactory.java create mode 100644 lib/jetty/org/eclipse/jetty/server/HttpInput.java create mode 100644 lib/jetty/org/eclipse/jetty/server/HttpInputOverHTTP.java create mode 100644 lib/jetty/org/eclipse/jetty/server/HttpOutput.java create mode 100644 lib/jetty/org/eclipse/jetty/server/HttpTransport.java create mode 100644 lib/jetty/org/eclipse/jetty/server/HttpWriter.java create mode 100644 lib/jetty/org/eclipse/jetty/server/InclusiveByteRange.java create mode 100644 lib/jetty/org/eclipse/jetty/server/Iso88591HttpWriter.java create mode 100644 lib/jetty/org/eclipse/jetty/server/LocalConnector.java create mode 100644 lib/jetty/org/eclipse/jetty/server/LowResourceMonitor.java create mode 100644 lib/jetty/org/eclipse/jetty/server/NCSARequestLog.java create mode 100644 lib/jetty/org/eclipse/jetty/server/NegotiatingServerConnection.java create mode 100644 lib/jetty/org/eclipse/jetty/server/NegotiatingServerConnectionFactory.java create mode 100644 lib/jetty/org/eclipse/jetty/server/NetworkConnector.java create mode 100644 lib/jetty/org/eclipse/jetty/server/NetworkTrafficServerConnector.java create mode 100644 lib/jetty/org/eclipse/jetty/server/QueuedHttpInput.java create mode 100644 lib/jetty/org/eclipse/jetty/server/QuietServletException.java create mode 100644 lib/jetty/org/eclipse/jetty/server/Request.java create mode 100644 lib/jetty/org/eclipse/jetty/server/RequestLog.java create mode 100644 lib/jetty/org/eclipse/jetty/server/ResourceCache.java create mode 100644 lib/jetty/org/eclipse/jetty/server/Response.java create mode 100644 lib/jetty/org/eclipse/jetty/server/SecureRequestCustomizer.java create mode 100644 lib/jetty/org/eclipse/jetty/server/Server.java create mode 100644 lib/jetty/org/eclipse/jetty/server/ServerConnector.java create mode 100644 lib/jetty/org/eclipse/jetty/server/ServletRequestHttpWrapper.java create mode 100644 lib/jetty/org/eclipse/jetty/server/ServletResponseHttpWrapper.java create mode 100644 lib/jetty/org/eclipse/jetty/server/SessionIdManager.java create mode 100644 lib/jetty/org/eclipse/jetty/server/SessionManager.java create mode 100644 lib/jetty/org/eclipse/jetty/server/ShutdownMonitor.java create mode 100644 lib/jetty/org/eclipse/jetty/server/SslConnectionFactory.java create mode 100644 lib/jetty/org/eclipse/jetty/server/UserIdentity.java create mode 100644 lib/jetty/org/eclipse/jetty/server/Utf8HttpWriter.java create mode 100644 lib/jetty/org/eclipse/jetty/server/handler/AbstractHandler.java create mode 100644 lib/jetty/org/eclipse/jetty/server/handler/AbstractHandlerContainer.java create mode 100644 lib/jetty/org/eclipse/jetty/server/handler/AllowSymLinkAliasChecker.java create mode 100644 lib/jetty/org/eclipse/jetty/server/handler/ContextHandler.java create mode 100644 lib/jetty/org/eclipse/jetty/server/handler/ContextHandlerCollection.java create mode 100644 lib/jetty/org/eclipse/jetty/server/handler/DebugHandler.java create mode 100644 lib/jetty/org/eclipse/jetty/server/handler/DefaultHandler.java create mode 100644 lib/jetty/org/eclipse/jetty/server/handler/ErrorHandler.java create mode 100644 lib/jetty/org/eclipse/jetty/server/handler/HandlerCollection.java create mode 100644 lib/jetty/org/eclipse/jetty/server/handler/HandlerList.java create mode 100644 lib/jetty/org/eclipse/jetty/server/handler/HandlerWrapper.java create mode 100644 lib/jetty/org/eclipse/jetty/server/handler/HotSwapHandler.java create mode 100644 lib/jetty/org/eclipse/jetty/server/handler/IPAccessHandler.java create mode 100644 lib/jetty/org/eclipse/jetty/server/handler/IdleTimeoutHandler.java create mode 100644 lib/jetty/org/eclipse/jetty/server/handler/MovedContextHandler.java create mode 100644 lib/jetty/org/eclipse/jetty/server/handler/RequestLogHandler.java create mode 100644 lib/jetty/org/eclipse/jetty/server/handler/ResourceHandler.java create mode 100644 lib/jetty/org/eclipse/jetty/server/handler/ScopedHandler.java create mode 100644 lib/jetty/org/eclipse/jetty/server/handler/ShutdownHandler.java create mode 100644 lib/jetty/org/eclipse/jetty/server/handler/StatisticsHandler.java create mode 100644 lib/jetty/org/eclipse/jetty/server/handler/package-info.java create mode 100644 lib/jetty/org/eclipse/jetty/server/nio/NetworkTrafficSelectChannelConnector.java create mode 100644 lib/jetty/org/eclipse/jetty/server/nio/package-info.java create mode 100644 lib/jetty/org/eclipse/jetty/server/package-info.java create mode 100644 lib/jetty/org/eclipse/jetty/server/session/AbstractSession.java create mode 100644 lib/jetty/org/eclipse/jetty/server/session/AbstractSessionIdManager.java create mode 100644 lib/jetty/org/eclipse/jetty/server/session/AbstractSessionManager.java create mode 100644 lib/jetty/org/eclipse/jetty/server/session/HashSessionIdManager.java create mode 100644 lib/jetty/org/eclipse/jetty/server/session/HashSessionManager.java create mode 100644 lib/jetty/org/eclipse/jetty/server/session/HashedSession.java create mode 100644 lib/jetty/org/eclipse/jetty/server/session/JDBCSessionIdManager.java create mode 100644 lib/jetty/org/eclipse/jetty/server/session/JDBCSessionManager.java create mode 100644 lib/jetty/org/eclipse/jetty/server/session/MemSession.java create mode 100644 lib/jetty/org/eclipse/jetty/server/session/SessionHandler.java create mode 100644 lib/jetty/org/eclipse/jetty/server/session/package-info.java create mode 100644 lib/jetty/org/eclipse/jetty/servlet/BaseHolder.java create mode 100644 lib/jetty/org/eclipse/jetty/servlet/DefaultServlet.java create mode 100644 lib/jetty/org/eclipse/jetty/servlet/ErrorPageErrorHandler.java create mode 100644 lib/jetty/org/eclipse/jetty/servlet/FilterHolder.java create mode 100644 lib/jetty/org/eclipse/jetty/servlet/FilterMapping.java create mode 100644 lib/jetty/org/eclipse/jetty/servlet/Holder.java create mode 100644 lib/jetty/org/eclipse/jetty/servlet/Invoker.java create mode 100644 lib/jetty/org/eclipse/jetty/servlet/JspPropertyGroupServlet.java create mode 100644 lib/jetty/org/eclipse/jetty/servlet/ListenerHolder.java create mode 100644 lib/jetty/org/eclipse/jetty/servlet/NoJspServlet.java create mode 100644 lib/jetty/org/eclipse/jetty/servlet/ServletContextHandler.java create mode 100644 lib/jetty/org/eclipse/jetty/servlet/ServletHandler.java create mode 100644 lib/jetty/org/eclipse/jetty/servlet/ServletHolder.java create mode 100644 lib/jetty/org/eclipse/jetty/servlet/ServletMapping.java create mode 100644 lib/jetty/org/eclipse/jetty/servlet/ServletTester.java create mode 100644 lib/jetty/org/eclipse/jetty/servlet/StatisticsServlet.java create mode 100644 lib/jetty/org/eclipse/jetty/servlet/listener/ELContextCleaner.java create mode 100644 lib/jetty/org/eclipse/jetty/servlet/listener/IntrospectorCleaner.java create mode 100644 lib/jetty/org/eclipse/jetty/servlet/listener/package-info.java create mode 100644 lib/jetty/org/eclipse/jetty/servlet/package-info.java create mode 100644 lib/jetty/org/eclipse/jetty/util/AbstractTrie.java create mode 100644 lib/jetty/org/eclipse/jetty/util/ArrayQueue.java create mode 100644 lib/jetty/org/eclipse/jetty/util/ArrayTernaryTrie.java create mode 100644 lib/jetty/org/eclipse/jetty/util/ArrayTrie.java create mode 100644 lib/jetty/org/eclipse/jetty/util/ArrayUtil.java create mode 100644 lib/jetty/org/eclipse/jetty/util/Atomics.java create mode 100644 lib/jetty/org/eclipse/jetty/util/Attributes.java create mode 100644 lib/jetty/org/eclipse/jetty/util/AttributesMap.java create mode 100644 lib/jetty/org/eclipse/jetty/util/B64Code.java create mode 100644 lib/jetty/org/eclipse/jetty/util/BlockingArrayQueue.java create mode 100644 lib/jetty/org/eclipse/jetty/util/BlockingCallback.java create mode 100644 lib/jetty/org/eclipse/jetty/util/BufferUtil.java create mode 100644 lib/jetty/org/eclipse/jetty/util/ByteArrayISO8859Writer.java create mode 100644 lib/jetty/org/eclipse/jetty/util/ByteArrayOutputStream2.java create mode 100644 lib/jetty/org/eclipse/jetty/util/Callback.java create mode 100644 lib/jetty/org/eclipse/jetty/util/ClassLoadingObjectInputStream.java create mode 100644 lib/jetty/org/eclipse/jetty/util/CompletableCallback.java create mode 100644 lib/jetty/org/eclipse/jetty/util/ConcurrentArrayQueue.java create mode 100644 lib/jetty/org/eclipse/jetty/util/ConcurrentHashSet.java create mode 100644 lib/jetty/org/eclipse/jetty/util/DateCache.java create mode 100644 lib/jetty/org/eclipse/jetty/util/Fields.java create mode 100644 lib/jetty/org/eclipse/jetty/util/ForkInvoker.java create mode 100644 lib/jetty/org/eclipse/jetty/util/FutureCallback.java create mode 100644 lib/jetty/org/eclipse/jetty/util/FuturePromise.java create mode 100644 lib/jetty/org/eclipse/jetty/util/HostMap.java create mode 100644 lib/jetty/org/eclipse/jetty/util/HttpCookieStore.java create mode 100644 lib/jetty/org/eclipse/jetty/util/IO.java create mode 100644 lib/jetty/org/eclipse/jetty/util/IPAddressMap.java create mode 100644 lib/jetty/org/eclipse/jetty/util/IntrospectionUtil.java create mode 100644 lib/jetty/org/eclipse/jetty/util/IteratingCallback.java create mode 100644 lib/jetty/org/eclipse/jetty/util/IteratingNestedCallback.java create mode 100644 lib/jetty/org/eclipse/jetty/util/Jetty.java create mode 100644 lib/jetty/org/eclipse/jetty/util/LazyList.java create mode 100644 lib/jetty/org/eclipse/jetty/util/LeakDetector.java create mode 100644 lib/jetty/org/eclipse/jetty/util/Loader.java create mode 100644 lib/jetty/org/eclipse/jetty/util/MemoryUtils.java create mode 100644 lib/jetty/org/eclipse/jetty/util/MultiException.java create mode 100644 lib/jetty/org/eclipse/jetty/util/MultiMap.java create mode 100644 lib/jetty/org/eclipse/jetty/util/MultiPartInputStreamParser.java create mode 100644 lib/jetty/org/eclipse/jetty/util/MultiPartOutputStream.java create mode 100644 lib/jetty/org/eclipse/jetty/util/MultiPartWriter.java create mode 100644 lib/jetty/org/eclipse/jetty/util/PatternMatcher.java create mode 100644 lib/jetty/org/eclipse/jetty/util/Promise.java create mode 100644 lib/jetty/org/eclipse/jetty/util/QuotedStringTokenizer.java create mode 100644 lib/jetty/org/eclipse/jetty/util/ReadLineInputStream.java create mode 100644 lib/jetty/org/eclipse/jetty/util/RolloverFileOutputStream.java create mode 100644 lib/jetty/org/eclipse/jetty/util/Scanner.java create mode 100644 lib/jetty/org/eclipse/jetty/util/SharedBlockingCallback.java create mode 100644 lib/jetty/org/eclipse/jetty/util/SocketAddressResolver.java create mode 100644 lib/jetty/org/eclipse/jetty/util/StringUtil.java create mode 100644 lib/jetty/org/eclipse/jetty/util/TreeTrie.java create mode 100644 lib/jetty/org/eclipse/jetty/util/Trie.java create mode 100644 lib/jetty/org/eclipse/jetty/util/TypeUtil.java create mode 100644 lib/jetty/org/eclipse/jetty/util/URIUtil.java create mode 100644 lib/jetty/org/eclipse/jetty/util/UrlEncoded.java create mode 100644 lib/jetty/org/eclipse/jetty/util/Utf8Appendable.java create mode 100644 lib/jetty/org/eclipse/jetty/util/Utf8LineParser.java create mode 100644 lib/jetty/org/eclipse/jetty/util/Utf8StringBuffer.java create mode 100644 lib/jetty/org/eclipse/jetty/util/Utf8StringBuilder.java create mode 100644 lib/jetty/org/eclipse/jetty/util/annotation/ManagedAttribute.java create mode 100644 lib/jetty/org/eclipse/jetty/util/annotation/ManagedObject.java create mode 100644 lib/jetty/org/eclipse/jetty/util/annotation/ManagedOperation.java create mode 100644 lib/jetty/org/eclipse/jetty/util/annotation/Name.java create mode 100644 lib/jetty/org/eclipse/jetty/util/annotation/package-info.java create mode 100644 lib/jetty/org/eclipse/jetty/util/component/AbstractLifeCycle.java create mode 100644 lib/jetty/org/eclipse/jetty/util/component/Container.java create mode 100644 lib/jetty/org/eclipse/jetty/util/component/ContainerLifeCycle.java create mode 100644 lib/jetty/org/eclipse/jetty/util/component/Destroyable.java create mode 100644 lib/jetty/org/eclipse/jetty/util/component/Dumpable.java create mode 100644 lib/jetty/org/eclipse/jetty/util/component/FileDestroyable.java create mode 100644 lib/jetty/org/eclipse/jetty/util/component/FileNoticeLifeCycleListener.java create mode 100644 lib/jetty/org/eclipse/jetty/util/component/Graceful.java create mode 100644 lib/jetty/org/eclipse/jetty/util/component/LifeCycle.java create mode 100644 lib/jetty/org/eclipse/jetty/util/component/package-info.java create mode 100644 lib/jetty/org/eclipse/jetty/util/log/AbstractLogger.java create mode 100644 lib/jetty/org/eclipse/jetty/util/log/JavaUtilLog.java create mode 100644 lib/jetty/org/eclipse/jetty/util/log/Log.java create mode 100644 lib/jetty/org/eclipse/jetty/util/log/Logger.java create mode 100644 lib/jetty/org/eclipse/jetty/util/log/LoggerLog.java create mode 100644 lib/jetty/org/eclipse/jetty/util/log/StacklessLogging.java create mode 100644 lib/jetty/org/eclipse/jetty/util/log/StdErrLog.java create mode 100644 lib/jetty/org/eclipse/jetty/util/log/package-info.java create mode 100644 lib/jetty/org/eclipse/jetty/util/package-info.java create mode 100644 lib/jetty/org/eclipse/jetty/util/preventers/AWTLeakPreventer.java create mode 100644 lib/jetty/org/eclipse/jetty/util/preventers/AbstractLeakPreventer.java create mode 100644 lib/jetty/org/eclipse/jetty/util/preventers/AppContextLeakPreventer.java create mode 100644 lib/jetty/org/eclipse/jetty/util/preventers/DOMLeakPreventer.java create mode 100644 lib/jetty/org/eclipse/jetty/util/preventers/DriverManagerLeakPreventer.java create mode 100644 lib/jetty/org/eclipse/jetty/util/preventers/GCThreadLeakPreventer.java create mode 100644 lib/jetty/org/eclipse/jetty/util/preventers/Java2DLeakPreventer.java create mode 100644 lib/jetty/org/eclipse/jetty/util/preventers/LDAPLeakPreventer.java create mode 100644 lib/jetty/org/eclipse/jetty/util/preventers/LoginConfigurationLeakPreventer.java create mode 100644 lib/jetty/org/eclipse/jetty/util/preventers/SecurityProviderLeakPreventer.java create mode 100644 lib/jetty/org/eclipse/jetty/util/preventers/package-info.java create mode 100644 lib/jetty/org/eclipse/jetty/util/resource/BadResource.java create mode 100644 lib/jetty/org/eclipse/jetty/util/resource/EmptyResource.java create mode 100644 lib/jetty/org/eclipse/jetty/util/resource/FileResource.java create mode 100644 lib/jetty/org/eclipse/jetty/util/resource/JarFileResource.java create mode 100644 lib/jetty/org/eclipse/jetty/util/resource/JarResource.java create mode 100644 lib/jetty/org/eclipse/jetty/util/resource/Resource.java create mode 100644 lib/jetty/org/eclipse/jetty/util/resource/ResourceCollection.java create mode 100644 lib/jetty/org/eclipse/jetty/util/resource/ResourceFactory.java create mode 100644 lib/jetty/org/eclipse/jetty/util/resource/URLResource.java create mode 100644 lib/jetty/org/eclipse/jetty/util/resource/package-info.java create mode 100644 lib/jetty/org/eclipse/jetty/util/security/CertificateUtils.java create mode 100644 lib/jetty/org/eclipse/jetty/util/security/CertificateValidator.java create mode 100644 lib/jetty/org/eclipse/jetty/util/security/Constraint.java create mode 100644 lib/jetty/org/eclipse/jetty/util/security/Credential.java create mode 100644 lib/jetty/org/eclipse/jetty/util/security/Password.java create mode 100644 lib/jetty/org/eclipse/jetty/util/security/UnixCrypt.java create mode 100644 lib/jetty/org/eclipse/jetty/util/security/package-info.java create mode 100644 lib/jetty/org/eclipse/jetty/util/ssl/AliasedX509ExtendedKeyManager.java create mode 100644 lib/jetty/org/eclipse/jetty/util/ssl/AliasedX509KeyManager.java create mode 100644 lib/jetty/org/eclipse/jetty/util/ssl/SslContextFactory.java create mode 100644 lib/jetty/org/eclipse/jetty/util/ssl/package-info.java create mode 100644 lib/jetty/org/eclipse/jetty/util/statistic/CounterStatistic.java create mode 100644 lib/jetty/org/eclipse/jetty/util/statistic/SampleStatistic.java create mode 100644 lib/jetty/org/eclipse/jetty/util/statistic/package-info.java create mode 100644 lib/jetty/org/eclipse/jetty/util/thread/ExecutorThreadPool.java create mode 100644 lib/jetty/org/eclipse/jetty/util/thread/NonBlockingThread.java create mode 100644 lib/jetty/org/eclipse/jetty/util/thread/QueuedThreadPool.java create mode 100644 lib/jetty/org/eclipse/jetty/util/thread/ScheduledExecutorScheduler.java create mode 100644 lib/jetty/org/eclipse/jetty/util/thread/Scheduler.java create mode 100644 lib/jetty/org/eclipse/jetty/util/thread/ShutdownThread.java create mode 100644 lib/jetty/org/eclipse/jetty/util/thread/ThreadPool.java create mode 100644 lib/jetty/org/eclipse/jetty/util/thread/TimerScheduler.java create mode 100644 lib/jetty/org/eclipse/jetty/util/thread/package-info.java diff --git a/.classpath b/.classpath index 342147de..0fba86b0 100644 --- a/.classpath +++ b/.classpath @@ -2,6 +2,7 @@ + diff --git a/lib/jetty/org/eclipse/jetty/http/DateGenerator.java b/lib/jetty/org/eclipse/jetty/http/DateGenerator.java new file mode 100644 index 00000000..3ecaad8c --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/http/DateGenerator.java @@ -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 =new ThreadLocal() + { + @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 index 00000000..1ede4cec --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/http/DateParser.java @@ -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 =new ThreadLocal() + { + @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 index 00000000..ebae56d1 --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/http/HttpContent.java @@ -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 index 00000000..1a2426b1 --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/http/HttpCookie.java @@ -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 index 00000000..50f29b1e --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/http/HttpField.java @@ -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 index 00000000..d4742274 --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/http/HttpFields.java @@ -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. + * + *

This class is not synchronized as it is expected that modifications will only be performed by a + * single thread. + * + *

The cookie handling provided by this class is guided by the Servlet specification and RFC6265. + * + */ +public class HttpFields implements Iterable +{ + 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 _fields = new ArrayList<>(20); + + /** + * Constructor. + */ + public HttpFields() + { + } + + /** + * Get Collection of header names. + */ + public Collection getFieldNamesCollection() + { + final Set 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 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 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 getValuesList(String name) + { + final List 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 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() + { + 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 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 getValues(String name, final String separators) + { + final Enumeration e = getValues(name); + if (e == null) + return null; + return new Enumeration() + { + 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 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 e = fields.getFieldNames(); + while (e.hasMoreElements()) + { + String name = e.nextElement(); + Enumeration 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: + * + *

+     *
+     * FieldName : Value ; param1=val1 ; param2=val2
+     *
+     * 
+ * + * @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 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 __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 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 qualityList(Enumeration 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 vl = LazyList.getList(list, false); + if (vl.size() < 2) + return vl; + + List 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 index 00000000..a51e4ba7 --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/http/HttpGenerator.java @@ -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 + *

+ */ +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;i0) + { + // 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=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;i0xff || 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;i0xff || 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 index 00000000..ab0ddcf3 --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/http/HttpHeader.java @@ -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 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 index 00000000..8338a322 --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/http/HttpHeaderValue.java @@ -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 CACHE= new ArrayTrie(); + 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 __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 index 00000000..8a262680 --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/http/HttpMethod.java @@ -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 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 index 00000000..79f1c28d --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/http/HttpParser.java @@ -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 + *

+ * 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. + *

+ *

+ * 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()} + *

+ *

+ * 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. + *

+ *

+ * 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 + *

+ */ +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:
    + *
  • Common static combinations such as:
      + *
    • Connection: close + *
    • Accept-Encoding: gzip + *
    • Content-Length: 0 + *
    + *
  • Combinations of Content-Type header for common mime types by common charsets + *
  • 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 + *
+ */ + public final static Trie 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 _handler; + private final RequestHandler _requestHandler; + private final ResponseHandler _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 _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 handler) + { + this(handler,-1,__STRICT); + } + + /* ------------------------------------------------------------------------------- */ + public HttpParser(ResponseHandler handler) + { + this(handler,-1,__STRICT); + } + + /* ------------------------------------------------------------------------------- */ + public HttpParser(RequestHandler handler,int maxHeaderBytes) + { + this(handler,maxHeaderBytes,__STRICT); + } + + /* ------------------------------------------------------------------------------- */ + public HttpParser(ResponseHandler handler,int maxHeaderBytes) + { + this(handler,maxHeaderBytes,__STRICT); + } + + /* ------------------------------------------------------------------------------- */ + public HttpParser(RequestHandler handler,int maxHeaderBytes,boolean strict) + { + _handler=handler; + _requestHandler=handler; + _responseHandler=null; + _maxHeaderBytes=maxHeaderBytes; + _strict=strict; + } + + /* ------------------------------------------------------------------------------- */ + public HttpParser(ResponseHandler 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()=0 && ch0 && _state.ordinal() 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()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 (iHttpTokens.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=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()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() && _state.ordinal()= State.CONTENT.ordinal() && _state.ordinal()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 + { + 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 extends HttpHandler + { + /** + * 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 extends HttpHandler + { + /** + * 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 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 index 00000000..13f2a8d3 --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/http/HttpScheme.java @@ -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 CACHE= new ArrayTrie(); + 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 index 00000000..e6ea1f71 --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/http/HttpStatus.java @@ -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; + +/** + *

+ * HttpStatusCode enum class, for status codes based on various HTTP RFCs. (see + * table below) + *

+ * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
EnumCodeMessage + * RFC 1945 - HTTP/1.0 + * RFC 2616 - HTTP/1.1 + * RFC 2518 - WEBDAV
Informational - 1xx{@link #isInformational(int)}
{@link #CONTINUE_100}100Continue  + * Sec. 10.1.1 
{@link #SWITCHING_PROTOCOLS_101}101Switching Protocols  + * Sec. 10.1.2 
{@link #PROCESSING_102}102Processing   + * Sec. 10.1
Success - 2xx{@link #isSuccess(int)}
{@link #OK_200}200OK + * Sec. 9.2 + * Sec. 10.2.1 
{@link #CREATED_201}201Created + * Sec. 9.2 + * Sec. 10.2.2 
{@link #ACCEPTED_202}202Accepted + * Sec. 9.2 + * Sec. 10.2.3 
{@link #NON_AUTHORITATIVE_INFORMATION_203}203Non Authoritative Information  + * Sec. 10.2.4 
{@link #NO_CONTENT_204}204No Content + * Sec. 9.2 + * Sec. 10.2.5 
{@link #RESET_CONTENT_205}205Reset Content  + * Sec. 10.2.6 
{@link #PARTIAL_CONTENT_206}206Partial Content  + * Sec. 10.2.7 
{@link #MULTI_STATUS_207}207Multi-Status   + * Sec. 10.2
 207Partial Update OK  + * draft/01 
Redirection - 3xx{@link #isRedirection(int)}
{@link #MULTIPLE_CHOICES_300}300Multiple Choices + * Sec. 9.3 + * Sec. 10.3.1 
{@link #MOVED_PERMANENTLY_301}301Moved Permanently + * Sec. 9.3 + * Sec. 10.3.2 
{@link #MOVED_TEMPORARILY_302}302Moved Temporarily + * Sec. 9.3(now "302 Found") 
{@link #FOUND_302}302Found(was "302 Moved Temporarily") + * Sec. 10.3.3 
{@link #SEE_OTHER_303}303See Other  + * Sec. 10.3.4 
{@link #NOT_MODIFIED_304}304Not Modified + * Sec. 9.3 + * Sec. 10.3.5 
{@link #USE_PROXY_305}305Use Proxy  + * Sec. 10.3.6 
 306(Unused)  + * Sec. 10.3.7 
{@link #TEMPORARY_REDIRECT_307}307Temporary Redirect  + * Sec. 10.3.8 
Client Error - 4xx{@link #isClientError(int)}
{@link #BAD_REQUEST_400}400Bad Request + * Sec. 9.4 + * Sec. 10.4.1 
{@link #UNAUTHORIZED_401}401Unauthorized + * Sec. 9.4 + * Sec. 10.4.2 
{@link #PAYMENT_REQUIRED_402}402Payment Required + * Sec. 9.4 + * Sec. 10.4.3 
{@link #FORBIDDEN_403}403Forbidden + * Sec. 9.4 + * Sec. 10.4.4 
{@link #NOT_FOUND_404}404Not Found + * Sec. 9.4 + * Sec. 10.4.5 
{@link #METHOD_NOT_ALLOWED_405}405Method Not Allowed  + * Sec. 10.4.6 
{@link #NOT_ACCEPTABLE_406}406Not Acceptable  + * Sec. 10.4.7 
{@link #PROXY_AUTHENTICATION_REQUIRED_407}407Proxy Authentication Required  + * Sec. 10.4.8 
{@link #REQUEST_TIMEOUT_408}408Request Timeout  + * Sec. 10.4.9 
{@link #CONFLICT_409}409Conflict  + * Sec. 10.4.10 + *  
{@link #GONE_410}410Gone  + * Sec. 10.4.11 + *  
{@link #LENGTH_REQUIRED_411}411Length Required  + * Sec. 10.4.12 + *  
{@link #PRECONDITION_FAILED_412}412Precondition Failed  + * Sec. 10.4.13 + *  
{@link #REQUEST_ENTITY_TOO_LARGE_413}413Request Entity Too Large  + * Sec. 10.4.14 + *  
{@link #REQUEST_URI_TOO_LONG_414}414Request-URI Too Long  + * Sec. 10.4.15 + *  
{@link #UNSUPPORTED_MEDIA_TYPE_415}415Unsupported Media Type  + * Sec. 10.4.16 + *  
{@link #REQUESTED_RANGE_NOT_SATISFIABLE_416}416Requested Range Not Satisfiable  + * Sec. 10.4.17 + *  
{@link #EXPECTATION_FAILED_417}417Expectation Failed  + * Sec. 10.4.18 + *  
 418Reauthentication Required  + * draft/01 
 418Unprocessable Entity   + * draft/05
 419Proxy Reauthentication Required  + * draft/01 
 419Insufficient Space on Resource   + * draft/05
 420Method Failure   + * draft/05
 421(Unused)   
{@link #UNPROCESSABLE_ENTITY_422}422Unprocessable Entity   + * Sec. 10.3
{@link #LOCKED_423}423Locked   + * Sec. 10.4
{@link #FAILED_DEPENDENCY_424}424Failed Dependency   + * Sec. 10.5
Server Error - 5xx{@link #isServerError(int)}
{@link #INTERNAL_SERVER_ERROR_500}500Internal Server Error + * Sec. 9.5 + * Sec. 10.5.1 
{@link #NOT_IMPLEMENTED_501}501Not Implemented + * Sec. 9.5 + * Sec. 10.5.2 
{@link #BAD_GATEWAY_502}502Bad Gateway + * Sec. 9.5 + * Sec. 10.5.3 
{@link #SERVICE_UNAVAILABLE_503}503Service Unavailable + * Sec. 9.5 + * Sec. 10.5.4 
{@link #GATEWAY_TIMEOUT_504}504Gateway Timeout  + * Sec. 10.5.5 
{@link #HTTP_VERSION_NOT_SUPPORTED_505}505HTTP Version Not Supported  + * Sec. 10.5.6 
 506(Unused)   
{@link #INSUFFICIENT_STORAGE_507}507Insufficient Storage   + * Sec. 10.6
+ * + * @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 + */ + + /** 100 Continue */ + CONTINUE(CONTINUE_100, "Continue"), + /** 101 Switching Protocols */ + SWITCHING_PROTOCOLS(SWITCHING_PROTOCOLS_101, "Switching Protocols"), + /** 102 Processing */ + 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 + */ + + /** 200 OK */ + OK(OK_200, "OK"), + /** 201 Created */ + CREATED(CREATED_201, "Created"), + /** 202 Accepted */ + ACCEPTED(ACCEPTED_202, "Accepted"), + /** 203 Non Authoritative Information */ + NON_AUTHORITATIVE_INFORMATION(NON_AUTHORITATIVE_INFORMATION_203, "Non Authoritative Information"), + /** 204 No Content */ + NO_CONTENT(NO_CONTENT_204, "No Content"), + /** 205 Reset Content */ + RESET_CONTENT(RESET_CONTENT_205, "Reset Content"), + /** 206 Partial Content */ + PARTIAL_CONTENT(PARTIAL_CONTENT_206, "Partial Content"), + /** 207 Multi-Status */ + 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 + */ + + /** 300 Mutliple Choices */ + MULTIPLE_CHOICES(MULTIPLE_CHOICES_300, "Multiple Choices"), + /** 301 Moved Permanently */ + MOVED_PERMANENTLY(MOVED_PERMANENTLY_301, "Moved Permanently"), + /** 302 Moved Temporarily */ + MOVED_TEMPORARILY(MOVED_TEMPORARILY_302, "Moved Temporarily"), + /** 302 Found */ + FOUND(FOUND_302, "Found"), + /** 303 See Other */ + SEE_OTHER(SEE_OTHER_303, "See Other"), + /** 304 Not Modified */ + NOT_MODIFIED(NOT_MODIFIED_304, "Not Modified"), + /** 305 Use Proxy */ + USE_PROXY(USE_PROXY_305, "Use Proxy"), + /** 307 Temporary Redirect */ + 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 + */ + + /** 400 Bad Request */ + BAD_REQUEST(BAD_REQUEST_400, "Bad Request"), + /** 401 Unauthorized */ + UNAUTHORIZED(UNAUTHORIZED_401, "Unauthorized"), + /** 402 Payment Required */ + PAYMENT_REQUIRED(PAYMENT_REQUIRED_402, "Payment Required"), + /** 403 Forbidden */ + FORBIDDEN(FORBIDDEN_403, "Forbidden"), + /** 404 Not Found */ + NOT_FOUND(NOT_FOUND_404, "Not Found"), + /** 405 Method Not Allowed */ + METHOD_NOT_ALLOWED(METHOD_NOT_ALLOWED_405, "Method Not Allowed"), + /** 406 Not Acceptable */ + NOT_ACCEPTABLE(NOT_ACCEPTABLE_406, "Not Acceptable"), + /** 407 Proxy Authentication Required */ + PROXY_AUTHENTICATION_REQUIRED(PROXY_AUTHENTICATION_REQUIRED_407, "Proxy Authentication Required"), + /** 408 Request Timeout */ + REQUEST_TIMEOUT(REQUEST_TIMEOUT_408, "Request Timeout"), + /** 409 Conflict */ + CONFLICT(CONFLICT_409, "Conflict"), + /** 410 Gone */ + GONE(GONE_410, "Gone"), + /** 411 Length Required */ + LENGTH_REQUIRED(LENGTH_REQUIRED_411, "Length Required"), + /** 412 Precondition Failed */ + PRECONDITION_FAILED(PRECONDITION_FAILED_412, "Precondition Failed"), + /** 413 Request Entity Too Large */ + REQUEST_ENTITY_TOO_LARGE(REQUEST_ENTITY_TOO_LARGE_413, "Request Entity Too Large"), + /** 414 Request-URI Too Long */ + REQUEST_URI_TOO_LONG(REQUEST_URI_TOO_LONG_414, "Request-URI Too Long"), + /** 415 Unsupported Media Type */ + UNSUPPORTED_MEDIA_TYPE(UNSUPPORTED_MEDIA_TYPE_415, "Unsupported Media Type"), + /** 416 Requested Range Not Satisfiable */ + REQUESTED_RANGE_NOT_SATISFIABLE(REQUESTED_RANGE_NOT_SATISFIABLE_416, "Requested Range Not Satisfiable"), + /** 417 Expectation Failed */ + EXPECTATION_FAILED(EXPECTATION_FAILED_417, "Expectation Failed"), + /** 422 Unprocessable Entity */ + UNPROCESSABLE_ENTITY(UNPROCESSABLE_ENTITY_422, "Unprocessable Entity"), + /** 423 Locked */ + LOCKED(LOCKED_423, "Locked"), + /** 424 Failed Dependency */ + 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 + */ + + /** 500 Server Error */ + INTERNAL_SERVER_ERROR(INTERNAL_SERVER_ERROR_500, "Server Error"), + /** 501 Not Implemented */ + NOT_IMPLEMENTED(NOT_IMPLEMENTED_501, "Not Implemented"), + /** 502 Bad Gateway */ + BAD_GATEWAY(BAD_GATEWAY_502, "Bad Gateway"), + /** 503 Service Unavailable */ + SERVICE_UNAVAILABLE(SERVICE_UNAVAILABLE_503, "Service Unavailable"), + /** 504 Gateway Timeout */ + GATEWAY_TIMEOUT(GATEWAY_TIMEOUT_504, "Gateway Timeout"), + /** 505 HTTP Version Not Supported */ + HTTP_VERSION_NOT_SUPPORTED(HTTP_VERSION_NOT_SUPPORTED_505, "HTTP Version Not Supported"), + /** 507 Insufficient Storage */ + 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 + * Informational message category as defined in the RFC 1945 - HTTP/1.0, + * and RFC 2616 - + * HTTP/1.1. + * + * @return true if within range of codes that belongs to + * Informational messages. + */ + public boolean isInformational() + { + return HttpStatus.isInformational(this._code); + } + + /** + * Simple test against an code to determine if it falls into the + * Success message category as defined in the RFC 1945 - HTTP/1.0, + * and RFC 2616 - + * HTTP/1.1. + * + * @return true if within range of codes that belongs to + * Success messages. + */ + public boolean isSuccess() + { + return HttpStatus.isSuccess(this._code); + } + + /** + * Simple test against an code to determine if it falls into the + * Redirection message category as defined in the RFC 1945 - HTTP/1.0, + * and RFC 2616 - + * HTTP/1.1. + * + * @return true if within range of codes that belongs to + * Redirection messages. + */ + public boolean isRedirection() + { + return HttpStatus.isRedirection(this._code); + } + + /** + * Simple test against an code to determine if it falls into the + * Client Error message category as defined in the RFC 1945 - HTTP/1.0, + * and RFC 2616 - + * HTTP/1.1. + * + * @return true if within range of codes that belongs to + * Client Error messages. + */ + public boolean isClientError() + { + return HttpStatus.isClientError(this._code); + } + + /** + * Simple test against an code to determine if it falls into the + * Server Error message category as defined in the RFC 1945 - HTTP/1.0, + * and RFC 2616 - + * HTTP/1.1. + * + * @return true if within range of codes that belongs to + * Server Error 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 + * Informational message category as defined in the RFC 1945 - HTTP/1.0, and RFC 2616 - HTTP/1.1. + * + * @param code + * the code to test. + * @return true if within range of codes that belongs to + * Informational messages. + */ + public static boolean isInformational(int code) + { + return ((100 <= code) && (code <= 199)); + } + + /** + * Simple test against an code to determine if it falls into the + * Success message category as defined in the RFC 1945 - HTTP/1.0, and RFC 2616 - HTTP/1.1. + * + * @param code + * the code to test. + * @return true if within range of codes that belongs to + * Success messages. + */ + public static boolean isSuccess(int code) + { + return ((200 <= code) && (code <= 299)); + } + + /** + * Simple test against an code to determine if it falls into the + * Redirection message category as defined in the RFC 1945 - HTTP/1.0, and RFC 2616 - HTTP/1.1. + * + * @param code + * the code to test. + * @return true if within range of codes that belongs to + * Redirection messages. + */ + public static boolean isRedirection(int code) + { + return ((300 <= code) && (code <= 399)); + } + + /** + * Simple test against an code to determine if it falls into the + * Client Error message category as defined in the RFC 1945 - HTTP/1.0, and RFC 2616 - HTTP/1.1. + * + * @param code + * the code to test. + * @return true if within range of codes that belongs to + * Client Error messages. + */ + public static boolean isClientError(int code) + { + return ((400 <= code) && (code <= 499)); + } + + /** + * Simple test against an code to determine if it falls into the + * Server Error message category as defined in the RFC 1945 - HTTP/1.0, and RFC 2616 - HTTP/1.1. + * + * @param code + * the code to test. + * @return true if within range of codes that belongs to + * Server Error 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 index 00000000..537c457c --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/http/HttpTester.java @@ -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 + { + 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 + { + 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 + { + 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 index 00000000..4138334d --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/http/HttpTokens.java @@ -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 index 00000000..afe925e1 --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/http/HttpURI.java @@ -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 + * http://user@host:port/path/info;param?query#fragment + * this class will split it into the following undecoded optional elements:
    + *
  • {@link #getScheme()} - http:
  • + *
  • {@link #getAuthority()} - //name@host:port
  • + *
  • {@link #getHost()} - host
  • + *
  • {@link #getPort()} - port
  • + *
  • {@link #getPath()} - /path/info
  • + *
  • {@link #getParam()} - param
  • + *
  • {@link #getQuery()} - query
  • + *
  • {@link #getFragment()} - fragment
  • + *
+ * + */ +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 (i6 && 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 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 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 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 index 00000000..eb889e56 --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/http/HttpVersion.java @@ -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 CACHE= new ArrayTrie(); + 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 index 00000000..9cac4abd --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/http/MimeTypes.java @@ -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 CACHE= new ArrayTrie<>(512); + private final static Trie TYPES= new ArrayTrie(512); + private final static Map __dftMimeMap = new HashMap(); + private final static Map __encodings = new HashMap(); + + 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 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 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 _mimeMap=new HashMap(); + + /* ------------------------------------------------------------ */ + /** Constructor. + */ + public MimeTypes() + { + } + + /* ------------------------------------------------------------ */ + public synchronized Map getMimeMap() + { + return _mimeMap; + } + + /* ------------------------------------------------------------ */ + /** + * @param mimeMap A Map of file extension to mime-type. + */ + public void setMimeMap(Map mimeMap) + { + _mimeMap.clear(); + if (mimeMap!=null) + { + for (Entry 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 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 + * /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 + * + * Matching is performed in the following order + *
  • Exact match. + *
  • Longest prefix match. + *
  • Longest suffix match. + *
  • default. + * + * 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) + *

    + * 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. + *

    + * This class is not synchronized. If concurrent modifications are + * possible then it should be synchronized at a higher level. + * + * + */ +public class PathMap extends HashMap +{ + /* ------------------------------------------------------------ */ + 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> _prefixMap=new ArrayTernaryTrie<>(false); + Trie> _suffixMap=new ArrayTernaryTrie<>(false); + final Map> _exactMap=new HashMap<>(); + + List> _defaultSingletonList=null; + MappedEntry _prefixDefault=null; + MappedEntry _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 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 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 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>)_prefixMap,1.5); + } + else if (spec.startsWith("*.")) + { + String suffix=spec.substring(2); + while(!_suffixMap.put(suffix,entry)) + _suffixMap=new ArrayTernaryTrie<>((ArrayTernaryTrie>)_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 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 getMatch(String path) + { + if (path==null) + return null; + + int l=path.length(); + + MappedEntry 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> 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> 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> getMatches(String path) + { + MappedEntry entry; + List> 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> 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> 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 implements Map.Entry + { + 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 index 00000000..311c8021 --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/http/encoding.properties @@ -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 index 00000000..b2709897 --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/http/mime.properties @@ -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 index 00000000..825422a3 --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/http/package-info.java @@ -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 index 00000000..e69de29b diff --git a/lib/jetty/org/eclipse/jetty/io/AbstractConnection.java b/lib/jetty/org/eclipse/jetty/io/AbstractConnection.java new file mode 100644 index 00000000..7f8e9f81 --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/io/AbstractConnection.java @@ -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; + +/** + *

    A convenience base implementation of {@link Connection}.

    + *

    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.

    + */ +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 listeners = new CopyOnWriteArrayList<>(); + private final AtomicReference _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); + } + } + + /** + *

    Utility method to be called to register read interest.

    + *

    After a call to this method, {@link #onFillable()} or {@link #onFillInterestedFailed(Throwable)} + * will be called back as appropriate.

    + * @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; + } + } + + /** + *

    Callback method invoked when the endpoint is ready to be read.

    + * @see #fillInterested() + */ + public abstract void onFillable(); + + /** + *

    Callback method invoked when the endpoint failed to be ready to be read.

    + * @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(); + } + + /** + *

    Callback method invoked when the endpoint failed to be ready to be read after a timeout

    + * @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 index 00000000..8fa2cc86 --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/io/AbstractEndPoint.java @@ -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 index 00000000..fa3a01d0 --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/io/ArrayByteBufferPool.java @@ -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 _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 index 00000000..4b8c527c --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/io/ByteArrayEndPoint.java @@ -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 true 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 index 00000000..302adde3 --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/io/ByteBufferPool.java @@ -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; + +/** + *

    A {@link ByteBuffer} pool.

    + *

    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.

    + */ +public interface ByteBufferPool +{ + /** + *

    Requests a {@link ByteBuffer} of the given size.

    + *

    The returned buffer may have a bigger capacity than the size being + * requested but it will have the limit set to the given size.

    + * + * @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); + + /** + *

    Returns a {@link ByteBuffer}, usually obtained with {@link #acquire(int, boolean)} + * (but not necessarily), making it available for recycling and reuse.

    + * + * @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 index 00000000..c65ca0cc --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/io/ChannelEndPoint.java @@ -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. + *

    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 index 00000000..7eb59318 --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/io/ClientConnectionFactory.java @@ -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 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. + *

    + * 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 index 00000000..96baa016 --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/io/Connection.java @@ -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; + +/** + *

    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}.

    + *

    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.

    + */ +public interface Connection extends Closeable +{ + public void addListener(Listener listener); + + /** + *

    Callback method invoked when this {@link Connection} is opened.

    + *

    Creators of the connection implementation are responsible for calling this method.

    + */ + public void onOpen(); + + /** + *

    Callback method invoked when this {@link Connection} is closed.

    + *

    Creators of the connection implementation are responsible for calling this method.

    + */ + public void onClose(); + + /** + * @return the {@link EndPoint} associated with this {@link Connection} + */ + public EndPoint getEndPoint(); + + /** + *

    Performs a logical close of this connection.

    + *

    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}.

    + */ + @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 index 00000000..87adb40b --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/io/EndPoint.java @@ -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 + * + *

    Asynchronous Methods

    + *

    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.

    + *

    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:

    + * + *

    Blocking Read

    + *

    A FutureCallback can be used to block until an endpoint is ready to be filled + * from: + *

    + * FutureCallback<String> future = new FutureCallback<>();
    + * endpoint.fillInterested("ContextObj",future);
    + * ...
    + * String context = future.get(); // This blocks
    + * int filled=endpoint.fill(mybuffer);
    + * 

    + * + *

    Dispatched Read

    + *

    By using a different callback, the read can be done asynchronously in its own dispatched thread: + *

    + * endpoint.fillInterested("ContextObj",new ExecutorCallback<String>(executor)
    + * {
    + *   public void onCompleted(String context)
    + *   {
    + *     int filled=endpoint.fill(mybuffer);
    + *     ...
    + *   }
    + *   public void onFailed(String context,Throwable cause) {...}
    + * });
    + * 

    + *

    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.

    + * + *

    Blocking Write

    + *

    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: + *

    + * FutureCallback<String> future = new FutureCallback<>();
    + * endpoint.write("ContextObj",future,headerBuffer,contentBuffer);
    + * String context = future.get(); // This blocks
    + * 

    + * + *

    Dispatched Write

    + *

    Note also that multiple buffers may be passed in write so that gather writes + * can be done: + *

    + * endpoint.write("ContextObj",new ExecutorCallback<String>(executor)
    + * {
    + *   public void onCompleted(String context)
    + *   {
    + *     int filled=endpoint.fill(mybuffer);
    + *     ...
    + *   }
    + *   public void onFailed(String context,Throwable cause) {...}
    + * },headerBuffer,contentBuffer);
    + * 

    + */ +public interface EndPoint extends Closeable +{ + /* ------------------------------------------------------------ */ + /** + * @return The local Inet address to which this EndPoint is bound, or null + * if this EndPoint does not represent a network connection. + */ + InetSocketAddress getLocalAddress(); + + /* ------------------------------------------------------------ */ + /** + * @return The remote Inet address to which this EndPoint is bound, or null + * if this EndPoint does not represent a network connection. + */ + InetSocketAddress getRemoteAddress(); + + /* ------------------------------------------------------------ */ + boolean isOpen(); + + /* ------------------------------------------------------------ */ + long getCreatedTimeStamp(); + + /* ------------------------------------------------------------ */ + /** Shutdown the output. + *

    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. + *

    + * 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 int 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. + *

    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); + + + /** + *

    Requests callback methods to be invoked when a call to {@link #fill(ByteBuffer)} would return data or EOF.

    + * + * @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; + + /** + *

    Writes the given buffers via {@link #flush(ByteBuffer...)} and invokes callback methods when either + * all the data has been flushed or an error occurs.

    + * + * @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); + + /** + *

    Callback method invoked when this {@link EndPoint} is opened.

    + * @see #onClose() + */ + void onOpen(); + + /** + *

    Callback method invoked when this {@link EndPoint} is close.

    + * @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 index 00000000..72042f40 --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/io/EofException.java @@ -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. + *

    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 index 00000000..b2c3f685 --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/io/FillInterest.java @@ -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 _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 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 index 00000000..8b251ac8 --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/io/IdleTimeout.java @@ -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. + *

    + * 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 _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 index 00000000..a6fc7562 --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/io/LeakTrackingByteBufferPool.java @@ -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 leakDetector = new LeakDetector() + { + @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.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 index 00000000..b331904c --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/io/MappedByteBufferPool.java @@ -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> directBuffers = new ConcurrentHashMap<>(); + private final ConcurrentMap> 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> buffers = buffersFor(direct); + + ByteBuffer result = null; + Queue 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> buffers = buffersFor(buffer.isDirect()); + + // Avoid to create a new queue every time, just to be discarded immediately + Queue byteBuffers = buffers.get(bucket); + if (byteBuffers == null) + { + byteBuffers = new ConcurrentLinkedQueue<>(); + Queue 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> 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 index 00000000..cd05630f --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/io/NegotiatingClientConnection.java @@ -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 context; + private volatile boolean completed; + + protected NegotiatingClientConnection(EndPoint endp, Executor executor, SSLEngine sslEngine, ClientConnectionFactory connectionFactory, Map 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 index 00000000..ff386007 --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/io/NegotiatingClientConnectionFactory.java @@ -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 index 00000000..6a4239f0 --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/io/NetworkTrafficListener.java @@ -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; + +/** + *

    A listener for raw network traffic within Jetty.

    + *

    {@link NetworkTrafficListener}s can be installed in a + * org.eclipse.jetty.server.nio.NetworkTrafficSelectChannelConnector, + * and are notified of the following network traffic events:

    + *
      + *
    • Connection opened, when the server has accepted the connection from a remote client
    • + *
    • Incoming bytes, when the server receives bytes sent from a remote client
    • + *
    • Outgoing bytes, when the server sends bytes to a remote client
    • + *
    • Connection closed, when the server has closed the connection to a remote client
    • + *
    + *

    {@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.

    + */ +public interface NetworkTrafficListener +{ + /** + *

    Callback method invoked when a connection from a remote client has been accepted.

    + *

    The {@code socket} parameter can be used to extract socket address information of + * the remote client.

    + * + * @param socket the socket associated with the remote client + */ + public void opened(Socket socket); + + /** + *

    Callback method invoked when bytes sent by a remote client arrived on the server.

    + * + * @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); + + /** + *

    Callback method invoked when bytes are sent to a remote client from the server.

    + *

    This method is invoked after the bytes have been actually written to the remote client.

    + * + * @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); + + /** + *

    Callback method invoked when a connection to a remote client has been closed.

    + *

    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.
    + * 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); + + /** + *

    A commodity class that implements {@link NetworkTrafficListener} with empty methods.

    + */ + 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 index 00000000..a4b6f7d2 --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/io/NetworkTrafficSelectChannelEndPoint.java @@ -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 listeners; + + public NetworkTrafficSelectChannelEndPoint(SocketChannel channel, SelectorManager.ManagedSelector selectSet, SelectionKey key, Scheduler scheduler, long idleTimeout, List 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 index 00000000..88c33f73 --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/io/RuntimeIOException.java @@ -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 index 00000000..30504029 --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/io/SelectChannelEndPoint.java @@ -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 index 00000000..fd3c6c6b --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/io/SelectorManager.java @@ -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; + +/** + *

    {@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.

    + *

    {@link SelectorManager} subclasses implement methods to return protocol-specific + * {@link EndPoint}s and {@link Connection}s.

    + */ +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]; + } + + /** + *

    Registers a channel to perform a non-blocking connect.

    + *

    The channel must be set in non-blocking mode, and {@link SocketChannel#connect(SocketAddress)} + * must be called prior to calling this method.

    + * + * @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)); + } + + /** + *

    Registers a channel to perform non-blocking read/write operations.

    + *

    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)}.

    + * + * @param channel the channel to register + */ + public void accept(final SocketChannel channel) + { + final ManagedSelector selector = chooseSelector(); + selector.submit(selector.new Accept(channel)); + } + + /** + *

    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)); + } + } + + /** + *

    Factory method for {@link ManagedSelector}.

    + * + * @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(); + } + + /** + *

    Callback method invoked when an endpoint is opened.

    + * + * @param endpoint the endpoint being opened + */ + protected void endPointOpened(EndPoint endpoint) + { + endpoint.onOpen(); + } + + /** + *

    Callback method invoked when an endpoint is closed.

    + * + * @param endpoint the endpoint being closed + */ + protected void endPointClosed(EndPoint endpoint) + { + endpoint.onClose(); + } + + /** + *

    Callback method invoked when a connection is opened.

    + * + * @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); + } + } + + /** + *

    Callback method invoked when a connection is closed.

    + * + * @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(); + } + + /** + *

    Callback method invoked when a non-blocking connect cannot be completed.

    + *

    By default it just logs with level warning.

    + * + * @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); + } + + /** + *

    Factory method to create {@link EndPoint}.

    + *

    This method is invoked as a result of the registration of a channel via {@link #connect(SocketChannel, Object)} + * or {@link #accept(SocketChannel)}.

    + * + * @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; + + /** + *

    Factory method to create {@link Connection}.

    + * + * @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 + } + + /** + *

    {@link ManagedSelector} wraps a {@link Selector} simplifying non-blocking operations on channels.

    + *

    {@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.

    + */ + public class ManagedSelector extends AbstractLifeCycle implements Runnable, Dumpable + { + private final AtomicReference _state= new AtomicReference<>(State.PROCESS); + private final Queue _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(); + } + } + + /** + *

    Submits a change to be executed in the selector thread.

    + *

    Changes may be submitted from any thread, and the selector thread woken up + * (if necessary) to execute the change.

    + * + * @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); + } + } + + /** + *

    Process changes and waits on {@link Selector#select()}.

    + * + * @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 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 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 dumps) + { + Selector selector = _selector; + Set 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 _dumps; + + private DumpKeys(List 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 + { + /** + *

    Callback method invoked when a read or write events has been detected by the {@link ManagedSelector} + * for this endpoint.

    + */ + 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 index 00000000..6898ddfb --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/io/UncheckedPrintWriter.java @@ -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 {@link + * java.lang.String#valueOf(boolean)} is translated into bytes + * according to the platform's default character encoding, and these bytes + * are written in exactly the manner of the {@link + * #write(int)} method. + * + * @param b + * The boolean 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 {@link + * #write(int)} method. + * + * @param c + * The char to be printed + */ + @Override + public void print(char c) + { + this.write(c); + } + + /* ------------------------------------------------------------ */ + /** + * Print an integer. The string produced by {@link + * java.lang.String#valueOf(int)} is translated into bytes according + * to the platform's default character encoding, and these bytes are written + * in exactly the manner of the {@link #write(int)} method. + * + * @param i + * The int 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 {@link + * java.lang.String#valueOf(long)} is translated into bytes according + * to the platform's default character encoding, and these bytes are written + * in exactly the manner of the {@link #write(int)} method. + * + * @param l + * The long 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 {@link + * java.lang.String#valueOf(float)} is translated into bytes + * according to the platform's default character encoding, and these bytes + * are written in exactly the manner of the {@link #write(int)} + * method. + * + * @param f + * The float 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 + * {@link java.lang.String#valueOf(double)} is translated into + * bytes according to the platform's default character encoding, and these + * bytes are written in exactly the manner of the {@link + * #write(int)} method. + * + * @param d + * The double 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 {@link #write(int)} + * method. + * + * @param s + * The array of chars to be printed + * + * @throws NullPointerException + * If s is null + */ + @Override + public void print(char s[]) + { + this.write(s); + } + + /* ------------------------------------------------------------ */ + /** + * Print a string. If the argument is null then the string + * "null" 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 + * {@link #write(int)} method. + * + * @param s + * The String 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 {@link + * java.lang.String#valueOf(Object)} method is translated into bytes + * according to the platform's default character encoding, and these bytes + * are written in exactly the manner of the {@link #write(int)} + * method. + * + * @param obj + * The Object 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 + * line.separator, and is not necessarily a single newline + * character ('\n'). + */ + @Override + public void println() + { + this.newLine(); + } + + /* ------------------------------------------------------------ */ + /** + * Print a boolean value and then terminate the line. This method behaves as + * though it invokes {@link #print(boolean)} and then + * {@link #println()}. + * + * @param x + * the boolean 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 {@link #print(char)} and then {@link + * #println()}. + * + * @param x + * the char 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 {@link #print(int)} and then {@link + * #println()}. + * + * @param x + * the int 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 {@link #print(long)} and then + * {@link #println()}. + * + * @param x + * the long 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 {@link #print(float)} and then + * {@link #println()}. + * + * @param x + * the float 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 {@link + * #print(double)} and then {@link #println()}. + * + * @param x + * the double 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 {@link #print(char[])} and then + * {@link #println()}. + * + * @param x + * the array of char 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 {@link #print(String)} and then + * {@link #println()}. + * + * @param x + * the String 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 {@link #print(Object)} and then + * {@link #println()}. + * + * @param x + * the Object 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 index 00000000..fccc6229 --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/io/WriteFlusher.java @@ -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. + *

    + */ +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> __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 = 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 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 PendingState 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 index 00000000..d08b4736 --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/io/WriterOutputStream.java @@ -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 index 00000000..6316823e --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/io/package-info.java @@ -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 index 00000000..ce3be14a --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/io/ssl/SslClientConnectionFactory.java @@ -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 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 index 00000000..ee9e449b --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/io/ssl/SslConnection.java @@ -0,0 +1,915 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// 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. + *

    + * 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). + *

    + * 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. + *

    + * 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)}. + *

    + * 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. + *

    + * 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 final SSLEngine _sslEngine; + 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 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._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 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 + 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(); + 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/package-info.java b/lib/jetty/org/eclipse/jetty/io/ssl/package-info.java new file mode 100644 index 00000000..f8676c3c --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/io/ssl/package-info.java @@ -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 index 00000000..bc049fd5 --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/security/AbstractUserAuthentication.java @@ -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 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 index 00000000..e36a3b30 --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/security/Authenticator.java @@ -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 + *

    + * 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 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 index 00000000..5cf1348a --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/security/ConstraintAware.java @@ -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 getConstraintMappings(); + Set getRoles(); + + /* ------------------------------------------------------------ */ + /** Set Constraint Mappings and roles. + * Can only be called during initialization. + * @param constraintMappings + * @param roles + */ + void setConstraintMappings(List constraintMappings, Set 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 index 00000000..dd99c5bf --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/security/ConstraintMapping.java @@ -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 index 00000000..201618d8 --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/security/ConstraintSecurityHandler.java @@ -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 _constraintMappings= new CopyOnWriteArrayList<>(); + private final Set _roles = new CopyOnWriteArraySet<>(); + private final PathMap> _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 with no roles + constraint.setName(name+"-Deny"); + constraint.setAuthenticate(true); + } + else + { + //Equivalent to no + constraint.setName(name+"-Permit"); + constraint.setAuthenticate(false); + } + } + else + { + //Equivalent to with list of s + constraint.setAuthenticate(true); + constraint.setRoles(rolesAllowed); + constraint.setName(name+"-RolesAllowed"); + } + + //Equivalent to //CONFIDENTIAL + constraint.setDataConstraint((transport.equals(TransportGuarantee.CONFIDENTIAL)?Constraint.DC_CONFIDENTIAL:Constraint.DC_NONE)); + return constraint; + } + + + + /* ------------------------------------------------------------ */ + /** + * @param pathSpec + * @param constraintMappings + */ + public static List getConstraintMappingsForPath(String pathSpec, List constraintMappings) + { + if (pathSpec == null || "".equals(pathSpec.trim()) || constraintMappings == null || constraintMappings.size() == 0) + return Collections.emptyList(); + + List mappings = new ArrayList(); + 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 removeConstraintMappingsForPath(String pathSpec, List constraintMappings) + { + if (pathSpec == null || "".equals(pathSpec.trim()) || constraintMappings == null || constraintMappings.size() == 0) + return Collections.emptyList(); + + List mappings = new ArrayList(); + 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 createConstraintsWithMappingsForPath (String name, String pathSpec, ServletSecurityElement securityElement) + { + List mappings = new ArrayList(); + + //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 methodOmissions = new ArrayList(); + + //make constraint mappings for this url for each of the HttpMethodConstraintElements + Collection methodConstraintElements = securityElement.getHttpMethodConstraints(); + if (methodConstraintElements != null) + { + for (HttpMethodConstraintElement methodConstraintElement:methodConstraintElements) + { + //Make a Constraint that captures the and 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 getConstraintMappings() + { + return _constraintMappings; + } + + /* ------------------------------------------------------------ */ + @Override + public Set 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 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 constraintMappings, Set 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 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 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 mappings = _constraintMap.get(mapping.getPathSpec()); + if (mappings == null) + { + mappings = new HashMap(); + _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 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 "<method>.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 + * <method>.omission, where the method of the Request is not named in the omission. + * @param mapping + * @param mappings + */ + protected void processConstraintMappingWithMethodOmissions (ConstraintMapping mapping, Map mappings) + { + String[] omissions = mapping.getMethodOmissions(); + StringBuilder sb = new StringBuilder(); + for (int i=0; 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 -: + *

      + *
    1. A mapping of an exact method name
    2. + *
    3. A mapping with key * that matches every method name
    4. + *
    5. Mappings with keys of the form "<method>.<method>.<method>.omission" that indicates it will match every method name EXCEPT those given
    6. + *
    + * + * @see org.eclipse.jetty.security.SecurityHandler#prepareConstraintInfo(java.lang.String, org.eclipse.jetty.server.Request) + */ + @Override + protected RoleInfo prepareConstraintInfo(String pathInContext, Request request) + { + Map mappings = (Map)_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 applicableConstraints = new ArrayList(); + + //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 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 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 getPathsWithUncoveredHttpMethods () + { + //if automatically denying uncovered methods, there are no uncovered methods + if (_denyUncoveredMethods) + return Collections.emptySet(); + + Set uncoveredPaths = new HashSet(); + + for (String path:_constraintMap.keySet()) + { + Map 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 ...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 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 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 <method>.<method>.omission + * split out the individual method names. + * + * @param omission + * @return + */ + protected Set getOmittedMethods (String omission) + { + if (omission == null || !omission.endsWith(OMISSION_SUFFIX)) + return Collections.emptySet(); + + String[] strings = omission.split("\\."); + Set methods = new HashSet(); + for (int i=0;i +{ + + 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 index 00000000..534a6d40 --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/security/DefaultAuthenticatorFactory.java @@ -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:
      + *
    • {@link org.eclipse.jetty.security.authentication.BasicAuthenticator}
    • + *
    • {@link org.eclipse.jetty.security.authentication.DigestAuthenticator}
    • + *
    • {@link org.eclipse.jetty.security.authentication.FormAuthenticator}
    • + *
    • {@link org.eclipse.jetty.security.authentication.ClientCertAuthenticator}
    • + *
    + * 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} + *

    + * 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 index 00000000..c8ae0c29 --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/security/DefaultIdentityService.java @@ -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 index 00000000..65e083d5 --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/security/DefaultUserIdentity.java @@ -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 index 00000000..8499a609 --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/security/HashCrossContextPsuedoSession.java @@ -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 implements CrossContextPsuedoSession +{ + private final String _cookieName; + + private final String _cookiePath; + + private final Random _random = new SecureRandom(); + + private final Map _data = new HashMap(); + + 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 index 00000000..335aabd7 --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/security/HashLoginService.java @@ -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. + *

    + * 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: + * + *

    + *  username: password [,rolename ...]
    + * 
    + * + * 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 index 00000000..99094c17 --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/security/IdentityService.java @@ -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 index 00000000..30b0a831 --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/security/JDBCLoginService.java @@ -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 roles = new ArrayList(); + + 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 index 00000000..1e64141f --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/security/LoginService.java @@ -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. + *

    + * 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 index 00000000..480131de --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/security/MappedLoginService.java @@ -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 _users=new ConcurrentHashMap(); + + /* ------------------------------------------------------------ */ + 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 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 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 index 00000000..7a8b5e75 --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/security/PropertyUserStore.java @@ -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. + * + *

    + *  username: password [,rolename ...]
    + * 
    + * + * 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 _knownUsers = new ArrayList(); + private final Map _knownUserIdentities = new HashMap(); + private List _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 known = new HashSet(); + + for (Map.Entry 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 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 dirList = new ArrayList(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 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 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 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(); + } + _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 index 00000000..55f1ae2e --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/security/RoleInfo.java @@ -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 _roles = new CopyOnWriteArraySet(); + + 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 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 index 00000000..13a7ea74 --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/security/RoleRunAsToken.java @@ -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 index 00000000..639c9726 --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/security/RunAsToken.java @@ -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 index 00000000..a6e108e9 --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/security/SecurityHandler.java @@ -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. + *

    + * 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. + *

    + * 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 _initParameters=new HashMap(); + 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 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 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 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. + *

    + * 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 index 00000000..85f532fa --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/security/ServerAuthException.java @@ -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 index 00000000..ba160f06 --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/security/SpnegoLoginService.java @@ -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 index 00000000..13cf0bb1 --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/security/SpnegoUserIdentity.java @@ -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 _roles; + + public SpnegoUserIdentity( Subject subject, Principal principal, List 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 index 00000000..3fe9445f --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/security/SpnegoUserPrincipal.java @@ -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 index 00000000..9174a06c --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/security/UserAuthentication.java @@ -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 index 00000000..c288e1dc --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/security/UserDataConstraint.java @@ -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 index 00000000..caa784f0 --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/security/authentication/BasicAuthenticator.java @@ -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 index 00000000..9694cf15 --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/security/authentication/ClientCertAuthenticator.java @@ -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 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 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 index 00000000..df0f7d92 --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/security/authentication/DeferredAuthentication.java @@ -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 getHeaderNames() + { + return Collections.emptyList(); + } + + @Override + public String getHeader(String arg0) + { + return null; + } + + @Override + public Collection 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 index 00000000..13537e6c --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/security/authentication/DigestAuthenticator.java @@ -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 _nonceMap = new ConcurrentHashMap(); + private Queue _nonceQueue = new ConcurrentLinkedQueue(); + 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=_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 index 00000000..dcfea416 --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/security/authentication/FormAuthenticator.java @@ -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. + * + *

    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.

    + * + *

    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.

    + * + * + */ +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 j_post = (MultiMap)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 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 getHeaderNames() + { + return Collections.enumeration(Collections.list(super.getHeaderNames())); + } + + @Override + public Enumeration getHeaders(String name) + { + if (name.toLowerCase(Locale.ENGLISH).startsWith("if-")) + return Collections.enumeration(Collections.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 index 00000000..9a1940d8 --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/security/authentication/LoginAuthenticator.java @@ -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:
      + *
    • A session exists. + *
    • The {@link AuthConfiguration#isSessionRenewedOnAuthentication()} returns true. + *
    • The session ID has been given to unauthenticated responses + *
    + * @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 index 00000000..e123b221 --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/security/authentication/LoginCallback.java @@ -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 index 00000000..5939caf7 --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/security/authentication/LoginCallbackImpl.java @@ -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 index 00000000..e3be5846 --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/security/authentication/SessionAuthentication.java @@ -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 index 00000000..8469c0a8 --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/security/authentication/SpnegoAuthenticator.java @@ -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 index 00000000..0e0c617b --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/security/authentication/package-info.java @@ -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 index 00000000..edb5ea4e --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/security/package-info.java @@ -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 index 00000000..96fb3a2d --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/server/AbstractConnectionFactory.java @@ -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 index 00000000..7d3402fb --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/server/AbstractConnector.java @@ -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; + +/** + *

    An abstract implementation of {@link Connector} that provides a {@link ConnectionFactory} mechanism + * for creating {@link Connection} instances for various protocols (HTTP, SSL, SPDY, etc).

    + * + *

    Connector Services

    + * The abstract connector manages the dependent services needed by all specific connector instances: + *
      + *
    • 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. + *
    • + *
    • 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. + *
    • + *
    • 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. + *
    • + *
    + * These services are managed as aggregate beans by the {@link ContainerLifeCycle} super class and + * may either be managed or unmanaged beans. + * + *

    Connection Factories

    + * 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. + * + *

    Configuring Connection Factories

    + * 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. + *

    + * 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. + *

    + * 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. + * + *

    Connection Factory Operation

    + * {@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. + *

    + * {@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. + *

    + * {@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. + *

    + *

    Acceptors

    + * 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: + * + *
  • block waiting for new connections + *
  • accept the connection (eg socket accept) + *
  • perform any configuration of the connection (eg. socket linger times) + *
  • call the {@link #getDefaultConnectionFactory()} {@link ConnectionFactory#newConnection(Connector, org.eclipse.jetty.io.EndPoint)} + * method to create a new Connection instance. + * + * 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 _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 _endpoints = Collections.newSetFromMap(new ConcurrentHashMap()); + private final Set _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; + } + + /** + *

    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.

    + *

    The max idle time is applied:

    + *
      + *
    • When waiting for a new message to be received on a connection
    • + *
    • When waiting for a new message to be sent on a connection
    • + *
    + *

    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.

    + * + * @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 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 getConnectionFactory(Class 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 getConnectionFactories() + { + synchronized (_factories) + { + return _factories.values(); + } + } + + public void setConnectionFactories(Collection factories) + { + synchronized (_factories) + { + List 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 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 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 index 00000000..dcaecbe5 --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/server/AbstractNCSARequestLog.java @@ -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 _buffers = new ThreadLocal() + { + @Override + protected StringBuilder initialValue() + { + return new StringBuilder(256); + } + }; + + + private String[] _ignorePaths; + private boolean _extended; + private transient PathMap _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 index 00000000..08b65bc4 --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/server/AbstractNetworkConnector.java @@ -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. + *

    + * 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 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 index 00000000..cc0eec89 --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/server/AsyncContextEvent.java @@ -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 index 00000000..6503424d --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/server/AsyncContextState.java @@ -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 createListener(Class 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 index 00000000..33ba21d7 --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/server/AsyncNCSARequestLog.java @@ -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 _queue; + private transient WriterThread _thread; + private boolean _warnedFull; + + public AsyncNCSARequestLog() + { + this(null,null); + } + + public AsyncNCSARequestLog(BlockingQueue queue) + { + this(null,queue); + } + + public AsyncNCSARequestLog(String filename) + { + this(filename,null); + } + + public AsyncNCSARequestLog(String filename,BlockingQueue 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 index 00000000..ccdf4c0f --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/server/Authentication.java @@ -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. + *

    + * 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. + *

    + * 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 + *

    + * 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. + *

    + * 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. + *

    + * 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 index 00000000..cd2d12e8 --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/server/ByteBufferQueuedHttpInput.java @@ -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; + +/** + *

    An implementation of HttpInput using {@link ByteBuffer} as items.

    + */ +public class ByteBufferQueuedHttpInput extends QueuedHttpInput +{ + @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 index 00000000..466775e4 --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/server/ClassLoaderDump.java @@ -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 index 00000000..e23739d8 --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/server/ConnectionFactory.java @@ -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; + +/** + *

    A Factory to create {@link Connection} instances for {@link Connector}s.

    + *

    A Connection factory is responsible for instantiating and configuring a {@link Connection} instance + * to handle an {@link EndPoint} accepted by a {@link Connector}.

    + *

    + * A ConnectionFactory has a protocol name that represents the protocol of the Connections + * created. Example of protocol names include:

    + *
    http
    Creates a HTTP connection that can handle multiple versions of HTTP from 0.9 to 1.1
    + *
    spdy/2
    Creates a HTTP connection that handles a specific version of the SPDY protocol
    + *
    SSL-XYZ
    Create an SSL connection chained to a connection obtained from a connection factory + * with a protocol "XYZ".
    + *
    SSL-http
    Create an SSL connection chained to a HTTP connection (aka https)
    + *
    SSL-npn
    Create an SSL connection chained to a NPN connection, that uses a negotiation with + * the client to determine the next protocol.
    + *
    + */ +public interface ConnectionFactory +{ + /* ------------------------------------------------------------ */ + /** + * @return A string representing the protocol name. + */ + public String getProtocol(); + + /** + *

    Creates a new {@link Connection} with the given parameters

    + * @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 index 00000000..ce4544c9 --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/server/Connector.java @@ -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; + +/** + *

    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.

    + */ +@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 getConnectionFactory(Class factoryType); + + /** + * @return the default {@link ConnectionFactory} associated with the default protocol name + */ + public ConnectionFactory getDefaultConnectionFactory(); + + public Collection getConnectionFactories(); + + public List 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 getConnectedEndPoints(); + + + /* ------------------------------------------------------------ */ + /** + * Get the connector name if set. + *

    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 index 00000000..cae5e6eb --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/server/ConnectorStatistics.java @@ -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 _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 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 index 00000000..703f8049 --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/server/CookieCutter.java @@ -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 + *

    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 _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 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 index 00000000..25672897 --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/server/Dispatcher.java @@ -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 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 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 getAttributeNames() + { + HashSet set=new HashSet<>(); + Enumeration 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 getAttributeNames() + { + HashSet set=new HashSet<>(); + Enumeration 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 index 00000000..88301d4b --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/server/EncodingHttpWriter.java @@ -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 index 00000000..891e8ee1 --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/server/ForwardedRequestCustomizer.java @@ -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. + *

    + * This customizer looks at at HTTP request for headers that indicate + * it has been forwarded by one or more proxies. Specifically handled are: + *

      + *
    • X-Forwarded-Host
    • + *
    • X-Forwarded-Server
    • + *
    • X-Forwarded-For
    • + *
    • X-Forwarded-Proto
    • + *
    + *

    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

    + *

    Headers can also be defined so that forwarded SSL Session IDs and Cipher + * suites may be customised

    + * @see Wikipedia: X-Forwarded-For + */ +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 index 00000000..cfe7b9a2 --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/server/Handler.java @@ -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:
      + *
    • Completely generate the HTTP Response
    • + *
    • Examine/modify the request and call another Handler (see {@link HandlerWrapper}). + *
    • Pass the request to one or more other Handlers (see {@link HandlerCollection}). + *
    + * + * 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 index 00000000..51fd4791 --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/server/HandlerContainer.java @@ -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. + *

    + * 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 getChildHandlerByClass(Class 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 index 00000000..b39b25dc --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/server/HostHeaderCustomizer.java @@ -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). + *

    + * 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. + *

    + * 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 index 00000000..07b09ab7 --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/server/HttpChannel.java @@ -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 implements HttpParser.RequestHandler, Runnable, HttpParser.ProxyHandler +{ + private static final Logger LOG = Log.getLogger(HttpChannel.class); + private static final ThreadLocal> __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 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 HttpChannellast = 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()Sends an error 500, performing a special logic to detect whether the request is suspended, + * to avoid concurrent writes from the application.

    + *

    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.

    + * + * @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 input = (HttpInput)_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 + 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(); + } + + /** + *

    Non-Blocking write, committing the response if needed.

    + * + * @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 index 00000000..7ff4d5a2 --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/server/HttpChannelState.java @@ -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 _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 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 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 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 index 00000000..352af25c --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/server/HttpConfiguration.java @@ -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. + *

    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).

    + *

    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. + *

    + */ +@ManagedObject("HTTP Configuration") +public class HttpConfiguration +{ + public static final String SERVER_VERSION = "Jetty(" + Jetty.VERSION + ")"; + + private List _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; + } + + /* ------------------------------------------------------------ */ + /** + *

    Add a {@link Customizer} that is invoked for every + * request received.

    + *

    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 getCustomizers() + { + return _customizers; + } + + public T getCustomizer(Class 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; + } + + /* ------------------------------------------------------------ */ + /** + *

    Set the {@link Customizer}s that are invoked for every + * request received.

    + *

    Customisers are often used to interpret optional headers (eg {@link ForwardedRequestCustomizer}) or + * optional protocol semantics (eg {@link SecureRequestCustomizer}). + * @param customizers + */ + public void setCustomizers(List 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. + *

    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.

    + * @param requestHeaderSize Max header size in bytes + */ + public void setRequestHeaderSize(int requestHeaderSize) + { + _requestHeaderSize = requestHeaderSize; + } + + /* ------------------------------------------------------------ */ + /** Set the maximum size of a response header. + * + *

    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.

    + * @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 index 00000000..16cbbf93 --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/server/HttpConnection.java @@ -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; + +/** + *

    A {@link Connection} that handles the HTTP protocol.

    + */ +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 __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 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 newHttpInput() + { + return new HttpInputOverHTTP(this); + } + + protected HttpChannelOverHttp newHttpChannel(HttpInput httpInput) + { + return new HttpChannelOverHttp(_connector, _config, getEndPoint(), this, httpInput); + } + + protected HttpParser newHttpParser() + { + return new HttpParser(newRequestHandler(), getHttpConfiguration().getRequestHeaderSize()); + } + + protected HttpParser.RequestHandler 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; + } + + /** + *

    Parses and handles HTTP messages.

    + *

    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.

    + *

    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.

    + */ + @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 + { + public HttpChannelOverHttp(Connector connector, HttpConfiguration config, EndPoint endPoint, HttpTransport transport, HttpInput 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 index 00000000..ce6ffdb7 --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/server/HttpConnectionFactory.java @@ -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. + *

    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 index 00000000..833ecded --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/server/HttpInput.java @@ -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}. + *

    + * 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 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. + *

    + * 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. + *

    + * 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 index 00000000..35524500 --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/server/HttpInputOverHTTP.java @@ -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 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 index 00000000..7c0fd899 --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/server/HttpOutput.java @@ -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; + +/** + *

    {@link HttpOutput} implements {@link ServletOutputStream} + * as required by the Servlet specification.

    + *

    {@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.

    + *

    {@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.

    + */ +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 _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 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 index 00000000..cb81d3c9 --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/server/InclusiveByteRange.java @@ -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. + *
    + * 
    + *   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
    + * 
    + * 
    + * + * Based on RFC2616 3.12, 14.16, 14.35.1, 14.35.2 + *

    + * 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 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 index 00000000..b4cdf8ff --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/server/Iso88591HttpWriter.java @@ -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 index 00000000..57962263 --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/server/LocalConnector.java @@ -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 _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. + *

    + * 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. + *

    + * 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. + *

    + * 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. + *

    + * 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 index 00000000..1aad488e --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/server/LowResourceMonitor.java @@ -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 + *

    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:

      + *
    • {@link ThreadPool#isLowOnThreads()} if {@link Connector#getExecutor()} is + * an instance of {@link ThreadPool} and {@link #setMonitorThreads(boolean)} is true.
    • + *
    • 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()}
    • + *
    • If {@link #setMaxConnections(int)} is non zero then low resources is dected if the total number + * of connections exceeds {@link #getMaxConnections()}
    • + *
    + *

    + *

    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()}. + *

    + */ +@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 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 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 index 00000000..2c2286e2 --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/server/NCSARequestLog.java @@ -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 index 00000000..3d010699 --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/server/NegotiatingServerConnection.java @@ -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 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 protocols, String defaultProtocol) + { + super(endPoint, connector.getExecutor()); + this.connector = connector; + this.protocols = protocols; + this.defaultProtocol = defaultProtocol; + this.engine = engine; + } + + protected List 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 index 00000000..f826d711 --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/server/NegotiatingServerConnectionFactory.java @@ -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 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 getProtocols() + { + return protocols; + } + + @Override + public Connection newConnection(Connector connector, EndPoint endPoint) + { + List protocols = this.protocols; + if (protocols.isEmpty()) + { + protocols = connector.getProtocols(); + Iterator 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 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 index 00000000..58fdc988 --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/server/NetworkConnector.java @@ -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; + +/** + *

    A {@link Connector} for TCP/IP network connectors

    + */ +public interface NetworkConnector extends Connector, Closeable +{ + /** + *

    Performs the activities needed to open the network communication + * (for example, to start accepting incoming network connections).

    + * + * @throws IOException if this connector cannot be opened + * @see #close() + */ + void open() throws IOException; + + /** + *

    Performs the activities needed to close the network communication + * (for example, to stop accepting network connections).

    + * 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 index 00000000..34de615b --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/server/NetworkTrafficServerConnector.java @@ -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; + +/** + *

    A specialized version of {@link ServerConnector} that supports {@link NetworkTrafficListener}s.

    + *

    {@link NetworkTrafficListener}s can be added and removed dynamically before and after this connector has + * been started without causing {@link java.util.ConcurrentModificationException}s.

    + */ +public class NetworkTrafficServerConnector extends ServerConnector +{ + private final List 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 index 00000000..881fbfa0 --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/server/QueuedHttpInput.java @@ -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)}. + *

    + * {@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. + *

    + * 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 extends HttpInput +{ + private final static Logger LOG = Log.getLogger(QueuedHttpInput.class); + + private final ArrayQueue _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 index 00000000..5221d638 --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/server/QuietServletException.java @@ -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. + *

    + * Used for container generated exceptions that need only a message rather + * than a stack trace. + *

    + */ +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 index 00000000..77c80abc --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/server/Request.java @@ -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. + *

    + * Implements {@link javax.servlet.http.HttpServletRequest} from the javax.servlet.http package. + *

    + *

    + * 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 + *

      + * + *
    • 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.
    • + * + *
    • 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.
    • + * + *
    • The {@link Request#getServletPath()} method will return null until the request has been passed to a org.eclipse.jetty.servlet.ServletHandler + * and the pathInfo matched against the servlet URL patterns and {@link Request#setServletPath(String)} called as a result.
    • + *
    + * + * 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. + * + *

    + * 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 __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 _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 _queryParameters; + private MultiMap _contentParameters; + private MultiMap _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 _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 extractQueryParameters() + { + MultiMap 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 extractContentParameters() + { + MultiMap 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 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 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. + *

    Also supports jetty specific attributes to gain access to Jetty APIs: + *

    + *
    org.eclipse.jetty.server.Server
    The Jetty Server instance
    + *
    org.eclipse.jetty.server.HttpChannel
    The HttpChannel for this request
    + *
    org.eclipse.jetty.server.HttpConnection
    The HttpConnection or null if another transport is used
    + *
    + * While these attributes may look like security problems, they are exposing nothing that is not already + * available via reflection from a Request instance. + *

    + * @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 getAttributeNames() + { + if (_attributes == null) + return Collections.enumeration(Collections.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 null 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 getHeaderNames() + { + return _fields.getFieldNames(); + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.http.HttpServletRequest#getHeaders(java.lang.String) + */ + @Override + public Enumeration getHeaders(String name) + { + Enumeration e = _fields.getValues(name); + if (e == null) + return Collections.enumeration(Collections.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 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 getLocales() + { + + Enumeration 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 acceptLanguage = HttpFields.qualityList(enm); + + if (acceptLanguage.size() == 0) + return Collections.enumeration(__defaultLocale); + + List 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 getParameterMap() + { + if (!_paramsExtracted) + extractParameters(); + if (_parameters == null) + _parameters = restoreParameters(); + return Collections.unmodifiableMap(_parameters.toStringArrayMap()); + } + + /* ------------------------------------------------------------ */ + /* + * @see javax.servlet.ServletRequest#getParameterNames() + */ + @Override + public Enumeration 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 vals = _parameters.getValues(name); + if (vals == null) + return null; + return vals.toArray(new String[vals.size()]); + } + + private MultiMap restoreParameters() + { + MultiMap result = new MultiMap<>(); + if (_queryParameters == null) + _queryParameters = extractQueryParameters(); + result.addAllValues(_queryParameters); + result.addAllValues(_contentParameters); + return result; + } + + public MultiMap getQueryParameters() + { + return _queryParameters; + } + + public void setQueryParameters(MultiMap queryParameters) + { + _queryParameters = queryParameters; + } + + public void setContentParameters(MultiMap 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. + *

    + * Because this method returns a StringBuffer, 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 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 getParts(MultiMap 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 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() : 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 newQueryParams = new MultiMap<>(); + // Have to assume ENCODING because we can't know otherwise. + UrlEncoded.decodeTo(newQuery, newQueryParams, UrlEncoded.ENCODING, -1); + + MultiMap oldQueryParams = _queryParameters; + if (oldQueryParams == null && _queryString != null) + { + oldQueryParams = new MultiMap<>(); + UrlEncoded.decodeTo(_queryString, oldQueryParams, getQueryEncoding(), -1); + } + + MultiMap 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> 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 upgrade(Class 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 index 00000000..0cb1a530 --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/server/RequestLog.java @@ -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 RequestLog 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 index 00000000..e39c4a60 --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/server/ResourceCache.java @@ -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 _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(); + _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 pathInContext, 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 sorted= new TreeSet( + new Comparator() + { + public int compare(Content c1, Content c2) + { + if (c1._lastAccessedc2._lastAccessed) + return 1; + + if (c1._length _indirectBuffer=new AtomicReference(); + AtomicReference _directBuffer=new AtomicReference(); + + /* ------------------------------------------------------------ */ + 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 index 00000000..0408c882 --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/server/Response.java @@ -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; + +/** + *

    {@link Response} provides the implementation for {@link HttpServletResponse}.

    + */ +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 __cookieBuilder = new ThreadLocal() + { + @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 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=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, "&", "&"); + message= StringUtil.replace(message, "<", "<"); + message= StringUtil.replace(message, ">", ">"); + } + String uri= request.getRequestURI(); + if (uri!=null) + { + uri= StringUtil.replace(uri, "&", "&"); + uri= StringUtil.replace(uri, "<", "<"); + uri= StringUtil.replace(uri, ">", ">"); + } + + writer.write("\n\n\n"); + writer.write("Error "); + writer.write(Integer.toString(code)); + writer.write(' '); + if (message==null) + writer.write(message); + writer.write("\n\n\n

    HTTP ERROR: "); + writer.write(Integer.toString(code)); + writer.write("

    \n

    Problem accessing "); + writer.write(uri); + writer.write(". Reason:\n

        ");
    +                    writer.write(message);
    +                    writer.write("
    "); + writer.write("

    \n
    Powered by Jetty://"); + writer.write("\n\n\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 getHeaderNames() + { + final HttpFields fields = _fields; + return fields.getFieldNamesCollection(); + } + + @Override + public String getHeader(String name) + { + return _fields.getStringField(name); + } + + @Override + public Collection getHeaders(String name) + { + final HttpFields fields = _fields; + Collection 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 cookieValues = new ArrayList(5); + Enumeration 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 index 00000000..b0174613 --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/server/SecureRequestCustomizer.java @@ -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.
    + * This allows the required attributes to be set for SSL requests.
    + * The requirements of the Servlet specs are: + *
      + *
    • an attribute named "javax.servlet.request.ssl_session_id" of type + * String (since Servlet Spec 3.0).
    • + *
    • an attribute named "javax.servlet.request.cipher_suite" of type + * String.
    • + *
    • an attribute named "javax.servlet.request.key_size" of type Integer.
    • + *
    • 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. + *
    • + *
    + * + * @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 index 00000000..b1437d6d --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/server/Server.java @@ -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 _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 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> 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 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 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 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 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 index 00000000..a7d5fe0f --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/server/ServerConnector.java @@ -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. + *

    + * 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. + *

    + *

    Connection Factories

    + * 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. + *

    + *

    Selectors

    + * 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. + *

    + * 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. + *

    Construct a ServerConnector with a private instance of {@link HttpConnectionFactory} as the only factory.

    + * @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. + *

    Construct a ServerConnector with a private instance of {@link HttpConnectionFactory} as the only factory.

    + * @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. + *

    Construct a Server Connector with the passed Connection factories.

    + * @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. + *

    Construct a ServerConnector with a private instance of {@link HttpConnectionFactory} as the primary protocol

    . + * @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. + *

    Construct a ServerConnector with a private instance of {@link HttpConnectionFactory} as the primary protocol

    . + * @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; + } + + /** + *

    Sets whether this connector uses a channel inherited from the JVM.

    + *

    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}.

    + *

    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.

    + * + * @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 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 index 00000000..11f249e1 --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/server/ServletRequestHttpWrapper.java @@ -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 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 upgrade(Class 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 index 00000000..076ab1b2 --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/server/ServletResponseHttpWrapper.java @@ -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 getHeaderNames() + { + return null; + } + + /** + * @see javax.servlet.http.HttpServletResponse#getHeaders(java.lang.String) + */ + public Collection 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 index 00000000..ef7ee623 --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/server/SessionIdManager.java @@ -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 index 00000000..267391b5 --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/server/SessionManager.java @@ -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 JSESSIONID, but can be set with the + * org.eclipse.jetty.servlet.SessionCookie 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 jsessionid, but can be set with the + * org.eclipse.jetty.servlet.SessionIdPathParameterName 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 HttpSession with the given session id + * + * @param id the session id + * @return the HttpSession with the corresponding id or null if no session with the given id exists + */ + public HttpSession getHttpSession(String id); + + /* ------------------------------------------------------------ */ + /** + * Creates a new HttpSession. + * + * @param request the HttpServletRequest containing the requested session id + * @return the new HttpSession + */ + 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 SessionHandler 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 SessionManager 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 session. If cookies are not in use, this method returns null. + */ + 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 getDefaultSessionTrackingModes(); + + public Set getEffectiveSessionTrackingModes(); + + public void setSessionTrackingModes(Set 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 index 00000000..abc6d61e --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/server/ShutdownMonitor.java @@ -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. + *

    + * 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. + *

    + * If the stop port is set to zero, then a random port is assigned and the port number is printed to stdout. + *

    + * 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. + *

    + * STOP.PORT = the port to listen on (empty, null, or values less than 0 disable the stop ability)
    + * STOP.KEY = the magic key/passphrase to allow the stop (defaults to "eclipse")
    + *

    + * 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 index 00000000..5fcc1038 --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/server/SslConnectionFactory.java @@ -0,0 +1,102 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// 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.util.annotation.Name; +import org.eclipse.jetty.util.ssl.SslContextFactory; + +public class SslConnectionFactory extends AbstractConnectionFactory +{ + 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); + } + + @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 index 00000000..4e20331d --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/server/UserIdentity.java @@ -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 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 index 00000000..cbd1adb9 --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/server/Utf8HttpWriter.java @@ -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 index 00000000..3d92512c --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/server/handler/AbstractHandler.java @@ -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 index 00000000..0d54e536 --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/server/handler/AbstractHandlerContainer.java @@ -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 list=new ArrayList<>(); + expandChildren(list,null); + return list.toArray(new Handler[list.size()]); + } + + /* ------------------------------------------------------------ */ + @Override + public Handler[] getChildHandlersByClass(Class byclass) + { + List list=new ArrayList<>(); + expandChildren(list,byclass); + return list.toArray(new Handler[list.size()]); + } + + /* ------------------------------------------------------------ */ + @Override + public T getChildHandlerByClass(Class byclass) + { + List list=new ArrayList<>(); + expandChildren(list,byclass); + if (list.isEmpty()) + return null; + return (T)list.get(0); + } + + /* ------------------------------------------------------------ */ + protected void expandChildren(List list, Class byClass) + { + } + + /* ------------------------------------------------------------ */ + protected void expandHandler(Handler handler, List 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 findContainerOf(HandlerContainer root,Classtype, 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 index 00000000..c3d8b593 --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/server/handler/AllowSymLinkAliasChecker.java @@ -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. + *

    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.

    + */ +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 index 00000000..397cde1f --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/server/handler/ContextHandler.java @@ -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. + * + *

    + * 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. + *

    + * 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)} + *

    + * 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 = new ThreadLocal(); + + /** + * 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 _initParams; + private ClassLoader _classLoader; + private String _contextPath = "/"; + + private String _displayName; + + private Resource _baseResource; + private MimeTypes _mimeTypes; + private Map _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 _eventListeners=new CopyOnWriteArrayList<>(); + private final List _programmaticListeners=new CopyOnWriteArrayList<>(); + private final List _contextListeners=new CopyOnWriteArrayList<>(); + private final List _contextAttributeListeners=new CopyOnWriteArrayList<>(); + private final List _requestListeners=new CopyOnWriteArrayList<>(); + private final List _requestAttributeListeners=new CopyOnWriteArrayList<>(); + private final List _durableListeners = new CopyOnWriteArrayList<>(); + private Map _managedAttributes; + private String[] _protectedTargets; + private final CopyOnWriteArrayList _aliasChecks = new CopyOnWriteArrayList(); + + public enum Availability { UNAVAILABLE,STARTING,AVAILABLE,SHUTDOWN,}; + private volatile Availability _availability; + + /* ------------------------------------------------------------ */ + /** + * + */ + public ContextHandler() + { + super(); + _scontext = new Context(); + _attributes = new AttributesMap(); + _initParams = new HashMap(); + addAliasCheck(new ApproveNonExistentDirectoryAliases()); + } + + /* ------------------------------------------------------------ */ + /** + * + */ + protected ContextHandler(Context context) + { + super(); + _scontext = context; + _attributes = new AttributesMap(); + _initParams = new HashMap(); + 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 currentVirtualHosts = null; + if (_vhosts != null) + { + currentVirtualHosts = new ArrayList(Arrays.asList(_vhosts)); + } + else + { + currentVirtualHosts = new ArrayList(); + } + + 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 existingVirtualHosts = new ArrayList(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 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 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 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[] attributes = managedAttributes.split(","); + for (String attribute : attributes) + _managedAttributes.put(attribute,null); + + Enumeration 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 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 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 e = _attributes.getAttributeNames(); + while (e.hasMoreElements()) + { + String name = e.nextElement(); + checkManagedAttribute(name,attributes.getAttribute(name)); + } + } + + /* ------------------------------------------------------------ */ + @Override + public void clearAttributes() + { + Enumeration 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 + * setBaseResource(newResource(resourceBase)); + */ + 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 The Servlet Specification + * @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(); + _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 Locale value + * @return a String 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 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 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 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 set = new HashSet(); + 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 getAliasChecks() + { + return _aliasChecks; + } + + /* ------------------------------------------------------------ */ + /** + * @param checks list of AliasCheck instances + */ + public void setAliasChecks(List checks) + { + _aliasChecks.clear(); + _aliasChecks.addAll(checks); + } + + /* ------------------------------------------------------------ */ + /** + * Context. + *

    + * A partial implementation of {@link javax.servlet.ServletContext}. A complete implementation is provided by the derived {@link ContextHandler}. + *

    + * + * + */ + 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 contexts = new ArrayList(); + 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 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 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 getAttributeNames() + { + HashSet set = new HashSet(); + Enumeration 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 clazz = _classLoader==null?Loader.loadClass(ContextHandler.class,className):_classLoader.loadClass(className); + addListener(clazz); + } + catch (ClassNotFoundException e) + { + throw new IllegalArgumentException(e); + } + } + + @Override + public 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 listenerClass) + { + if (!_enabled) + throw new UnsupportedOperationException(); + + try + { + EventListener e = createListener(listenerClass); + addListener(e); + } + catch (ServletException e) + { + throw new IllegalArgumentException(e); + } + } + + @Override + public T createListener(Class clazz) throws ServletException + { + try + { + return createInstance(clazz); + } + catch (Exception e) + { + throw new ServletException(e); + } + } + + + public void checkListener (Class listener) throws IllegalStateException + { + boolean ok = false; + int startIndex = (isExtendedListenerTypes()?EXTENDED_LISTENER_TYPE_INDEX:DEFAULT_LISTENER_TYPE_INDEX); + for (int i=startIndex;i T createInstance (Class 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 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 getServletNames() + { + return Collections.enumeration(Collections.EMPTY_LIST); + } + + @SuppressWarnings("unchecked") + @Override + @Deprecated + public Enumeration 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 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 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 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 createFilter(Class c) throws ServletException + { + LOG.warn(__unimplmented); + return null; + } + + @Override + public T createServlet(Class c) throws ServletException + { + LOG.warn(__unimplmented); + return null; + } + + @Override + public Set getDefaultSessionTrackingModes() + { + LOG.warn(__unimplmented); + return null; + } + + @Override + public Set getEffectiveSessionTrackingModes() + { + LOG.warn(__unimplmented); + return null; + } + + @Override + public FilterRegistration getFilterRegistration(String filterName) + { + LOG.warn(__unimplmented); + return null; + } + + @Override + public Map getFilterRegistrations() + { + LOG.warn(__unimplmented); + return null; + } + + @Override + public ServletRegistration getServletRegistration(String servletName) + { + LOG.warn(__unimplmented); + return null; + } + + @Override + public Map getServletRegistrations() + { + LOG.warn(__unimplmented); + return null; + } + + @Override + public SessionCookieConfig getSessionCookieConfig() + { + LOG.warn(__unimplmented); + return null; + } + + @Override + public void setSessionTrackingModes(Set sessionTrackingModes) + { + LOG.warn(__unimplmented); + } + + @Override + public void addListener(String className) + { + LOG.warn(__unimplmented); + } + + @Override + public void addListener(T t) + { + LOG.warn(__unimplmented); + } + + @Override + public void addListener(Class listenerClass) + { + LOG.warn(__unimplmented); + } + + @Override + public T createListener(Class 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() _contexts; + private Class _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 trie; + loop: while(true) + { + trie=new ArrayTernaryTrie<>(false,capacity); + + Handler[] branches = getHandlers(); + + // loop over each group of contexts + for (int b=0;branches!=null && b0) + 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 getContextClass() + { + return _contextClass; + } + + + /* ------------------------------------------------------------ */ + /** + * @param contextClass The class to use to add new Contexts + */ + public void setContextClass(Class 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 index 00000000..0ed72612 --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/server/handler/DebugHandler.java @@ -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 index 00000000..f71720af --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/server/handler/DefaultHandler.java @@ -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("\n\nError 404 - Not Found"); + writer.write("\n\n

    Error 404 - Not Found.

    \n"); + writer.write("No context on this server matched or handled this request.
    "); + writer.write("Contexts known to this server are:
    "); + writer.write(" "); + writer.write("Powered by Jetty:// Java Web Server
    \n"); + + writer.write("\n\n\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 index 00000000..b1af5208 --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/server/handler/ErrorHandler.java @@ -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("\n\n"); + writeErrorPageHead(request,writer,code,message); + writer.write("\n"); + writeErrorPageBody(request,writer,code,message,showStacks); + writer.write("\n\n\n"); + } + + /* ------------------------------------------------------------ */ + protected void writeErrorPageHead(HttpServletRequest request, Writer writer, int code, String message) + throws IOException + { + writer.write("\n"); + writer.write("Error "); + writer.write(Integer.toString(code)); + + if (_showMessageInTitle) + { + writer.write(' '); + write(writer,message); + } + writer.write("\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("
    Powered by Jetty://
    \n"); + } + + /* ------------------------------------------------------------ */ + protected void writeErrorPageMessage(HttpServletRequest request, Writer writer, int code, String message,String uri) + throws IOException + { + writer.write("

    HTTP ERROR "); + writer.write(Integer.toString(code)); + writer.write("

    \n

    Problem accessing "); + write(writer,uri); + writer.write(". Reason:\n

        ");
    +        write(writer,message);
    +        writer.write("

    "); + } + + /* ------------------------------------------------------------ */ + protected void writeErrorPageStacks(HttpServletRequest request, Writer writer) + throws IOException + { + Throwable th = (Throwable)request.getAttribute("javax.servlet.error.exception"); + while(th!=null) + { + writer.write("

    Caused by:

    ");
    +            StringWriter sw = new StringWriter();
    +            PrintWriter pw = new PrintWriter(sw);
    +            th.printStackTrace(pw);
    +            pw.flush();
    +            write(writer,sw.getBuffer().toString());
    +            writer.write("
    \n"); + + th =th.getCause(); + } + } + + /* ------------------------------------------------------------ */ + /** Bad Message Error body + *

    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("

    Bad Message " + status + "

    reason: " + reason + "
    "); + } + + /* ------------------------------------------------------------ */ + /** 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' : + writer.write(">"); + 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 index 00000000..c118c7ec --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/server/handler/HandlerCollection.java @@ -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. + *

    + * 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. + *

    + * + */ +@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 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 index 00000000..74320b08 --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/server/handler/HandlerList.java @@ -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;iHandlerWrapper 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 Decorator 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 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 index 00000000..162a719e --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/server/handler/HotSwapHandler.java @@ -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 HandlerContainer 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 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 index 00000000..97a3ac8d --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/server/handler/IPAccessHandler.java @@ -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 + *

    + * 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. + *

    + * Typically, the black/white lists will be used in one of three modes: + *

      + *
    • Blocking a few specific IPs/URLs by specifying several black list entries. + *
    • Allowing only some specific IPs/URLs by specifying several white lists entries. + *
    • 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 + *
    + *

    + * 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. + *

    + *

    + * 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. + *

    + * 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. + *

    + *
    + * 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
    + * 
    + *

    + * 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). + *

    + * 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. + *

    + * Examples of the entry specifications are: + *

      + *
    • 10.10.1.2 - all requests from IP 10.10.1.2 + *
    • 10.10.1.2|/foo/bar - all requests from IP 10.10.1.2 to URI /foo/bar + *
    • 10.10.1.2|/foo/* - all requests from IP 10.10.1.2 to URIs starting with /foo/ + *
    • 10.10.1.2|*.html - all requests from IP 10.10.1.2 to URIs ending with .html + *
    • 10.10.0-255.0-255 - all requests from IPs within 10.10.0.0/16 subnet + *
    • 10.10.0-.-255|/foo/bar - all requests from IPs within 10.10.0.0/16 subnet to URI /foo/bar + *
    • 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/ + *
    + *

    + * 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> _white = new PathMap>(true); + PathMap> _black = new PathMap>(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> 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 addrMap = patternMap.get(path); + if (addrMap == null) + { + addrMap = new IPAddressMap(); + 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> 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> entry : _white.getMatches(path)) + { + matchedByPath=true; + IPAddressMap 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> entry : _black.getMatches(path)) + { + IPAddressMap 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> 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 index 00000000..04a90f17 --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/server/handler/IdleTimeoutHandler.java @@ -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 + *

    + *   <Get id='handler' name='Handler'/>
    + *   <Set name='Handler'>
    + *     <New id='idleTimeoutHandler' class='org.eclipse.jetty.server.handler.IdleTimeoutHandler'>
    + *       <Set name='Handler'><Ref id='handler'/></Set>
    + *       <Set name='IdleTimeoutMs'>5000</Set>
    + *     </New>
    + *   </Set>
    + * 
    + */ +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 index 00000000..5e9fd854 --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/server/handler/MovedContextHandler.java @@ -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 index 00000000..706b988b --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/server/handler/RequestLogHandler.java @@ -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 index 00000000..10d1e051 --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/server/handler/ResourceHandler.java @@ -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 index 00000000..50998bea --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/server/handler/ScopedHandler.java @@ -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. + * + *

    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.

    + * + *

    For example if Scoped handlers A, B & C were chained together, then + * the calling order would be:

    + *
    + * A.handle(...)
    + *   A.doScope(...)
    + *     B.doScope(...)
    + *       C.doScope(...)
    + *         A.doHandle(...)
    + *           B.doHandle(...)
    + *              C.doHandle(...)
    + * 
    + * + *

    If non scoped handler X was in the chained A, B, X & C, then + * the calling order would be:

    + *
    + * A.handle(...)
    + *   A.doScope(...)
    + *     B.doScope(...)
    + *       C.doScope(...)
    + *         A.doHandle(...)
    + *           B.doHandle(...)
    + *             X.handle(...)
    + *               C.handle(...)
    + *                 C.doHandle(...)
    + * 
    + * + *

    A typical usage pattern is:

    + *
    + *     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();
    + *             }
    + *         }
    + *     }
    + * 
    + */ +public abstract class ScopedHandler extends HandlerWrapper +{ + private static final ThreadLocal __outerScope= new ThreadLocal(); + 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 index 00000000..e946fb67 --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/server/handler/ShutdownHandler.java @@ -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: + * + *
    +    Server server = new Server(8080);
    +    HandlerList handlers = new HandlerList();
    +    handlers.setHandlers(new Handler[]
    +    { someOtherHandler, new ShutdownHandler("secret password") });
    +    server.setHandler(handlers);
    +    server.start();
    +   
    + * +
    +   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);
    +        }
    +    }
    +  
    + */ +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 index 00000000..98bb4134 --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/server/handler/StatisticsHandler.java @@ -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 _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("

    Statistics:

    \n"); + sb.append("Statistics gathering started ").append(getStatsOnMs()).append("ms ago").append("
    \n"); + + sb.append("

    Requests:

    \n"); + sb.append("Total requests: ").append(getRequests()).append("
    \n"); + sb.append("Active requests: ").append(getRequestsActive()).append("
    \n"); + sb.append("Max active requests: ").append(getRequestsActiveMax()).append("
    \n"); + sb.append("Total requests time: ").append(getRequestTimeTotal()).append("
    \n"); + sb.append("Mean request time: ").append(getRequestTimeMean()).append("
    \n"); + sb.append("Max request time: ").append(getRequestTimeMax()).append("
    \n"); + sb.append("Request time standard deviation: ").append(getRequestTimeStdDev()).append("
    \n"); + + + sb.append("

    Dispatches:

    \n"); + sb.append("Total dispatched: ").append(getDispatched()).append("
    \n"); + sb.append("Active dispatched: ").append(getDispatchedActive()).append("
    \n"); + sb.append("Max active dispatched: ").append(getDispatchedActiveMax()).append("
    \n"); + sb.append("Total dispatched time: ").append(getDispatchedTimeTotal()).append("
    \n"); + sb.append("Mean dispatched time: ").append(getDispatchedTimeMean()).append("
    \n"); + sb.append("Max dispatched time: ").append(getDispatchedTimeMax()).append("
    \n"); + sb.append("Dispatched time standard deviation: ").append(getDispatchedTimeStdDev()).append("
    \n"); + + + sb.append("Total requests suspended: ").append(getAsyncRequests()).append("
    \n"); + sb.append("Total requests expired: ").append(getExpires()).append("
    \n"); + sb.append("Total requests resumed: ").append(getAsyncDispatches()).append("
    \n"); + + sb.append("

    Responses:

    \n"); + sb.append("1xx responses: ").append(getResponses1xx()).append("
    \n"); + sb.append("2xx responses: ").append(getResponses2xx()).append("
    \n"); + sb.append("3xx responses: ").append(getResponses3xx()).append("
    \n"); + sb.append("4xx responses: ").append(getResponses4xx()).append("
    \n"); + sb.append("5xx responses: ").append(getResponses5xx()).append("
    \n"); + sb.append("Bytes sent total: ").append(getResponsesBytesTotal()).append("
    \n"); + + return sb.toString(); + + } + + @Override + public Future 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 index 00000000..1571d7b9 --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/server/handler/package-info.java @@ -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 index 00000000..f4c59583 --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/server/nio/NetworkTrafficSelectChannelConnector.java @@ -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 index 00000000..8450e18b --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/server/nio/package-info.java @@ -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 index 00000000..46883049 --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/server/package-info.java @@ -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 index 00000000..809d9d98 --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/server/session/AbstractSession.java @@ -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; + +/** + * + *

    + * Implements {@link javax.servlet.http.HttpSession} from the javax.servlet package. + *

    + * + */ +@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 getAttributeMap(); + + + + + + /* ------------------------------------------------------------ */ + public abstract int getAttributes(); + + + + + /* ------------------------------------------------------------ */ + public abstract Set 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 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 anames = doGetAttributeNames(); + if (anames == null) + return new String[0]; + ArrayList names = new ArrayList(); + 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 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 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 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 index 00000000..94144083 --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/server/session/AbstractSessionIdManager.java @@ -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 index 00000000..1f966c59 --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/server/session/AbstractSessionManager.java @@ -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. + *

    + */ +@SuppressWarnings("deprecation") +@ManagedObject("Abstract Session Manager") +public abstract class AbstractSessionManager extends ContainerLifeCycle implements SessionManager +{ + final static Logger __log = SessionHandler.LOG; + + public Set __defaultSessionTrackingModes = + Collections.unmodifiableSet( + new HashSet( + 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 _sessionAttributeListeners = new CopyOnWriteArrayList(); + protected final List _sessionListeners= new CopyOnWriteArrayList(); + protected final List _sessionIdListeners = new CopyOnWriteArrayList(); + + 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 _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: + *

      + *
    1. SessionCookieConfig.setSecure == true
    2. + *
    3. SessionCookieConfig.setSecure == false && _secureRequestOnly==true && request is HTTPS
    4. + *
    + * 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 getDefaultSessionTrackingModes() + { + return __defaultSessionTrackingModes; + } + + /* ------------------------------------------------------------ */ + @Override + public Set getEffectiveSessionTrackingModes() + { + return Collections.unmodifiableSet(_sessionTrackingModes); + } + + /* ------------------------------------------------------------ */ + @Override + public void setSessionTrackingModes(Set sessionTrackingModes) + { + _sessionTrackingModes=new HashSet(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 index 00000000..a17bc06f --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/server/session/HashSessionIdManager.java @@ -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>> _sessions = new HashMap>>(); + + /* ------------------------------------------------------------ */ + public HashSessionIdManager() + { + } + + /* ------------------------------------------------------------ */ + public HashSessionIdManager(Random random) + { + super(random); + } + + /* ------------------------------------------------------------ */ + /** + * @return Collection of String session IDs + */ + public Collection getSessions() + { + return Collections.unmodifiableCollection(_sessions.keySet()); + } + + /* ------------------------------------------------------------ */ + /** + * @return Collection of Sessions for the passed session ID + */ + public Collection getSession(String id) + { + ArrayList sessions = new ArrayList(); + Set> refs =_sessions.get(id); + if (refs!=null) + { + for (WeakReference 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 ref = new WeakReference(session); + + synchronized (this) + { + Set> sessions = _sessions.get(id); + if (sessions==null) + { + sessions=new HashSet>(); + _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> sessions = _sessions.get(id); + if (sessions!=null) + { + for (Iterator> iter = sessions.iterator(); iter.hasNext();) + { + WeakReference 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> sessions; + synchronized (this) + { + sessions = _sessions.remove(id); + } + + if (sessions!=null) + { + for (WeakReference 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> sessions = _sessions.remove(oldClusterId); //get the list of sessions with same id from other contexts + if (sessions!=null) + { + for (Iterator> iter = sessions.iterator(); iter.hasNext();) + { + WeakReference 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 index 00000000..1effd32f --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/server/session/HashSessionManager.java @@ -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. + *

    + * 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. + *

    + * 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 _sessions=new ConcurrentHashMap(); + 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 i=_sessions.values().iterator(); i.hasNext();) + { + HashedSession session=i.next(); + long idleTime=session.getMaxInactiveInterval()*1000L; + if (idleTime>0&&session.getAccessed()+idleTime 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 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 sessions=new ArrayList(_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(_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 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&&i0) + { + ClassLoadingObjectInputStream ois = new ClassLoadingObjectInputStream(is); + for (int i=0; i0&&(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 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 index 00000000..4c7227d7 --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/server/session/JDBCSessionIdManager.java @@ -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 _sessionIds = new HashSet(); + 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 0) + { + connection = getConnection(); + connection.setAutoCommit(true); + Set expiredSessionIds = new HashSet(); + + + //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 expiredSessionIds, boolean forceDelete) + { + Set remainingIds = new HashSet(expiredSessionIds); + Handler[] contexts = _server.getChildHandlersByClass(ContextHandler.class); + for (int i=0; contexts!=null && i 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 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 _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(); + + 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 sessions = (_sessions == null? new ArrayList() :new ArrayList(_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(_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 expire (Set 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 successfullyExpiredIds = new HashSet(); + 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 _reference = new AtomicReference(); + final AtomicReference _exception = new AtomicReference(); + 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)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 index 00000000..72dbea8f --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/server/session/MemSession.java @@ -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 _attributes=new HashMap(); + + 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 getAttributeMap() + { + return _attributes; + } + + + /* ------------------------------------------------------------ */ + @Override + public int getAttributes() + { + synchronized (this) + { + checkValid(); + return _attributes.size(); + } + } + + /* ------------------------------------------------------------ */ + @SuppressWarnings({ "unchecked" }) + @Override + public Enumeration doGetAttributeNames() + { + List names=_attributes==null?Collections.EMPTY_LIST:new ArrayList(_attributes.keySet()); + return Collections.enumeration(names); + } + + + /* ------------------------------------------------------------ */ + @Override + public Set getNames() + { + synchronized (this) + { + return new HashSet(_attributes.keySet()); + } + } + + + /* ------------------------------------------------------------- */ + @Override + public void clearAttributes() + { + while (_attributes!=null && _attributes.size()>0) + { + ArrayList keys; + synchronized(this) + { + keys=new ArrayList(_attributes.keySet()); + } + + Iterator 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 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 index 00000000..e6aedc5b --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/server/session/SessionHandler.java @@ -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 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 index 00000000..ed180a18 --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/server/session/package-info.java @@ -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 index 00000000..a32e1a61 --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/servlet/BaseHolder.java @@ -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 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 _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 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 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 index 00000000..c3787395 --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/servlet/DefaultServlet.java @@ -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. : + *

    + *  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.
    + *
    + *
    + * 
    + * + * + * + * + */ +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.
    + * 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 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 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 Resource. + * 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 null is returned. + * The list of welcome files is read from the {@link ContextHandler} for this servlet, or + * "index.jsp" , "index.html" if that is null. + * @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 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 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;i0)?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=0) + response.setDateHeader(HttpHeader.LAST_MODIFIED.asString(),lml); + + if (count != -1) + { + if (count _errorPages= new HashMap(); // code or exception to URL + private final List _errorPageList=new ArrayList(); // 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 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 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 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 index 00000000..d9635fae --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/servlet/FilterHolder.java @@ -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 +{ + 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 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 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 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 getServletNameMappings() + { + FilterMapping[] mappings =_servletHandler.getFilterMappings(); + List names=new ArrayList(); + 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 getUrlPatternMappings() + { + FilterMapping[] mappings =_servletHandler.getFilterMappings(); + List patterns=new ArrayList(); + 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 index 00000000..1d5a9a42 --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/servlet/FilterMapping.java @@ -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 true 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 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 index 00000000..2f690b17 --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/servlet/Holder.java @@ -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 extends BaseHolder +{ + private static final Logger LOG = Log.getLogger(Holder.class); + + protected final Map _initParams=new HashMap(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 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 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 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 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 setInitParameters(Map initParameters) + { + illegalStateIfContextStarted(); + Set clash=null; + for (Map.Entry 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(); + 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 index 00000000..4613144b --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/servlet/Invoker.java @@ -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: + *
    + *  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
    + * 
    + * @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 + * 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 index 00000000..346d4788 --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/servlet/ListenerHolder.java @@ -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 +{ + 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 index 00000000..afd066bb --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/servlet/NoJspServlet.java @@ -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 index 00000000..d01ddd2a --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/servlet/ServletContextHandler.java @@ -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.
    + *   new ServletContext("/context",Context.SESSIONS|Context.NO_SECURITY);
    + * 
    + *

    + * 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 _decorators= new ArrayList<>(); + protected Class _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 getDefaultSecurityHandlerClass() + { + return _defaultSecurityHandlerClass; + } + + /* ------------------------------------------------------------ */ + /** Set the defaultSecurityHandlerClass. + * @param defaultSecurityHandlerClass the defaultSecurityHandlerClass to set + */ + public void setDefaultSecurityHandlerClass(Class 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 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 dispatches) + { + getServletHandler().addFilterWithMapping(holder,pathSpec,dispatches); + } + + /* ------------------------------------------------------------ */ + /** convenience method to add a filter + */ + public FilterHolder addFilter(Class filterClass,String pathSpec,EnumSet dispatches) + { + return getServletHandler().addFilterWithMapping(filterClass,pathSpec,dispatches); + } + + /* ------------------------------------------------------------ */ + /** convenience method to add a filter + */ + public FilterHolder addFilter(String filterClass,String pathSpec,EnumSet 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 union = new HashSet(); + Set 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 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 pathSpecs = registration.getMappings(); + if (pathSpecs != null) + { + for (String pathSpec:pathSpecs) + { + List 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 getDecorators() + { + return Collections.unmodifiableList(_decorators); + } + + /* ------------------------------------------------------------ */ + /** + * @param decorators The lis of {@link Decorator}s + */ + public void setDecorators(List 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 _urlPatterns = new ArrayList(); + private String _elIgnored; + private String _pageEncoding; + private String _scriptingInvalid; + private String _isXml; + private List _includePreludes = new ArrayList(); + private List _includeCodas = new ArrayList(); + private String _deferredSyntaxAllowedAsLiteral; + private String _trimDirectiveWhitespaces; + private String _defaultContentType; + private String _buffer; + private String _errorOnUndeclaredNamespace; + + + + /** + * @see javax.servlet.descriptor.JspPropertyGroupDescriptor#getUrlPatterns() + */ + public Collection getUrlPatterns() + { + return new ArrayList(_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 getIncludePreludes() + { + return new ArrayList(_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 getIncludeCodas() + { + return new ArrayList(_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 _taglibs = new ArrayList(); + private List _jspPropertyGroups = new ArrayList(); + + public JspConfig() {} + + /** + * @see javax.servlet.descriptor.JspConfigDescriptor#getTaglibs() + */ + public Collection getTaglibs() + { + return new ArrayList(_taglibs); + } + + public void addTaglibDescriptor (TaglibDescriptor d) + { + _taglibs.add(d); + } + + /** + * @see javax.servlet.descriptor.JspConfigDescriptor#getJspPropertyGroups() + */ + public Collection getJspPropertyGroups() + { + return new ArrayList(_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 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 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 createFilter(Class 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 createServlet(Class 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 getDefaultSessionTrackingModes() + { + if (_sessionHandler!=null) + return _sessionHandler.getSessionManager().getDefaultSessionTrackingModes(); + return null; + } + + @Override + public Set 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 getFilterRegistrations() + { + if (!_enabled) + throw new UnsupportedOperationException(); + + HashMap registrations = new HashMap(); + 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 getServletRegistrations() + { + if (!_enabled) + throw new UnsupportedOperationException(); + + HashMap registrations = new HashMap(); + 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 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 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 listenerClass) + { + if (!isStarting()) + throw new IllegalStateException(); + if (!_enabled) + throw new UnsupportedOperationException(); + super.addListener(listenerClass); + } + + @Override + public T createListener(Class 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 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 index 00000000..81243e01 --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/servlet/ServletHandler.java @@ -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. + *

    + * 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 org.eclipse.jetty.webapp.WebAppContext. + *

    + * 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 _servletPathMappings = new HashMap(); + + private final Map _filterNameMap= new HashMap<>(); + private List _filterPathMappings; + private MultiMap _filterNameMappings; + + private final Map _servletNameMap=new HashMap<>(); + private PathMap _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(); + _chainCache[FilterMapping.FORWARD]=new ConcurrentHashMap(); + _chainCache[FilterMapping.INCLUDE]=new ConcurrentHashMap(); + _chainCache[FilterMapping.ERROR]=new ConcurrentHashMap(); + _chainCache[FilterMapping.ASYNC]=new ConcurrentHashMap(); + + _chainLRU[FilterMapping.REQUEST]=new ConcurrentLinkedQueue(); + _chainLRU[FilterMapping.FORWARD]=new ConcurrentLinkedQueue(); + _chainLRU[FilterMapping.INCLUDE]=new ConcurrentLinkedQueue(); + _chainLRU[FilterMapping.ERROR]=new ConcurrentLinkedQueue(); + _chainLRU[FilterMapping.ASYNC]=new ConcurrentLinkedQueue(); + } + + 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 filterHolders = new ArrayList(); + List 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 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 servletHolders = new ArrayList(); //will be remaining servlets + List 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 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 listenerHolders = new ArrayList(); + 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 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 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 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 0) + chain= new CachedChain(filters, servletHolder); + + final Map cache=(Map)_chainCache[dispatch]; + final Queue lru=(Queue)_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 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 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 filter,String pathSpec,EnumSet 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 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 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 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 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(); + 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 pm = new PathMap<>(); + Map servletPathMappings = new HashMap(); + + //create a map of paths to set of ServletMappings that define that mapping + HashMap> sms = new HashMap>(); + for (ServletMapping servletMapping : _servletMappings) + { + String[] pathSpecs = servletMapping.getPathSpecs(); + if (pathSpecs != null) + { + for (String pathSpec : pathSpecs) + { + Set mappings = sms.get(pathSpec); + if (mappings == null) + { + mappings = new HashSet(); + 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 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 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 _chain; + final ServletHolder _servletHolder; + int _filter= 0; + + /* ------------------------------------------------------------ */ + Chain(Request baseRequest, List 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 index 00000000..1df08b33 --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/servlet/ServletHolder.java @@ -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 implements UserIdentity.Scope, Comparable +{ + private static final Logger LOG = Log.getLogger(ServletHolder.class); + + /* ---------------------------------------------------------------- */ + private int _initOrder = -1; + private boolean _initOnStartup=false; + private Map _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 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 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 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(); + _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 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 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 addMapping(String... urlPatterns) + { + illegalStateIfContextStarted(); + Set 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(); + 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 getMappings() + { + ServletMapping[] mappings =_servletHandler.getServletMappings(); + List patterns=new ArrayList(); + 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 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 _stack=new Stack(); + + @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 index 00000000..df026df6 --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/servlet/ServletMapping.java @@ -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 index 00000000..2317195a --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/servlet/ServletTester.java @@ -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 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 dispatches) + { + _context.addFilter(holder,pathSpec,dispatches); + } + + public FilterHolder addFilter(Class filterClass, String pathSpec, EnumSet dispatches) + { + return _context.addFilter(filterClass,pathSpec,dispatches); + } + + public FilterHolder addFilter(String filterClass, String pathSpec, EnumSet 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 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 index 00000000..6d1bf7d4 --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/servlet/StatisticsServlet.java @@ -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("\n"); + + sb.append(" \n"); + sb.append(" ").append(_statsHandler.getStatsOnMs()).append("\n"); + + sb.append(" ").append(_statsHandler.getRequests()).append("\n"); + sb.append(" ").append(_statsHandler.getRequestsActive()).append("\n"); + sb.append(" ").append(_statsHandler.getRequestsActiveMax()).append("\n"); + sb.append(" ").append(_statsHandler.getRequestTimeTotal()).append("\n"); + sb.append(" ").append(_statsHandler.getRequestTimeMean()).append("\n"); + sb.append(" ").append(_statsHandler.getRequestTimeMax()).append("\n"); + sb.append(" ").append(_statsHandler.getRequestTimeStdDev()).append("\n"); + + sb.append(" ").append(_statsHandler.getDispatched()).append("\n"); + sb.append(" ").append(_statsHandler.getDispatchedActive()).append("\n"); + sb.append(" ").append(_statsHandler.getDispatchedActiveMax()).append("\n"); + sb.append(" ").append(_statsHandler.getDispatchedTimeTotal()).append("\n"); + sb.append(" ").append(_statsHandler.getDispatchedTimeMean()).append("\n"); + sb.append(" ").append(_statsHandler.getDispatchedTimeMax()).append("\n"); + sb.append(" ").append(_statsHandler.getDispatchedTimeStdDev()).append("\n"); + + sb.append(" ").append(_statsHandler.getAsyncRequests()).append("\n"); + sb.append(" ").append(_statsHandler.getAsyncRequestsWaiting()).append("\n"); + sb.append(" ").append(_statsHandler.getAsyncRequestsWaitingMax()).append("\n"); + sb.append(" ").append(_statsHandler.getAsyncDispatches()).append("\n"); + sb.append(" ").append(_statsHandler.getExpires()).append("\n"); + sb.append(" \n"); + + sb.append(" \n"); + sb.append(" ").append(_statsHandler.getResponses1xx()).append("\n"); + sb.append(" ").append(_statsHandler.getResponses2xx()).append("\n"); + sb.append(" ").append(_statsHandler.getResponses3xx()).append("\n"); + sb.append(" ").append(_statsHandler.getResponses4xx()).append("\n"); + sb.append(" ").append(_statsHandler.getResponses5xx()).append("\n"); + sb.append(" ").append(_statsHandler.getResponsesBytesTotal()).append("\n"); + sb.append(" \n"); + + sb.append(" \n"); + for (Connector connector : _connectors) + { + sb.append(" \n"); + sb.append(" ").append(connector.getClass().getName()).append("@").append(connector.hashCode()).append("\n"); + sb.append(" \n"); + for (String protocol:connector.getProtocols()) + sb.append(" ").append(protocol).append("\n"); + sb.append(" \n"); + + ConnectorStatistics connectorStats = null; + + if (connector instanceof AbstractConnector) + connectorStats = ((AbstractConnector)connector).getBean(ConnectorStatistics.class); + if (connectorStats == null) + sb.append(" false\n"); + else + { + sb.append(" true\n"); + sb.append(" ").append(connectorStats.getConnections()).append("\n"); + sb.append(" ").append(connectorStats.getConnectionsOpen()).append("\n"); + sb.append(" ").append(connectorStats.getConnectionsOpenMax()).append("\n"); + sb.append(" ").append(connectorStats.getConnectionDurationMean()).append("\n"); + sb.append(" ").append(connectorStats.getConnectionDurationMax()).append("\n"); + sb.append(" ").append(connectorStats.getConnectionDurationStdDev()).append("\n"); + sb.append(" ").append(connectorStats.getMessagesIn()).append("\n"); + sb.append(" ").append(connectorStats.getMessagesIn()).append("\n"); + sb.append(" ").append(connectorStats.getStartedMillis()).append("\n"); + } + sb.append(" \n"); + } + sb.append(" \n"); + + sb.append(" \n"); + sb.append(" ").append(_memoryBean.getHeapMemoryUsage().getUsed()).append("\n"); + sb.append(" ").append(_memoryBean.getNonHeapMemoryUsage().getUsed()).append("\n"); + sb.append(" \n"); + + sb.append("\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("

    Connections:

    \n"); + for (Connector connector : _connectors) + { + sb.append("

    ").append(connector.getClass().getName()).append("@").append(connector.hashCode()).append("

    "); + sb.append("Protocols:"); + for (String protocol:connector.getProtocols()) + sb.append(protocol).append(" "); + sb.append("
    \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("
    \n"); + sb.append("Total connections: ").append(connectorStats.getConnections()).append("
    \n"); + sb.append("Current connections open: ").append(connectorStats.getConnectionsOpen()).append("
    \n");; + sb.append("Max concurrent connections open: ").append(connectorStats.getConnectionsOpenMax()).append("
    \n"); + sb.append("Mean connection duration: ").append(connectorStats.getConnectionDurationMean()).append("
    \n"); + sb.append("Max connection duration: ").append(connectorStats.getConnectionDurationMax()).append("
    \n"); + sb.append("Connection duration standard deviation: ").append(connectorStats.getConnectionDurationStdDev()).append("
    \n"); + sb.append("Total messages in: ").append(connectorStats.getMessagesIn()).append("
    \n"); + sb.append("Total messages out: ").append(connectorStats.getMessagesOut()).append("
    \n"); + } + else + { + sb.append("Statistics gathering off.\n"); + } + + } + + sb.append("

    Memory:

    \n"); + sb.append("Heap memory usage: ").append(_memoryBean.getHeapMemoryUsage().getUsed()).append(" bytes").append("
    \n"); + sb.append("Non-heap memory usage: ").append(_memoryBean.getNonHeapMemoryUsage().getUsed()).append(" bytes").append("
    \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 index 00000000..81b75288 --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/servlet/listener/ELContextCleaner.java @@ -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 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 index 00000000..72a6f0dd --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/servlet/listener/IntrospectorCleaner.java @@ -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 index 00000000..2cb69e3b --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/servlet/listener/package-info.java @@ -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 index 00000000..f7a9cd55 --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/servlet/package-info.java @@ -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 index 00000000..b49bec11 --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/util/AbstractTrie.java @@ -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. + *

    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

    + * @param + */ +public abstract class AbstractTrie implements Trie +{ + 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 index 00000000..671439e5 --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/util/ArrayQueue.java @@ -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. + *

    + * This partial Queue implementation (also with {@link #remove()} for stack operation) + * is backed by a growable circular array. + * + * @param + */ +public class ArrayQueue extends AbstractList implements Queue +{ + 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 index 00000000..fdca4ce1 --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/util/ArrayTernaryTrie.java @@ -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). + *

    + * The Trie is stored in 3 arrays:

    + *
    char[] _tree
    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.
    + *
    String[] _key
    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.
    + *
    V[] _value
    An array of values corresponding to the _key array
    + *
    + *

    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. + *

    + * @param + */ +public class ArrayTernaryTrie extends AbstractTrie +{ + 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 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 keySet() + { + Set 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 index 00000000..73ccc424 --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/util/ArrayTrie.java @@ -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. + *

    This implementation is always case insensitive and is optimal for + * a small number of fixed strings with few special characters. + *

    + * @param + */ +public class ArrayTrie extends AbstractTrie +{ + /** + * 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 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 keySet() + { + Set keys = new HashSet<>(); + keySet(keys,0); + return keys; + } + + private void keySet(Set 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 index 00000000..76b195da --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/util/ArrayUtil.java @@ -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[] 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 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[] 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 modifiable list initialised with the elements from array. + */ + public static List asMutableList(E[] array) + { + if (array==null || array.length==0) + return new ArrayList(); + return new ArrayList(Arrays.asList(array)); + } + + /* ------------------------------------------------------------ */ + public static T[] removeNulls(T[] array) + { + for (T t : array) + { + if (t==null) + { + List 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 index 00000000..42fb4890 --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/util/Atomics.java @@ -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 index 00000000..c65abc26 --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/util/Attributes.java @@ -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 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 index 00000000..c605e099 --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/util/AttributesMap.java @@ -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> _map = new AtomicReference<>(); + + public AttributesMap() + { + } + + public AttributesMap(AttributesMap attributes) + { + ConcurrentMap map = attributes.map(); + if (map != null) + _map.set(new ConcurrentHashMap<>(map)); + } + + private ConcurrentMap map() + { + return _map.get(); + } + + private ConcurrentMap ensureMap() + { + while (true) + { + ConcurrentMap map = map(); + if (map != null) + return map; + map = new ConcurrentHashMap<>(); + if (_map.compareAndSet(null, map)) + return map; + } + } + + @Override + public void removeAttribute(String name) + { + Map 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 map = map(); + return map == null ? null : map.get(name); + } + + @Override + public Enumeration getAttributeNames() + { + return Collections.enumeration(getAttributeNameSet()); + } + + public Set getAttributeNameSet() + { + return keySet(); + } + + public Set> getAttributeEntrySet() + { + Map map = map(); + return map == null ? Collections.>emptySet() : map.entrySet(); + } + + public static Enumeration getAttributeNamesCopy(Attributes attrs) + { + if (attrs instanceof AttributesMap) + return Collections.enumeration(((AttributesMap)attrs).keySet()); + + List names = new ArrayList<>(); + names.addAll(Collections.list(attrs.getAttributeNames())); + return Collections.enumeration(names); + } + + @Override + public void clearAttributes() + { + Map map = map(); + if (map != null) + map.clear(); + } + + public int size() + { + Map map = map(); + return map == null ? 0 : map.size(); + } + + @Override + public String toString() + { + Map map = map(); + return map == null ? "{}" : map.toString(); + } + + private Set keySet() + { + Map map = map(); + return map == null ? Collections.emptySet() : map.keySet(); + } + + public void addAll(Attributes attributes) + { + Enumeration 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 index 00000000..7fbbb187 --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/util/B64Code.java @@ -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. + *

    Does not insert or interpret whitespace as described in RFC + * 1521. If you require this you must pre/post process your data. + *

    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. + *

    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. + *

    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. + *

    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. + *

    Does not insert whitespace as described in RFC 1521. + *

    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>>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 + *

    Does not insert whitespace as described in RFC 1521, unless rfc2045 is passed as true. + *

    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>>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. + *

    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. + *

    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. + * + *

    Unlike other decode methods, this does not attempt to + * cope with extra whitespace as described in RFC 1521/2045. + *

    Avoids creating extra copies of the input/output. + *

    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>>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. + *

    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. + *

    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>>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 index 00000000..7acbdafc --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/util/BlockingArrayQueue.java @@ -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. + *

    + * 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. + *

    + * Unlike {@link java.util.concurrent.ArrayBlockingQueue}, this class is able to grow and provides a blocking put call. + *

    + * 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 + * The element type + */ +public class BlockingArrayQueue extends AbstractList implements BlockingQueue +{ + /** + * 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 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 c) + { + throw new UnsupportedOperationException(); + } + + @Override + public int drainTo(Collection 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 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 + { + 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 index 00000000..a3cf9dae --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/util/BlockingCallback.java @@ -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 _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 index 00000000..bd7cb06e --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/util/BufferUtil.java @@ -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. + *

    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: + *

    + *     buffer.clear();
    + *     channel.write(buffer);
    + * 
    + * Which looks as if it should write no data, but in fact writes the buffer worth of garbage. + *

    + *

    + * 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.

    + *

    + * 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:

    + *     ByteBuffer buffer = BufferUtil.allocate(1024);
    + *     assert(buffer.remaining()==0);
    + *     BufferUtil.clear(buffer);
    + *     assert(buffer.remaining()==0);
    + * 
    + *

    + *

    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: + *

    + *      int pos = BufferUtil.flipToFill(buffer);
    + *      try
    + *      {
    + *          buffer.put(data);
    + *      }
    + *      finally
    + *      {
    + *          flipToFlush(buffer, pos);
    + *      }
    + * 
    + * 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. + *

    + * 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. + *

    + * 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 index 00000000..80df8819 --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/util/ByteArrayISO8859Writer.java @@ -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=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=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=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=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 index 00000000..bfde0186 --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/util/ByteArrayOutputStream2.java @@ -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.lengthA callback abstraction that handles completed/failed events of asynchronous operations.

    + * + *

    Semantically this is equivalent to an optimise Promise<Void>, but callback is a more meaningful + * name than EmptyPromise

    + */ +public interface Callback +{ + /** + *

    Callback invoked when the operation completes.

    + * + * @see #failed(Throwable) + */ + public abstract void succeeded(); + + /** + *

    Callback invoked when the operation fails.

    + * @param x the reason for the operation failure + */ + public void failed(Throwable x); + + /** + *

    Empty implementation of {@link Callback}

    + */ + 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 index 00000000..5b734db7 --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/util/ClassLoadingObjectInputStream.java @@ -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 index 00000000..ec5a30a8 --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/util/CompletableCallback.java @@ -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. + *

    + * 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. + *

    + * Typical usage: + *

    + * CompletableCallback callback = new CompletableCallback()
    + * {
    + *     @Override
    + *     public void resume()
    + *     {
    + *         // continue processing
    + *     }
    + *
    + *     @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
    + * 
    + */ +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 + * after a first call to {@link #tryComplete()}. + */ + public abstract void resume(); + + /** + * Callback method invoked when this callback is failed + * after a first call to {@link #tryComplete()}. + */ + public abstract void abort(Throwable failure); + + /** + * Tries to complete this callback; driver code should call + * this method once after 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 index 00000000..fc1326d6 --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/util/ConcurrentArrayQueue.java @@ -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. + *

    + * 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. + *

    + * 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 + */ +public class ConcurrentArrayQueue extends AbstractQueue +{ + 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> _blocks = new AtomicReferenceArray<>(TAIL_OFFSET + 1); + private final int _blockSize; + + public ConcurrentArrayQueue() + { + this(DEFAULT_BLOCK_SIZE); + } + + public ConcurrentArrayQueue(int blockSize) + { + _blockSize = blockSize; + Block block = newBlock(); + _blocks.set(HEAD_OFFSET,block); + _blocks.set(TAIL_OFFSET,block); + } + + public int getBlockSize() + { + return _blockSize; + } + + protected Block getHeadBlock() + { + return _blocks.get(HEAD_OFFSET); + } + + protected Block getTailBlock() + { + return _blocks.get(TAIL_OFFSET); + } + + @Override + public boolean offer(T item) + { + item = Objects.requireNonNull(item); + + final Block initialTailBlock = getTailBlock(); + Block currentTailBlock = initialTailBlock; + int tail = currentTailBlock.tail(); + while (true) + { + if (tail == getBlockSize()) + { + Block 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 oldTailBlock, Block 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 current, Block update) + { + return _blocks.compareAndSet(TAIL_OFFSET,current,update); + } + + @Override + public T poll() + { + final Block initialHeadBlock = getHeadBlock(); + Block currentHeadBlock = initialHeadBlock; + int head = currentHeadBlock.head(); + T result = null; + while (true) + { + if (head == getBlockSize()) + { + Block 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 oldHeadBlock, Block 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 current, Block update) + { + return _blocks.compareAndSet(HEAD_OFFSET,current,update); + } + + @Override + public T peek() + { + Block currentHeadBlock = getHeadBlock(); + int head = currentHeadBlock.head(); + while (true) + { + if (head == getBlockSize()) + { + Block 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 currentHeadBlock = getHeadBlock(); + int head = currentHeadBlock.head(); + boolean result = false; + while (true) + { + if (head == getBlockSize()) + { + Block 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 iterator() + { + final List blocks = new ArrayList<>(); + Block currentHeadBlock = getHeadBlock(); + while (currentHeadBlock != null) + { + Object[] elements = currentHeadBlock.arrayCopy(); + blocks.add(elements); + currentHeadBlock = currentHeadBlock.next(); + } + return new Iterator() + { + 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 currentHeadBlock = getHeadBlock(); + int head = currentHeadBlock.head(); + int size = 0; + while (true) + { + if (head == getBlockSize()) + { + Block 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 newBlock() + { + return new Block<>(getBlockSize()); + } + + protected int getBlockCount() + { + int result = 0; + Block headBlock = getHeadBlock(); + while (headBlock != null) + { + ++result; + headBlock = headBlock.next(); + } + return result; + } + + protected static final class Block + { + private static final int headOffset = MemoryUtils.getIntegersPerCacheLine()-1; + private static final int tailOffset = MemoryUtils.getIntegersPerCacheLine()*2-1; + + private final AtomicReferenceArray elements; + private final AtomicReference> 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 next() + { + return next.get(); + } + + public boolean link(Block 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 index 00000000..4a4c8e62 --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/util/ConcurrentHashSet.java @@ -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 extends AbstractSet implements Set +{ + private final Map _map = new ConcurrentHashMap(); + private transient Set _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 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[] 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 index 00000000..31097ee7 --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/util/DateCache.java @@ -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 index 00000000..152934de --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/util/Fields.java @@ -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; + +/** + *

    A container for name/value pairs, known as fields.

    + *

    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.

    + *

    The implementation of this class is not thread safe.

    + */ +public class Fields implements Iterable +{ + private final boolean caseSensitive; + private final Map fields; + + /** + *

    Creates an empty, modifiable, case insensitive {@link Fields} instance.

    + * @see #Fields(Fields, boolean) + */ + public Fields() + { + this(false); + } + + /** + *

    Creates an empty, modifiable, case insensitive {@link Fields} instance.

    + * @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<>(); + } + + /** + *

    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

    + * + * @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 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 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 getNames() + { + Set 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)); + } + + /** + *

    Inserts or replaces the given name/value pair as a single-valued {@link Field}.

    + * + * @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); + } + + /** + *

    Inserts or replaces the given {@link Field}, mapped to the {@link Field#getName() field's name}

    + * + * @param field the field to put + */ + public void put(Field field) + { + if (field != null) + fields.put(normalizeName(field.getName()), field); + } + + /** + *

    Adds the given value to a field with the given name, + * creating a {@link Field} is none exists for the given name.

    + * + * @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); + } + } + + /** + *

    Removes the {@link Field} with the given name

    + * + * @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)); + } + + /** + *

    Empties this {@link Fields} instance from all fields

    + * @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 iterator() + { + return fields.values().iterator(); + } + + @Override + public String toString() + { + return fields.toString(); + } + + /** + *

    A named list of string values.

    + *

    The name is case-sensitive and there must be at least one value.

    + */ + public static class Field + { + private final String name; + private final List values; + + public Field(String name, String value) + { + this(name, Collections.singletonList(value)); + } + + private Field(String name, List values, String... moreValues) + { + this.name = name; + List 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); + } + + /** + *

    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.

    + * + * @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 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 index 00000000..c15b313b --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/util/ForkInvoker.java @@ -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)}. + *

    + * This class prevents {@link StackOverflowError}s in case of methods that end up invoking themselves, + * such is common for {@link Callback#succeeded()}. + *

    + * Typical use case is: + *

    + * public void reentrantMethod(Object param)
    + * {
    + *     if (condition || tooManyReenters)
    + *         fork(param)
    + *     else
    + *         call(param)
    + * }
    + * 
    + * 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. + *

    + * The same code using this class becomes: + *

    + * private final ForkInvoker invoker = ...;
    + *
    + * public void reentrantMethod(Object param)
    + * {
    + *     invoker.invoke(param);
    + * }
    + * 
    + * + */ +public abstract class ForkInvoker +{ + private static final ThreadLocal __invocations = new ThreadLocal() + { + @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)} + *

    + * 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 index 00000000..5bad6b2e --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/util/FutureCallback.java @@ -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,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 index 00000000..f781c850 --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/util/FuturePromise.java @@ -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 implements Future,Promise +{ + 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 index 00000000..23e10bb6 --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/util/HostMap.java @@ -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 extends HashMap +{ + + /* --------------------------------------------------------------- */ + /** 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 domains = new HashSet(); + do { + domains.add(domain); + if ((idx = domain.indexOf('.')) > 0) + { + domain = domain.substring(idx+1); + } + } while (idx > 0); + + Object entries = null; + for(Map.Entry 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 index 00000000..99aa742d --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/util/HttpCookieStore.java @@ -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 get(URI uri) + { + return delegate.get(uri); + } + + @Override + public List getCookies() + { + return delegate.getCookies(); + } + + @Override + public List 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 get(URI uri) + { + return Collections.emptyList(); + } + + @Override + public List getCookies() + { + return Collections.emptyList(); + } + + @Override + public List 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 index 00000000..51fbed94 --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/util/IO.java @@ -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=0) + { + while (byteCount>0) + { + if (byteCount + * 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. + *

    + *
    + * 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
    + * 
    + */ +@SuppressWarnings("serial") +public class IPAddressMap extends HashMap +{ + private final HashMap _patterns = new HashMap(); + + /* --------------------------------------------------------------- */ + /** 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 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 getMatch(String addr) + { + if (addr != null) + { + for(Map.Entry 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 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 index 00000000..8588675c --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/util/IntrospectionUtil.java @@ -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 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> parameterTypesA = Arrays.asList(methodA.getParameterTypes()); + List> 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 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 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 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 index 00000000..1c74d5ea --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/util/IteratingCallback.java @@ -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. + *

    + * 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. + *

    + * 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. + *

    + * 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. + *

    + * 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 = new AtomicReference<>(State.INACTIVE); + + /** + * Method called by {@link #iterate()} to process the sub task. + *

    + * Implementations must start the asynchronous execution of the sub task + * (if any) and return an appropriate action: + *

      + *
    • {@link Action#IDLE} when no sub tasks are available for execution + * but the overall job is not completed yet
    • + *
    • {@link Action#SCHEDULED} when the sub task asynchronous execution + * has been started
    • + *
    • {@link Action#SUCCEEDED} when the overall job is completed
    • + *
    • {@link Action#FAILED} when the overall job cannot be completed
    • + *
    + * + * @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. + *

    + * 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 index 00000000..e018f43a --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/util/IteratingNestedCallback.java @@ -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. + *

    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. + *

    + *

    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. + *

    + *

    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.

    + * + */ +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 index 00000000..5a9b2570 --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/util/Jetty.java @@ -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 index 00000000..6ff416e6 --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/util/LazyList.java @@ -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. + *

    + * 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. + * + *

    Usage

    + *
    + *   Object lazylist =null;
    + *   while(loopCondition)
    + *   {
    + *     Object item = getItem();
    + *     if (item.isToBeAdded())
    + *         lazylist = LazyList.add(lazylist,item);
    + *   }
    + *   return LazyList.getList(lazylist);
    + * 
    + * + * 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 l = new ArrayList(); + l.add(item); + return l; + } + + return item; + } + + if (list instanceof List) + { + ((List)list).add(item); + return list; + } + + List l=new ArrayList(); + 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 l = new ArrayList(); + l.add(index,item); + return l; + } + return item; + } + + if (list instanceof List) + { + ((List)list).add(index,item); + return list; + } + + List l=new ArrayList(); + 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(initialSize); + if (list instanceof ArrayList) + { + ArrayList ol=(ArrayList)list; + if (ol.size()>initialSize) + return ol; + ArrayList nl = new ArrayList(initialSize); + nl.addAll(ol); + return nl; + } + List l= new ArrayList(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 List 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 List getList(Object list, boolean nullForEmpty) + { + if (list==null) + { + if (nullForEmpty) + return null; + return Collections.emptyList(); + } + if (list instanceof List) + return (List)list; + + return (List)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)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 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((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 Iterator iterator(Object list) + { + if (list==null) + { + List empty=Collections.emptyList(); + return empty.iterator(); + } + if (list instanceof List) + { + return ((List)list).iterator(); + } + List l=getList(list); + return l.iterator(); + } + + /* ------------------------------------------------------------ */ + @SuppressWarnings("unchecked") + public static ListIterator listIterator(Object list) + { + if (list==null) + { + List empty=Collections.emptyList(); + return empty.listIterator(); + } + if (list instanceof List) + return ((List)list).listIterator(); + + List 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 index 00000000..b0ac94e9 --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/util/LeakDetector.java @@ -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. + *

    + * Resource pools usually have a method to acquire a pooled resource + * and a method to released it back to the pool. + *

    + * 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). + *

    + * 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. + *

    + * IMPLEMENTATION NOTES + *

    + * 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. + *

    + * {@link LeakDetector} uses {@link PhantomReference}s to detect leaks. + * {@link PhantomReference}s are enqueued in their {@link ReferenceQueue} + * after they have been garbage collected (differently from + * {@link WeakReference}s that are enqueued before). + * 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 the resource type. + */ +public class LeakDetector extends AbstractLifeCycle implements Runnable +{ + private static final Logger LOG = Log.getLogger(LeakDetector.class); + + private final ReferenceQueue queue = new ReferenceQueue<>(); + private final ConcurrentMap 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 + { + 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 index 00000000..dfb87fd7 --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/util/Loader.java @@ -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. + * + * Usage:

    + * public class MyClass {
    + *     void myMethod() {
    + *          ...
    + *          Class c=Loader.loadClass(this.getClass(),classname);
    + *          ...
    + *     }
    + * 
    + * + */ +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;i0) + 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 index 00000000..764ad600 --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/util/MemoryUtils.java @@ -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. + *

    + */ +public class MemoryUtils +{ + private static final int cacheLineBytes; + static + { + final int defaultValue = 64; + int value = defaultValue; + try + { + value = Integer.parseInt(AccessController.doPrivileged(new PrivilegedAction() + { + @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 index 00000000..2e71f3c5 --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/util/MultiException.java @@ -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 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 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 index 00000000..a3c905a3 --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/util/MultiMap.java @@ -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 extends HashMap> +{ + public MultiMap() + { + super(); + } + + public MultiMap(Map> map) + { + super(map); + } + + public MultiMap(MultiMap 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 getValues(String name) + { + List 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 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 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 put(String name, V value) + { + if(value == null) { + return super.put(name, null); + } + List vals = new ArrayList<>(); + vals.add(value); + return put(name,vals); + } + + /** + * Shorthand version of putAll + * @param input the input map + */ + public void putAllValues(Map input) + { + for(Map.Entry 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 putValues(String name, List 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 putValues(String name, V... values) + { + List 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 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 values) + { + List 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 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 map) + { + boolean merged = false; + + if ((map == null) || (map.isEmpty())) + { + // done + return merged; + } + + for (Map.Entry> entry : map.entrySet()) + { + String name = entry.getKey(); + List 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 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. + *

    + * 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 vals : values()) + { + if ((vals.size() == 1) && vals.contains(value)) + { + return true; + } + } + return false; + } + + @Override + public String toString() + { + Iterator>> iter = entrySet().iterator(); + StringBuilder sb = new StringBuilder(); + sb.append('{'); + boolean delim = false; + while (iter.hasNext()) + { + Entry> e = iter.next(); + if (delim) + { + sb.append(", "); + } + String key = e.getKey(); + List 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 toStringArrayMap() + { + HashMap map = new HashMap(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> 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 index 00000000..441d648e --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/util/MultiPartInputStreamParser.java @@ -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 getHeaderNames() + { + return _headers.keySet(); + } + + /** + * @see javax.servlet.http.Part#getHeaders(java.lang.String) + */ + public Collection 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 getParsedParts() + { + if (_parts == null) + return Collections.emptyList(); + + Collection values = _parts.values(); + List parts = new ArrayList(); + for (Object o: values) + { + List 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 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 getParts() + throws IOException, ServletException + { + parse(); + Collection values = _parts.values(); + List parts = new ArrayList(); + for (Object o: values) + { + List 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&&b0) + 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&&b0||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 index 00000000..f554e767 --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/util/MultiPartOutputStream.java @@ -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 subPatterns = new ArrayList(); + for (int i=0; patterns!=null && iA callback abstraction that handles completed/failed events of asynchronous operations.

    + * + * @param the type of the context object + */ +public interface Promise +{ + /** + *

    Callback invoked when the operation completes.

    + * + * @param result the context + * @see #failed(Throwable) + */ + public abstract void succeeded(C result); + + /** + *

    Callback invoked when the operation fails.

    + * + * @param x the reason for the operation failure + */ + public void failed(Throwable x); + + + /** + *

    Empty implementation of {@link Promise}

    + * + * @param the type of the context object + */ + public static class Adapter implements Promise + { + @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 index 00000000..3a4d5189 --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/util/QuotedStringTokenizer.java @@ -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=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;i0 && 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 index 00000000..067b2c14 --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/util/ReadLineInputStream.java @@ -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 && pos0) + { + _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 index 00000000..60f5da4b --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/util/RolloverFileOutputStream.java @@ -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=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 index 00000000..f29db3da --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/util/Scanner.java @@ -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 _listeners = new ArrayList(); + private final Map _prevScan = new HashMap (); + private final Map _currentScan = new HashMap (); + private FilenameFilter _filter; + private final List _scanDirs = new ArrayList(); + 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 _notifications = new HashMap(); + + 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 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 dirs) + { + _scanDirs.clear(); + _scanDirs.addAll(dirs); + } + + public synchronized void addScanDir( File dir ) + { + _scanDirs.add( dir ); + } + + public List 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 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 currentScan, Map oldScan) + { + // scan the differences and add what was found to the map of notifications: + + Set oldScanKeys = new HashSet(oldScan.keySet()); + + // Look for new and changed files + for (Map.Entry 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 bulkChanges = new ArrayList(); + for (Iterator> iter = _notifications.entrySet().iterator();iter.hasNext();) + { + Entry 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 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 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 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 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 filenames) + { + Iterator 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 index 00000000..3085a687 --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/util/SharedBlockingCallback.java @@ -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: + *
    + * void someBlockingCall(Object... args) throws IOException
    + * {
    + *   try(Blocker blocker=sharedBlockingCallback.acquire())
    + *   {
    + *     someAsyncCall(args,blocker);
    + *     blocker.block();
    + *   }
    + * }
    + * 
    + */ +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 index 00000000..e75abfc0 --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/util/SocketAddressResolver.java @@ -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. + *

    + * {@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. + *

    + * Example usage: + *

    + * SocketAddressResolver resolver = new SocketAddressResolver(executor, scheduler);
    + * resolver.resolve("www.google.com", 80, new Promise<SocketAddress>()
    + * {
    + *     public void succeeded(SocketAddress result)
    + *     {
    + *         // The address was resolved
    + *     }
    + *
    + *     public void failed(Throwable failure)
    + *     {
    + *         // The address resolution failed
    + *     }
    + * });
    + * 
    + */ +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 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 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 index 00000000..55868ad5 --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/util/StringUtil.java @@ -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 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()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=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()) + 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 + * Note: uses codepoint version of {@link Character#isWhitespace(int)} to support Unicode better. + * + *
    +     *   isBlank(null)   == true
    +     *   isBlank("")     == true
    +     *   isBlank("\r\n") == true
    +     *   isBlank("\t")   == true
    +     *   isBlank("   ")  == true
    +     *   isBlank("a")    == false
    +     *   isBlank(".")    == false
    +     *   isBlank(";\n")  == false
    +     * 
    + * + * @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. + *

    + * Note: uses codepoint version of {@link Character#isWhitespace(int)} to support Unicode better. + * + *

    +     *   isNotBlank(null)   == false
    +     *   isNotBlank("")     == false
    +     *   isNotBlank("\r\n") == false
    +     *   isNotBlank("\t")   == false
    +     *   isNotBlank("   ")  == false
    +     *   isNotBlank("a")    == true
    +     *   isNotBlank(".")    == true
    +     *   isNotBlank(";\n")  == true
    +     * 
    + * + * @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' ' && 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 str 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 index 00000000..42f3bdcc --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/util/TreeTrie.java @@ -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 + *

    This implementation is always case insensitive and is optimal for + * a variable number of fixed strings with few special characters. + *

    + * @param + */ +public class TreeTrie extends AbstractTrie +{ + 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[] _nextIndex; + private final List> _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 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(c); + t = t._nextIndex[index]; + } + else + { + TreeTrie 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(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 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 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 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 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 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 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 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 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 void toString(Appendable out, TreeTrie 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 keySet() + { + Set keys = new HashSet<>(); + keySet(keys,this); + return keys; + } + + private static void keySet(Set set, TreeTrie 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 index 00000000..9c749241 --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/util/Trie.java @@ -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 + */ +public interface Trie +{ + /* ------------------------------------------------------------ */ + /** 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 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 index 00000000..b96a520c --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/util/TypeUtil.java @@ -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> 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, 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, 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. + *

    + * Works like {@link Arrays#asList(Object...)}, but handles null arrays. + * @return a list backed by the array. + */ + public static List 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=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=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'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'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 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(""); + } + + public static Object construct(Class klass, Object[] arguments, Map 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(""); + } + + /* ------------------------------------------------------------ */ + /** + * @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 index 00000000..f333fc3b --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/util/URIUtil.java @@ -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. + *

    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': + 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': + 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': + 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=0) + { + buf=new StringBuilder(path.length()<<1); + break loop; + } + } + if (buf==null) + return null; + } + + synchronized(buf) + { + for (int i=0;i=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;i0) + { + 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=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=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='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 index 00000000..ef7f2f5f --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/util/UrlEncoded.java @@ -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". + *

    + * This class handles the encoding and decoding for either + * the query string of a URL or the _content of a POST HTTP request. + * + *

    Notes

    + * 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. + *

    + * The hashtable either contains String single values, vectors + * of String or arrays of Strings. + *

    + * 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 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 map, Charset charset, boolean equalsForNullValue) + { + if (charset==null) + charset=ENCODING; + + StringBuilder result = new StringBuilder(128); + + boolean delim = false; + for(Map.Entry> entry: map.entrySet()) + { + String key = entry.getKey().toString(); + List 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;i0) + 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 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 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;i0) + { + 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 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 map) + { + Utf8StringBuilder buffer = new Utf8StringBuilder(); + synchronized(map) + { + String key = null; + String value = null; + + int end=offset+length; + for (int i=offset;i0) + { + 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+20) + { + 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 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 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 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 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 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;i0xff) + { + 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)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) + 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='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 index 00000000..ff58764a --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/util/Utf8Appendable.java @@ -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 + * 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 index 00000000..b54cf419 --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/util/Utf8LineParser.java @@ -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 "\n" as a line termination character. + *

    + * 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 index 00000000..63fb1aca --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/util/Utf8StringBuffer.java @@ -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 index 00000000..28fa20b6 --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/util/Utf8StringBuilder.java @@ -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 index 00000000..29a805fe --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/util/annotation/ManagedAttribute.java @@ -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 index 00000000..15f4b550 --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/util/annotation/ManagedObject.java @@ -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 index 00000000..3a29368f --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/util/annotation/ManagedOperation.java @@ -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 index 00000000..b79e76e9 --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/util/annotation/Name.java @@ -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 index 00000000..5f0038b2 --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/util/annotation/package-info.java @@ -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 index 00000000..8f2d9dc7 --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/util/component/AbstractLifeCycle.java @@ -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 _listeners=new CopyOnWriteArrayList(); + 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 index 00000000..a5a4c758 --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/util/component/Container.java @@ -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 getBeans(); + + /** + * @param clazz the class of the beans + * @return the list of beans of the given class (or subclass) + * @see #getBeans() + */ + public Collection getBeans(Class 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 getBean(Class 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 index 00000000..464c0f7e --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/util/component/ContainerLifeCycle.java @@ -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. + *

    + * 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. + *

    + * When a {@link LifeCycle} bean is added without a managed state being specified the state is determined heuristically: + *

      + *
    • If the added bean is running, it will be added as an unmanaged bean. + *
    • If the added bean is !running and the container is !running, it will be added as an AUTO bean (see below). + *
    • 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). + *
    • If the added bean is !running and the container is started, it will be added as an unmanaged bean. + *
    + * 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. + *

    + * When stopping the container, a contained bean will be stopped by this aggregate only if it + * is started by this aggregate. + *

    + * The methods {@link #addBean(Object, boolean)}, {@link #manage(Object)} and {@link #unmanage(Object)} can be used to + * explicitly control the life cycle relationship. + *

    + * 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. + *

    + * 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: + *

    + * 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.
    + * 
    + */ + +/* ------------------------------------------------------------ */ +/** + */ +@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 _beans = new CopyOnWriteArrayList<>(); + private final List _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 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 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. + *

    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 getBeans() + { + return getBeans(Object.class); + } + + public void setBeans(Collection beans) + { + for (Object bean : beans) + addBean(bean); + } + + @Override + public Collection getBeans(Class clazz) + { + ArrayList beans = new ArrayList<>(); + for (Bean b : _beans) + { + if (clazz.isInstance(b._bean)) + beans.add(clazz.cast(b._bean)); + } + return beans; + } + + @Override + public T getBean(Class clazz) + { + for (Bean b : _beans) + { + if (clazz.isInstance(b._bean)) + return clazz.cast(b._bean); + } + return null; + } + + /** + * Removes all bean + */ + public void removeBeans() + { + ArrayList 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 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 index 00000000..2e7e441c --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/util/component/Destroyable.java @@ -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; + + +/** + *

    A Destroyable is an object which can be destroyed.

    + *

    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.

    + */ +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 index 00000000..2a1882bc --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/util/component/Dumpable.java @@ -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 index 00000000..94936452 --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/util/component/FileDestroyable.java @@ -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 _files = new ArrayList(); + + 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 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 index 00000000..1c4dcecd --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/util/component/FileNoticeLifeCycleListener.java @@ -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. + *

    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 index 00000000..96bec336 --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/util/component/Graceful.java @@ -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 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 index 00000000..f58fd1a5 --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/util/component/LifeCycle.java @@ -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. + *
    + * 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 index 00000000..2ae3d198 --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/util/component/package-info.java @@ -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 index 00000000..66453768 --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/util/log/AbstractLogger.java @@ -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 index 00000000..094d9783 --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/util/log/JavaUtilLog.java @@ -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; + +/** + *

    + * Implementation of Jetty {@link Logger} based on {@link java.util.logging.Logger}. + *

    + * + *

    + * You can also set the logger level using + * standard java.util.logging configuration. + *

    + */ +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 index 00000000..91b61f9a --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/util/log/Log.java @@ -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. + *

    + * The "org.eclipse.jetty.util.log.class" system property can be used + * to select a specific logging implementation. + *

    + * 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 __loggers = new ConcurrentHashMap<>(); + + + static + { + /* Instantiate a default configuration properties (empty) + */ + __props = new Properties(); + + AccessController.doPrivileged(new PrivilegedAction() + { + 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 systemKeyEnum = (Enumeration)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. + *

    + * 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. + *

    + * This should be used if a webapp is using Log, but wishes the logging to be + * directed to the containers log. + *

    + * If there is not parent Log, then this call is equivalent to

    +     *   Log.setLog(Log.getLogger(name));
    +     * 
    + * @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 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 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 index 00000000..ac6e0861 --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/util/log/Logger.java @@ -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. + *

    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 index 00000000..1026cb07 --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/util/log/LoggerLog.java @@ -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 index 00000000..fd6fb327 --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/util/log/StacklessLogging.java @@ -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 try block when + * logging with {@link StdErrLog} implementation. + *

    + * Use of other logging implementation cause no effect when using this class + *

    + * Example: + * + *

    + * try (StacklessLogging scope = new StacklessLogging(EventDriver.class,Noisy.class))
    + * {
    + *     doActionThatCausesStackTraces();
    + * }
    + * 
    + */ +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 index 00000000..fda1c722 --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/util/log/StdErrLog.java @@ -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. + *

    + * A Jetty {@link Logger} that sends all logs to STDERR ({@link System#err}) with basic formatting. + *

    + * Supports named loggers, and properties based configuration. + *

    + * Configuration Properties: + *

    + *
    ${name|hierarchy}.LEVEL=(ALL|DEBUG|INFO|WARN|OFF)
    + *
    + * Sets the level that the Logger should log at.
    + * Names can be a package name, or a fully qualified class name.
    + * Default: INFO
    + *
    + * Examples: + *
    + *
    org.eclipse.jetty.LEVEL=WARN
    + *
    indicates that all of the jetty specific classes, in any package that + * starts with org.eclipse.jetty should log at level WARN.
    + *
    org.eclipse.jetty.io.ChannelEndPoint.LEVEL=ALL
    + *
    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).
    + *
    + *
    + * + *
    ${name}.SOURCE=(true|false)
    + *
    + * Logger specific, attempt to print the java source file name and line number + * where the logging event originated from.
    + * Name must be a fully qualified class name (package name hierarchy is not supported + * by this configurable)
    + * Warning: this is a slow operation and will have an impact on performance!
    + * Default: false + *
    + * + *
    ${name}.STACKS=(true|false)
    + *
    + * Logger specific, control the display of stacktraces.
    + * Name must be a fully qualified class name (package name hierarchy is not supported + * by this configurable)
    + * Default: true + *
    + * + *
    org.eclipse.jetty.util.log.stderr.SOURCE=(true|false)
    + *
    Special Global Configuration, attempt to print the java source file name and line number + * where the logging event originated from.
    + * Default: false + *
    + * + *
    org.eclipse.jetty.util.log.stderr.LONG=(true|false)
    + *
    Special Global Configuration, when true, output logging events to STDERR using + * long form, fully qualified class names. when false, use abbreviated package names
    + * Default: false + *
    + *
    org.eclipse.jetty.util.log.stderr.ESCAPE=(true|false)
    + *
    Global Configuration, when true output logging events to STDERR are always + * escaped so that control characters are replaced with '?"; '\r' with '<' and '\n' replaced '|'
    + * Default: true + *
    + *
    + */ +@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. + *

    + * 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). + *

    + * 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 + *

    + * + *

    +     * Examples:
    +     * "org.eclipse.jetty.test.FooTest"           = "oejt.FooTest"
    +     * "org.eclipse.jetty.server.logging.LogTest" = "orjsl.LogTest"
    +     * 
    + * + * @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. + *

    + * 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 index 00000000..27166b69 --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/util/log/package-info.java @@ -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 index 00000000..6545cbee --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/util/package-info.java @@ -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 index 00000000..0f6caf9b --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/util/preventers/AWTLeakPreventer.java @@ -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 index 00000000..2322a762 --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/util/preventers/AbstractLeakPreventer.java @@ -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 index 00000000..0cfd3c95 --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/util/preventers/AppContextLeakPreventer.java @@ -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 index 00000000..5fee3659 --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/util/preventers/DOMLeakPreventer.java @@ -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 index 00000000..d229ba74 --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/util/preventers/DriverManagerLeakPreventer.java @@ -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 index 00000000..6ea4de2e --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/util/preventers/GCThreadLeakPreventer.java @@ -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 index 00000000..3a2ad823 --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/util/preventers/Java2DLeakPreventer.java @@ -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 index 00000000..5c497d1c --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/util/preventers/LDAPLeakPreventer.java @@ -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 index 00000000..c1d9fe28 --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/util/preventers/LoginConfigurationLeakPreventer.java @@ -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 index 00000000..9976c560 --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/util/preventers/SecurityProviderLeakPreventer.java @@ -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 index 00000000..a6800e8a --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/util/preventers/package-info.java @@ -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 index 00000000..72a9ed4c --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/util/resource/BadResource.java @@ -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 index 00000000..3dad17b3 --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/util/resource/EmptyResource.java @@ -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 index 00000000..44988424 --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/util/resource/FileResource.java @@ -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 true of the object o 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 index 00000000..434aa887 --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/util/resource/JarFileResource.java @@ -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 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 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 listEntries () + { + checkConnection(); + + ArrayList list = new ArrayList(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 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 jar:file:///a/b/c/foo.jar!/x.html isContainedIn file:///a/b/c/foo.jar + * @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 index 00000000..3dbb70a9 --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/util/resource/JarResource.java @@ -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 JarResource 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 index 00000000..b36a9355 --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/util/resource/Resource.java @@ -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. + *

    + * This class provides a resource abstraction, where a resource may be + * a file, a URL or an entry in a jar file. + *

    + */ +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. + *

    + * 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(""); + buf.append(""); + buf.append(title); + buf.append("\n

    "); + buf.append(title); + buf.append("

    \n\n"); + + if (parent) + { + buf.append("\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"); + } + buf.append("
    Parent Directory
    "); + buf.append(deTag(ls[i])); + buf.append(" "); + buf.append(""); + buf.append(item.length()); + buf.append(" bytes "); + buf.append(dfmt.format(new Date(item.lastModified()))); + buf.append("
    \n"); + buf.append("\n"); + + return buf.toString(); + } + + /** + * Encode any characters that could break the URI string in an HREF. + * Such as ">Link + * + * 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': + buf=new StringBuffer(raw.length()<<1); + break loop; + } + } + if (buf==null) + return raw; + + for (int i=0;i': + buf.append("%3E"); + continue; + default: + buf.append(c); + continue; + } + } + + return buf.toString(); + } + + private static String deTag(String raw) + { + return StringUtil.replace( StringUtil.replace(raw,"<","<"), ">", ">"); + } + + /* ------------------------------------------------------------ */ + /** + * @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 getAllResources() + { + try + { + ArrayList 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 index 00000000..8135e0ba --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/util/resource/ResourceCollection.java @@ -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 list = new ArrayList(); + 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 = 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 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(); + + 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 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(); + 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 set = new HashSet(); + 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 index 00000000..707a6724 --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/util/resource/ResourceFactory.java @@ -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 index 00000000..b696817e --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/util/resource/URLResource.java @@ -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 index 00000000..f8d2428a --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/util/resource/package-info.java @@ -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 index 00000000..4ed9ae21 --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/util/security/CertificateUtils.java @@ -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 loadCRL(String crlPath) throws Exception + { + Collection 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 index 00000000..2ead3878 --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/util/security/CertificateValidator.java @@ -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 _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 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 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 certList = new ArrayList(); + 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 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 index 00000000..28c003b8 --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/util/security/Constraint.java @@ -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 index 00000000..1feb6047 --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/util/security/Credential.java @@ -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. + *

    + * 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. + *

    + * 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 index 00000000..13160c1d --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/util/security/Password.java @@ -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: + * + *

    + *  + 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.
    + * 
    + * + * 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. + *

    + * 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 + *

      + *
    • Calling System.getProperty(realm,dft) + *
    • Prompting for a password + *
    • Using promptDft if nothing was entered. + *
    + * + * @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 [] "); + 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 index 00000000..e3f98e80 --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/util/security/UnixCrypt.java @@ -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 "); + 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 index 00000000..a9271a5d --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/util/security/package-info.java @@ -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 index 00000000..b97aa344 --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/util/ssl/AliasedX509ExtendedKeyManager.java @@ -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 index 00000000..29d33bb4 --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/util/ssl/AliasedX509KeyManager.java @@ -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 index 00000000..016c8127 --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/util/ssl/SslContextFactory.java @@ -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 _excludeProtocols = new LinkedHashSet<>(); + + /** Included protocols. */ + private Set _includeProtocols = null; + + /** Excluded cipher suites. */ + private final Set _excludeCipherSuites = new LinkedHashSet<>(); + /** Included cipher suites. */ + private Set _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 crls = loadCRL(_crlPath); + + if (_validateCerts && keyStore != null) + { + if (_certAlias == null) + { + List 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 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 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 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 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 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 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. + *

    + * 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. + *

    + * If the given {@code address} is null, it is equivalent to {@link #newSSLEngine()}, otherwise + * {@link #newSSLEngine(String, int)} is called. + *

    + * 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). + *

    + * Otherwise, the host address is passed to {@link #newSSLEngine(String, int)} without DNS lookup + * penalties. + *

    + * 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 + * This is based on the information on effective key lengths in RFC 2246 - The TLS Protocol + * Version 1.0, Appendix C. CipherSuite definitions: + * + *

    +     *                         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
    +     * 
    + * + * @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 index 00000000..26ffa876 --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/util/ssl/package-info.java @@ -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 index 00000000..027dfe36 --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/util/statistic/CounterStatistic.java @@ -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. + *

    + * 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 index 00000000..eae7d470 --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/util/statistic/SampleStatistic.java @@ -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 + *

    + * Provides max, total, mean, count, variance, and standard + * deviation of continuous sequence of samples. + *

    + * 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 + *

    + * This algorithm is also described in Wikipedia at + * http://en.wikipedia.org/w/index.php?title=Algorithms_for_calculating_variance§ion=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 index 00000000..fac39e4e --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/util/statistic/package-info.java @@ -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 index 00000000..4f6b5fe6 --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/util/thread/ExecutorThreadPool.java @@ -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 org.eclipse.jetty.server.Server + */ +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())); + } + + /* ------------------------------------------------------------ */ + /** + * 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()) : + queueSize == 0 ? new ThreadPoolExecutor(32, 256, 60, TimeUnit.SECONDS, new SynchronousQueue()) : + new ThreadPoolExecutor(32, 256, 60, TimeUnit.SECONDS, new ArrayBlockingQueue(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()); + } + + /* ------------------------------------------------------------ */ + + /** + * 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 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 index 00000000..4fb75c7f --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/util/thread/NonBlockingThread.java @@ -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. + *

    + * 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 __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 index 00000000..5cc75121 --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/util/thread/QueuedThreadPool.java @@ -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 _threads = new ConcurrentLinkedQueue<>(); + private final Object _joinLock = new Object(); + private final BlockingQueue _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 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 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 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()); + } + } + }; + + /** + *

    Runs the given job in the {@link Thread#currentThread() current thread}.

    + *

    Subclasses may override to perform pre/post actions before/after the job is run.

    + * + * @param job the job to run + */ + protected void runJob(Runnable job) + { + job.run(); + } + + /** + * @return the job queue + */ + protected BlockingQueue getQueue() + { + return _jobs; + } + + /** + * @param queue the job queue + */ + public void setQueue(BlockingQueue 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 index 00000000..5f8d62a6 --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/util/thread/ScheduledExecutorScheduler.java @@ -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}. + *

    + * 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 index 00000000..f5191846 --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/util/thread/Scheduler.java @@ -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 index 00000000..168b444c --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/util/thread/ShutdownThread.java @@ -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 _lifeCycles = new CopyOnWriteArrayList(); + + /* ------------------------------------------------------------ */ + /** + * 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 index 00000000..c2abcfc8 --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/util/thread/ThreadPool.java @@ -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 index 00000000..c07390a7 --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/util/thread/TimerScheduler.java @@ -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 index 00000000..2637e49e --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/util/thread/package-info.java @@ -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; + -- 2.39.2