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
.jcr
.JcrUtils
;
33 import org
.argeo
.node
.NodeConstants
;
34 import org
.osgi
.framework
.BundleContext
;
35 import org
.osgi
.framework
.ServiceReference
;
36 import org
.osgi
.service
.http
.HttpService
;
39 * Intercepts and enriches http access, mainly focusing on security and
42 class NodeHttp
implements KernelConstants
{
43 private final static Log log
= LogFactory
.getLog(NodeHttp
.class);
46 // private final RootFilter rootFilter;
48 // private final DoSFilter dosFilter;
49 // private final QoSFilter qosFilter;
51 private BundleContext bc
;
53 NodeHttp(HttpService httpService
, BundleContext bc
) {
55 // rootFilter = new RootFilter();
56 // dosFilter = new CustomDosFilter();
57 // qosFilter = new QoSFilter();
60 httpService
.registerServlet("/!", new LinkServlet(), null, null);
61 httpService
.registerServlet("/robots.txt", new RobotServlet(), null, null);
62 } catch (Exception e
) {
63 throw new CmsException("Cannot register filters", e
);
67 public void destroy() {
70 class LinkServlet
extends HttpServlet
{
71 private static final long serialVersionUID
= 3749990143146845708L;
74 protected void service(HttpServletRequest request
, HttpServletResponse response
)
75 throws ServletException
, IOException
{
76 String path
= request
.getPathInfo();
77 String userAgent
= request
.getHeader("User-Agent").toLowerCase();
78 boolean isBot
= false;
79 boolean isCompatibleBrowser
= false;
80 if (userAgent
.contains("bot") || userAgent
.contains("facebook") || userAgent
.contains("twitter")) {
82 } else if (userAgent
.contains("webkit") || userAgent
.contains("gecko") || userAgent
.contains("firefox")
83 || userAgent
.contains("msie") || userAgent
.contains("chrome") || userAgent
.contains("chromium")
84 || userAgent
.contains("opera") || userAgent
.contains("browser")) {
85 isCompatibleBrowser
= true;
89 log
.warn("# BOT " + request
.getHeader("User-Agent"));
90 canonicalAnswer(request
, response
, path
);
94 if (isCompatibleBrowser
&& log
.isTraceEnabled())
95 log
.trace("# BWS " + request
.getHeader("User-Agent"));
96 redirectTo(response
, "/#" + path
);
99 private void redirectTo(HttpServletResponse response
, String location
) {
100 response
.setHeader("Location", location
);
101 response
.setStatus(HttpServletResponse
.SC_FOUND
);
104 // private boolean canonicalAnswerNeededBy(HttpServletRequest request) {
105 // String userAgent = request.getHeader("User-Agent").toLowerCase();
106 // return userAgent.startsWith("facebookexternalhit/");
109 /** For bots which don't understand RWT. */
110 private void canonicalAnswer(HttpServletRequest request
, HttpServletResponse response
, String path
) {
111 Session session
= null;
113 PrintWriter writer
= response
.getWriter();
114 session
= Subject
.doAs(KernelUtils
.anonymousLogin(), new PrivilegedExceptionAction
<Session
>() {
117 public Session
run() throws Exception
{
118 Collection
<ServiceReference
<Repository
>> srs
= bc
.getServiceReferences(Repository
.class, "("
119 + NodeConstants
.JCR_REPOSITORY_ALIAS
+ "=" + NodeConstants
.ALIAS_NODE
+ ")");
120 Repository repository
= bc
.getService(srs
.iterator().next());
121 return repository
.login();
125 Node node
= session
.getNode(path
);
126 String title
= node
.hasProperty(JCR_TITLE
) ? node
.getProperty(JCR_TITLE
).getString() : node
.getName();
127 String desc
= node
.hasProperty(JCR_DESCRIPTION
) ? node
.getProperty(JCR_DESCRIPTION
).getString() : null;
128 Calendar lastUpdate
= node
.hasProperty(JCR_LAST_MODIFIED
)
129 ? node
.getProperty(JCR_LAST_MODIFIED
).getDate() : null;
130 String url
= KernelUtils
.getCanonicalUrl(node
, request
);
131 String imgUrl
= null;
132 loop
: for (NodeIterator it
= node
.getNodes(); it
.hasNext();) {
133 // Takes the first found cms:image
134 Node child
= it
.nextNode();
135 if (child
.isNodeType(CMS_IMAGE
)) {
136 imgUrl
= KernelUtils
.getDataUrl(child
, request
);
140 StringBuilder buf
= new StringBuilder();
141 buf
.append("<html>");
142 buf
.append("<head>");
143 writeMeta(buf
, "og:title", escapeHTML(title
));
144 writeMeta(buf
, "og:type", "website");
145 buf
.append("<meta name='twitter:card' content='summary' />");
146 buf
.append("<meta name='twitter:site' content='@argeo_org' />");
147 writeMeta(buf
, "og:url", url
);
149 writeMeta(buf
, "og:description", escapeHTML(desc
));
151 writeMeta(buf
, "og:image", imgUrl
);
152 if (lastUpdate
!= null)
153 writeMeta(buf
, "og:updated_time", Long
.toString(lastUpdate
.getTime().getTime()));
154 buf
.append("</head>");
155 buf
.append("<body>");
157 "<p><b>!! This page is meant for indexing robots, not for real people," + " visit <a href='/#")
158 .append(path
).append("'>").append(escapeHTML(title
)).append("</a> instead.</b></p>");
159 writeCanonical(buf
, node
);
160 buf
.append("</body>");
161 buf
.append("</html>");
162 writer
.print(buf
.toString());
164 response
.setHeader("Content-Type", "text/html");
166 } catch (Exception e
) {
167 throw new CmsException("Cannot write canonical answer", e
);
169 JcrUtils
.logoutQuietly(session
);
175 * http://stackoverflow.com/questions/1265282/recommended-method-for-
176 * escaping-html-in-java (+ escaping '). TODO Use
177 * org.apache.commons.lang.StringEscapeUtils
179 private String
escapeHTML(String s
) {
180 StringBuilder out
= new StringBuilder(Math
.max(16, s
.length()));
181 for (int i
= 0; i
< s
.length(); i
++) {
182 char c
= s
.charAt(i
);
183 if (c
> 127 || c
== '\'' || c
== '"' || c
== '<' || c
== '>' || c
== '&') {
191 return out
.toString();
194 private void writeMeta(StringBuilder buf
, String tag
, String value
) {
195 buf
.append("<meta property='").append(tag
).append("' content='").append(value
).append("'/>");
198 private void writeCanonical(StringBuilder buf
, Node node
) throws RepositoryException
{
200 if (node
.hasProperty(JCR_TITLE
))
201 buf
.append("<p>").append(node
.getProperty(JCR_TITLE
).getString()).append("</p>");
202 if (node
.hasProperty(JCR_DESCRIPTION
))
203 buf
.append("<p>").append(node
.getProperty(JCR_DESCRIPTION
).getString()).append("</p>");
204 NodeIterator children
= node
.getNodes();
205 while (children
.hasNext()) {
206 writeCanonical(buf
, children
.nextNode());
208 buf
.append("</div>");
212 class RobotServlet
extends HttpServlet
{
213 private static final long serialVersionUID
= 7935661175336419089L;
216 protected void service(HttpServletRequest request
, HttpServletResponse response
)
217 throws ServletException
, IOException
{
218 PrintWriter writer
= response
.getWriter();
219 writer
.append("User-agent: *\n");
220 writer
.append("Disallow:\n");
221 response
.setHeader("Content-Type", "text/plain");
227 /** Intercepts all requests. Authenticates. */
228 class RootFilter
extends HttpFilter
{
231 public void doFilter(HttpSession httpSession
, HttpServletRequest request
, HttpServletResponse response
,
232 FilterChain filterChain
) throws IOException
, ServletException
{
233 if (log
.isTraceEnabled()) {
234 log
.trace(request
.getRequestURL()
235 .append(request
.getQueryString() != null ?
"?" + request
.getQueryString() : ""));
239 String servletPath
= request
.getServletPath();
241 // client certificate
242 X509Certificate clientCert
= extractCertificate(request
);
243 if (clientCert
!= null) {
245 // if (log.isDebugEnabled())
246 // log.debug(clientCert.getSubjectX500Principal().getName());
250 if (servletPath
.startsWith(PATH_DATA
)) {
251 filterChain
.doFilter(request
, response
);
255 // skip /ui (workbench) for the time being
256 if (servletPath
.startsWith(PATH_WORKBENCH
)) {
257 filterChain
.doFilter(request
, response
);
261 // redirect long RWT paths to anchor
262 String path
= request
.getRequestURI().substring(servletPath
.length());
263 int pathLength
= path
.length();
264 if (pathLength
!= 0 && (path
.charAt(0) == '/') && !servletPath
.endsWith("rwt-resources")
265 && !path
.startsWith(KernelConstants
.PATH_WORKBENCH
) && path
.lastIndexOf('/') != 0) {
266 String newLocation
= request
.getServletPath() + "#" + path
;
267 response
.setHeader("Location", newLocation
);
268 response
.setStatus(HttpServletResponse
.SC_FOUND
);
273 filterChain
.doFilter(request
, response
);
277 private void logRequest(HttpServletRequest request
) {
278 log
.debug("contextPath=" + request
.getContextPath());
279 log
.debug("servletPath=" + request
.getServletPath());
280 log
.debug("requestURI=" + request
.getRequestURI());
281 log
.debug("queryString=" + request
.getQueryString());
282 StringBuilder buf
= new StringBuilder();
284 Enumeration
<String
> en
= request
.getHeaderNames();
285 while (en
.hasMoreElements()) {
286 String header
= en
.nextElement();
287 Enumeration
<String
> values
= request
.getHeaders(header
);
288 while (values
.hasMoreElements())
289 buf
.append(" " + header
+ ": " + values
.nextElement());
294 Enumeration
<String
> an
= request
.getAttributeNames();
295 while (an
.hasMoreElements()) {
296 String attr
= an
.nextElement();
297 Object value
= request
.getAttribute(attr
);
298 buf
.append(" " + attr
+ ": " + value
);
301 log
.debug("\n" + buf
);
304 private X509Certificate
extractCertificate(HttpServletRequest req
) {
305 X509Certificate
[] certs
= (X509Certificate
[]) req
.getAttribute("javax.servlet.request.X509Certificate");
306 if (null != certs
&& certs
.length
> 0) {
312 // class CustomDosFilter extends DoSFilter {
314 // protected String extractUserId(ServletRequest request) {
315 // HttpSession httpSession = ((HttpServletRequest) request)
317 // if (isSessionAuthenticated(httpSession)) {
318 // String userId = ((SecurityContext) httpSession
319 // .getAttribute(SPRING_SECURITY_CONTEXT_KEY))
320 // .getAuthentication().getName();
323 // return super.extractUserId(request);