Skip to content

Commit

Permalink
Allow data uris as attachment paths, bumped version to v1.2.2
Browse files Browse the repository at this point in the history
  • Loading branch information
andris9 committed Sep 5, 2014
1 parent 759c473 commit e22e9ef
Show file tree
Hide file tree
Showing 8 changed files with 140 additions and 7 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Changelog

## v1.2.2 2014-09-05

Proper handling of data uris as attachments. Attachment `path` property can also be defined as a data uri, not just regular url or file path.

## v1.2.1 2014-08-21

Bumped libmime and mailbuild versions to properly handle filenames with spaces (short ascii only filenames with spaces were left unquoted).
Expand Down
2 changes: 1 addition & 1 deletion Gruntfile.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ module.exports = function(grunt) {
// Project configuration.
grunt.initConfig({
jshint: {
all: ['src/*.js', 'test/*.js', 'examples/*.js'],
all: ['src/*.js', 'test/*.js', 'examples/*.js', 'Gruntfile.js'],
options: {
jshintrc: '.jshintrc'
}
Expand Down
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -250,7 +250,7 @@ Attachment object consists of the following properties:
* **cid** - optional content id for using inline images in HTML message source
* **content** - String, Buffer or a Stream contents for the attachment
* **encoding** - If set and `content` is string, then encodes the content to a Buffer using the specified encoding. Example values: `base64`, `hex`, 'binary' etc. Useful if you want to use binary attachments in a JSON formatted e-mail object.
* **path** - path to a file or an URL if you want to stream the file instead of including it (better for larger attachments)
* **path** - path to a file or an URL (data uris are allowed as well) if you want to stream the file instead of including it (better for larger attachments)
* **contentType** - optional content type for the attachment, if not set will be derived from the `filename` property
* **contentDisposition** - optional content disposition type for the attachment, defaults to 'attachment'

Expand Down Expand Up @@ -292,6 +292,9 @@ var mailOptions = {
filename: 'text1.txt',
content: 'aGVsbG8gd29ybGQh',
encoding: 'base64'
},
{ // data uri as an attachment
path: 'data:text/plain;base64,aGVsbG8gd29ybGQ='
}
]
}
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "nodemailer",
"version": "1.2.1",
"version": "1.2.2",
"description": "Easy as cake e-mail sending from your Node.js applications",
"main": "src/nodemailer.js",
"scripts": {
Expand Down
47 changes: 45 additions & 2 deletions src/compiler.js
Original file line number Diff line number Diff line change
Expand Up @@ -217,7 +217,13 @@ Compiler.prototype._createContentNode = function(parentNode, element) {
*/
Compiler.prototype._getAttachments = function(findRelated) {
var attachments = [].concat(this.mail.attachments || []).map(function(attachment, i) {
var data = {
var data;

if (/^data:/i.test(attachment.path || attachment.href)) {
attachment = this._processDataUrl(attachment);
}

data = {
contentType: attachment.contentType ||
libmime.detectMimeType(attachment.filename || attachment.path || attachment.href || 'bin'),
contentDisposition: attachment.contentDisposition || 'attachment'
Expand Down Expand Up @@ -309,7 +315,13 @@ Compiler.prototype._getAlternatives = function() {
}

[].concat(text || []).concat(html || []).concat(this.mail.alternatives || []).forEach(function(alternative) {
var data = {
var data;

if (/^data:/i.test(alternative.path || alternative.href)) {
alternative = this._processDataUrl(alternative);
}

data = {
contentType: alternative.contentType ||
libmime.detectMimeType(alternative.filename || alternative.path || alternative.href || 'txt')
};
Expand Down Expand Up @@ -343,4 +355,35 @@ Compiler.prototype._getAlternatives = function() {
}.bind(this));

return alternatives;
};

/**
* Parses data uri and converts it to a Buffer
*
* @param {Object} element Content element
* @return {Object} Parsed element
*/
Compiler.prototype._processDataUrl = function(element) {
var parts = (element.path || element.href).match(/^data:((?:[^;]*;)*(?:[^,]*)),(.*)$/i);
if (!parts) {
return element;
}

element.content = /\bbase64$/i.test(parts[1]) ? new Buffer(parts[2], 'base64') : new Buffer(decodeURIComponent(parts[2]));

if ('path' in element) {
element.path = false;
}

if ('href' in element) {
element.href = false;
}

parts[1].split(';').forEach(function(item) {
if (/^\w+\/[^\/]+$/i.test(item)) {
element.contentType = element.contentType || item.toLowerCase();
}
});

return element;
};
10 changes: 8 additions & 2 deletions src/nodemailer.js
Original file line number Diff line number Diff line change
Expand Up @@ -140,8 +140,14 @@ Nodemailer.prototype.resolveContent = function(data, key, callback) {
data[key] = value;
callback(null, value);
}.bind(this));
} else if (/^https?:\/\//i.test(content.path)) {
return this._resolveStream(hyperquest(content.path), callback);
} else if (/^https?:\/\//i.test(content.path || content.href)) {
return this._resolveStream(hyperquest(content.path || content.href), callback);
} else if (/^data:/i.test(content.path || content.href)) {
var parts = (content.path || content.href).match(/^data:((?:[^;]*;)*(?:[^,]*)),(.*)$/i);
if (!parts) {
return callback(null, new Buffer(0));
}
return callback(null, /\bbase64$/i.test(parts[1]) ? new Buffer(parts[2], 'base64') : new Buffer(decodeURIComponent(parts[2])));
} else if (content.path) {
return this._resolveStream(fs.createReadStream(content.path), callback);
}
Expand Down
14 changes: 14 additions & 0 deletions test/compiler-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -136,5 +136,19 @@ describe('Compiler unit tests', function() {
compiler.compile();
expect(compiler.message.content).to.deep.equal(new Buffer(str));
});

it('should create content node from data url', function() {
var str = 'tere tere';
var data = {
attachments: [{
href: 'data:image/png,tere%20tere'
}]
};

var compiler = new Compiler(data);
compiler.compile();
expect(compiler.mail.attachments[0].content).to.deep.equal(new Buffer(str));
expect(compiler.mail.attachments[0].contentType).to.equal('image/png');
});
});
});
63 changes: 63 additions & 0 deletions test/nodemailer-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,69 @@ describe('Nodemailer unit tests', function() {
done();
});
});

describe('data uri tests', function() {

it('should resolve with mime type and base64', function(done) {
var mail = {
data: {
attachment: {
path: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg=='
}
}
};
nm.resolveContent(mail.data, 'attachment', function(err, value) {
expect(err).to.not.exist;
expect(value).to.deep.equal(new Buffer('iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==', 'base64'));
done();
});
});

it('should resolve with mime type and plaintext', function(done) {
var mail = {
data: {
attachment: {
path: 'data:image/png,tere%20tere'
}
}
};
nm.resolveContent(mail.data, 'attachment', function(err, value) {
expect(err).to.not.exist;
expect(value).to.deep.equal(new Buffer('tere tere'));
done();
});
});

it('should resolve with plaintext', function(done) {
var mail = {
data: {
attachment: {
path: 'data:,tere%20tere'
}
}
};
nm.resolveContent(mail.data, 'attachment', function(err, value) {
expect(err).to.not.exist;
expect(value).to.deep.equal(new Buffer('tere tere'));
done();
});
});

it('should resolve with mime type, charset and base64', function(done) {
var mail = {
data: {
attachment: {
path: 'data:image/png;charset=iso-8859-1;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg=='
}
}
};
nm.resolveContent(mail.data, 'attachment', function(err, value) {
expect(err).to.not.exist;
expect(value).to.deep.equal(new Buffer('iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==', 'base64'));
done();
});
});
});
});
});

Expand Down

0 comments on commit e22e9ef

Please sign in to comment.