]> git.argeo.org Git - lgpl/argeo-commons.git/blob - org.argeo.cms/src/org/argeo/cms/internal/kernel/NodeHttp.java
Fix typo in string validation
[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_LAST_MODIFIED;
5 import static javax.jcr.Property.JCR_TITLE;
6 import static org.argeo.cms.CmsTypes.CMS_IMAGE;
7
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;
14
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;
27
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;
35
36 /**
37 * Intercepts and enriches http access, mainly focusing on security and
38 * transactionality.
39 */
40 class NodeHttp implements KernelConstants, ArgeoJcrConstants {
41 private final static Log log = LogFactory.getLog(NodeHttp.class);
42
43 // Filters
44 // private final RootFilter rootFilter;
45
46 // private final DoSFilter dosFilter;
47 // private final QoSFilter qosFilter;
48
49 private Repository repository;
50
51 NodeHttp(ExtendedHttpService httpService, NodeRepository node) {
52 this.repository = node;
53 // rootFilter = new RootFilter();
54 // dosFilter = new CustomDosFilter();
55 // qosFilter = new QoSFilter();
56
57 try {
58 httpService.registerServlet("/!", new LinkServlet(repository),
59 null, null);
60 httpService.registerServlet("/robots.txt", new RobotServlet(),
61 null, null);
62 } catch (Exception e) {
63 throw new CmsException("Cannot register filters", e);
64 }
65 }
66
67 public void destroy() {
68 }
69
70 static class LinkServlet extends HttpServlet {
71 private static final long serialVersionUID = 3749990143146845708L;
72 private final Repository repository;
73
74 public LinkServlet(Repository repository) {
75 this.repository = repository;
76 }
77
78 @Override
79 protected void service(HttpServletRequest request,
80 HttpServletResponse response) throws ServletException,
81 IOException {
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")) {
88 isBot = true;
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;
98 }
99
100 if (isBot) {
101 log.warn("# BOT " + request.getHeader("User-Agent"));
102 canonicalAnswer(request, response, path);
103 return;
104 }
105
106 if (isCompatibleBrowser && log.isTraceEnabled())
107 log.trace("# BWS " + request.getHeader("User-Agent"));
108 redirectTo(response, "/#" + path);
109 }
110
111 private void redirectTo(HttpServletResponse response, String location) {
112 response.setHeader("Location", location);
113 response.setStatus(HttpServletResponse.SC_FOUND);
114 }
115
116 // private boolean canonicalAnswerNeededBy(HttpServletRequest request) {
117 // String userAgent = request.getHeader("User-Agent").toLowerCase();
118 // return userAgent.startsWith("facebookexternalhit/");
119 // }
120
121 /** For bots which don't understand RWT. */
122 private void canonicalAnswer(HttpServletRequest request,
123 HttpServletResponse response, String path) {
124 Session session = null;
125 try {
126 PrintWriter writer = response.getWriter();
127 session = Subject.doAs(KernelUtils.anonymousLogin(),
128 new PrivilegedExceptionAction<Session>() {
129
130 @Override
131 public Session run() throws Exception {
132 return repository.login();
133 }
134
135 });
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);
149 }
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);
158 if (desc != null)
159 writeMeta(buf, "og:description", desc);
160 if (imgUrl != null)
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>");
167 buf.append(
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());
176
177 response.setHeader("Content-Type", "text/html");
178 writer.flush();
179 } catch (Exception e) {
180 throw new CmsException("Cannot write canonical answer", e);
181 } finally {
182 JcrUtils.logoutQuietly(session);
183 }
184 }
185
186 private void writeMeta(StringBuilder buf, String tag, String value) {
187 buf.append("<meta property='").append(tag).append("' content='")
188 .append(value).append("'/>");
189 }
190
191 private void writeCanonical(StringBuilder buf, Node node)
192 throws RepositoryException {
193 buf.append("<div>");
194 if (node.hasProperty(JCR_TITLE))
195 buf.append("<p>")
196 .append(node.getProperty(JCR_TITLE).getString())
197 .append("</p>");
198 if (node.hasProperty(JCR_DESCRIPTION))
199 buf.append("<p>")
200 .append(node.getProperty(JCR_DESCRIPTION).getString())
201 .append("</p>");
202 NodeIterator children = node.getNodes();
203 while (children.hasNext()) {
204 writeCanonical(buf, children.nextNode());
205 }
206 buf.append("</div>");
207 }
208 }
209
210 class RobotServlet extends HttpServlet {
211 private static final long serialVersionUID = 7935661175336419089L;
212
213 @Override
214 protected void service(HttpServletRequest request,
215 HttpServletResponse response) throws ServletException,
216 IOException {
217 PrintWriter writer = response.getWriter();
218 writer.append("User-agent: *\n");
219 writer.append("Disallow:\n");
220 response.setHeader("Content-Type", "text/plain");
221 writer.flush();
222 }
223
224 }
225
226 /** Intercepts all requests. Authenticates. */
227 class RootFilter extends HttpFilter {
228
229 @Override
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() : ""));
237 logRequest(request);
238 }
239
240 String servletPath = request.getServletPath();
241
242 // client certificate
243 X509Certificate clientCert = extractCertificate(request);
244 if (clientCert != null) {
245 // TODO authenticate
246 // if (log.isDebugEnabled())
247 // log.debug(clientCert.getSubjectX500Principal().getName());
248 }
249
250 // skip data
251 if (servletPath.startsWith(PATH_DATA)) {
252 filterChain.doFilter(request, response);
253 return;
254 }
255
256 // skip /ui (workbench) for the time being
257 if (servletPath.startsWith(PATH_WORKBENCH)) {
258 filterChain.doFilter(request, response);
259 return;
260 }
261
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);
273 return;
274 }
275
276 // process normally
277 filterChain.doFilter(request, response);
278 }
279 }
280
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();
287 // headers
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());
294 buf.append('\n');
295 }
296
297 // attributed
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);
303 buf.append('\n');
304 }
305 log.debug("\n" + buf);
306 }
307
308 private X509Certificate extractCertificate(HttpServletRequest req) {
309 X509Certificate[] certs = (X509Certificate[]) req
310 .getAttribute("javax.servlet.request.X509Certificate");
311 if (null != certs && certs.length > 0) {
312 return certs[0];
313 }
314 return null;
315 }
316
317 // class CustomDosFilter extends DoSFilter {
318 // @Override
319 // protected String extractUserId(ServletRequest request) {
320 // HttpSession httpSession = ((HttpServletRequest) request)
321 // .getSession();
322 // if (isSessionAuthenticated(httpSession)) {
323 // String userId = ((SecurityContext) httpSession
324 // .getAttribute(SPRING_SECURITY_CONTEXT_KEY))
325 // .getAuthentication().getName();
326 // return userId;
327 // }
328 // return super.extractUserId(request);
329 //
330 // }
331 // }
332 }