]> git.argeo.org Git - lgpl/argeo-commons.git/blob - org.argeo.cms/src/org/argeo/cms/internal/kernel/NodeHttp.java
Improve social networking
[lgpl/argeo-commons.git] / org.argeo.cms / src / org / argeo / cms / internal / kernel / NodeHttp.java
1 package org.argeo.cms.internal.kernel;
2
3 import static javax.jcr.Property.JCR_DESCRIPTION;
4 import static javax.jcr.Property.JCR_TITLE;
5 import static org.argeo.cms.CmsTypes.CMS_IMAGE;
6
7 import java.io.IOException;
8 import java.io.PrintWriter;
9 import java.security.PrivilegedExceptionAction;
10 import java.security.cert.X509Certificate;
11 import java.util.Enumeration;
12
13 import javax.jcr.Node;
14 import javax.jcr.NodeIterator;
15 import javax.jcr.Repository;
16 import javax.jcr.RepositoryException;
17 import javax.jcr.Session;
18 import javax.security.auth.Subject;
19 import javax.servlet.FilterChain;
20 import javax.servlet.ServletException;
21 import javax.servlet.http.HttpServlet;
22 import javax.servlet.http.HttpServletRequest;
23 import javax.servlet.http.HttpServletResponse;
24 import javax.servlet.http.HttpSession;
25
26 import org.apache.commons.logging.Log;
27 import org.apache.commons.logging.LogFactory;
28 import org.argeo.cms.CmsException;
29 import org.argeo.cms.util.CmsUtils;
30 import org.argeo.jcr.ArgeoJcrConstants;
31 import org.argeo.jcr.JcrUtils;
32 import org.eclipse.equinox.http.servlet.ExtendedHttpService;
33
34 /**
35 * Intercepts and enriches http access, mainly focusing on security and
36 * transactionality.
37 */
38 class NodeHttp implements KernelConstants, ArgeoJcrConstants {
39 private final static Log log = LogFactory.getLog(NodeHttp.class);
40
41 // Filters
42 // private final RootFilter rootFilter;
43
44 // private final DoSFilter dosFilter;
45 // private final QoSFilter qosFilter;
46
47 private Repository repository;
48
49 NodeHttp(ExtendedHttpService httpService, NodeRepository node) {
50 this.repository = node;
51 // rootFilter = new RootFilter();
52 // dosFilter = new CustomDosFilter();
53 // qosFilter = new QoSFilter();
54
55 try {
56 httpService.registerServlet("/!", new LinkServlet(repository),
57 null, null);
58 } catch (Exception e) {
59 throw new CmsException("Cannot register filters", e);
60 }
61 }
62
63 public void destroy() {
64 }
65
66 static class LinkServlet extends HttpServlet {
67 private static final long serialVersionUID = 3749990143146845708L;
68 private final Repository repository;
69
70 public LinkServlet(Repository repository) {
71 this.repository = repository;
72 }
73
74 @Override
75 protected void service(HttpServletRequest request,
76 HttpServletResponse response) throws ServletException,
77 IOException {
78 String path = request.getPathInfo();
79 String userAgent = request.getHeader("User-Agent").toLowerCase();
80 boolean isBot = false;
81 boolean isCompatibleBrowser = false;
82 if (userAgent.contains("bot") || userAgent.contains("facebook")
83 || userAgent.contains("twitter")) {
84 isBot = true;
85 } else if (userAgent.contains("webkit")
86 || userAgent.contains("gecko")
87 || userAgent.contains("firefox")
88 || userAgent.contains("msie")
89 || userAgent.contains("chrome")
90 || userAgent.contains("chromium")
91 || userAgent.contains("opera")
92 || userAgent.contains("browser")) {
93 isCompatibleBrowser = true;
94 }
95
96 if (isBot) {
97 log.warn("# BOT " + request.getHeader("User-Agent"));
98 canonicalAnswer(request, response, path);
99 return;
100 }
101
102 if (isCompatibleBrowser && log.isTraceEnabled())
103 log.trace("# BWS " + request.getHeader("User-Agent"));
104 redirectTo(response, "/#" + path);
105 }
106
107 private void redirectTo(HttpServletResponse response, String location) {
108 response.setHeader("Location", location);
109 response.setStatus(HttpServletResponse.SC_FOUND);
110 }
111
112 // private boolean canonicalAnswerNeededBy(HttpServletRequest request) {
113 // String userAgent = request.getHeader("User-Agent").toLowerCase();
114 // return userAgent.startsWith("facebookexternalhit/");
115 // }
116
117 /** For bots which don't understand RWT. */
118 private void canonicalAnswer(HttpServletRequest request,
119 HttpServletResponse response, String path) {
120 Session session = null;
121 try {
122 PrintWriter writer = response.getWriter();
123 session = Subject.doAs(KernelUtils.anonymousLogin(),
124 new PrivilegedExceptionAction<Session>() {
125
126 @Override
127 public Session run() throws Exception {
128 return repository.login();
129 }
130
131 });
132 Node node = session.getNode(path);
133 String title = node.hasProperty(JCR_TITLE) ? node.getProperty(
134 JCR_TITLE).getString() : node.getName();
135 String desc = node.hasProperty(JCR_DESCRIPTION) ? node
136 .getProperty(JCR_DESCRIPTION).getString() : null;
137 String url = CmsUtils.getCanonicalUrl(node, request);
138 String imgUrl = null;
139 for (NodeIterator it = node.getNodes(); it.hasNext();) {
140 Node child = it.nextNode();
141 if (child.isNodeType(CMS_IMAGE))
142 imgUrl = CmsUtils.getDataUrl(child, request);
143 }
144 StringBuilder buf = new StringBuilder();
145 buf.append("<html>");
146 buf.append("<head>");
147 writeMeta(buf, "og:title", title);
148 writeMeta(buf, "og:type", "website");
149 writeMeta(buf, "og:url", url);
150 if (desc != null)
151 writeMeta(buf, "og:description", desc);
152 if (imgUrl != null)
153 writeMeta(buf, "og:image", imgUrl);
154 buf.append("</head>");
155 buf.append("<body>");
156 buf.append(
157 "<p><b>!! This page is meant for indexing robots, not for real people,"
158 + " visit <a href='/#").append(path)
159 .append("'>").append(title)
160 .append("</a> instead.</b></p>");
161 writeCanonical(buf, node);
162 buf.append("</body>");
163 buf.append("</html>");
164 writer.print(buf.toString());
165 writer.flush();
166 } catch (Exception e) {
167 throw new CmsException("Cannot write canonical answer", e);
168 } finally {
169 JcrUtils.logoutQuietly(session);
170 }
171 }
172
173 private void writeMeta(StringBuilder buf, String tag, String value) {
174 buf.append("<meta property='").append(tag).append("' content='")
175 .append(value).append("'/>");
176 }
177
178 private void writeCanonical(StringBuilder buf, Node node)
179 throws RepositoryException {
180 buf.append("<div>");
181 if (node.hasProperty(JCR_TITLE))
182 buf.append("<p>")
183 .append(node.getProperty(JCR_TITLE).getString())
184 .append("</p>");
185 if (node.hasProperty(JCR_DESCRIPTION))
186 buf.append("<p>")
187 .append(node.getProperty(JCR_DESCRIPTION).getString())
188 .append("</p>");
189 NodeIterator children = node.getNodes();
190 while (children.hasNext()) {
191 writeCanonical(buf, children.nextNode());
192 }
193 buf.append("</div>");
194 }
195 }
196
197 /** Intercepts all requests. Authenticates. */
198 class RootFilter extends HttpFilter {
199
200 @Override
201 public void doFilter(HttpSession httpSession,
202 HttpServletRequest request, HttpServletResponse response,
203 FilterChain filterChain) throws IOException, ServletException {
204 if (log.isTraceEnabled()) {
205 log.trace(request.getRequestURL().append(
206 request.getQueryString() != null ? "?"
207 + request.getQueryString() : ""));
208 logRequest(request);
209 }
210
211 String servletPath = request.getServletPath();
212
213 // client certificate
214 X509Certificate clientCert = extractCertificate(request);
215 if (clientCert != null) {
216 // TODO authenticate
217 // if (log.isDebugEnabled())
218 // log.debug(clientCert.getSubjectX500Principal().getName());
219 }
220
221 // skip data
222 if (servletPath.startsWith(PATH_DATA)) {
223 filterChain.doFilter(request, response);
224 return;
225 }
226
227 // skip /ui (workbench) for the time being
228 if (servletPath.startsWith(PATH_WORKBENCH)) {
229 filterChain.doFilter(request, response);
230 return;
231 }
232
233 // redirect long RWT paths to anchor
234 String path = request.getRequestURI().substring(
235 servletPath.length());
236 int pathLength = path.length();
237 if (pathLength != 0 && (path.charAt(0) == '/')
238 && !servletPath.endsWith("rwt-resources")
239 && !path.startsWith(KernelConstants.PATH_WORKBENCH)
240 && path.lastIndexOf('/') != 0) {
241 String newLocation = request.getServletPath() + "#" + path;
242 response.setHeader("Location", newLocation);
243 response.setStatus(HttpServletResponse.SC_FOUND);
244 return;
245 }
246
247 // process normally
248 filterChain.doFilter(request, response);
249 }
250 }
251
252 private void logRequest(HttpServletRequest request) {
253 log.debug("contextPath=" + request.getContextPath());
254 log.debug("servletPath=" + request.getServletPath());
255 log.debug("requestURI=" + request.getRequestURI());
256 log.debug("queryString=" + request.getQueryString());
257 StringBuilder buf = new StringBuilder();
258 // headers
259 Enumeration<String> en = request.getHeaderNames();
260 while (en.hasMoreElements()) {
261 String header = en.nextElement();
262 Enumeration<String> values = request.getHeaders(header);
263 while (values.hasMoreElements())
264 buf.append(" " + header + ": " + values.nextElement());
265 buf.append('\n');
266 }
267
268 // attributed
269 Enumeration<String> an = request.getAttributeNames();
270 while (an.hasMoreElements()) {
271 String attr = an.nextElement();
272 Object value = request.getAttribute(attr);
273 buf.append(" " + attr + ": " + value);
274 buf.append('\n');
275 }
276 log.debug("\n" + buf);
277 }
278
279 private X509Certificate extractCertificate(HttpServletRequest req) {
280 X509Certificate[] certs = (X509Certificate[]) req
281 .getAttribute("javax.servlet.request.X509Certificate");
282 if (null != certs && certs.length > 0) {
283 return certs[0];
284 }
285 return null;
286 }
287
288 // class CustomDosFilter extends DoSFilter {
289 // @Override
290 // protected String extractUserId(ServletRequest request) {
291 // HttpSession httpSession = ((HttpServletRequest) request)
292 // .getSession();
293 // if (isSessionAuthenticated(httpSession)) {
294 // String userId = ((SecurityContext) httpSession
295 // .getAttribute(SPRING_SECURITY_CONTEXT_KEY))
296 // .getAuthentication().getName();
297 // return userId;
298 // }
299 // return super.extractUserId(request);
300 //
301 // }
302 // }
303 }