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
.net
.MalformedURLException
;
12 import java
.security
.PrivilegedExceptionAction
;
13 import java
.security
.cert
.X509Certificate
;
14 import java
.util
.Calendar
;
15 import java
.util
.Collection
;
16 import java
.util
.Enumeration
;
18 import javax
.jcr
.Node
;
19 import javax
.jcr
.NodeIterator
;
20 import javax
.jcr
.Repository
;
21 import javax
.jcr
.RepositoryException
;
22 import javax
.jcr
.Session
;
23 import javax
.security
.auth
.Subject
;
24 import javax
.servlet
.FilterChain
;
25 import javax
.servlet
.ServletException
;
26 import javax
.servlet
.http
.HttpServlet
;
27 import javax
.servlet
.http
.HttpServletRequest
;
28 import javax
.servlet
.http
.HttpServletResponse
;
29 import javax
.servlet
.http
.HttpSession
;
31 import org
.apache
.commons
.logging
.Log
;
32 import org
.apache
.commons
.logging
.LogFactory
;
33 import org
.argeo
.cms
.CmsException
;
34 import org
.argeo
.jcr
.JcrUtils
;
35 import org
.argeo
.node
.NodeConstants
;
36 import org
.argeo
.node
.NodeUtils
;
37 import org
.osgi
.framework
.BundleContext
;
38 import org
.osgi
.framework
.ServiceReference
;
39 import org
.osgi
.service
.http
.HttpService
;
42 * Intercepts and enriches http access, mainly focusing on security and
45 class NodeHttp
implements KernelConstants
{
46 private final static Log log
= LogFactory
.getLog(NodeHttp
.class);
49 // private final RootFilter rootFilter;
51 // private final DoSFilter dosFilter;
52 // private final QoSFilter qosFilter;
54 private BundleContext bc
;
56 NodeHttp(HttpService httpService
, BundleContext bc
) {
58 // rootFilter = new RootFilter();
59 // dosFilter = new CustomDosFilter();
60 // qosFilter = new QoSFilter();
63 httpService
.registerServlet("/!", new LinkServlet(), null, null);
64 httpService
.registerServlet("/robots.txt", new RobotServlet(), null, null);
65 } catch (Exception e
) {
66 throw new CmsException("Cannot register filters", e
);
70 public void destroy() {
73 class LinkServlet
extends HttpServlet
{
74 private static final long serialVersionUID
= 3749990143146845708L;
77 protected void service(HttpServletRequest request
, HttpServletResponse response
)
78 throws ServletException
, IOException
{
79 String path
= request
.getPathInfo();
80 String userAgent
= request
.getHeader("User-Agent").toLowerCase();
81 boolean isBot
= false;
82 boolean isCompatibleBrowser
= false;
83 if (userAgent
.contains("bot") || userAgent
.contains("facebook") || userAgent
.contains("twitter")) {
85 } else if (userAgent
.contains("webkit") || userAgent
.contains("gecko") || userAgent
.contains("firefox")
86 || userAgent
.contains("msie") || userAgent
.contains("chrome") || userAgent
.contains("chromium")
87 || userAgent
.contains("opera") || userAgent
.contains("browser")) {
88 isCompatibleBrowser
= true;
92 log
.warn("# BOT " + request
.getHeader("User-Agent"));
93 canonicalAnswer(request
, response
, path
);
97 if (isCompatibleBrowser
&& log
.isTraceEnabled())
98 log
.trace("# BWS " + request
.getHeader("User-Agent"));
99 redirectTo(response
, "/#" + path
);
102 private void redirectTo(HttpServletResponse response
, String location
) {
103 response
.setHeader("Location", location
);
104 response
.setStatus(HttpServletResponse
.SC_FOUND
);
107 // private boolean canonicalAnswerNeededBy(HttpServletRequest request) {
108 // String userAgent = request.getHeader("User-Agent").toLowerCase();
109 // return userAgent.startsWith("facebookexternalhit/");
112 /** For bots which don't understand RWT. */
113 private void canonicalAnswer(HttpServletRequest request
, HttpServletResponse response
, String path
) {
114 Session session
= null;
116 PrintWriter writer
= response
.getWriter();
117 session
= Subject
.doAs(KernelUtils
.anonymousLogin(), new PrivilegedExceptionAction
<Session
>() {
120 public Session
run() throws Exception
{
121 Collection
<ServiceReference
<Repository
>> srs
= bc
.getServiceReferences(Repository
.class,
122 "(" + NodeConstants
.CN
+ "=" + NodeConstants
.NODE
+ ")");
123 Repository repository
= bc
.getService(srs
.iterator().next());
124 return repository
.login();
128 Node node
= session
.getNode(path
);
129 String title
= node
.hasProperty(JCR_TITLE
) ? node
.getProperty(JCR_TITLE
).getString() : node
.getName();
130 String desc
= node
.hasProperty(JCR_DESCRIPTION
) ? node
.getProperty(JCR_DESCRIPTION
).getString() : null;
131 Calendar lastUpdate
= node
.hasProperty(JCR_LAST_MODIFIED
)
132 ? node
.getProperty(JCR_LAST_MODIFIED
).getDate() : null;
133 String url
= getCanonicalUrl(node
, request
);
134 String imgUrl
= null;
135 loop
: for (NodeIterator it
= node
.getNodes(); it
.hasNext();) {
136 // Takes the first found cms:image
137 Node child
= it
.nextNode();
138 if (child
.isNodeType(CMS_IMAGE
)) {
139 imgUrl
= getDataUrl(child
, request
);
143 StringBuilder buf
= new StringBuilder();
144 buf
.append("<html>");
145 buf
.append("<head>");
146 writeMeta(buf
, "og:title", escapeHTML(title
));
147 writeMeta(buf
, "og:type", "website");
148 buf
.append("<meta name='twitter:card' content='summary' />");
149 buf
.append("<meta name='twitter:site' content='@argeo_org' />");
150 writeMeta(buf
, "og:url", url
);
152 writeMeta(buf
, "og:description", escapeHTML(desc
));
154 writeMeta(buf
, "og:image", imgUrl
);
155 if (lastUpdate
!= null)
156 writeMeta(buf
, "og:updated_time", Long
.toString(lastUpdate
.getTime().getTime()));
157 buf
.append("</head>");
158 buf
.append("<body>");
160 "<p><b>!! This page is meant for indexing robots, not for real people," + " visit <a href='/#")
161 .append(path
).append("'>").append(escapeHTML(title
)).append("</a> instead.</b></p>");
162 writeCanonical(buf
, node
);
163 buf
.append("</body>");
164 buf
.append("</html>");
165 writer
.print(buf
.toString());
167 response
.setHeader("Content-Type", "text/html");
169 } catch (Exception e
) {
170 throw new CmsException("Cannot write canonical answer", e
);
172 JcrUtils
.logoutQuietly(session
);
178 * http://stackoverflow.com/questions/1265282/recommended-method-for-
179 * escaping-html-in-java (+ escaping '). TODO Use
180 * org.apache.commons.lang.StringEscapeUtils
182 private String
escapeHTML(String s
) {
183 StringBuilder out
= new StringBuilder(Math
.max(16, s
.length()));
184 for (int i
= 0; i
< s
.length(); i
++) {
185 char c
= s
.charAt(i
);
186 if (c
> 127 || c
== '\'' || c
== '"' || c
== '<' || c
== '>' || c
== '&') {
194 return out
.toString();
197 private void writeMeta(StringBuilder buf
, String tag
, String value
) {
198 buf
.append("<meta property='").append(tag
).append("' content='").append(value
).append("'/>");
201 private void writeCanonical(StringBuilder buf
, Node node
) throws RepositoryException
{
203 if (node
.hasProperty(JCR_TITLE
))
204 buf
.append("<p>").append(node
.getProperty(JCR_TITLE
).getString()).append("</p>");
205 if (node
.hasProperty(JCR_DESCRIPTION
))
206 buf
.append("<p>").append(node
.getProperty(JCR_DESCRIPTION
).getString()).append("</p>");
207 NodeIterator children
= node
.getNodes();
208 while (children
.hasNext()) {
209 writeCanonical(buf
, children
.nextNode());
211 buf
.append("</div>");
215 private StringBuilder
getServerBaseUrl(HttpServletRequest request
) {
217 URL url
= new URL(request
.getRequestURL().toString());
218 StringBuilder buf
= new StringBuilder();
219 buf
.append(url
.getProtocol()).append("://").append(url
.getHost());
220 if (url
.getPort() != -1)
221 buf
.append(':').append(url
.getPort());
223 } catch (MalformedURLException e
) {
224 throw new CmsException("Cannot extract server base URL from " + request
.getRequestURL(), e
);
228 private String
getDataUrl(Node node
, HttpServletRequest request
) throws RepositoryException
{
230 StringBuilder buf
= getServerBaseUrl(request
);
231 buf
.append(NodeUtils
.getDataPath(NodeConstants
.NODE
, node
));
232 return new URL(buf
.toString()).toString();
233 } catch (MalformedURLException e
) {
234 throw new CmsException("Cannot build data URL for " + node
, e
);
238 // public static String getDataPath(Node node) throws
239 // RepositoryException {
240 // assert node != null;
241 // String userId = node.getSession().getUserID();
242 //// if (log.isTraceEnabled())
243 //// log.trace(userId + " : " + node.getPath());
244 // StringBuilder buf = new StringBuilder();
245 // boolean isAnonymous =
246 // userId.equalsIgnoreCase(NodeConstants.ROLE_ANONYMOUS);
248 // buf.append(WEBDAV_PUBLIC);
250 // buf.append(WEBDAV_PRIVATE);
251 // Session session = node.getSession();
252 // Repository repository = session.getRepository();
254 // if (repository.isSingleValueDescriptor(NodeConstants.CN)) {
255 // cn = repository.getDescriptor(NodeConstants.CN);
257 //// log.warn("No cn defined in repository, using " +
258 // NodeConstants.NODE);
259 // cn = NodeConstants.NODE;
262 // buf.append('/').append(cn).append('/').append(session.getWorkspace().getName()).append(node.getPath())
266 private String
getCanonicalUrl(Node node
, HttpServletRequest request
) throws RepositoryException
{
268 StringBuilder buf
= getServerBaseUrl(request
);
269 buf
.append('/').append('!').append(node
.getPath());
270 return new URL(buf
.toString()).toString();
271 } catch (MalformedURLException e
) {
272 throw new CmsException("Cannot build data URL for " + node
, e
);
274 // return request.getRequestURL().append('!').append(node.getPath())
280 class RobotServlet
extends HttpServlet
{
281 private static final long serialVersionUID
= 7935661175336419089L;
284 protected void service(HttpServletRequest request
, HttpServletResponse response
)
285 throws ServletException
, IOException
{
286 PrintWriter writer
= response
.getWriter();
287 writer
.append("User-agent: *\n");
288 writer
.append("Disallow:\n");
289 response
.setHeader("Content-Type", "text/plain");
295 /** Intercepts all requests. Authenticates. */
296 class RootFilter
extends HttpFilter
{
299 public void doFilter(HttpSession httpSession
, HttpServletRequest request
, HttpServletResponse response
,
300 FilterChain filterChain
) throws IOException
, ServletException
{
301 if (log
.isTraceEnabled()) {
302 log
.trace(request
.getRequestURL()
303 .append(request
.getQueryString() != null ?
"?" + request
.getQueryString() : ""));
307 String servletPath
= request
.getServletPath();
309 // client certificate
310 X509Certificate clientCert
= extractCertificate(request
);
311 if (clientCert
!= null) {
313 // if (log.isDebugEnabled())
314 // log.debug(clientCert.getSubjectX500Principal().getName());
318 if (servletPath
.startsWith(NodeConstants
.PATH_DATA
)) {
319 filterChain
.doFilter(request
, response
);
323 // skip /ui (workbench) for the time being
324 if (servletPath
.startsWith(PATH_WORKBENCH
)) {
325 filterChain
.doFilter(request
, response
);
329 // redirect long RWT paths to anchor
330 String path
= request
.getRequestURI().substring(servletPath
.length());
331 int pathLength
= path
.length();
332 if (pathLength
!= 0 && (path
.charAt(0) == '/') && !servletPath
.endsWith("rwt-resources")
333 && !path
.startsWith(KernelConstants
.PATH_WORKBENCH
) && path
.lastIndexOf('/') != 0) {
334 String newLocation
= request
.getServletPath() + "#" + path
;
335 response
.setHeader("Location", newLocation
);
336 response
.setStatus(HttpServletResponse
.SC_FOUND
);
341 filterChain
.doFilter(request
, response
);
345 private void logRequest(HttpServletRequest request
) {
346 log
.debug("contextPath=" + request
.getContextPath());
347 log
.debug("servletPath=" + request
.getServletPath());
348 log
.debug("requestURI=" + request
.getRequestURI());
349 log
.debug("queryString=" + request
.getQueryString());
350 StringBuilder buf
= new StringBuilder();
352 Enumeration
<String
> en
= request
.getHeaderNames();
353 while (en
.hasMoreElements()) {
354 String header
= en
.nextElement();
355 Enumeration
<String
> values
= request
.getHeaders(header
);
356 while (values
.hasMoreElements())
357 buf
.append(" " + header
+ ": " + values
.nextElement());
362 Enumeration
<String
> an
= request
.getAttributeNames();
363 while (an
.hasMoreElements()) {
364 String attr
= an
.nextElement();
365 Object value
= request
.getAttribute(attr
);
366 buf
.append(" " + attr
+ ": " + value
);
369 log
.debug("\n" + buf
);
372 private X509Certificate
extractCertificate(HttpServletRequest req
) {
373 X509Certificate
[] certs
= (X509Certificate
[]) req
.getAttribute("javax.servlet.request.X509Certificate");
374 if (null != certs
&& certs
.length
> 0) {
380 // class CustomDosFilter extends DoSFilter {
382 // protected String extractUserId(ServletRequest request) {
383 // HttpSession httpSession = ((HttpServletRequest) request)
385 // if (isSessionAuthenticated(httpSession)) {
386 // String userId = ((SecurityContext) httpSession
387 // .getAttribute(SPRING_SECURITY_CONTEXT_KEY))
388 // .getAuthentication().getName();
391 // return super.extractUserId(request);