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