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
.Collection
;
14 import java
.util
.Enumeration
;
16 import javax
.jcr
.Node
;
17 import javax
.jcr
.NodeIterator
;
18 import javax
.jcr
.Repository
;
19 import javax
.jcr
.RepositoryException
;
20 import javax
.jcr
.Session
;
21 import javax
.security
.auth
.Subject
;
22 import javax
.servlet
.FilterChain
;
23 import javax
.servlet
.ServletException
;
24 import javax
.servlet
.http
.HttpServlet
;
25 import javax
.servlet
.http
.HttpServletRequest
;
26 import javax
.servlet
.http
.HttpServletResponse
;
27 import javax
.servlet
.http
.HttpSession
;
29 import org
.apache
.commons
.logging
.Log
;
30 import org
.apache
.commons
.logging
.LogFactory
;
31 import org
.argeo
.cms
.CmsException
;
32 import org
.argeo
.cms
.util
.CmsUtils
;
33 import org
.argeo
.jcr
.ArgeoJcrConstants
;
34 import org
.argeo
.jcr
.JcrUtils
;
35 import org
.eclipse
.equinox
.http
.servlet
.ExtendedHttpService
;
36 import org
.osgi
.framework
.BundleContext
;
37 import org
.osgi
.framework
.ServiceReference
;
40 * Intercepts and enriches http access, mainly focusing on security and
43 class NodeHttp
implements KernelConstants
, ArgeoJcrConstants
{
44 private final static Log log
= LogFactory
.getLog(NodeHttp
.class);
47 // private final RootFilter rootFilter;
49 // private final DoSFilter dosFilter;
50 // private final QoSFilter qosFilter;
52 private BundleContext bc
;
54 NodeHttp(ExtendedHttpService httpService
, BundleContext bc
) {
56 // rootFilter = new RootFilter();
57 // dosFilter = new CustomDosFilter();
58 // qosFilter = new QoSFilter();
61 httpService
.registerServlet("/!", new LinkServlet(), null, null);
62 httpService
.registerServlet("/robots.txt", new RobotServlet(), null, null);
63 } catch (Exception e
) {
64 throw new CmsException("Cannot register filters", e
);
68 public void destroy() {
71 class LinkServlet
extends HttpServlet
{
72 private static final long serialVersionUID
= 3749990143146845708L;
75 protected void service(HttpServletRequest request
, HttpServletResponse response
)
76 throws ServletException
, IOException
{
77 String path
= request
.getPathInfo();
78 String userAgent
= request
.getHeader("User-Agent").toLowerCase();
80 boolean isCompatibleBrowser
= false;
81 if (userAgent
.contains("bot") || userAgent
.contains("facebook") || userAgent
.contains("twitter")) {
83 } else if (userAgent
.contains("webkit") || userAgent
.contains("gecko") || userAgent
.contains("firefox")
84 || userAgent
.contains("msie") || userAgent
.contains("chrome") || userAgent
.contains("chromium")
85 || userAgent
.contains("opera") || userAgent
.contains("browser")) {
86 isCompatibleBrowser
= true;
90 log
.warn("# BOT " + request
.getHeader("User-Agent"));
91 canonicalAnswer(request
, response
, path
);
95 if (isCompatibleBrowser
&& log
.isTraceEnabled())
96 log
.trace("# BWS " + request
.getHeader("User-Agent"));
97 redirectTo(response
, "/#" + path
);
100 private void redirectTo(HttpServletResponse response
, String location
) {
101 response
.setHeader("Location", location
);
102 response
.setStatus(HttpServletResponse
.SC_FOUND
);
105 // private boolean canonicalAnswerNeededBy(HttpServletRequest request) {
106 // String userAgent = request.getHeader("User-Agent").toLowerCase();
107 // return userAgent.startsWith("facebookexternalhit/");
110 /** For bots which don't understand RWT. */
111 private void canonicalAnswer(HttpServletRequest request
, HttpServletResponse response
, String path
) {
112 Session session
= null;
114 PrintWriter writer
= response
.getWriter();
115 session
= Subject
.doAs(KernelUtils
.anonymousLogin(), new PrivilegedExceptionAction
<Session
>() {
118 public Session
run() throws Exception
{
119 Collection
<ServiceReference
<Repository
>> srs
= bc
.getServiceReferences(Repository
.class, "("
120 + ArgeoJcrConstants
.JCR_REPOSITORY_ALIAS
+ "=" + ArgeoJcrConstants
.ALIAS_NODE
+ ")");
121 Repository repository
= bc
.getService(srs
.iterator().next());
122 return repository
.login();
126 Node node
= session
.getNode(path
);
127 String title
= node
.hasProperty(JCR_TITLE
) ? node
.getProperty(JCR_TITLE
).getString() : node
.getName();
128 String desc
= node
.hasProperty(JCR_DESCRIPTION
) ? node
.getProperty(JCR_DESCRIPTION
).getString() : null;
129 Calendar lastUpdate
= node
.hasProperty(JCR_LAST_MODIFIED
)
130 ? node
.getProperty(JCR_LAST_MODIFIED
).getDate() : null;
131 String url
= CmsUtils
.getCanonicalUrl(node
, request
);
132 String imgUrl
= null;
133 loop
: for (NodeIterator it
= node
.getNodes(); it
.hasNext();) {
134 // Takes the first found cms:image
135 Node child
= it
.nextNode();
136 if (child
.isNodeType(CMS_IMAGE
)) {
137 imgUrl
= CmsUtils
.getDataUrl(child
, request
);
141 StringBuilder buf
= new StringBuilder();
142 buf
.append("<html>");
143 buf
.append("<head>");
144 writeMeta(buf
, "og:title", escapeHTML(title
));
145 writeMeta(buf
, "og:type", "website");
146 buf
.append("<meta name='twitter:card' content='summary' />");
147 buf
.append("<meta name='twitter:site' content='@argeo_org' />");
148 writeMeta(buf
, "og:url", url
);
150 writeMeta(buf
, "og:description", escapeHTML(desc
));
152 writeMeta(buf
, "og:image", imgUrl
);
153 if (lastUpdate
!= null)
154 writeMeta(buf
, "og:updated_time", Long
.toString(lastUpdate
.getTime().getTime()));
155 buf
.append("</head>");
156 buf
.append("<body>");
158 "<p><b>!! This page is meant for indexing robots, not for real people," + " visit <a href='/#")
159 .append(path
).append("'>").append(escapeHTML(title
)).append("</a> instead.</b></p>");
160 writeCanonical(buf
, node
);
161 buf
.append("</body>");
162 buf
.append("</html>");
163 writer
.print(buf
.toString());
165 response
.setHeader("Content-Type", "text/html");
167 } catch (Exception e
) {
168 throw new CmsException("Cannot write canonical answer", e
);
170 JcrUtils
.logoutQuietly(session
);
176 * http://stackoverflow.com/questions/1265282/recommended-method-for-
177 * escaping-html-in-java (+ escaping '). TODO Use
178 * org.apache.commons.lang.StringEscapeUtils
180 private String
escapeHTML(String s
) {
181 StringBuilder out
= new StringBuilder(Math
.max(16, s
.length()));
182 for (int i
= 0; i
< s
.length(); i
++) {
183 char c
= s
.charAt(i
);
184 if (c
> 127 || c
== '\'' || c
== '"' || c
== '<' || c
== '>' || c
== '&') {
192 return out
.toString();
195 private void writeMeta(StringBuilder buf
, String tag
, String value
) {
196 buf
.append("<meta property='").append(tag
).append("' content='").append(value
).append("'/>");
199 private void writeCanonical(StringBuilder buf
, Node node
) throws RepositoryException
{
201 if (node
.hasProperty(JCR_TITLE
))
202 buf
.append("<p>").append(node
.getProperty(JCR_TITLE
).getString()).append("</p>");
203 if (node
.hasProperty(JCR_DESCRIPTION
))
204 buf
.append("<p>").append(node
.getProperty(JCR_DESCRIPTION
).getString()).append("</p>");
205 NodeIterator children
= node
.getNodes();
206 while (children
.hasNext()) {
207 writeCanonical(buf
, children
.nextNode());
209 buf
.append("</div>");
213 class RobotServlet
extends HttpServlet
{
214 private static final long serialVersionUID
= 7935661175336419089L;
217 protected void service(HttpServletRequest request
, HttpServletResponse response
)
218 throws ServletException
, IOException
{
219 PrintWriter writer
= response
.getWriter();
220 writer
.append("User-agent: *\n");
221 writer
.append("Disallow:\n");
222 response
.setHeader("Content-Type", "text/plain");
228 /** Intercepts all requests. Authenticates. */
229 class RootFilter
extends HttpFilter
{
232 public void doFilter(HttpSession httpSession
, HttpServletRequest request
, HttpServletResponse response
,
233 FilterChain filterChain
) throws IOException
, ServletException
{
234 if (log
.isTraceEnabled()) {
235 log
.trace(request
.getRequestURL()
236 .append(request
.getQueryString() != null ?
"?" + 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(servletPath
.length());
264 int pathLength
= path
.length();
265 if (pathLength
!= 0 && (path
.charAt(0) == '/') && !servletPath
.endsWith("rwt-resources")
266 && !path
.startsWith(KernelConstants
.PATH_WORKBENCH
) && path
.lastIndexOf('/') != 0) {
267 String newLocation
= request
.getServletPath() + "#" + path
;
268 response
.setHeader("Location", newLocation
);
269 response
.setStatus(HttpServletResponse
.SC_FOUND
);
274 filterChain
.doFilter(request
, response
);
278 private void logRequest(HttpServletRequest request
) {
279 log
.debug("contextPath=" + request
.getContextPath());
280 log
.debug("servletPath=" + request
.getServletPath());
281 log
.debug("requestURI=" + request
.getRequestURI());
282 log
.debug("queryString=" + request
.getQueryString());
283 StringBuilder buf
= new StringBuilder();
285 Enumeration
<String
> en
= request
.getHeaderNames();
286 while (en
.hasMoreElements()) {
287 String header
= en
.nextElement();
288 Enumeration
<String
> values
= request
.getHeaders(header
);
289 while (values
.hasMoreElements())
290 buf
.append(" " + header
+ ": " + values
.nextElement());
295 Enumeration
<String
> an
= request
.getAttributeNames();
296 while (an
.hasMoreElements()) {
297 String attr
= an
.nextElement();
298 Object value
= request
.getAttribute(attr
);
299 buf
.append(" " + attr
+ ": " + value
);
302 log
.debug("\n" + buf
);
305 private X509Certificate
extractCertificate(HttpServletRequest req
) {
306 X509Certificate
[] certs
= (X509Certificate
[]) req
.getAttribute("javax.servlet.request.X509Certificate");
307 if (null != certs
&& certs
.length
> 0) {
313 // class CustomDosFilter extends DoSFilter {
315 // protected String extractUserId(ServletRequest request) {
316 // HttpSession httpSession = ((HttpServletRequest) request)
318 // if (isSessionAuthenticated(httpSession)) {
319 // String userId = ((SecurityContext) httpSession
320 // .getAttribute(SPRING_SECURITY_CONTEXT_KEY))
321 // .getAuthentication().getName();
324 // return super.extractUserId(request);