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