View Javadoc

1   /**
2    * This file Copyright (c) 2003-2011 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 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.xml.sax.EntityResolver;
57  import org.xml.sax.InputSource;
58  import org.xml.sax.SAXException;
59  
60  import java.io.BufferedInputStream;
61  import java.io.File;
62  import java.io.FileOutputStream;
63  import java.io.IOException;
64  import java.io.InputStream;
65  import java.io.StringReader;
66  import java.io.StringWriter;
67  import java.net.MalformedURLException;
68  import java.net.URL;
69  import java.util.ArrayList;
70  import java.util.HashMap;
71  import java.util.Iterator;
72  import java.util.Map;
73  
74  
75  /**
76   * MgnlPageEmail.
77   * Date: Apr 6, 2006 Time: 9:24:29 PM
78   * @author <a href="mailto:niko@macnica.com">Nicolas Modrzyk</a>
79   *
80   */
81  public class MgnlPageEmail extends FreemarkerEmail {
82  
83      public static final String SUFFIX = "?mgnlIntercept=PREVIEW&mgnlPreview=true&mail=draw";
84  
85      private HttpClient client;
86  
87      private int cid = 0;
88  
89      private static final String UTF_8 = "UTF-8";
90  
91      private static final String MAGNOLIA = "magnolia";
92  
93      private static final String IMG = "img";
94  
95      private static final String SRC = "src";
96  
97      private static final String CID = "cid:";
98  
99      private static final String LINK = "link";
100 
101     private static final String HREF = "href";
102 
103     private static final String STYLE = "style";
104 
105     private static final String REL = "rel";
106 
107     private static final String ACTION = "action";
108 
109     private static final String LOGIN = "login";
110 
111     private static final String URL = "url";
112 
113     private static final String MGNL_USER_ID = "mgnlUserId";
114 
115     private static final String MGNL_USER_PSWD = "mgnlUserPSWD";
116 
117     private static final String SLASH = "/";
118 
119     private static final String HTTP = "http://";
120 
121     private static final String A_LINK = "a";
122 
123     private static final String FORM = "form";
124 
125     private static final String BODY = "body";
126 
127     private static final String FORM_ACTION = "action";
128 
129     private static final String DEFAULT_FORM_ACTION = "#";
130 
131 
132     public MgnlPageEmail(MailTemplate template) {
133         super(template);
134     }
135 
136     @Override
137     public void setBodyFromResourceFile() throws Exception {
138         String resourceFile = this.getTemplate().getTemplateFile();
139 
140         if(!StringUtils.contains(resourceFile, "http") ) {
141             String oriurl = MgnlContext.getAggregationState().getOriginalURL();
142             String temp = StringUtils.substring(oriurl, 0, StringUtils.indexOf(oriurl, MgnlContext.getContextPath()));
143             resourceFile = temp + MgnlContext.getContextPath() + resourceFile;
144         }
145 
146         URL url = new URL(resourceFile);
147         // retrieve the html content
148         String _content = retrieveContentFromMagnolia(resourceFile);
149         StringReader reader = new StringReader(_content);
150 
151         // filter the images
152         String urlBasePath = url.getProtocol() + "://" + url.getHost() + (url.getPort() > -1? ":" + url.getPort() : "" + "/");
153         String tmp = filterImages(urlBasePath, reader, url.toString());
154 
155         tmp = StringUtils.remove(tmp, "&#xD;");
156         super.setBody(tmp);
157     }
158 
159     // TODO : this is not used !
160     public void setBodyFromTemplate(Template template, Map _map) throws Exception {
161         final StringWriter writer = new StringWriter();
162         template.process(_map, writer);
163         writer.flush();
164         setBody(writer.toString());
165     }
166 
167 
168     private String filterImages(String urlBasePath, StringReader reader, String pageUrl) throws Exception {
169         log.info("Filtering images");
170         SAXBuilder parser = new SAXBuilder();
171         parser.setEntityResolver(new EntityResolver() {
172 
173             @Override
174             public InputSource resolveEntity(String publicId, String systemId) throws SAXException, IOException {
175                 return new InputSource(new java.io.ByteArrayInputStream(new byte[0]));
176             }
177 
178         });
179         Document doc = parser.build(reader);
180         ArrayList toremove = new ArrayList();
181         ArrayList toadd = new ArrayList();
182         Element body = null;
183         // Filter content
184         Iterator iter = doc.getDescendants(new ContentFilter());
185         while (iter.hasNext()) {
186             Element elem = (Element) iter.next();
187             String name = elem.getName();
188             if (name.equalsIgnoreCase(IMG)) {
189                 // stream image and attach it to the email
190                 Attribute att = elem.getAttribute(SRC);
191                 if (log.isDebugEnabled()) {
192                     log.debug("Found new img:" + att.toString());
193                 }
194                 String value = att.getValue();
195                 this.cid++;
196                 att.setValue(CID + (this.cid));
197                 String url = getUrl(pageUrl, value);
198 
199                 if (log.isDebugEnabled()) {
200                     log.debug("Url is:" + url);
201                 }
202                 this.getTemplate().addAttachment(new MailAttachment(getAttachmentFile(url).toURL(), String.valueOf(this.cid)));
203             }
204             else if (name.equalsIgnoreCase(LINK)) {
205                 // stream the css and put the content into a <style> tag and add to body tag
206                 Attribute att = elem.getAttribute(HREF);
207                 Element el = (Element) elem.clone();
208                 //Element el = elem;
209                 if (log.isDebugEnabled()) {
210                     log.debug("Found new css:" + att.toString());
211                 }
212                 String url = getUrl(pageUrl, att.getValue());
213                 el.setName(STYLE);
214                 el.removeAttribute(HREF);
215                 el.removeAttribute(REL);
216                 GetMethod streamCss = new GetMethod(url);
217                 getHttpClient(url).executeMethod(streamCss);
218                 String tmp = streamCss.getResponseBodyAsString();
219                 tmp = processUrls(tmp, url);
220                 el.setText(tmp);
221                 toremove.add(elem);
222                 toadd.add(el);
223 
224             } else if(name.equalsIgnoreCase(A_LINK)) {
225                 Attribute att = elem.getAttribute(HREF);
226 
227                 String url = getUrl(pageUrl, att.getValue());
228                 if(!att.getValue().startsWith(DEFAULT_FORM_ACTION)) {
229                     att.setValue(url);
230                 }
231 
232             } else if(name.equalsIgnoreCase(FORM)) {
233                 Attribute att = elem.getAttribute(FORM_ACTION);
234                 String url = att.getValue();
235                 if(att.getValue().equals(DEFAULT_FORM_ACTION)) {
236                     url = pageUrl;
237                 }
238                 att.setValue(url);
239 
240             } else if(name.equalsIgnoreCase(BODY)) {
241                 body = elem;
242             }
243         }
244 
245         // this is ugly but is there to
246         // avoid concurrent modification exception on the Document
247         for (int i = 0; i < toremove.size(); i++) {
248             Element elem = (Element) toremove.get(i);
249             Element parent = elem.getParentElement();
250 
251             body.addContent(0, (Element) toadd.get(i));
252             parent.removeContent(elem);
253 
254         }
255 
256         // create the return string reader with new document content
257         Format format = Format.getRawFormat();
258         format.setExpandEmptyElements(true);
259 
260 
261         XMLOutputter outputter = new XMLOutputter(format);
262         StringWriter writer = new StringWriter();
263         return outputter.outputString(doc);
264     }
265 
266 
267     private String getUrl(String currentPagePath, String path) {
268         String urlBasePath = currentPagePath.substring(0, currentPagePath.indexOf(MgnlContext.getContextPath()) );
269         if(!StringUtils.contains(path, HTTP) && !StringUtils.contains(path, MgnlContext.getContextPath())) {
270             return currentPagePath.substring(0, currentPagePath.lastIndexOf("/") + 1) + path;
271         } else if(!StringUtils.contains(path, HTTP)) {
272             return urlBasePath + path;
273         }
274         return path;
275     }
276 
277     private String processUrls(String responseBodyAsString, String cssPath) throws MalformedURLException, Exception {
278         String tmp = "";
279         Map<String, String> map = new HashMap<String, String>();
280 
281         int urlIndex = 0;
282         int closeIndex = 0;
283         int begin = 0;
284         int cid;
285 
286       //  List urls = new Array
287         while(StringUtils.indexOf(responseBodyAsString, "url(", begin) >= 0) {
288             urlIndex = StringUtils.indexOf(responseBodyAsString, "url(", begin) + "url(".length();
289             closeIndex = StringUtils.indexOf(responseBodyAsString, ")", urlIndex) ;
290 
291             String url = StringUtils.substring(responseBodyAsString, urlIndex, closeIndex);
292             url = getUrl(cssPath, url).replaceAll("\"", "");
293             url = "\"" + url + "\"";
294             if(!StringUtils.isEmpty(url) && !map.containsKey(url)) {
295                 map.put(url, url);
296 
297             } else if (map.containsKey(url)) {
298                 url = map.get(url);
299 
300             }
301             tmp += StringUtils.substring(responseBodyAsString, begin, urlIndex) + url;
302             begin = closeIndex;
303 
304         }
305 
306         tmp += StringUtils.substring(responseBodyAsString, closeIndex);
307         return tmp;
308     }
309 
310 
311     private File getAttachmentFile(String url) throws Exception {
312         log.info("Streaming content of url:" + url + " to a temporary file");
313 
314         // Execute an http get on the url
315         GetMethod redirect = new GetMethod(url);
316         getHttpClient(url).executeMethod(redirect);
317 
318         URL _url = new URL(url);
319         String file = _url.getFile();
320 
321         // create file in temp dir, with just the file name.
322         File tempFile = new File(Path.getTempDirectoryPath()
323             + File.separator
324             + file.substring(file.lastIndexOf(SLASH) + 1));
325         // if same file and same size, return, do not process
326         if (tempFile.exists() && redirect.getResponseContentLength() == tempFile.length()) {
327             redirect.releaseConnection();
328             return tempFile;
329         }
330 
331         // stream the content to the temp file
332         FileOutputStream out = new FileOutputStream(tempFile);
333         final int BUFFER_SIZE = 1 << 10 << 3; // 8KiB buffer
334         byte[] buffer = new byte[BUFFER_SIZE];
335         int bytesRead;
336         InputStream in = new BufferedInputStream(redirect.getResponseBodyAsStream());
337         while (true) {
338             bytesRead = in.read(buffer);
339             if (bytesRead > -1) {
340                 out.write(buffer, 0, bytesRead);
341             }
342             else {
343                 break;
344             }
345         }
346 
347         // cleanup
348         in.close();
349         out.close();
350         redirect.releaseConnection();
351 
352         return tempFile;
353     }
354 
355 
356     private HttpClient getHttpClient(String baseURL) throws Exception {
357         if (this.client == null) {
358             URL location = new URL(baseURL);
359             User user = MgnlContext.getUser();
360             this.client = getHttpClientForUser(location, user);
361         }
362         return this.client;
363     }
364 
365 
366     private HttpClient getHttpClientForUser(URL location, User _user) throws IOException {
367         HttpClient _client = new HttpClient();
368         _client.getHostConfiguration().setHost(location.getHost(), location.getPort(), location.getProtocol());
369         _client.getParams().setCookiePolicy(CookiePolicy.BROWSER_COMPATIBILITY);
370         String user = _user.getName();
371         String pass = _user.getPassword();
372         log.info("Creating http client for user:" + user);
373         // login using the id and password of the current user
374         PostMethod authpost = new PostMethod(location.getPath());
375         NameValuePair action = new NameValuePair(ACTION, LOGIN);
376         NameValuePair url = new NameValuePair(URL, location.getPath());
377         NameValuePair userid = new NameValuePair(MGNL_USER_ID, user);
378         NameValuePair password = new NameValuePair(MGNL_USER_PSWD, pass);
379         authpost.setRequestBody(new NameValuePair[]{action, url, userid, password});
380         _client.executeMethod(authpost);
381         authpost.releaseConnection();
382         return _client;
383     }
384 
385     private String retrieveContentFromMagnolia(String _url) throws Exception {
386         log.info("Retrieving content from magnolia:" + _url);
387         GetMethod redirect = new GetMethod(_url + SUFFIX);
388         getHttpClient(_url).executeMethod(redirect);
389         String response = redirect.getResponseBodyAsString();
390         redirect.releaseConnection();
391         return response;
392     }
393 
394     /**
395      * Content filter.
396      *
397      */
398     static class ContentFilter implements Filter {
399 
400 
401         private static final long serialVersionUID = 1L;
402 
403         @Override
404         public boolean matches(Object object) {
405             if (object instanceof Element) {
406                 Element e = (Element) object;
407                 return e.getName().equalsIgnoreCase(LINK) || e.getName().equalsIgnoreCase(IMG)
408                 || e.getName().equalsIgnoreCase(A_LINK) || e.getName().equalsIgnoreCase(FORM)
409                 || e.getName().equalsIgnoreCase(BODY);
410             }
411 
412             return false;
413 
414         }
415     }
416 }
417