Clover icon

Magnolia Mail module 5.5.2

  1. Project Clover database Mon May 6 2019 07:05:59 CEST
  2. Package info.magnolia.module.mail.templates.impl

File MgnlPageEmail.java

 

Coverage histogram

../../../../../../img/srcFileCovDistChart1.png
53% of files have more coverage

Code metrics

52
178
14
2
448
322
44
0.25
12.71
7
3.14
0.8% of code in this file is excluded from these metrics.

Classes

Class Line # Actions
MgnlPageEmail 95 174 0.8% 42 219
0.075949367.6%
MgnlPageEmail.ContentFilter 426 4 0% 2 7
0.00%
 

Contributing tests

This file is covered by 3 tests. .

Source view

1    /**
2    * This file Copyright (c) 2003-2018 Magnolia International
3    * Ltd. (http://www.magnolia-cms.com). All rights reserved.
4    *
5    *
6    * This file is dual-licensed under both the Magnolia
7    * Network Agreement and the GNU General Public License.
8    * You may elect to use one or the other of these licenses.
9    *
10    * This file is distributed in the hope that it will be
11    * useful, but AS-IS and WITHOUT ANY WARRANTY; without even the
12    * implied warranty of MERCHANTABILITY or FITNESS FOR A
13    * PARTICULAR PURPOSE, TITLE, or NONINFRINGEMENT.
14    * Redistribution, except as permitted by whichever of the GPL
15    * or MNA you select, is prohibited.
16    *
17    * 1. For the GPL license (GPL), you can redistribute and/or
18    * modify this file under the terms of the GNU General
19    * Public License, Version 3, as published by the Free Software
20    * Foundation. You should have received a copy of the GNU
21    * General Public License, Version 3 along with this program;
22    * if not, write to the Free Software Foundation, Inc., 51
23    * Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
24    *
25    * 2. For the Magnolia Network Agreement (MNA), this file
26    * and the accompanying materials are made available under the
27    * terms of the MNA which accompanies this distribution, and
28    * is available at http://www.magnolia-cms.com/mna.html
29    *
30    * Any modifications to this file must keep this entire header
31    * intact.
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    * MgnlPageEmail.
91    * Date: Apr 6, 2006 Time: 9:24:29 PM
92    * @author <a href="mailto:niko@macnica.com">Nicolas Modrzyk</a>
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  3 toggle public MgnlPageEmail(MailTemplate template) {
123  3 super(template);
124    }
125   
 
126  0 toggle @Override
127    public void setBodyFromResourceFile() throws Exception {
128  0 String resourceFile = this.getTemplate().getTemplateFile();
129  0 if (!StringUtils.contains(resourceFile, "http")) {
130  0 String reqUrl = MgnlContext.getAggregationState().getOriginalURL();
131  0 resourceFile = reqUrl.substring(0, StringUtils.indexOf(reqUrl, MgnlContext.getAggregationState().getOriginalURI())) + resourceFile;
132    }
133   
134  0 URL url = new URL(resourceFile);
135    // retrieve the html content
136  0 String content = retrieveContentFromMagnolia(resourceFile);
137    // TODO: review below processing of HTML content processing
138  0 content = cleanupHtmlCode(content);
139  0 if (StringUtils.isNotBlank(content)) {
140  0 try {
141    // filter the images
142  0 String tmp = filterImages(new StringReader(content), url.toString());
143  0 tmp = StringUtils.remove(content, "&#xD;");
144  0 super.setBody(tmp);
145    } catch (Exception e) {
146  0 log.warn("Can't filter image from email content.", e);
147  0 super.setBody(StringUtils.EMPTY);
148    }
149    } else {
150  0 super.setBody("<a href='" + url.toString() + "'>" + url.toString() + "</a>");
151    }
152    }
153   
 
154  5 toggle protected String cleanupHtmlCode(String content) {
155  5 log.info("Cleaning html code");
156  5 content = content.replaceAll("<div ([.[^<>]]*)cms:edit([.[^<>]]*)>", "<div $1$2>");
157  5 content = cleanupHtmlCodeFromPageEditorCode(content);
158   
159  5 Tidy tidy = new Tidy();
160  5 tidy.setTidyMark(false);
161  5 tidy.setIndentContent(true);
162  5 tidy.setXmlTags(true);
163   
164  5 StringWriter writer = new StringWriter();
165   
166  5 tidy.parse(new StringReader(content), writer);
167   
168  5 return writer.toString();
169    }
170   
 
171  5 toggle private String cleanupHtmlCodeFromPageEditorCode(String content) {
172  5 String cleanContent = StringUtils.substringBefore(content, "<!-- begin js and css added by @cms.init -->");
173  5 cleanContent += StringUtils.substringAfter(content, "<!-- end js and css added by @cms.init -->");
174  5 cleanContent = cleanContent.replaceAll("<!-- (/?)cms:(component|area|page) (.*)-->", "");
175  5 return cleanContent;
176    }
177   
178    // TODO : this is not used !
 
179  0 toggle public void setBodyFromTemplate(Template template, Map _map) throws Exception {
180  0 final StringWriter writer = new StringWriter();
181  0 template.process(_map, writer);
182  0 writer.flush();
183  0 setBody(writer.toString());
184    }
185   
186   
 
187  0 toggle private String filterImages(StringReader reader, String pageUrl) throws Exception {
188  0 log.info("Filtering images");
189  0 SAXBuilder parser = new SAXBuilder();
190  0 parser.setEntityResolver(new EntityResolver() {
191   
 
192  0 toggle @Override
193    public InputSource resolveEntity(String publicId, String systemId) throws SAXException, IOException {
194  0 return new InputSource(new java.io.ByteArrayInputStream(new byte[0]));
195    }
196   
197    });
198  0 Document doc = parser.build(reader);
199  0 List<Element> toremove = Lists.newArrayList();
200  0 List<Element> toadd = Lists.newArrayList();
201  0 Element body = null;
202    // Filter content
203  0 Iterator iter = doc.getDescendants(new ContentFilter());
204  0 while (iter.hasNext()) {
205  0 Element elem = (Element) iter.next();
206  0 String name = elem.getName();
207  0 if (name.equalsIgnoreCase(IMG)) {
208    // stream image and attach it to the email
209  0 Attribute att = elem.getAttribute(SRC);
210  0 if (log.isDebugEnabled()) {
211  0 log.debug("Found new img:" + att.toString());
212    }
213  0 String value = att.getValue();
214  0 this.cid++;
215  0 att.setValue(CID + (this.cid));
216  0 String url = getUrl(pageUrl, value);
217   
218  0 if (log.isDebugEnabled()) {
219  0 log.debug("Url is:" + url);
220    }
221  0 this.getTemplate().addAttachment(new MailAttachment(getAttachmentFile(url).toURL(), String.valueOf(this.cid), MailAttachment.MIME_RELATED));
222  0 } else if (name.equalsIgnoreCase(LINK)) {
223    // stream the css and put the content into a <style> tag and add to body tag
224  0 Attribute att = elem.getAttribute(HREF);
225  0 Element el = (Element) elem.clone();
226    //Element el = elem;
227  0 if (log.isDebugEnabled()) {
228  0 log.debug("Found new css:" + att.toString());
229    }
230  0 String url = getUrl(pageUrl, att.getValue());
231  0 el.setName(STYLE);
232  0 el.removeAttribute(HREF);
233  0 el.removeAttribute(REL);
234  0 HttpGet streamCss = new HttpGet(url);
235  0 HttpResponse response = getHttpClient(url).execute(streamCss);
236  0 String tmp = new BasicResponseHandler().handleResponse(response);
237  0 tmp = processUrls(tmp, url);
238  0 el.setText(tmp);
239  0 toremove.add(elem);
240  0 toadd.add(el);
241   
242  0 } else if (name.equalsIgnoreCase(A_LINK)) {
243  0 Attribute att = elem.getAttribute(HREF);
244   
245  0 String url = getUrl(pageUrl, att.getValue());
246  0 if (!att.getValue().startsWith(DEFAULT_FORM_ACTION)) {
247  0 att.setValue(url);
248    }
249   
250  0 } else if (name.equalsIgnoreCase(FORM)) {
251  0 Attribute att = elem.getAttribute(FORM_ACTION);
252  0 String url = att.getValue();
253  0 if (att.getValue().equals(DEFAULT_FORM_ACTION)) {
254  0 url = pageUrl;
255    }
256  0 att.setValue(url);
257   
258  0 } else if (name.equalsIgnoreCase(BODY)) {
259  0 body = elem;
260    }
261    }
262   
263    // this is ugly but is there to
264    // avoid concurrent modification exception on the Document
265  0 for (int i = 0; i < toremove.size(); i++) {
266  0 Element elem = toremove.get(i);
267  0 Element parent = elem.getParentElement();
268   
269  0 body.addContent(0, toadd.get(i));
270  0 parent.removeContent(elem);
271   
272    }
273   
274    // create the return string reader with new document content
275  0 Format format = Format.getRawFormat();
276  0 format.setExpandEmptyElements(true);
277   
278   
279  0 XMLOutputter outputter = new XMLOutputter(format);
280  0 return outputter.outputString(doc);
281    }
282   
283   
 
284  0 toggle private String getUrl(String currentPagePath, String path) {
285  0 String urlBasePath = currentPagePath.substring(0, currentPagePath.indexOf(MgnlContext.getContextPath()));
286  0 if (!StringUtils.contains(path, HTTP) && !StringUtils.contains(path, MgnlContext.getContextPath())) {
287  0 return currentPagePath.substring(0, currentPagePath.lastIndexOf("/") + 1) + path;
288  0 } else if (!StringUtils.contains(path, HTTP)) {
289  0 return urlBasePath + path;
290    }
291  0 return path;
292    }
293   
 
294  0 toggle private String processUrls(String responseBodyAsString, String cssPath) throws Exception {
295  0 String tmp = "";
296  0 Map<String, String> map = new HashMap<>();
297   
298  0 int urlIndex;
299  0 int closeIndex = 0;
300  0 int begin = 0;
301   
302    // List urls = new Array
303  0 while (StringUtils.indexOf(responseBodyAsString, "url(", begin) >= 0) {
304  0 urlIndex = StringUtils.indexOf(responseBodyAsString, "url(", begin) + "url(".length();
305  0 closeIndex = StringUtils.indexOf(responseBodyAsString, ")", urlIndex);
306   
307  0 String url = StringUtils.substring(responseBodyAsString, urlIndex, closeIndex);
308  0 url = getUrl(cssPath, url).replaceAll("\"", "");
309  0 url = "\"" + url + "\"";
310  0 if (!StringUtils.isEmpty(url) && !map.containsKey(url)) {
311  0 map.put(url, url);
312   
313  0 } else if (map.containsKey(url)) {
314  0 url = map.get(url);
315   
316    }
317  0 tmp += StringUtils.substring(responseBodyAsString, begin, urlIndex) + url;
318  0 begin = closeIndex;
319   
320    }
321   
322  0 tmp += StringUtils.substring(responseBodyAsString, closeIndex);
323  0 return tmp;
324    }
325   
 
326  0 toggle private File getAttachmentFile(String urlString) throws Exception {
327  0 log.info("Streaming content of url:" + urlString + " to a temporary file");
328   
329    // Execute an http get on the url
330  0 HttpGet redirect = new HttpGet(urlString);
331  0 HttpResponse response = getHttpClient(urlString).execute(redirect);
332   
333  0 URL url = new URL(urlString);
334  0 String file = URLDecoder.decode(url.getFile(), "UTF-8");
335   
336    // create file in temp dir, with just the file name.
337  0 File tempFile = new File(Path.getTempDirectoryPath()
338    + File.separator
339    + file.substring(file.lastIndexOf(SLASH) + 1));
340    // if same file and same size, return, do not process
341  0 if (tempFile.exists() && response.getEntity().getContentLength() == tempFile.length()) {
342  0 redirect.releaseConnection();
343  0 return tempFile;
344    }
345   
346    // stream the content to the temp file
347  0 FileOutputStream out = new FileOutputStream(tempFile);
348  0 final int BUFFER_SIZE = 1 << 10 << 3; // 8KiB buffer
349  0 byte[] buffer = new byte[BUFFER_SIZE];
350  0 int bytesRead;
351  0 InputStream in = new BufferedInputStream(response.getEntity().getContent());
352  0 while (true) {
353  0 bytesRead = in.read(buffer);
354  0 if (bytesRead > -1) {
355  0 out.write(buffer, 0, bytesRead);
356    } else {
357  0 break;
358    }
359    }
360   
361    // cleanup
362  0 in.close();
363  0 out.close();
364  0 redirect.releaseConnection();
365   
366  0 return tempFile;
367    }
368   
369   
 
370  0 toggle private HttpClient getHttpClient(String baseURL) throws Exception {
371  0 if (this.client == null) {
372  0 URL location = new URL(baseURL);
373  0 this.client = getHttpClientForUser(location);
374    }
375  0 return this.client;
376    }
377   
378   
 
379  0 toggle private HttpClient getHttpClientForUser(URL location) throws IOException {
380  0 RequestConfig requestConfig = RequestConfig.custom()
381    .setCookieSpec(CookieSpecs.BROWSER_COMPATIBILITY)
382    .build();
383  0 HttpClient client = HttpClientBuilder.create()
384    .setProxy(new HttpHost(location.getHost(), location.getPort(), location.getProtocol()))
385    .setDefaultRequestConfig(requestConfig)
386    .build();
387   
388   
389  0 String user = getTemplate().getUsername();
390  0 HttpPost authpost = new HttpPost(location.toString());
391  0 NameValuePair url = new BasicNameValuePair(URL, location.getPath());
392  0 List<NameValuePair> params = Arrays.asList(url);
393  0 if (user != null) {
394    // login using the id and password of the current user
395  0 log.info("Creating http client for user:" + user);
396  0 String pass = getTemplate().getPassword();
397  0 NameValuePair action = new BasicNameValuePair(ACTION, LOGIN);
398  0 NameValuePair userid = new BasicNameValuePair(MGNL_USER_ID, user);
399  0 NameValuePair password = new BasicNameValuePair(MGNL_USER_PSWD, pass);
400  0 params = Arrays.asList(action, url, userid, password);
401    }
402  0 authpost.setEntity(new UrlEncodedFormEntity(params));
403  0 client.execute(authpost);
404  0 authpost.releaseConnection();
405  0 return client;
406    }
407   
 
408  0 toggle private String retrieveContentFromMagnolia(String _url) throws Exception {
409  0 log.info("Retrieving content from magnolia:" + _url);
410  0 HttpGet redirect = new HttpGet(_url + SUFFIX);
411  0 HttpResponse response = getHttpClient(_url).execute(redirect);
412  0 int code = response.getStatusLine().getStatusCode();
413  0 if (code == 403) {
414  0 String user = getTemplate().getUsername().isEmpty() ? Security.getAnonymousUser().getName() : getTemplate().getUsername();
415  0 throw new AccessDeniedException(getMessages().get("page.form.page.message.accessdenied", new String[]{user, _url}));
416    }
417  0 String responseBody = new BasicResponseHandler().handleResponse(response);
418  0 redirect.releaseConnection();
419  0 return responseBody;
420    }
421   
422    /**
423    * Content filter.
424    *
425    */
 
426    static class ContentFilter implements Filter {
427   
428   
429    private static final long serialVersionUID = 1L;
430   
 
431  0 toggle @Override
432    public boolean matches(Object object) {
433  0 if (object instanceof Element) {
434  0 Element e = (Element) object;
435  0 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  0 return false;
441   
442    }
443    }
444   
 
445    toggle public Messages getMessages() {
446    return MessagesManager.getMessages("info.magnolia.module.mail.messages");
447    }
448    }