📜 ⬆️ ⬇️

Diary of a bug: how I repaired pictures in e-mail

There is an internal system that spins on Weblogic, there is a ready-made mail template, there is a programmer and there is a bug. So you knew that email clients with high probability will not be able to display a picture that is inserted into the markup of the letter, the source of which begins with data:image/gif;base64 ?

For example, I did not know. What is really there, I didn’t even know that pictures can be inserted into HTML markup without, in fact, the picture itself. It so happened that the same .jsp is used to create a page for printing, and, in some cases, for an email. As a result, the letter in the browser opens normally, and mail clients show a battered picture.

This will be a little story about the process of finding one solution. Now about everything in order.

In the middle of the day there comes a complaint from the client that in the letter that the system sends, the pictures are not shown. The project manager did the task, gave me, however, everything as usual. It is useful to check through the interface and in fact, my Outlook issued a blank with red crosses in the squares instead of pictures. In the code I noticed that the same .jsp file template is used to compose the document data for printing. At that moment I thought that it was so good that I wouldn’t have to go through all the business logic to get a letter, but I could just look at the blank for printing. Opening the workpiece everything turned out beautifully and rightly.
')
The challenge has become a little less enjoyable, but who is afraid of the challenges? At first I fiddled with the Internet looking for ways to get Outlook to show me the markup of the letter, which I managed quite quickly. In the markup, I found that, unlike the web interface, the base64 image instead of the “+” was “& # 43;”. The first guess was that the method that sends the HTML template to the SMTP server filters some special characters. This was not the case, until the " email.send (); " line contained all the necessary characters.

It is time to ask the good company for directions to the reasons for this behavior. After some time, I found a resource in which the author checked such pictures for compatibility with email clients. In the tablet given by him without flaws, only the apple client showed pictures. In the code of his letter, I saw the cherished "+" and realized that this was my case. He told about the opening of the analyst. He said that he still needed to be repaired, and at the same time he complained that everything was fine before.

And before that, the system was spinning on OC4J, and after moving to Weblogic, the relative paths also disappeared somewhere. Because of the ways the error was thrown already mentioned preparation for printing. The colleague corrected the printout, but the letters or no one noticed, or simply did not test, because the printing works. I found a method where the string was formed from .jsp, and a little deeper and found something interesting.

Sender method
 protected boolean sendHtmlEmail(String fromAddress, String fromName, String toAddress, String toName, String subject, String textContent, String htmlContent, String serverName, String serverPort, String serverPath, String docRoot) { boolean result = true; HtmlEmail email = new HtmlEmail(); try { // Setup email parameters email.setHostName(SMTP_HOST); email.setSmtpPort(SMTP_PORT); email.setCharset("UTF-8"); email.addTo(toAddress, toName); email.setFrom(fromAddress, fromName); email.setSubject(subject); // Add text body if (!"".equals(textContent)) { email.setTextMsg(textContent); } // Add HTML body with inline images if (!"".equals(htmlContent)) { // System.out.println("htmlContent src: " + htmlContent); Matcher matcher = ManagerBase.imageSrcPattern.matcher(htmlContent); //URL imageURL = null; String imageCID = null; while (matcher.find()) { //System.out.println("sendHtmlEmail:"); //System.out.println("imageURL done: " + imageURL.toString()); //ClassLoader classLoader = getClass().getClassLoader(); //File file = new File(classLoader.getResource("../../" + matcher.group(2)).getFile()); // File image = new File(docRoot + matcher.group(2)); // imageCID = email.embed(image/*, matcher.group( 2 )*/); // System.out.println("imageCID done:" + imageCID); // htmlContent = htmlContent.replaceFirst(matcher.group(2), "cid:" + imageCID); } //System.out.println("htmlContent done: " + htmlContent); email.setHtmlMsg(htmlContent); } // Build && send email //email.buildMimeMessage(); result = result && filterDebugMail(toAddress, errors); if (result == true) email.send(); } catch (Exception e) { // ......  ,   // ......        :) } return result; } 


Looks like it was used before the encoded pictures? I thought so. But CVS told me that the first version of the file was already with this method. But nothing, then almost everything is ready. I read about "cid" and everything will be fine. So I thought ...

An hour has passed since the beginning of the investigation of the problem, although most likely all two. It's time to code something at last. First you need to find pictures. In resources, only templates .doc documents, strange. Found pictures in the folder next to the .jsp bundle. Okay, let them, but I still copy the images I need to resources, from there at least the standard getResource () can be obtained. So what do you need for cid?

ABOUT! Next to HtmlEmail is its ImageHtmlEmail receiver, it is clearly better suited, I just need pictures. Then I decided to separate the initial .jsp after all and made mail almost identical to the past (I know about DRY, don't hit, please). But on the guides I changed the values ​​of the src attributes to “cid: [file name without extension]”. It didn’t have to be the name of the file, but it seemed more logical to me, especially since the Matcher pattern already selects what is inside the img tag.
Further wrote that the system on Matcher selected the correct file from resources:

Start
 while (matcher.find()) { //   , src    cid:[ ] String cidInFile = matcher.group(2); //   String imageName = cidInFile.substring(cidInFile.indexOf(":")+1); //    String imageFullResPath = "/images/print/" + imageName + ".gif"; } 


So I need to push the image in email.embed () , the method requires a DataSource . Appeal to the corporation of good, 5 new open tabs, a new idea of ​​a new acquaintance. Of the four full-fledged implementation classes, two were interesting - ByteArrayDataSource and FileDataSource . But the file involves working with the paths, and since the past solution was redone because of the paths, we will leave this as a last resort. ByteArrayDataSource in the constructor wants an array of bytes and a data type. Another appeal to the good, 7 more tabs. Let me be corrected if I am wrong, but the type must be submitted MIME type. I have gifs - “image / gif”. The actual data array is obtained using getResourceAsStream () and IOUtils from apache-commons-io .

Now everything is something like this:

Half way
 while (matcher.find()) { //   , src    cid:[ ] String cidInFile = matcher.group(2); //   String imageName = cidInFile.substring(cidInFile.indexOf(":")+1); //    String imageFullResPath = "/images/print/" + imageName + ".gif"; //    ClassLoader classLoader = getClass().getClassLoader(); InputStream is = classLoader.getResourceAsStream(imageFullResPath); byte[] imageData = IOUtils.toByteArray(is); // embed  y DataSource DataSource ds = new ByteArrayDataSource(imageData, "image/gif"); String cid = email.embed(ds, imageName); } 


It looks much better, especially since when I drove through the debager, there were no errors. I commented out email.send () , I run - NullPointerException , the problem. It seems that everything is not null, but repeated attempts lead to the same results. Well that good showed where you can see the source ImageHtmlEmail , which throws NPE. Hmm ... the only thing NPE can throw is a DataSourceResolver , it was not in one of the three readings about ImageHtmlEmail . Well, there is no ... there is, and there is, and in the latter, too.

An attempt to add the necessary one stopped at the choice of implementation, because there was a DataSourceFileResolver , but there was no DataSourceByteResolver .
Here we will miss two hours of vain attempts to translate everything already written under FileDataSource . But in the end, in desperation, I spied that the DataSourceFileResolver does not matter what my DataSource is if the src of the picture starts with a “cid”.

In general, the final version looked something like this:

the end
 protected boolean sendHtmlEmail(String fromAddress, String fromName, String toAddress, String toName, String subject, String textContent, String htmlContent, String serverName, String serverPort, String serverPath, String docRoot) { boolean result = true; ImageHtmlEmail email = new ImageHtmlEmail(); try { // Setup email parameters email.setHostName(SMTP_HOST); email.setSmtpPort(SMTP_PORT); email.setCharset("UTF-8"); email.addTo(toAddress, toName); email.setFrom(fromAddress, fromName); email.setSubject(subject); // Add text body if (!"".equals(textContent)) { email.setTextMsg(textContent); } // Add HTML body with inline images if (!"".equals(htmlContent)) { URL resurl = getClass().getResource("/"); URI resURI = resurl.toURI(); File resFolder = new File( resURI ); resFolder = resFolder.getParentFile(); DataSourceResolver resolver = new DataSourceFileResolver(resFolder); email.setDataSourceResolver(resolver); Matcher matcher = ManagerBase.imageSrcPattern.matcher(htmlContent); while (matcher.find()) { //   , src    cid:[ ] String cidInFile = matcher.group(2); //   String imageName = cidInFile.substring(cidInFile.indexOf(":")+1); //    String imageFullResPath = "/images/print/" + imageName + ".gif"; //    ClassLoader classLoader = getClass().getClassLoader(); InputStream is = classLoader.getResourceAsStream(imageFullResPath); byte[] imageData = IOUtils.toByteArray(is); // embed  y DataSource DataSource ds = new ByteArrayDataSource(imageData, "image/gif"); String cid = email.embed(ds, imageName); } // email ready, enjoy your transfer email.setHtmlMsg(htmlContent); } result = result && filterDebugMail(toAddress, errors); if (result == true) email.send(); } catch (Exception e) { // ......... } return result; } 


I consider it necessary to note that it is missing and in fact ~ 6 lines of code required:

- 6 hours of work time
-> 30 tabs with trainings, StackOverflow, etc.
- one discreet strong word every third local build
-> 30 local builds

I wanted to write myself a note about the found solution, but suddenly someone else will come in handy ... if they find it.

Source: https://habr.com/ru/post/313346/


All Articles