Inline image rendered twice by OSX mail app - asp.net

My .NET 4.5 web application uses class SmtpClient to create and send e-mail messages to various recipients.
Each e-mail message consists of:
an HTML message body
an embedded inline image (JPeg or PNG or GIF)
an attachment (PDF)
Sample code is below. It works fine, but there is one gripe from OSX users. Apple's standard mail app renders the image twice; once inlined in the message body, and again following the message body, next to the preview of the PDF attachment.
I tinkered with the following properties; none of which would help.
SmtpClient's DeliveryFormat
MailMessage's IsBodyHtml and BodyTransferEncoding
Attachment's MimeType, Inline, DispositionType, ContentId, FileName, Size, CreationDate, ModificationDate
If I compose a similar e-mail message in MS Outlook and send it off to the Apple user, the image is rendered once, inlined in the message body; exactly as I would like it to be. So apparently it is possible.
After reading this, I inspected the raw MIME data, and noticed Outlook uses multipart/related to group together the message body and the images.
My question:
How do I mimic Outlook's behavior with the classes found in System.Net.Mail?
Things I would rather not do:
Employ external images instead of embedded ones (many e-mail clients initially block these to protect recipient's privacy).
Use third party libraries (to avoid legal hassle). The SmtpDirect class I found here seems to solve the problem (though I got a server exception in return), but it is hard for me to accept a complete rewrite of MS's SmtpClient implementation is necessary for such a subtle change.
Send the e-mail message to a pickup folder, manipulate the resulting .eml file, push the file to our Exchange server.
Minimal code to reproduce the problem:
using System.IO;
using System.Net.Mail;
using System.Net.Mime;
namespace SendMail
{
class Program
{
const string body = "Body text <img src=\"cid:ampersand.gif\" /> image.";
static Attachment CreateGif()
{
var att = new Attachment(new MemoryStream(Resource1.ampersand), "ampersand.gif")
{
ContentId = "ampersand.gif",
ContentType = new ContentType(MediaTypeNames.Image.Gif)
};
att.ContentDisposition.Inline = true;
return att;
}
static Attachment CreatePdf()
{
var att = new Attachment(new MemoryStream(Resource1.Hello), "Hello.pdf")
{
ContentId = "Hello.pdf",
ContentType = new ContentType(MediaTypeNames.Application.Pdf)
};
att.ContentDisposition.Inline = false;
return att;
}
static MailMessage CreateMessage()
{
var msg = new MailMessage(Resource1.from, Resource1.to, "The subject", body)
{
IsBodyHtml = true
};
msg.Attachments.Add(CreateGif());
msg.Attachments.Add(CreatePdf());
return msg;
}
static void Main(string[] args)
{
new SmtpClient(Resource1.host).Send(CreateMessage());
}
}
}
To actually build and run it, you will need an additional resource file Resource1.resx with the two attachments (ampersand and Hello) and three strings host (the SMTP server), from and to (both of which are e-mail addresses).

(I found this solution myself before I got to posting the question, but decided to publish anyway; it may help out others. I am still open for alternative solutions!)
I managed to get the desired effect by using class AlternateView.
static MailMessage CreateMessage()
{
var client = new SmtpClient(Resource1.host);
var msg = new MailMessage(Resource1.from, Resource1.to, "The subject", "Alternative message body in plain text.");
var view = AlternateView.CreateAlternateViewFromString(body, System.Text.Encoding.UTF8, MediaTypeNames.Text.Html);
var res = new LinkedResource(new MemoryStream(Resource1.ampersand), new ContentType(MediaTypeNames.Image.Gif))
{
ContentId = "ampersand.gif"
};
view.LinkedResources.Add(res);
msg.AlternateViews.Add(view);
msg.Attachments.Add(CreatePdf());
return msg;
}
As a side effect, the message now also contains a plain text version of the body (for paranoid web clients that reject HTML). Though it is a bit of a burden ("Alternative message body in plain text" needs improvement), it does give you more control as to how the message is rendered under different security settings.

Related

How do I parse specific data from a website within Codename One?

I have run into a road block developing my Codename One app. One of my classes in my project parses 3 specific html "td" elements from a website and saves the text to a string where I then input that text data into a Codename One multibutton. I originally used jSoup for this operation but soon realized that Codename One doesn't support 3rd party jar files so I used this method as shown below.
public void showOilPrice() {
if (current != null) {
current.show();
return;
}
WebBrowser b = new WebBrowser() {
#Override
public void onLoad(String url) {
BrowserComponent c = (BrowserComponent) this.getInternal();
JavascriptContext ctx = new JavascriptContext(c);
String wtiLast = (String) ctx.get("document.getElementById('pair_8849').childNodes[4].innerText");
String wtiPrev = (String) ctx.get("document.getElementById('pair_8849').childNodes[5].innerText");
String wtiChange = (String) ctx.get("document.getElementById('pair_8849').childNodes[8].innerText");
Form op = new Form("Oil Prices", new BoxLayout(BoxLayout.Y_AXIS));
MultiButton wti = new MultiButton("West Texas Intermediate");
Image icon = null;
Image emblem = null;
wti.setEmblem(emblem);
wti.setTextLine2("Current Price: " + wtiLast);
wti.setTextLine3("Previous: " + wtiPrev);
wti.setTextLine4("Change: " + wtiChange);
op.add(wti);
op.show();
}
};
b.setURL("https://sslcomrates.forexprostools.com/index.php?force_lang=1&pairs_ids=8833;8849;954867;8988;8861;8862;&header-text-color=%23FFFFFF&curr-name-color=%230059b0&inner-text-color=%23000000&green-text-color=%232A8215&green-background=%23B7F4C2&red-text-color=%23DC0001&red-background=%23FFE2E2&inner-border-color=%23CBCBCB&border-color=%23cbcbcb&bg1=%23F6F6F6&bg2=%23ffffff&open=show&last_update=show");
}
This method works in the simulator (and gives a "depreciated API" warning), but does not run when I submit my build online after signing. I have imported the parse4cn1 and cn1JSON libraries and have gone through a series of obstacles but I still receive a build error when I submit. I want to start fresh and use an alternative method if one exists. Is there a way that I can rewrite this segment of code without having to use these libraries? Maybe by using the XMLParser class?
The deprecation is for the WebBrowser class. You can use BrowserComponent directly so WebBrowser is redundant in this case.
I used XMLParser for this use case in the past. It should work with HTML as it was originally designed to show HTML.
It might also be possible to port JSoup to Codename One although I'm not sure about the scope of effort involved.
It's very possible that onLoad isn't invoked for a site you don't actually see rendered so the question is what specifically failed on the device?

ASP.NET Inconsistent PDF file download results

I'm working on a ASP.NET (3.5) website that contains a treeview; the nodes' values are set to a filepath (on the server) of a PDF file. When the user clicks a treenode, the server-side code gets the node value (file path), creates a FileInfo object, and (after setting all the header Content-type, Cache-Control, etc. correctly) calls Response.TransmitFile(xxxxpath) to send to the user's browser.
This works fine on the major browsers, on major devices (PCs, Macs, iOS devices). The file downloads correctly and opens on the user's machine. But on certain devices and certain browsers, the PDF file does not open. On Android devices, it appears that Firefox downloads and opens the PDFs correctly, but the stock Android browser does not. On a Kindle Fire, it appears the Silk browser downloads the file successfully, but when trying to open it, I see an error: "PDF trailer not found"....or it says the PDF is DRM-protected (which it is not). I haven't tried another browser (if there is one) on the Fire.
I've experimented using anchor links in static HTML markup, and the problem browsers appear to download and display the PDFs correctly when accessed this way. There seems to be an issue (inconsistency?) with the way ASP.NET sends the response to the browser when done via code. I've used Response.Flush, Response.WriteFile, Response.BinaryWrite, Response.Close, Response.End, etc., and they all produce the same result: MOST browsers handle the file, but SOME cannot handle the PDF.
So, is there some issue with the way ASP.NET constructs the Response object (especially when sending back PDFs) that some browsers don't like? TIA.
Quite simply, the answer to your question is "No." You may want to post your code if you have doubts about whether or not you're doing it correctly; otherwise: 'no'. :)
I would think the browsers you mentioned are much more suspect than something as simple and established as writing to the Response stream.
For reference here is a tried and true way I do it using iHttpHandler:
public void ProcessRequest(System.Web.HttpContext context)
{
string sFilePath = context.Server.MapPath(String.Concat("~/App_LocalResources", context.Request.QueryString["path"]));
try
{
context.Response.Clear();
context.Response.ContentType = "application/octet-stream";
int iReadLength = 16384;
int iLastReadLength = iReadLength;
byte[] buffer = new byte[iReadLength];
if (System.IO.File.Exists(sFilePath))
{
System.IO.FileInfo fInfo = new System.IO.FileInfo(sFilePath);
context.Response.AddHeader("Content-Length", fInfo.Length.ToString());
context.Response.AddHeader("Content-Disposition", String.Format("attachment; filename=\"{0}\"", fInfo.Name));
using (System.IO.FileStream input = new System.IO.FileStream(sFilePath, System.IO.FileMode.Open, System.IO.FileAccess.Read))
{
try
{
while (iLastReadLength > 0 && context.Response.IsClientConnected)
{
iLastReadLength = input.Read(buffer, 0, iLastReadLength);
if (iLastReadLength > 0)
{
context.Response.OutputStream.Write(buffer, 0, iLastReadLength);
context.Response.OutputStream.Flush();
}
}
context.Response.Flush();
}
catch
{
}
finally
{
input.Close();
}
}
}
}
catch
{
}
finally
{
}
}
Since you've indicated you are pulling the file from another place, here is how to write that to a memory stream. Just pull from your Response Stream from the remote server (or File Stream, Sql Binary Reader, w/e really), then reset your MemoryStream position and then use the functionality above to write to your Response stream to the client.
int iReadLength = 16384;
long lLastReadLength = iReadLength;
long lDataIndex = 0;
byte[] buffer = new byte[iReadLength];
using (System.IO.MemoryStream msTemp = new System.IO.MemoryStream())
{
while (lLastReadLength > 0)
{
lLastReadLength = reader.GetBytes(0, lDataIndex, buffer, 0, iReadLength);
lDataIndex += lLastReadLength;
if (lLastReadLength > 0)
{
msTemp.Write(buffer, 0, Convert.ToInt32(lLastReadLength));
msTemp.Flush();
}
}
// Reset Memory Position
msTemp.Position = 0;
// Now write to the Response Stream here
}

Lose HttpServletRequest Parts After Reading Them

I have a servlet that receives an uploaded file. We've been having issues with a certain client's request not having a file attached or so the servlet thinks. The upload servlet is a replacement for an old one and we're using the Apache Commons FileUpload library to parse the file from the request. The old code uses the JavaZoom library. The requests client we're having issues with work perfectly fine in the old code.
In order to troubleshoot the problem, I added a bunch of logging to look at the request headers and parts to compare requests from a client that works with the one that doesn't. This is a snippet of how I'm looking at the parts:
Collection<Part> parts = request.getParts();
for(Part part : parts)
{
String partName = part.getName();
log.debug("Part=" + partName);
Collection<String> headerNames = part.getHeaderNames();
for(String headerName : headerNames)
{
String headerValue = part.getHeader(headerName);
log.debug(headerName + "=" + headerValue);
InputStream inputStream = part.getInputStream();
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream, "UTF-8"));
StringBuilder builder = new StringBuilder();
try
{
for(String line=bufferedReader.readLine(); line!=null; line=bufferedReader.readLine())
{
builder.append(line);
builder.append('\n');
}
}
catch (IOException ignore)
{
// empty
}
finally
{
inputStream.reset();
}
log.debug("InputStream=" + builder.toString());
}
}
All this code works fine and I get the logging I'm expecting. However, this next bit of code doesn't act as expected:
if (isMultipart)
{
// Create a factory for disk-based file items
FileItemFactory factory = new DiskFileItemFactory();
// Create a new file upload handler
ServletFileUpload upload = new ServletFileUpload(factory);
#SuppressWarnings("rawtypes")
List items = null;
// Parse the request
try
{
items = upload.parseRequest(request);
log.debug("items=" + items);
}
catch (FileUploadException ex)
{
log.warn("Error parsing request", ex);
response.sendError(HttpServletResponse.SC_BAD_REQUEST, ex.getMessage());
}
the items variable is empty when it's logged. If I comment out the code for logging the request parts, this bit of code works and the items variable contains the uploaded file.
I can only assume that the act of getting/reading the parts from the request somehow removes them from it and are no longer available for further processing. Is there some way to read them for logging purposes and still retain them in the request for further processing?
The Collection<Part> parts = request.getParts(); is an Sevlet 3.0 API which is replacement for Commons Apache File Upload API.
You should be using only one of the two methods. Both have the support for processing uploaded files and parameters along with it.
Here is the Example for File Upload Using Servlet 3.0
The problem you are facing is because you are invoking this Collection<Part> parts = request.getParts(); request will consume the request input stream. And then you are using Apache Commons API to read the parts again. Because the stream is already read you are seeing no parts are available.
References for Servlet 3.0 File Upload:
Posting Data along with File
Servlet 3.0 Multipart Example
Servlet 3.0 MultipartConfig

how to make a picture file downloadable?

I have an ASP.NET MVC3 application and I want to link_to an image file (png, jpeg, gif, etc), and when user clicks on it, the file goes to download, instead of the browser shows it; is there any way to do this?
take your link something like this:
#Html.ActionLink(
"Download Image", // text to show
"Download", // action name
["DownloadManager", // if need, controller]
new { filename = "my-image", fileext = "jpeg" } // file-name and extension
)
and action-method is here:
public FilePathResult Download(string filename, string fileext) {
var basePath = Server.MapPath("~/Contents/Images/");
var fullPath = System.IO.Path.Combine(
basePath, string.Concat(filename.Trim(), '.', fileext.Trim()));
var contentType = GetContentType(fileext);
// The file name to use in the file-download dialog box that is displayed in the browser.
var downloadName = "one-name-for-client-file." + fileext;
return File(fullPath, contentType, downloadName);
}
private string GetContentType(string fileext) {
switch (fileext) {
case "jpg":
case "jpe":
case "jpeg": return "image/jpeg";
case "png": return "image/x-png";
case "gif": return "image/gif";
default: throw new NotSupportedException();
}
}
UPDATE:
in fact, when a file is sending to a browser, this key/value will be generated in http-header:
Content-Disposition: attachment; filename=file-client-name.ext
which file-client-name.ext is the name.extension that you want the file save-as it on client system; for example, if you want to do this in ASP.NET (none mvc), you can create a HttpHandler, write the file-stream to Response, and just add the above key/value to the http-header:
Response.Headers.Add("Content-Disposition", "attachment; filename=" + "file-client-name.ext");
just this, enjoy :D
Well technically your browser is downloading it.
I don't think you can directly link to an image, and have the browser prompt to download.
You could try something where instead of linking directly to the image, you link to a page, which serves up the image in a zip file perhaps - which of course would prompt the download to occur.
Yes, you can.
Now, you'll need to customize this to suit your needs, but I created a FileController that returned files by an identifier (you can easily return by name).
public class FileController : Controller
{
public ActionResult Download(string name)
{
// check the existence of the filename, and load it in to memory
byte[] data = SomeFunctionToReadTheFile(name);
FileContentResult result = new FileContentResult(data, "image/jpg"); // or whatever it is
return result;
}
}
Now, how you read that file or where you get it from is up to you. I then created a route like this:
routes.MapRoute(null, "files/{name}", new { controller = "File", action = "Download"});
My database has a map of identifiers to files (it's actually more complex than this, but I am omitting that logic for brevity), I can write urls like:
"~/files/somefile"
And the relevant file is downloaded.
I don't think this is possible but a simple message saying right click to save image would suffice I think.

Attach An Email with an attachments to another email

So I know how to send emails with attachments... thats easy.
The problem now is I need to add an MailMessage, that has an attachment of its own, to a different MailMessage. This will allow the user to review things and take the email that is pre-made and send it if everything is ok.
I am not sure this will be the final work flow, but I would like to know if easy.
I see a bunch of software out there that is for money, the users getting these emails will be using an outlook client.
This would be deployed to a cheap shared hosting solutions, must be able to run in Meduim Trust!
I would prefer not to have to lic a 3rd party software, No $ :(
Any ideas would be awesome.
MailMessages cannot be attached to other MailMessages. What you will do is create an .msg file, which is basically a file that stores an e-mail and all of its attachments, and attach that to your actual MailMessage. MSG files are supported by Outlook.
For more information about the file extension, go here: http://www.fileformat.info/format/outlookmsg/
As Justin said, there is no facility to attach one MailMessage to another in the API. I worked around this using the SmtpClient to "deliver" my inner message to a directory, and then attached the resulting file to my outer message. This solution isn't terribly appealing, as it has to make use of the file system, but it does get the job done. It would be much cleaner if SmtpDeliveryMethod had a Stream option.
One thing to note, the SmtpClient adds X-Sender/X-Receiver headers for the SMTP envelope information when creating the message file. If this is an issue, you will have to strip them off the top of the message file before attaching it.
// message to be attached
MailMessage attachedMessage = new MailMessage("bob#example.com"
, "carol#example.com", "Attached Message Subject"
, "Attached Message Body");
// message to send
MailMessage sendingMessage = new MailMessage();
sendingMessage.From = new MailAddress("ted#example.com", "Ted");
sendingMessage.To.Add(new MailAddress("alice#example.com", "Alice"));
sendingMessage.Subject = "Attached Message: " + attachedMessage.Subject;
sendingMessage.Body = "This message has a message attached.";
// find a temporary directory path that doesn't exist
string tempDirPath = null;
do {
tempDirPath = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());
} while(Directory.Exists(tempDirPath));
// create temp dir
DirectoryInfo tempDir = Directory.CreateDirectory(tempDirPath);
// use an SmptClient to deliver the message to the temp dir
using(SmtpClient attachmentClient = new SmtpClient("localhost")) {
attachmentClient.DeliveryMethod
= SmtpDeliveryMethod.SpecifiedPickupDirectory;
attachmentClient.PickupDirectoryLocation = tempDirPath;
attachmentClient.Send(attachedMessage);
}
tempDir.Refresh();
// load the created file into a stream
FileInfo mailFile = tempDir.GetFiles().Single();
using(FileStream mailStream = mailFile.OpenRead()) {
// create/add an attachment from the stream
sendingMessage.Attachments.Add(new Attachment(mailStream
, Regex.Replace(attachedMessage.Subject
, "[^a-zA-Z0-9 _.-]+", "") + ".eml"
, "message/rfc822"));
// send the message
using(SmtpClient smtp = new SmtpClient("smtp.example.com")) {
smtp.Send(sendingMessage);
}
mailStream.Close();
}
// clean up temp
mailFile.Delete();
tempDir.Delete();

Resources