1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34 package info.magnolia.module.mail.templates.impl;
35
36 import info.magnolia.cms.core.Path;
37 import info.magnolia.cms.i18n.Messages;
38 import info.magnolia.cms.i18n.MessagesManager;
39 import info.magnolia.cms.security.AccessDeniedException;
40 import info.magnolia.cms.security.Security;
41 import info.magnolia.context.MgnlContext;
42 import info.magnolia.module.mail.MailTemplate;
43 import info.magnolia.module.mail.templates.MailAttachment;
44
45 import java.io.BufferedInputStream;
46 import java.io.File;
47 import java.io.FileOutputStream;
48 import java.io.IOException;
49 import java.io.InputStream;
50 import java.io.StringReader;
51 import java.io.StringWriter;
52 import java.net.URL;
53 import java.net.URLDecoder;
54 import java.util.Arrays;
55 import java.util.HashMap;
56 import java.util.Iterator;
57 import java.util.List;
58 import java.util.Map;
59
60 import org.apache.commons.lang3.StringUtils;
61 import org.apache.http.HttpHost;
62 import org.apache.http.HttpResponse;
63 import org.apache.http.NameValuePair;
64 import org.apache.http.client.HttpClient;
65 import org.apache.http.client.config.CookieSpecs;
66 import org.apache.http.client.config.RequestConfig;
67 import org.apache.http.client.entity.UrlEncodedFormEntity;
68 import org.apache.http.client.methods.HttpGet;
69 import org.apache.http.client.methods.HttpPost;
70 import org.apache.http.impl.client.BasicResponseHandler;
71 import org.apache.http.impl.client.HttpClientBuilder;
72 import org.apache.http.message.BasicNameValuePair;
73 import org.jdom.Attribute;
74 import org.jdom.Document;
75 import org.jdom.Element;
76 import org.jdom.filter.Filter;
77 import org.jdom.input.SAXBuilder;
78 import org.jdom.output.Format;
79 import org.jdom.output.XMLOutputter;
80 import org.w3c.tidy.Tidy;
81 import org.xml.sax.EntityResolver;
82 import org.xml.sax.InputSource;
83 import org.xml.sax.SAXException;
84
85 import com.google.common.collect.Lists;
86
87 import freemarker.template.Template;
88
89
90
91
92
93
94
95 public class MgnlPageEmail extends FreemarkerEmail {
96
97 public static final String SUFFIX = "?mgnlIntercept=PREVIEW&mgnlPreview=true&mail=draw";
98
99 private HttpClient client;
100 private int cid = 0;
101
102 private static final String IMG = "img";
103 private static final String SRC = "src";
104 private static final String CID = "cid:";
105 private static final String LINK = "link";
106 private static final String HREF = "href";
107 private static final String STYLE = "style";
108 private static final String REL = "rel";
109 private static final String ACTION = "action";
110 private static final String LOGIN = "login";
111 private static final String URL = "url";
112 private static final String MGNL_USER_ID = "mgnlUserId";
113 private static final String MGNL_USER_PSWD = "mgnlUserPSWD";
114 private static final String SLASH = "/";
115 private static final String HTTP = "http://";
116 private static final String A_LINK = "a";
117 private static final String FORM = "form";
118 private static final String BODY = "body";
119 private static final String FORM_ACTION = "action";
120 private static final String DEFAULT_FORM_ACTION = "#";
121
122 public MgnlPageEmail(MailTemplate template) {
123 super(template);
124 }
125
126 @Override
127 public void setBodyFromResourceFile() throws Exception {
128 String resourceFile = this.getTemplate().getTemplateFile();
129 if (!StringUtils.contains(resourceFile, "http")) {
130 String reqUrl = MgnlContext.getAggregationState().getOriginalURL();
131 resourceFile = reqUrl.substring(0, StringUtils.indexOf(reqUrl, MgnlContext.getAggregationState().getOriginalURI())) + resourceFile;
132 }
133
134 URL url = new URL(resourceFile);
135
136 String content = retrieveContentFromMagnolia(resourceFile);
137
138 content = cleanupHtmlCode(content);
139 if (StringUtils.isNotBlank(content)) {
140 try {
141
142 String tmp = filterImages(new StringReader(content), url.toString());
143 tmp = StringUtils.remove(content, "
");
144 super.setBody(tmp);
145 } catch (Exception e) {
146 log.warn("Can't filter image from email content.", e);
147 super.setBody(StringUtils.EMPTY);
148 }
149 } else {
150 super.setBody("<a href='" + url.toString() + "'>" + url.toString() + "</a>");
151 }
152 }
153
154 protected String cleanupHtmlCode(String content) {
155 log.info("Cleaning html code");
156 content = content.replaceAll("<div ([.[^<>]]*)cms:edit([.[^<>]]*)>", "<div $1$2>");
157 content = cleanupHtmlCodeFromPageEditorCode(content);
158
159 Tidy tidy = new Tidy();
160 tidy.setTidyMark(false);
161 tidy.setIndentContent(true);
162 tidy.setXmlTags(true);
163
164 StringWriter writer = new StringWriter();
165
166 tidy.parse(new StringReader(content), writer);
167
168 return writer.toString();
169 }
170
171 private String cleanupHtmlCodeFromPageEditorCode(String content) {
172 String cleanContent = StringUtils.substringBefore(content, "<!-- begin js and css added by @cms.init -->");
173 cleanContent += StringUtils.substringAfter(content, "<!-- end js and css added by @cms.init -->");
174 cleanContent = cleanContent.replaceAll("<!-- (/?)cms:(component|area|page) (.*)-->", "");
175 return cleanContent;
176 }
177
178
179 public void setBodyFromTemplate(Template template, Map _map) throws Exception {
180 final StringWriter writer = new StringWriter();
181 template.process(_map, writer);
182 writer.flush();
183 setBody(writer.toString());
184 }
185
186
187 private String filterImages(StringReader reader, String pageUrl) throws Exception {
188 log.info("Filtering images");
189 SAXBuilder parser = new SAXBuilder();
190 parser.setEntityResolver(new EntityResolver() {
191
192 @Override
193 public InputSource resolveEntity(String publicId, String systemId) throws SAXException, IOException {
194 return new InputSource(new java.io.ByteArrayInputStream(new byte[0]));
195 }
196
197 });
198 Document doc = parser.build(reader);
199 List<Element> toremove = Lists.newArrayList();
200 List<Element> toadd = Lists.newArrayList();
201 Element body = null;
202
203 Iterator iter = doc.getDescendants(new ContentFilter());
204 while (iter.hasNext()) {
205 Element elem = (Element) iter.next();
206 String name = elem.getName();
207 if (name.equalsIgnoreCase(IMG)) {
208
209 Attribute att = elem.getAttribute(SRC);
210 if (log.isDebugEnabled()) {
211 log.debug("Found new img:" + att.toString());
212 }
213 String value = att.getValue();
214 this.cid++;
215 att.setValue(CID + (this.cid));
216 String url = getUrl(pageUrl, value);
217
218 if (log.isDebugEnabled()) {
219 log.debug("Url is:" + url);
220 }
221 this.getTemplate().addAttachment(new MailAttachment(getAttachmentFile(url).toURL(), String.valueOf(this.cid), MailAttachment.MIME_RELATED));
222 } else if (name.equalsIgnoreCase(LINK)) {
223
224 Attribute att = elem.getAttribute(HREF);
225 Element el = (Element) elem.clone();
226
227 if (log.isDebugEnabled()) {
228 log.debug("Found new css:" + att.toString());
229 }
230 String url = getUrl(pageUrl, att.getValue());
231 el.setName(STYLE);
232 el.removeAttribute(HREF);
233 el.removeAttribute(REL);
234 HttpGet streamCss = new HttpGet(url);
235 HttpResponse response = getHttpClient(url).execute(streamCss);
236 String tmp = new BasicResponseHandler().handleResponse(response);
237 tmp = processUrls(tmp, url);
238 el.setText(tmp);
239 toremove.add(elem);
240 toadd.add(el);
241
242 } else if (name.equalsIgnoreCase(A_LINK)) {
243 Attribute att = elem.getAttribute(HREF);
244
245 String url = getUrl(pageUrl, att.getValue());
246 if (!att.getValue().startsWith(DEFAULT_FORM_ACTION)) {
247 att.setValue(url);
248 }
249
250 } else if (name.equalsIgnoreCase(FORM)) {
251 Attribute att = elem.getAttribute(FORM_ACTION);
252 String url = att.getValue();
253 if (att.getValue().equals(DEFAULT_FORM_ACTION)) {
254 url = pageUrl;
255 }
256 att.setValue(url);
257
258 } else if (name.equalsIgnoreCase(BODY)) {
259 body = elem;
260 }
261 }
262
263
264
265 for (int i = 0; i < toremove.size(); i++) {
266 Element elem = toremove.get(i);
267 Element parent = elem.getParentElement();
268
269 body.addContent(0, toadd.get(i));
270 parent.removeContent(elem);
271
272 }
273
274
275 Format format = Format.getRawFormat();
276 format.setExpandEmptyElements(true);
277
278
279 XMLOutputter outputter = new XMLOutputter(format);
280 return outputter.outputString(doc);
281 }
282
283
284 private String getUrl(String currentPagePath, String path) {
285 String urlBasePath = currentPagePath.substring(0, currentPagePath.indexOf(MgnlContext.getContextPath()));
286 if (!StringUtils.contains(path, HTTP) && !StringUtils.contains(path, MgnlContext.getContextPath())) {
287 return currentPagePath.substring(0, currentPagePath.lastIndexOf("/") + 1) + path;
288 } else if (!StringUtils.contains(path, HTTP)) {
289 return urlBasePath + path;
290 }
291 return path;
292 }
293
294 private String processUrls(String responseBodyAsString, String cssPath) throws Exception {
295 String tmp = "";
296 Map<String, String> map = new HashMap<>();
297
298 int urlIndex;
299 int closeIndex = 0;
300 int begin = 0;
301
302
303 while (StringUtils.indexOf(responseBodyAsString, "url(", begin) >= 0) {
304 urlIndex = StringUtils.indexOf(responseBodyAsString, "url(", begin) + "url(".length();
305 closeIndex = StringUtils.indexOf(responseBodyAsString, ")", urlIndex);
306
307 String url = StringUtils.substring(responseBodyAsString, urlIndex, closeIndex);
308 url = getUrl(cssPath, url).replaceAll("\"", "");
309 url = "\"" + url + "\"";
310 if (!StringUtils.isEmpty(url) && !map.containsKey(url)) {
311 map.put(url, url);
312
313 } else if (map.containsKey(url)) {
314 url = map.get(url);
315
316 }
317 tmp += StringUtils.substring(responseBodyAsString, begin, urlIndex) + url;
318 begin = closeIndex;
319
320 }
321
322 tmp += StringUtils.substring(responseBodyAsString, closeIndex);
323 return tmp;
324 }
325
326 private File getAttachmentFile(String urlString) throws Exception {
327 log.info("Streaming content of url:" + urlString + " to a temporary file");
328
329
330 HttpGet redirect = new HttpGet(urlString);
331 HttpResponse response = getHttpClient(urlString).execute(redirect);
332
333 URL url = new URL(urlString);
334 String file = URLDecoder.decode(url.getFile(), "UTF-8");
335
336
337 File tempFile = new File(Path.getTempDirectoryPath()
338 + File.separator
339 + file.substring(file.lastIndexOf(SLASH) + 1));
340
341 if (tempFile.exists() && response.getEntity().getContentLength() == tempFile.length()) {
342 redirect.releaseConnection();
343 return tempFile;
344 }
345
346
347 FileOutputStream out = new FileOutputStream(tempFile);
348 final int BUFFER_SIZE = 1 << 10 << 3;
349 byte[] buffer = new byte[BUFFER_SIZE];
350 int bytesRead;
351 InputStream in = new BufferedInputStream(response.getEntity().getContent());
352 while (true) {
353 bytesRead = in.read(buffer);
354 if (bytesRead > -1) {
355 out.write(buffer, 0, bytesRead);
356 } else {
357 break;
358 }
359 }
360
361
362 in.close();
363 out.close();
364 redirect.releaseConnection();
365
366 return tempFile;
367 }
368
369
370 private HttpClient getHttpClient(String baseURL) throws Exception {
371 if (this.client == null) {
372 URL location = new URL(baseURL);
373 this.client = getHttpClientForUser(location);
374 }
375 return this.client;
376 }
377
378
379 private HttpClient getHttpClientForUser(URL location) throws IOException {
380 RequestConfig requestConfig = RequestConfig.custom()
381 .setCookieSpec(CookieSpecs.BROWSER_COMPATIBILITY)
382 .build();
383 HttpClient client = HttpClientBuilder.create()
384 .setProxy(new HttpHost(location.getHost(), location.getPort(), location.getProtocol()))
385 .setDefaultRequestConfig(requestConfig)
386 .build();
387
388
389 String user = getTemplate().getUsername();
390 HttpPost authpost = new HttpPost(location.toString());
391 NameValuePair url = new BasicNameValuePair(URL, location.getPath());
392 List<NameValuePair> params = Arrays.asList(url);
393 if (user != null) {
394
395 log.info("Creating http client for user:" + user);
396 String pass = getTemplate().getPassword();
397 NameValuePair action = new BasicNameValuePair(ACTION, LOGIN);
398 NameValuePair userid = new BasicNameValuePair(MGNL_USER_ID, user);
399 NameValuePair password = new BasicNameValuePair(MGNL_USER_PSWD, pass);
400 params = Arrays.asList(action, url, userid, password);
401 }
402 authpost.setEntity(new UrlEncodedFormEntity(params));
403 client.execute(authpost);
404 authpost.releaseConnection();
405 return client;
406 }
407
408 private String retrieveContentFromMagnolia(String _url) throws Exception {
409 log.info("Retrieving content from magnolia:" + _url);
410 HttpGet redirect = new HttpGet(_url + SUFFIX);
411 HttpResponse response = getHttpClient(_url).execute(redirect);
412 int code = response.getStatusLine().getStatusCode();
413 if (code == 403) {
414 String user = getTemplate().getUsername().isEmpty() ? Security.getAnonymousUser().getName() : getTemplate().getUsername();
415 throw new AccessDeniedException(getMessages().get("page.form.page.message.accessdenied", new String[]{user, _url}));
416 }
417 String responseBody = new BasicResponseHandler().handleResponse(response);
418 redirect.releaseConnection();
419 return responseBody;
420 }
421
422
423
424
425
426 static class ContentFilter implements Filter {
427
428
429 private static final long serialVersionUID = 1L;
430
431 @Override
432 public boolean matches(Object object) {
433 if (object instanceof Element) {
434 Element e = (Element) object;
435 return e.getName().equalsIgnoreCase(LINK) || e.getName().equalsIgnoreCase(IMG)
436 || e.getName().equalsIgnoreCase(A_LINK) || e.getName().equalsIgnoreCase(FORM)
437 || e.getName().equalsIgnoreCase(BODY);
438 }
439
440 return false;
441
442 }
443 }
444
445 public Messages getMessages() {
446 return MessagesManager.getMessages("info.magnolia.module.mail.messages");
447 }
448 }