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
130 if (!StringUtils.contains(resourceFile, "http")) {
131 String originalURL = MgnlContext.getAggregationState().getOriginalURL();
132 String temp = StringUtils.substring(originalURL, 0, StringUtils.indexOf(originalURL, MgnlContext.getContextPath()));
133 resourceFile = temp + MgnlContext.getContextPath() + resourceFile;
134 }
135
136 URL url = new URL(resourceFile);
137
138 String content = retrieveContentFromMagnolia(resourceFile);
139 content = cleanupHtmlCode(content);
140 StringReader reader = new StringReader(content);
141
142
143 String tmp = filterImages(reader, url.toString());
144
145 tmp = StringUtils.remove(tmp, "
");
146 super.setBody(tmp);
147 }
148
149 protected String cleanupHtmlCode(String content) {
150 log.info("Cleaning html code");
151 content = content.replaceAll("<div ([.[^<>]]*)cms:edit([.[^<>]]*)>", "<div $1$2>");
152 content = cleanupHtmlCodeFromPageEditorCode(content);
153
154 Tidy tidy = new Tidy();
155 tidy.setTidyMark(false);
156 tidy.setIndentContent(true);
157 tidy.setXmlTags(true);
158
159 StringWriter writer = new StringWriter();
160
161 tidy.parse(new StringReader(content), writer);
162
163 return writer.toString();
164 }
165
166 private String cleanupHtmlCodeFromPageEditorCode(String content) {
167 String cleanContent = StringUtils.substringBefore(content, "<!-- begin js and css added by @cms.init -->");
168 cleanContent += StringUtils.substringAfter(content, "<!-- end js and css added by @cms.init -->");
169 cleanContent = cleanContent.replaceAll("<!-- (/?)cms:(component|area|page) (.*)-->", "");
170 return cleanContent;
171 }
172
173
174 public void setBodyFromTemplate(Template template, Map _map) throws Exception {
175 final StringWriter writer = new StringWriter();
176 template.process(_map, writer);
177 writer.flush();
178 setBody(writer.toString());
179 }
180
181
182 private String filterImages(StringReader reader, String pageUrl) throws Exception {
183 log.info("Filtering images");
184 SAXBuilder parser = new SAXBuilder();
185 parser.setEntityResolver(new EntityResolver() {
186
187 @Override
188 public InputSource resolveEntity(String publicId, String systemId) throws SAXException, IOException {
189 return new InputSource(new java.io.ByteArrayInputStream(new byte[0]));
190 }
191
192 });
193 Document doc = parser.build(reader);
194 List<Element> toremove = Lists.newArrayList();
195 List<Element> toadd = Lists.newArrayList();
196 Element body = null;
197
198 Iterator iter = doc.getDescendants(new ContentFilter());
199 while (iter.hasNext()) {
200 Element elem = (Element) iter.next();
201 String name = elem.getName();
202 if (name.equalsIgnoreCase(IMG)) {
203
204 Attribute att = elem.getAttribute(SRC);
205 if (log.isDebugEnabled()) {
206 log.debug("Found new img:" + att.toString());
207 }
208 String value = att.getValue();
209 this.cid++;
210 att.setValue(CID + (this.cid));
211 String url = getUrl(pageUrl, value);
212
213 if (log.isDebugEnabled()) {
214 log.debug("Url is:" + url);
215 }
216 this.getTemplate().addAttachment(new MailAttachment(getAttachmentFile(url).toURL(), String.valueOf(this.cid), MailAttachment.MIME_RELATED));
217 } else if (name.equalsIgnoreCase(LINK)) {
218
219 Attribute att = elem.getAttribute(HREF);
220 Element el = (Element) elem.clone();
221
222 if (log.isDebugEnabled()) {
223 log.debug("Found new css:" + att.toString());
224 }
225 String url = getUrl(pageUrl, att.getValue());
226 el.setName(STYLE);
227 el.removeAttribute(HREF);
228 el.removeAttribute(REL);
229 HttpGet streamCss = new HttpGet(url);
230 HttpResponse response = getHttpClient(url).execute(streamCss);
231 String tmp = new BasicResponseHandler().handleResponse(response);
232 tmp = processUrls(tmp, url);
233 el.setText(tmp);
234 toremove.add(elem);
235 toadd.add(el);
236
237 } else if (name.equalsIgnoreCase(A_LINK)) {
238 Attribute att = elem.getAttribute(HREF);
239
240 String url = getUrl(pageUrl, att.getValue());
241 if (!att.getValue().startsWith(DEFAULT_FORM_ACTION)) {
242 att.setValue(url);
243 }
244
245 } else if (name.equalsIgnoreCase(FORM)) {
246 Attribute att = elem.getAttribute(FORM_ACTION);
247 String url = att.getValue();
248 if (att.getValue().equals(DEFAULT_FORM_ACTION)) {
249 url = pageUrl;
250 }
251 att.setValue(url);
252
253 } else if (name.equalsIgnoreCase(BODY)) {
254 body = elem;
255 }
256 }
257
258
259
260 for (int i = 0; i < toremove.size(); i++) {
261 Element elem = toremove.get(i);
262 Element parent = elem.getParentElement();
263
264 body.addContent(0, toadd.get(i));
265 parent.removeContent(elem);
266
267 }
268
269
270 Format format = Format.getRawFormat();
271 format.setExpandEmptyElements(true);
272
273
274 XMLOutputter outputter = new XMLOutputter(format);
275 return outputter.outputString(doc);
276 }
277
278
279 private String getUrl(String currentPagePath, String path) {
280 String urlBasePath = currentPagePath.substring(0, currentPagePath.indexOf(MgnlContext.getContextPath()));
281 if (!StringUtils.contains(path, HTTP) && !StringUtils.contains(path, MgnlContext.getContextPath())) {
282 return currentPagePath.substring(0, currentPagePath.lastIndexOf("/") + 1) + path;
283 } else if (!StringUtils.contains(path, HTTP)) {
284 return urlBasePath + path;
285 }
286 return path;
287 }
288
289 private String processUrls(String responseBodyAsString, String cssPath) throws Exception {
290 String tmp = "";
291 Map<String, String> map = new HashMap<>();
292
293 int urlIndex;
294 int closeIndex = 0;
295 int begin = 0;
296
297
298 while (StringUtils.indexOf(responseBodyAsString, "url(", begin) >= 0) {
299 urlIndex = StringUtils.indexOf(responseBodyAsString, "url(", begin) + "url(".length();
300 closeIndex = StringUtils.indexOf(responseBodyAsString, ")", urlIndex);
301
302 String url = StringUtils.substring(responseBodyAsString, urlIndex, closeIndex);
303 url = getUrl(cssPath, url).replaceAll("\"", "");
304 url = "\"" + url + "\"";
305 if (!StringUtils.isEmpty(url) && !map.containsKey(url)) {
306 map.put(url, url);
307
308 } else if (map.containsKey(url)) {
309 url = map.get(url);
310
311 }
312 tmp += StringUtils.substring(responseBodyAsString, begin, urlIndex) + url;
313 begin = closeIndex;
314
315 }
316
317 tmp += StringUtils.substring(responseBodyAsString, closeIndex);
318 return tmp;
319 }
320
321 private File getAttachmentFile(String urlString) throws Exception {
322 log.info("Streaming content of url:" + urlString + " to a temporary file");
323
324
325 HttpGet redirect = new HttpGet(urlString);
326 HttpResponse response = getHttpClient(urlString).execute(redirect);
327
328 URL url = new URL(urlString);
329 String file = URLDecoder.decode(url.getFile(), "UTF-8");
330
331
332 File tempFile = new File(Path.getTempDirectoryPath()
333 + File.separator
334 + file.substring(file.lastIndexOf(SLASH) + 1));
335
336 if (tempFile.exists() && response.getEntity().getContentLength() == tempFile.length()) {
337 redirect.releaseConnection();
338 return tempFile;
339 }
340
341
342 FileOutputStream out = new FileOutputStream(tempFile);
343 final int BUFFER_SIZE = 1 << 10 << 3;
344 byte[] buffer = new byte[BUFFER_SIZE];
345 int bytesRead;
346 InputStream in = new BufferedInputStream(response.getEntity().getContent());
347 while (true) {
348 bytesRead = in.read(buffer);
349 if (bytesRead > -1) {
350 out.write(buffer, 0, bytesRead);
351 } else {
352 break;
353 }
354 }
355
356
357 in.close();
358 out.close();
359 redirect.releaseConnection();
360
361 return tempFile;
362 }
363
364
365 private HttpClient getHttpClient(String baseURL) throws Exception {
366 if (this.client == null) {
367 URL location = new URL(baseURL);
368 this.client = getHttpClientForUser(location);
369 }
370 return this.client;
371 }
372
373
374 private HttpClient getHttpClientForUser(URL location) throws IOException {
375 RequestConfig requestConfig = RequestConfig.custom()
376 .setCookieSpec(CookieSpecs.BROWSER_COMPATIBILITY)
377 .build();
378 HttpClient client = HttpClientBuilder.create()
379 .setProxy(new HttpHost(location.getHost(), location.getPort(), location.getProtocol()))
380 .setDefaultRequestConfig(requestConfig)
381 .build();
382
383
384 String user = getTemplate().getUsername();
385 String pass = getTemplate().getPassword();
386 log.info("Creating http client for user:" + user);
387
388 HttpPost authpost = new HttpPost(location.getPath());
389 NameValuePair action = new BasicNameValuePair(ACTION, LOGIN);
390 NameValuePair url = new BasicNameValuePair(URL, location.getPath());
391 NameValuePair userid = new BasicNameValuePair(MGNL_USER_ID, user);
392 NameValuePair password = new BasicNameValuePair(MGNL_USER_PSWD, pass);
393 List<NameValuePair> params = Arrays.asList(action, url, userid, password);
394 authpost.setEntity(new UrlEncodedFormEntity(params));
395 client.execute(authpost);
396 authpost.releaseConnection();
397 return client;
398 }
399
400 private String retrieveContentFromMagnolia(String _url) throws Exception {
401 log.info("Retrieving content from magnolia:" + _url);
402 HttpGet redirect = new HttpGet(_url + SUFFIX);
403 HttpResponse response = getHttpClient(_url).execute(redirect);
404 int code = response.getStatusLine().getStatusCode();
405 if (code == 403) {
406 String user = getTemplate().getUsername().isEmpty() ? Security.getAnonymousUser().getName() : getTemplate().getUsername();
407 throw new AccessDeniedException(getMessages().get("page.form.page.message.accessdenied", new String[]{user, _url}));
408 }
409 String responseBody = new BasicResponseHandler().handleResponse(response);
410 redirect.releaseConnection();
411 return responseBody;
412 }
413
414
415
416
417
418 static class ContentFilter implements Filter {
419
420
421 private static final long serialVersionUID = 1L;
422
423 @Override
424 public boolean matches(Object object) {
425 if (object instanceof Element) {
426 Element e = (Element) object;
427 return e.getName().equalsIgnoreCase(LINK) || e.getName().equalsIgnoreCase(IMG)
428 || e.getName().equalsIgnoreCase(A_LINK) || e.getName().equalsIgnoreCase(FORM)
429 || e.getName().equalsIgnoreCase(BODY);
430 }
431
432 return false;
433
434 }
435 }
436
437 public Messages getMessages() {
438 return MessagesManager.getMessages("info.magnolia.module.mail.messages");
439 }
440 }
441