1 package org
.argeo
.cms
.internal
.kernel
;
3 import static javax
.jcr
.Property
.JCR_DESCRIPTION
;
4 import static javax
.jcr
.Property
.JCR_LAST_MODIFIED
;
5 import static javax
.jcr
.Property
.JCR_TITLE
;
6 import static org
.argeo
.cms
.CmsTypes
.CMS_IMAGE
;
8 import java
.io
.IOException
;
9 import java
.io
.PrintWriter
;
10 import java
.security
.PrivilegedExceptionAction
;
11 import java
.security
.cert
.X509Certificate
;
12 import java
.util
.Calendar
;
13 import java
.util
.Enumeration
;
15 import javax
.jcr
.Node
;
16 import javax
.jcr
.NodeIterator
;
17 import javax
.jcr
.Repository
;
18 import javax
.jcr
.RepositoryException
;
19 import javax
.jcr
.Session
;
20 import javax
.security
.auth
.Subject
;
21 import javax
.servlet
.FilterChain
;
22 import javax
.servlet
.ServletException
;
23 import javax
.servlet
.http
.HttpServlet
;
24 import javax
.servlet
.http
.HttpServletRequest
;
25 import javax
.servlet
.http
.HttpServletResponse
;
26 import javax
.servlet
.http
.HttpSession
;
28 import org
.apache
.commons
.logging
.Log
;
29 import org
.apache
.commons
.logging
.LogFactory
;
30 import org
.argeo
.cms
.CmsException
;
31 import org
.argeo
.cms
.util
.CmsUtils
;
32 import org
.argeo
.jcr
.ArgeoJcrConstants
;
33 import org
.argeo
.jcr
.JcrUtils
;
34 import org
.eclipse
.equinox
.http
.servlet
.ExtendedHttpService
;
37 * Intercepts and enriches http access, mainly focusing on security and
40 class NodeHttp
implements KernelConstants
, ArgeoJcrConstants
{
41 private final static Log log
= LogFactory
.getLog(NodeHttp
.class);
44 // private final RootFilter rootFilter;
46 // private final DoSFilter dosFilter;
47 // private final QoSFilter qosFilter;
49 private Repository repository
;
51 NodeHttp(ExtendedHttpService httpService
, NodeRepository node
) {
52 this.repository
= node
;
53 // rootFilter = new RootFilter();
54 // dosFilter = new CustomDosFilter();
55 // qosFilter = new QoSFilter();
58 httpService
.registerServlet("/!", new LinkServlet(repository
),
60 httpService
.registerServlet("/robots.txt", new RobotServlet(),
62 } catch (Exception e
) {
63 throw new CmsException("Cannot register filters", e
);
67 public void destroy() {
70 static class LinkServlet
extends HttpServlet
{
71 private static final long serialVersionUID
= 3749990143146845708L;
72 private final Repository repository
;
74 public LinkServlet(Repository repository
) {
75 this.repository
= repository
;
79 protected void service(HttpServletRequest request
,
80 HttpServletResponse response
) throws ServletException
,
82 String path
= request
.getPathInfo();
83 String userAgent
= request
.getHeader("User-Agent").toLowerCase();
84 boolean isBot
= false;
85 boolean isCompatibleBrowser
= false;
86 if (userAgent
.contains("bot") || userAgent
.contains("facebook")
87 || userAgent
.contains("twitter")) {
89 } else if (userAgent
.contains("webkit")
90 || userAgent
.contains("gecko")
91 || userAgent
.contains("firefox")
92 || userAgent
.contains("msie")
93 || userAgent
.contains("chrome")
94 || userAgent
.contains("chromium")
95 || userAgent
.contains("opera")
96 || userAgent
.contains("browser")) {
97 isCompatibleBrowser
= true;
101 log
.warn("# BOT " + request
.getHeader("User-Agent"));
102 canonicalAnswer(request
, response
, path
);
106 if (isCompatibleBrowser
&& log
.isTraceEnabled())
107 log
.trace("# BWS " + request
.getHeader("User-Agent"));
108 redirectTo(response
, "/#" + path
);
111 private void redirectTo(HttpServletResponse response
, String location
) {
112 response
.setHeader("Location", location
);
113 response
.setStatus(HttpServletResponse
.SC_FOUND
);
116 // private boolean canonicalAnswerNeededBy(HttpServletRequest request) {
117 // String userAgent = request.getHeader("User-Agent").toLowerCase();
118 // return userAgent.startsWith("facebookexternalhit/");
121 /** For bots which don't understand RWT. */
122 private void canonicalAnswer(HttpServletRequest request
,
123 HttpServletResponse response
, String path
) {
124 Session session
= null;
126 PrintWriter writer
= response
.getWriter();
127 session
= Subject
.doAs(KernelUtils
.anonymousLogin(),
128 new PrivilegedExceptionAction
<Session
>() {
131 public Session
run() throws Exception
{
132 return repository
.login();
136 Node node
= session
.getNode(path
);
137 String title
= node
.hasProperty(JCR_TITLE
) ? node
.getProperty(
138 JCR_TITLE
).getString() : node
.getName();
139 String desc
= node
.hasProperty(JCR_DESCRIPTION
) ? node
140 .getProperty(JCR_DESCRIPTION
).getString() : null;
141 Calendar lastUpdate
= node
.hasProperty(JCR_LAST_MODIFIED
) ? node
142 .getProperty(JCR_LAST_MODIFIED
).getDate() : null;
143 String url
= CmsUtils
.getCanonicalUrl(node
, request
);
144 String imgUrl
= null;
145 for (NodeIterator it
= node
.getNodes(); it
.hasNext();) {
146 Node child
= it
.nextNode();
147 if (child
.isNodeType(CMS_IMAGE
))
148 imgUrl
= CmsUtils
.getDataUrl(child
, request
);
150 StringBuilder buf
= new StringBuilder();
151 buf
.append("<html>");
152 buf
.append("<head>");
153 writeMeta(buf
, "og:title", title
);
154 writeMeta(buf
, "og:type", "website");
155 buf
.append("<meta name='twitter:card' content='summary' />");
156 buf
.append("<meta name='twitter:site' content='@argeo_org' />");
157 writeMeta(buf
, "og:url", url
);
159 writeMeta(buf
, "og:description", desc
);
161 writeMeta(buf
, "og:image", imgUrl
);
162 if (lastUpdate
!= null)
163 writeMeta(buf
, "og:updated_time",
164 Long
.toString(lastUpdate
.getTime().getTime()));
165 buf
.append("</head>");
166 buf
.append("<body>");
168 "<p><b>!! This page is meant for indexing robots, not for real people,"
169 + " visit <a href='/#").append(path
)
170 .append("'>").append(title
)
171 .append("</a> instead.</b></p>");
172 writeCanonical(buf
, node
);
173 buf
.append("</body>");
174 buf
.append("</html>");
175 writer
.print(buf
.toString());
177 response
.setHeader("Content-Type", "text/html");
179 } catch (Exception e
) {
180 throw new CmsException("Cannot write canonical answer", e
);
182 JcrUtils
.logoutQuietly(session
);
186 private void writeMeta(StringBuilder buf
, String tag
, String value
) {
187 buf
.append("<meta property='").append(tag
).append("' content='")
188 .append(value
).append("'/>");
191 private void writeCanonical(StringBuilder buf
, Node node
)
192 throws RepositoryException
{
194 if (node
.hasProperty(JCR_TITLE
))
196 .append(node
.getProperty(JCR_TITLE
).getString())
198 if (node
.hasProperty(JCR_DESCRIPTION
))
200 .append(node
.getProperty(JCR_DESCRIPTION
).getString())
202 NodeIterator children
= node
.getNodes();
203 while (children
.hasNext()) {
204 writeCanonical(buf
, children
.nextNode());
206 buf
.append("</div>");
210 class RobotServlet
extends HttpServlet
{
211 private static final long serialVersionUID
= 7935661175336419089L;
214 protected void service(HttpServletRequest request
,
215 HttpServletResponse response
) throws ServletException
,
217 PrintWriter writer
= response
.getWriter();
218 writer
.append("User-agent: *\n");
219 writer
.append("Disallow:\n");
220 response
.setHeader("Content-Type", "text/plain");
226 /** Intercepts all requests. Authenticates. */
227 class RootFilter
extends HttpFilter
{
230 public void doFilter(HttpSession httpSession
,
231 HttpServletRequest request
, HttpServletResponse response
,
232 FilterChain filterChain
) throws IOException
, ServletException
{
233 if (log
.isTraceEnabled()) {
234 log
.trace(request
.getRequestURL().append(
235 request
.getQueryString() != null ?
"?"
236 + request
.getQueryString() : ""));
240 String servletPath
= request
.getServletPath();
242 // client certificate
243 X509Certificate clientCert
= extractCertificate(request
);
244 if (clientCert
!= null) {
246 // if (log.isDebugEnabled())
247 // log.debug(clientCert.getSubjectX500Principal().getName());
251 if (servletPath
.startsWith(PATH_DATA
)) {
252 filterChain
.doFilter(request
, response
);
256 // skip /ui (workbench) for the time being
257 if (servletPath
.startsWith(PATH_WORKBENCH
)) {
258 filterChain
.doFilter(request
, response
);
262 // redirect long RWT paths to anchor
263 String path
= request
.getRequestURI().substring(
264 servletPath
.length());
265 int pathLength
= path
.length();
266 if (pathLength
!= 0 && (path
.charAt(0) == '/')
267 && !servletPath
.endsWith("rwt-resources")
268 && !path
.startsWith(KernelConstants
.PATH_WORKBENCH
)
269 && path
.lastIndexOf('/') != 0) {
270 String newLocation
= request
.getServletPath() + "#" + path
;
271 response
.setHeader("Location", newLocation
);
272 response
.setStatus(HttpServletResponse
.SC_FOUND
);
277 filterChain
.doFilter(request
, response
);
281 private void logRequest(HttpServletRequest request
) {
282 log
.debug("contextPath=" + request
.getContextPath());
283 log
.debug("servletPath=" + request
.getServletPath());
284 log
.debug("requestURI=" + request
.getRequestURI());
285 log
.debug("queryString=" + request
.getQueryString());
286 StringBuilder buf
= new StringBuilder();
288 Enumeration
<String
> en
= request
.getHeaderNames();
289 while (en
.hasMoreElements()) {
290 String header
= en
.nextElement();
291 Enumeration
<String
> values
= request
.getHeaders(header
);
292 while (values
.hasMoreElements())
293 buf
.append(" " + header
+ ": " + values
.nextElement());
298 Enumeration
<String
> an
= request
.getAttributeNames();
299 while (an
.hasMoreElements()) {
300 String attr
= an
.nextElement();
301 Object value
= request
.getAttribute(attr
);
302 buf
.append(" " + attr
+ ": " + value
);
305 log
.debug("\n" + buf
);
308 private X509Certificate
extractCertificate(HttpServletRequest req
) {
309 X509Certificate
[] certs
= (X509Certificate
[]) req
310 .getAttribute("javax.servlet.request.X509Certificate");
311 if (null != certs
&& certs
.length
> 0) {
317 // class CustomDosFilter extends DoSFilter {
319 // protected String extractUserId(ServletRequest request) {
320 // HttpSession httpSession = ((HttpServletRequest) request)
322 // if (isSessionAuthenticated(httpSession)) {
323 // String userId = ((SecurityContext) httpSession
324 // .getAttribute(SPRING_SECURITY_CONTEXT_KEY))
325 // .getAuthentication().getName();
328 // return super.extractUserId(request);