mirror of
https://github.com/TracksApp/tracks.git
synced 2025-12-17 07:40:12 +01:00
Updated the vendor directory for Rails 1.1. Also got rid of the RJS plugin as it should no longer be needed with Rails 1.1. Javascripts updated.
git-svn-id: http://www.rousette.org.uk/svn/tracks-repos/trunk@211 a4c988fc-2ded-0310-b66e-134b36920a42
This commit is contained in:
parent
99851e1eab
commit
bd521d0e03
783 changed files with 800 additions and 96936 deletions
89
tracks/public/javascripts/controls.js
vendored
89
tracks/public/javascripts/controls.js
vendored
|
|
@ -141,8 +141,8 @@ Autocompleter.Base.prototype = {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
if(event.keyCode==Event.KEY_TAB || event.keyCode==Event.KEY_RETURN)
|
if(event.keyCode==Event.KEY_TAB || event.keyCode==Event.KEY_RETURN ||
|
||||||
return;
|
(navigator.appVersion.indexOf('AppleWebKit') > 0 && event.keyCode == 0)) return;
|
||||||
|
|
||||||
this.changed = true;
|
this.changed = true;
|
||||||
this.hasFocus = true;
|
this.hasFocus = true;
|
||||||
|
|
@ -152,6 +152,12 @@ Autocompleter.Base.prototype = {
|
||||||
setTimeout(this.onObserverEvent.bind(this), this.options.frequency*1000);
|
setTimeout(this.onObserverEvent.bind(this), this.options.frequency*1000);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
activate: function() {
|
||||||
|
this.changed = false;
|
||||||
|
this.hasFocus = true;
|
||||||
|
this.getUpdatedChoices();
|
||||||
|
},
|
||||||
|
|
||||||
onHover: function(event) {
|
onHover: function(event) {
|
||||||
var element = Event.findElement(event, 'LI');
|
var element = Event.findElement(event, 'LI');
|
||||||
if(this.index != element.autocompleteIndex)
|
if(this.index != element.autocompleteIndex)
|
||||||
|
|
@ -221,8 +227,13 @@ Autocompleter.Base.prototype = {
|
||||||
this.options.updateElement(selectedElement);
|
this.options.updateElement(selectedElement);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
var value = '';
|
||||||
|
if (this.options.select) {
|
||||||
|
var nodes = document.getElementsByClassName(this.options.select, selectedElement) || [];
|
||||||
|
if(nodes.length>0) value = Element.collectTextNodes(nodes[0], this.options.select);
|
||||||
|
} else
|
||||||
|
value = Element.collectTextNodesIgnoreClass(selectedElement, 'informal');
|
||||||
|
|
||||||
var value = Element.collectTextNodesIgnoreClass(selectedElement, 'informal');
|
|
||||||
var lastTokenPos = this.findLastToken();
|
var lastTokenPos = this.findLastToken();
|
||||||
if (lastTokenPos != -1) {
|
if (lastTokenPos != -1) {
|
||||||
var newValue = this.element.value.substr(0, lastTokenPos + 1);
|
var newValue = this.element.value.substr(0, lastTokenPos + 1);
|
||||||
|
|
@ -448,7 +459,9 @@ Ajax.InPlaceEditor.prototype = {
|
||||||
this.element = $(element);
|
this.element = $(element);
|
||||||
|
|
||||||
this.options = Object.extend({
|
this.options = Object.extend({
|
||||||
|
okButton: true,
|
||||||
okText: "ok",
|
okText: "ok",
|
||||||
|
cancelLink: true,
|
||||||
cancelText: "cancel",
|
cancelText: "cancel",
|
||||||
savingText: "Saving...",
|
savingText: "Saving...",
|
||||||
clickToEditText: "Click to edit",
|
clickToEditText: "Click to edit",
|
||||||
|
|
@ -471,7 +484,9 @@ Ajax.InPlaceEditor.prototype = {
|
||||||
highlightcolor: Ajax.InPlaceEditor.defaultHighlightColor,
|
highlightcolor: Ajax.InPlaceEditor.defaultHighlightColor,
|
||||||
highlightendcolor: "#FFFFFF",
|
highlightendcolor: "#FFFFFF",
|
||||||
externalControl: null,
|
externalControl: null,
|
||||||
ajaxOptions: {}
|
submitOnBlur: false,
|
||||||
|
ajaxOptions: {},
|
||||||
|
evalScripts: false
|
||||||
}, options || {});
|
}, options || {});
|
||||||
|
|
||||||
if(!this.options.formId && this.element.id) {
|
if(!this.options.formId && this.element.id) {
|
||||||
|
|
@ -536,16 +551,22 @@ Ajax.InPlaceEditor.prototype = {
|
||||||
this.form.appendChild(br);
|
this.form.appendChild(br);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.options.okButton) {
|
||||||
okButton = document.createElement("input");
|
okButton = document.createElement("input");
|
||||||
okButton.type = "submit";
|
okButton.type = "submit";
|
||||||
okButton.value = this.options.okText;
|
okButton.value = this.options.okText;
|
||||||
|
okButton.className = 'editor_ok_button';
|
||||||
this.form.appendChild(okButton);
|
this.form.appendChild(okButton);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.options.cancelLink) {
|
||||||
cancelLink = document.createElement("a");
|
cancelLink = document.createElement("a");
|
||||||
cancelLink.href = "#";
|
cancelLink.href = "#";
|
||||||
cancelLink.appendChild(document.createTextNode(this.options.cancelText));
|
cancelLink.appendChild(document.createTextNode(this.options.cancelText));
|
||||||
cancelLink.onclick = this.onclickCancel.bind(this);
|
cancelLink.onclick = this.onclickCancel.bind(this);
|
||||||
|
cancelLink.className = 'editor_cancel';
|
||||||
this.form.appendChild(cancelLink);
|
this.form.appendChild(cancelLink);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
hasHTMLLineBreaks: function(string) {
|
hasHTMLLineBreaks: function(string) {
|
||||||
if (!this.options.handleLineBreaks) return false;
|
if (!this.options.handleLineBreaks) return false;
|
||||||
|
|
@ -562,23 +583,33 @@ Ajax.InPlaceEditor.prototype = {
|
||||||
text = this.getText();
|
text = this.getText();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var obj = this;
|
||||||
|
|
||||||
if (this.options.rows == 1 && !this.hasHTMLLineBreaks(text)) {
|
if (this.options.rows == 1 && !this.hasHTMLLineBreaks(text)) {
|
||||||
this.options.textarea = false;
|
this.options.textarea = false;
|
||||||
var textField = document.createElement("input");
|
var textField = document.createElement("input");
|
||||||
|
textField.obj = this;
|
||||||
textField.type = "text";
|
textField.type = "text";
|
||||||
textField.name = "value";
|
textField.name = "value";
|
||||||
textField.value = text;
|
textField.value = text;
|
||||||
textField.style.backgroundColor = this.options.highlightcolor;
|
textField.style.backgroundColor = this.options.highlightcolor;
|
||||||
|
textField.className = 'editor_field';
|
||||||
var size = this.options.size || this.options.cols || 0;
|
var size = this.options.size || this.options.cols || 0;
|
||||||
if (size != 0) textField.size = size;
|
if (size != 0) textField.size = size;
|
||||||
|
if (this.options.submitOnBlur)
|
||||||
|
textField.onblur = this.onSubmit.bind(this);
|
||||||
this.editField = textField;
|
this.editField = textField;
|
||||||
} else {
|
} else {
|
||||||
this.options.textarea = true;
|
this.options.textarea = true;
|
||||||
var textArea = document.createElement("textarea");
|
var textArea = document.createElement("textarea");
|
||||||
|
textArea.obj = this;
|
||||||
textArea.name = "value";
|
textArea.name = "value";
|
||||||
textArea.value = this.convertHTMLLineBreaks(text);
|
textArea.value = this.convertHTMLLineBreaks(text);
|
||||||
textArea.rows = this.options.rows;
|
textArea.rows = this.options.rows;
|
||||||
textArea.cols = this.options.cols || 40;
|
textArea.cols = this.options.cols || 40;
|
||||||
|
textArea.className = 'editor_field';
|
||||||
|
if (this.options.submitOnBlur)
|
||||||
|
textArea.onblur = this.onSubmit.bind(this);
|
||||||
this.editField = textArea;
|
this.editField = textArea;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -629,19 +660,26 @@ Ajax.InPlaceEditor.prototype = {
|
||||||
// to be displayed indefinitely
|
// to be displayed indefinitely
|
||||||
this.onLoading();
|
this.onLoading();
|
||||||
|
|
||||||
|
if (this.options.evalScripts) {
|
||||||
|
new Ajax.Request(
|
||||||
|
this.url, Object.extend({
|
||||||
|
parameters: this.options.callback(form, value),
|
||||||
|
onComplete: this.onComplete.bind(this),
|
||||||
|
onFailure: this.onFailure.bind(this),
|
||||||
|
asynchronous:true,
|
||||||
|
evalScripts:true
|
||||||
|
}, this.options.ajaxOptions));
|
||||||
|
} else {
|
||||||
new Ajax.Updater(
|
new Ajax.Updater(
|
||||||
{
|
{ success: this.element,
|
||||||
success: this.element,
|
|
||||||
// don't update on failure (this could be an option)
|
// don't update on failure (this could be an option)
|
||||||
failure: null
|
failure: null },
|
||||||
},
|
this.url, Object.extend({
|
||||||
this.url,
|
|
||||||
Object.extend({
|
|
||||||
parameters: this.options.callback(form, value),
|
parameters: this.options.callback(form, value),
|
||||||
onComplete: this.onComplete.bind(this),
|
onComplete: this.onComplete.bind(this),
|
||||||
onFailure: this.onFailure.bind(this)
|
onFailure: this.onFailure.bind(this)
|
||||||
}, this.options.ajaxOptions)
|
}, this.options.ajaxOptions));
|
||||||
);
|
}
|
||||||
// stop the event to avoid a page refresh in Safari
|
// stop the event to avoid a page refresh in Safari
|
||||||
if (arguments.length > 1) {
|
if (arguments.length > 1) {
|
||||||
Event.stop(arguments[0]);
|
Event.stop(arguments[0]);
|
||||||
|
|
@ -723,6 +761,33 @@ Ajax.InPlaceEditor.prototype = {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Ajax.InPlaceCollectionEditor = Class.create();
|
||||||
|
Object.extend(Ajax.InPlaceCollectionEditor.prototype, Ajax.InPlaceEditor.prototype);
|
||||||
|
Object.extend(Ajax.InPlaceCollectionEditor.prototype, {
|
||||||
|
createEditField: function() {
|
||||||
|
if (!this.cached_selectTag) {
|
||||||
|
var selectTag = document.createElement("select");
|
||||||
|
var collection = this.options.collection || [];
|
||||||
|
var optionTag;
|
||||||
|
collection.each(function(e,i) {
|
||||||
|
optionTag = document.createElement("option");
|
||||||
|
optionTag.value = (e instanceof Array) ? e[0] : e;
|
||||||
|
if(this.options.value==optionTag.value) optionTag.selected = true;
|
||||||
|
optionTag.appendChild(document.createTextNode((e instanceof Array) ? e[1] : e));
|
||||||
|
selectTag.appendChild(optionTag);
|
||||||
|
}.bind(this));
|
||||||
|
this.cached_selectTag = selectTag;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.editField = this.cached_selectTag;
|
||||||
|
if(this.options.loadTextURL) this.loadExternalText();
|
||||||
|
this.form.appendChild(this.editField);
|
||||||
|
this.options.callback = function(form, value) {
|
||||||
|
return "value=" + encodeURIComponent(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// Delayed observer, like Form.Element.Observer,
|
// Delayed observer, like Form.Element.Observer,
|
||||||
// but waits for delay after last key input
|
// but waits for delay after last key input
|
||||||
// Ideal for live-search fields
|
// Ideal for live-search fields
|
||||||
|
|
|
||||||
180
tracks/public/javascripts/dragdrop.js
vendored
180
tracks/public/javascripts/dragdrop.js
vendored
|
|
@ -128,7 +128,7 @@ var Draggables = {
|
||||||
this.activeDraggable = draggable;
|
this.activeDraggable = draggable;
|
||||||
},
|
},
|
||||||
|
|
||||||
deactivate: function(draggbale) {
|
deactivate: function() {
|
||||||
this.activeDraggable = null;
|
this.activeDraggable = null;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
@ -146,6 +146,7 @@ var Draggables = {
|
||||||
if(!this.activeDraggable) return;
|
if(!this.activeDraggable) return;
|
||||||
this._lastPointer = null;
|
this._lastPointer = null;
|
||||||
this.activeDraggable.endDrag(event);
|
this.activeDraggable.endDrag(event);
|
||||||
|
this.activeDraggable = null;
|
||||||
},
|
},
|
||||||
|
|
||||||
keyPress: function(event) {
|
keyPress: function(event) {
|
||||||
|
|
@ -191,23 +192,29 @@ Draggable.prototype = {
|
||||||
},
|
},
|
||||||
reverteffect: function(element, top_offset, left_offset) {
|
reverteffect: function(element, top_offset, left_offset) {
|
||||||
var dur = Math.sqrt(Math.abs(top_offset^2)+Math.abs(left_offset^2))*0.02;
|
var dur = Math.sqrt(Math.abs(top_offset^2)+Math.abs(left_offset^2))*0.02;
|
||||||
element._revert = new Effect.MoveBy(element, -top_offset, -left_offset, {duration:dur});
|
element._revert = new Effect.Move(element, { x: -left_offset, y: -top_offset, duration: dur});
|
||||||
},
|
},
|
||||||
endeffect: function(element) {
|
endeffect: function(element) {
|
||||||
new Effect.Opacity(element, {duration:0.2, from:0.7, to:1.0});
|
new Effect.Opacity(element, {duration:0.2, from:0.7, to:1.0});
|
||||||
},
|
},
|
||||||
zindex: 1000,
|
zindex: 1000,
|
||||||
revert: false,
|
revert: false,
|
||||||
|
scroll: false,
|
||||||
|
scrollSensitivity: 20,
|
||||||
|
scrollSpeed: 15,
|
||||||
snap: false // false, or xy or [x,y] or function(x,y){ return [x,y] }
|
snap: false // false, or xy or [x,y] or function(x,y){ return [x,y] }
|
||||||
}, arguments[1] || {});
|
}, arguments[1] || {});
|
||||||
|
|
||||||
this.element = $(element);
|
this.element = $(element);
|
||||||
|
|
||||||
if(options.handle && (typeof options.handle == 'string'))
|
if(options.handle && (typeof options.handle == 'string'))
|
||||||
this.handle = Element.childrenWithClassName(this.element, options.handle)[0];
|
this.handle = Element.childrenWithClassName(this.element, options.handle, true)[0];
|
||||||
if(!this.handle) this.handle = $(options.handle);
|
if(!this.handle) this.handle = $(options.handle);
|
||||||
if(!this.handle) this.handle = this.element;
|
if(!this.handle) this.handle = this.element;
|
||||||
|
|
||||||
|
if(options.scroll && !options.scroll.scrollTo && !options.scroll.outerHTML)
|
||||||
|
options.scroll = $(options.scroll);
|
||||||
|
|
||||||
Element.makePositioned(this.element); // fix IE
|
Element.makePositioned(this.element); // fix IE
|
||||||
|
|
||||||
this.delta = this.currentDelta();
|
this.delta = this.currentDelta();
|
||||||
|
|
@ -227,8 +234,8 @@ Draggable.prototype = {
|
||||||
|
|
||||||
currentDelta: function() {
|
currentDelta: function() {
|
||||||
return([
|
return([
|
||||||
parseInt(this.element.style.left || '0'),
|
parseInt(Element.getStyle(this.element,'left') || '0'),
|
||||||
parseInt(this.element.style.top || '0')]);
|
parseInt(Element.getStyle(this.element,'top') || '0')]);
|
||||||
},
|
},
|
||||||
|
|
||||||
initDrag: function(event) {
|
initDrag: function(event) {
|
||||||
|
|
@ -238,6 +245,7 @@ Draggable.prototype = {
|
||||||
if(src.tagName && (
|
if(src.tagName && (
|
||||||
src.tagName=='INPUT' ||
|
src.tagName=='INPUT' ||
|
||||||
src.tagName=='SELECT' ||
|
src.tagName=='SELECT' ||
|
||||||
|
src.tagName=='OPTION' ||
|
||||||
src.tagName=='BUTTON' ||
|
src.tagName=='BUTTON' ||
|
||||||
src.tagName=='TEXTAREA')) return;
|
src.tagName=='TEXTAREA')) return;
|
||||||
|
|
||||||
|
|
@ -269,6 +277,17 @@ Draggable.prototype = {
|
||||||
this.element.parentNode.insertBefore(this._clone, this.element);
|
this.element.parentNode.insertBefore(this._clone, this.element);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(this.options.scroll) {
|
||||||
|
if (this.options.scroll == window) {
|
||||||
|
var where = this._getWindowScroll(this.options.scroll);
|
||||||
|
this.originalScrollLeft = where.left;
|
||||||
|
this.originalScrollTop = where.top;
|
||||||
|
} else {
|
||||||
|
this.originalScrollLeft = this.options.scroll.scrollLeft;
|
||||||
|
this.originalScrollTop = this.options.scroll.scrollTop;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Draggables.notify('onStart', this, event);
|
Draggables.notify('onStart', this, event);
|
||||||
if(this.options.starteffect) this.options.starteffect(this.element);
|
if(this.options.starteffect) this.options.starteffect(this.element);
|
||||||
},
|
},
|
||||||
|
|
@ -281,8 +300,30 @@ Draggable.prototype = {
|
||||||
this.draw(pointer);
|
this.draw(pointer);
|
||||||
if(this.options.change) this.options.change(this);
|
if(this.options.change) this.options.change(this);
|
||||||
|
|
||||||
|
if(this.options.scroll) {
|
||||||
|
this.stopScrolling();
|
||||||
|
|
||||||
|
var p;
|
||||||
|
if (this.options.scroll == window) {
|
||||||
|
with(this._getWindowScroll(this.options.scroll)) { p = [ left, top, left+width, top+height ]; }
|
||||||
|
} else {
|
||||||
|
p = Position.page(this.options.scroll);
|
||||||
|
p[0] += this.options.scroll.scrollLeft;
|
||||||
|
p[1] += this.options.scroll.scrollTop;
|
||||||
|
p.push(p[0]+this.options.scroll.offsetWidth);
|
||||||
|
p.push(p[1]+this.options.scroll.offsetHeight);
|
||||||
|
}
|
||||||
|
var speed = [0,0];
|
||||||
|
if(pointer[0] < (p[0]+this.options.scrollSensitivity)) speed[0] = pointer[0]-(p[0]+this.options.scrollSensitivity);
|
||||||
|
if(pointer[1] < (p[1]+this.options.scrollSensitivity)) speed[1] = pointer[1]-(p[1]+this.options.scrollSensitivity);
|
||||||
|
if(pointer[0] > (p[2]-this.options.scrollSensitivity)) speed[0] = pointer[0]-(p[2]-this.options.scrollSensitivity);
|
||||||
|
if(pointer[1] > (p[3]-this.options.scrollSensitivity)) speed[1] = pointer[1]-(p[3]-this.options.scrollSensitivity);
|
||||||
|
this.startScrolling(speed);
|
||||||
|
}
|
||||||
|
|
||||||
// fix AppleWebKit rendering
|
// fix AppleWebKit rendering
|
||||||
if(navigator.appVersion.indexOf('AppleWebKit')>0) window.scrollBy(0,0);
|
if(navigator.appVersion.indexOf('AppleWebKit')>0) window.scrollBy(0,0);
|
||||||
|
|
||||||
Event.stop(event);
|
Event.stop(event);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
@ -320,13 +361,14 @@ Draggable.prototype = {
|
||||||
},
|
},
|
||||||
|
|
||||||
keyPress: function(event) {
|
keyPress: function(event) {
|
||||||
if(!event.keyCode==Event.KEY_ESC) return;
|
if(event.keyCode!=Event.KEY_ESC) return;
|
||||||
this.finishDrag(event, false);
|
this.finishDrag(event, false);
|
||||||
Event.stop(event);
|
Event.stop(event);
|
||||||
},
|
},
|
||||||
|
|
||||||
endDrag: function(event) {
|
endDrag: function(event) {
|
||||||
if(!this.dragging) return;
|
if(!this.dragging) return;
|
||||||
|
this.stopScrolling();
|
||||||
this.finishDrag(event, true);
|
this.finishDrag(event, true);
|
||||||
Event.stop(event);
|
Event.stop(event);
|
||||||
},
|
},
|
||||||
|
|
@ -336,7 +378,14 @@ Draggable.prototype = {
|
||||||
var d = this.currentDelta();
|
var d = this.currentDelta();
|
||||||
pos[0] -= d[0]; pos[1] -= d[1];
|
pos[0] -= d[0]; pos[1] -= d[1];
|
||||||
|
|
||||||
var p = [0,1].map(function(i){ return (point[i]-pos[i]-this.offset[i]) }.bind(this));
|
if(this.options.scroll && (this.options.scroll != window)) {
|
||||||
|
pos[0] -= this.options.scroll.scrollLeft-this.originalScrollLeft;
|
||||||
|
pos[1] -= this.options.scroll.scrollTop-this.originalScrollTop;
|
||||||
|
}
|
||||||
|
|
||||||
|
var p = [0,1].map(function(i){
|
||||||
|
return (point[i]-pos[i]-this.offset[i])
|
||||||
|
}.bind(this));
|
||||||
|
|
||||||
if(this.options.snap) {
|
if(this.options.snap) {
|
||||||
if(typeof this.options.snap == 'function') {
|
if(typeof this.options.snap == 'function') {
|
||||||
|
|
@ -357,6 +406,67 @@ Draggable.prototype = {
|
||||||
if((!this.options.constraint) || (this.options.constraint=='vertical'))
|
if((!this.options.constraint) || (this.options.constraint=='vertical'))
|
||||||
style.top = p[1] + "px";
|
style.top = p[1] + "px";
|
||||||
if(style.visibility=="hidden") style.visibility = ""; // fix gecko rendering
|
if(style.visibility=="hidden") style.visibility = ""; // fix gecko rendering
|
||||||
|
},
|
||||||
|
|
||||||
|
stopScrolling: function() {
|
||||||
|
if(this.scrollInterval) {
|
||||||
|
clearInterval(this.scrollInterval);
|
||||||
|
this.scrollInterval = null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
startScrolling: function(speed) {
|
||||||
|
this.scrollSpeed = [speed[0]*this.options.scrollSpeed,speed[1]*this.options.scrollSpeed];
|
||||||
|
this.lastScrolled = new Date();
|
||||||
|
this.scrollInterval = setInterval(this.scroll.bind(this), 10);
|
||||||
|
},
|
||||||
|
|
||||||
|
scroll: function() {
|
||||||
|
var current = new Date();
|
||||||
|
var delta = current - this.lastScrolled;
|
||||||
|
this.lastScrolled = current;
|
||||||
|
if(this.options.scroll == window) {
|
||||||
|
with (this._getWindowScroll(this.options.scroll)) {
|
||||||
|
if (this.scrollSpeed[0] || this.scrollSpeed[1]) {
|
||||||
|
var d = delta / 1000;
|
||||||
|
this.options.scroll.scrollTo( left + d*this.scrollSpeed[0], top + d*this.scrollSpeed[1] );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.options.scroll.scrollLeft += this.scrollSpeed[0] * delta / 1000;
|
||||||
|
this.options.scroll.scrollTop += this.scrollSpeed[1] * delta / 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
Position.prepare();
|
||||||
|
Droppables.show(Draggables._lastPointer, this.element);
|
||||||
|
Draggables.notify('onDrag', this);
|
||||||
|
this.draw(Draggables._lastPointer);
|
||||||
|
|
||||||
|
if(this.options.change) this.options.change(this);
|
||||||
|
},
|
||||||
|
|
||||||
|
_getWindowScroll: function(w) {
|
||||||
|
var T, L, W, H;
|
||||||
|
with (w.document) {
|
||||||
|
if (w.document.documentElement && documentElement.scrollTop) {
|
||||||
|
T = documentElement.scrollTop;
|
||||||
|
L = documentElement.scrollLeft;
|
||||||
|
} else if (w.document.body) {
|
||||||
|
T = body.scrollTop;
|
||||||
|
L = body.scrollLeft;
|
||||||
|
}
|
||||||
|
if (w.innerWidth) {
|
||||||
|
W = w.innerWidth;
|
||||||
|
H = w.innerHeight;
|
||||||
|
} else if (w.document.documentElement && documentElement.clientWidth) {
|
||||||
|
W = documentElement.clientWidth;
|
||||||
|
H = documentElement.clientHeight;
|
||||||
|
} else {
|
||||||
|
W = body.offsetWidth;
|
||||||
|
H = body.offsetHeight
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return { top: T, left: L, width: W, height: H };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -413,7 +523,10 @@ var Sortable = {
|
||||||
only: false,
|
only: false,
|
||||||
hoverclass: null,
|
hoverclass: null,
|
||||||
ghosting: false,
|
ghosting: false,
|
||||||
format: null,
|
scroll: false,
|
||||||
|
scrollSensitivity: 20,
|
||||||
|
scrollSpeed: 15,
|
||||||
|
format: /^[^_]*_(.*)$/,
|
||||||
onChange: Prototype.emptyFunction,
|
onChange: Prototype.emptyFunction,
|
||||||
onUpdate: Prototype.emptyFunction
|
onUpdate: Prototype.emptyFunction
|
||||||
}, arguments[1] || {});
|
}, arguments[1] || {});
|
||||||
|
|
@ -424,6 +537,9 @@ var Sortable = {
|
||||||
// build options for the draggables
|
// build options for the draggables
|
||||||
var options_for_draggable = {
|
var options_for_draggable = {
|
||||||
revert: true,
|
revert: true,
|
||||||
|
scroll: options.scroll,
|
||||||
|
scrollSpeed: options.scrollSpeed,
|
||||||
|
scrollSensitivity: options.scrollSensitivity,
|
||||||
ghosting: options.ghosting,
|
ghosting: options.ghosting,
|
||||||
constraint: options.constraint,
|
constraint: options.constraint,
|
||||||
handle: options.handle };
|
handle: options.handle };
|
||||||
|
|
@ -491,9 +607,10 @@ var Sortable = {
|
||||||
findElements: function(element, options) {
|
findElements: function(element, options) {
|
||||||
if(!element.hasChildNodes()) return null;
|
if(!element.hasChildNodes()) return null;
|
||||||
var elements = [];
|
var elements = [];
|
||||||
|
var only = options.only ? [options.only].flatten() : null;
|
||||||
$A(element.childNodes).each( function(e) {
|
$A(element.childNodes).each( function(e) {
|
||||||
if(e.tagName && e.tagName.toUpperCase()==options.tag.toUpperCase() &&
|
if(e.tagName && e.tagName.toUpperCase()==options.tag.toUpperCase() &&
|
||||||
(!options.only || (Element.hasClassName(e, options.only))))
|
(!only || (Element.classNames(e).detect(function(v) { return only.include(v) }))))
|
||||||
elements.push(e);
|
elements.push(e);
|
||||||
if(options.tree) {
|
if(options.tree) {
|
||||||
var grandchildren = this.findElements(e, options);
|
var grandchildren = this.findElements(e, options);
|
||||||
|
|
@ -567,18 +684,41 @@ var Sortable = {
|
||||||
Element.show(Sortable._marker);
|
Element.show(Sortable._marker);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
sequence: function(element) {
|
||||||
|
element = $(element);
|
||||||
|
var options = Object.extend(this.options(element), arguments[1] || {});
|
||||||
|
|
||||||
|
return $(this.findElements(element, options) || []).map( function(item) {
|
||||||
|
return item.id.match(options.format) ? item.id.match(options.format)[1] : '';
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
setSequence: function(element, new_sequence) {
|
||||||
|
element = $(element);
|
||||||
|
var options = Object.extend(this.options(element), arguments[2] || {});
|
||||||
|
|
||||||
|
var nodeMap = {};
|
||||||
|
this.findElements(element, options).each( function(n) {
|
||||||
|
if (n.id.match(options.format))
|
||||||
|
nodeMap[n.id.match(options.format)[1]] = [n, n.parentNode];
|
||||||
|
n.parentNode.removeChild(n);
|
||||||
|
});
|
||||||
|
|
||||||
|
new_sequence.each(function(ident) {
|
||||||
|
var n = nodeMap[ident];
|
||||||
|
if (n) {
|
||||||
|
n[1].appendChild(n[0]);
|
||||||
|
delete nodeMap[ident];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
serialize: function(element) {
|
serialize: function(element) {
|
||||||
element = $(element);
|
element = $(element);
|
||||||
var sortableOptions = this.options(element);
|
var name = encodeURIComponent(
|
||||||
var options = Object.extend({
|
(arguments[1] && arguments[1].name) ? arguments[1].name : element.id);
|
||||||
tag: sortableOptions.tag,
|
return Sortable.sequence(element, arguments[1]).map( function(item) {
|
||||||
only: sortableOptions.only,
|
return name + "[]=" + encodeURIComponent(item);
|
||||||
name: element.id,
|
}).join('&');
|
||||||
format: sortableOptions.format || /^[^_]*_(.*)$/
|
|
||||||
}, arguments[1] || {});
|
|
||||||
return $(this.findElements(element, options) || []).map( function(item) {
|
|
||||||
return (encodeURIComponent(options.name) + "[]=" +
|
|
||||||
encodeURIComponent(item.id.match(options.format) ? item.id.match(options.format)[1] : ''));
|
|
||||||
}).join("&");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
499
tracks/public/javascripts/effects.js
vendored
499
tracks/public/javascripts/effects.js
vendored
|
|
@ -6,8 +6,6 @@
|
||||||
//
|
//
|
||||||
// See scriptaculous.js for full license.
|
// See scriptaculous.js for full license.
|
||||||
|
|
||||||
/* ------------- element ext -------------- */
|
|
||||||
|
|
||||||
// converts rgb() and #xxx to #xxxxxx format,
|
// converts rgb() and #xxx to #xxxxxx format,
|
||||||
// returns self (or first argument) if not convertable
|
// returns self (or first argument) if not convertable
|
||||||
String.prototype.parseColor = function() {
|
String.prototype.parseColor = function() {
|
||||||
|
|
@ -24,29 +22,25 @@ String.prototype.parseColor = function() {
|
||||||
return(color.length==7 ? color : (arguments[0] || this));
|
return(color.length==7 ? color : (arguments[0] || this));
|
||||||
}
|
}
|
||||||
|
|
||||||
Element.collectTextNodesIgnoreClass = function(element, ignoreclass) {
|
/*--------------------------------------------------------------------------*/
|
||||||
var children = $(element).childNodes;
|
|
||||||
var text = '';
|
|
||||||
var classtest = new RegExp('^([^ ]+ )*' + ignoreclass+ '( [^ ]+)*$','i');
|
|
||||||
|
|
||||||
for (var i = 0; i < children.length; i++) {
|
Element.collectTextNodes = function(element) {
|
||||||
if(children[i].nodeType==3) {
|
return $A($(element).childNodes).collect( function(node) {
|
||||||
text+=children[i].nodeValue;
|
return (node.nodeType==3 ? node.nodeValue :
|
||||||
} else {
|
(node.hasChildNodes() ? Element.collectTextNodes(node) : ''));
|
||||||
if((!children[i].className.match(classtest)) && children[i].hasChildNodes())
|
}).flatten().join('');
|
||||||
text += Element.collectTextNodesIgnoreClass(children[i], ignoreclass);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return text;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Element.setStyle = function(element, style) {
|
Element.collectTextNodesIgnoreClass = function(element, className) {
|
||||||
element = $(element);
|
return $A($(element).childNodes).collect( function(node) {
|
||||||
for(k in style) element.style[k.camelize()] = style[k];
|
return (node.nodeType==3 ? node.nodeValue :
|
||||||
|
((node.hasChildNodes() && !Element.hasClassName(node,className)) ?
|
||||||
|
Element.collectTextNodesIgnoreClass(node, className) : ''));
|
||||||
|
}).flatten().join('');
|
||||||
}
|
}
|
||||||
|
|
||||||
Element.setContentZoom = function(element, percent) {
|
Element.setContentZoom = function(element, percent) {
|
||||||
|
element = $(element);
|
||||||
Element.setStyle(element, {fontSize: (percent/100) + 'em'});
|
Element.setStyle(element, {fontSize: (percent/100) + 'em'});
|
||||||
if(navigator.appVersion.indexOf('AppleWebKit')>0) window.scrollBy(0,0);
|
if(navigator.appVersion.indexOf('AppleWebKit')>0) window.scrollBy(0,0);
|
||||||
}
|
}
|
||||||
|
|
@ -82,11 +76,28 @@ Element.getInlineOpacity = function(element){
|
||||||
return $(element).style.opacity || '';
|
return $(element).style.opacity || '';
|
||||||
}
|
}
|
||||||
|
|
||||||
Element.childrenWithClassName = function(element, className) {
|
Element.childrenWithClassName = function(element, className, findFirst) {
|
||||||
return $A($(element).getElementsByTagName('*')).select(
|
return [$A($(element).getElementsByTagName('*'))[findFirst ? 'detect' : 'select']( function(c) {
|
||||||
function(c) { return Element.hasClassName(c, className) });
|
return c.className ? Element.hasClassName(c, className) : false;
|
||||||
|
})].flatten();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Element.forceRerendering = function(element) {
|
||||||
|
try {
|
||||||
|
element = $(element);
|
||||||
|
var n = document.createTextNode(' ');
|
||||||
|
element.appendChild(n);
|
||||||
|
element.removeChild(n);
|
||||||
|
} catch(e) { }
|
||||||
|
};
|
||||||
|
|
||||||
|
['setOpacity','getOpacity','getInlineOpacity','forceRerendering','setContentZoom',
|
||||||
|
'collectTextNodes','collectTextNodesIgnoreClass','childrenWithClassName'].each(
|
||||||
|
function(f) { Element.Methods[f] = Element[f]; }
|
||||||
|
);
|
||||||
|
|
||||||
|
/*--------------------------------------------------------------------------*/
|
||||||
|
|
||||||
Array.prototype.call = function() {
|
Array.prototype.call = function() {
|
||||||
var args = arguments;
|
var args = arguments;
|
||||||
this.each(function(f){ f.apply(this, args) });
|
this.each(function(f){ f.apply(this, args) });
|
||||||
|
|
@ -129,6 +140,20 @@ var Effect = {
|
||||||
$A(elements).each( function(element, index) {
|
$A(elements).each( function(element, index) {
|
||||||
new effect(element, Object.extend(options, { delay: index * options.speed + masterDelay }));
|
new effect(element, Object.extend(options, { delay: index * options.speed + masterDelay }));
|
||||||
});
|
});
|
||||||
|
},
|
||||||
|
PAIRS: {
|
||||||
|
'slide': ['SlideDown','SlideUp'],
|
||||||
|
'blind': ['BlindDown','BlindUp'],
|
||||||
|
'appear': ['Appear','Fade']
|
||||||
|
},
|
||||||
|
toggle: function(element, effect) {
|
||||||
|
element = $(element);
|
||||||
|
effect = (effect || 'appear').toLowerCase();
|
||||||
|
var options = Object.extend({
|
||||||
|
queue: { position:'end', scope:(element.id || 'global'), limit: 1 }
|
||||||
|
}, arguments[2] || {});
|
||||||
|
Effect[element.visible() ?
|
||||||
|
Effect.PAIRS[effect][1] : Effect.PAIRS[effect][0]](element, options);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -166,16 +191,22 @@ Effect.Transitions.full = function(pos) {
|
||||||
|
|
||||||
/* ------------- core effects ------------- */
|
/* ------------- core effects ------------- */
|
||||||
|
|
||||||
Effect.Queue = {
|
Effect.ScopedQueue = Class.create();
|
||||||
effects: [],
|
Object.extend(Object.extend(Effect.ScopedQueue.prototype, Enumerable), {
|
||||||
|
initialize: function() {
|
||||||
|
this.effects = [];
|
||||||
|
this.interval = null;
|
||||||
|
},
|
||||||
_each: function(iterator) {
|
_each: function(iterator) {
|
||||||
this.effects._each(iterator);
|
this.effects._each(iterator);
|
||||||
},
|
},
|
||||||
interval: null,
|
|
||||||
add: function(effect) {
|
add: function(effect) {
|
||||||
var timestamp = new Date().getTime();
|
var timestamp = new Date().getTime();
|
||||||
|
|
||||||
switch(effect.options.queue) {
|
var position = (typeof effect.options.queue == 'string') ?
|
||||||
|
effect.options.queue : effect.options.queue.position;
|
||||||
|
|
||||||
|
switch(position) {
|
||||||
case 'front':
|
case 'front':
|
||||||
// move unstarted effects after this effect
|
// move unstarted effects after this effect
|
||||||
this.effects.findAll(function(e){ return e.state=='idle' }).each( function(e) {
|
this.effects.findAll(function(e){ return e.state=='idle' }).each( function(e) {
|
||||||
|
|
@ -191,7 +222,10 @@ Effect.Queue = {
|
||||||
|
|
||||||
effect.startOn += timestamp;
|
effect.startOn += timestamp;
|
||||||
effect.finishOn += timestamp;
|
effect.finishOn += timestamp;
|
||||||
|
|
||||||
|
if(!effect.options.queue.limit || (this.effects.length < effect.options.queue.limit))
|
||||||
this.effects.push(effect);
|
this.effects.push(effect);
|
||||||
|
|
||||||
if(!this.interval)
|
if(!this.interval)
|
||||||
this.interval = setInterval(this.loop.bind(this), 40);
|
this.interval = setInterval(this.loop.bind(this), 40);
|
||||||
},
|
},
|
||||||
|
|
@ -206,14 +240,22 @@ Effect.Queue = {
|
||||||
var timePos = new Date().getTime();
|
var timePos = new Date().getTime();
|
||||||
this.effects.invoke('loop', timePos);
|
this.effects.invoke('loop', timePos);
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
Object.extend(Effect.Queue, Enumerable);
|
|
||||||
|
|
||||||
Effect.Base = function() {};
|
Effect.Queues = {
|
||||||
Effect.Base.prototype = {
|
instances: $H(),
|
||||||
position: null,
|
get: function(queueName) {
|
||||||
setOptions: function(options) {
|
if(typeof queueName != 'string') return queueName;
|
||||||
this.options = Object.extend({
|
|
||||||
|
if(!this.instances[queueName])
|
||||||
|
this.instances[queueName] = new Effect.ScopedQueue();
|
||||||
|
|
||||||
|
return this.instances[queueName];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Effect.Queue = Effect.Queues.get('global');
|
||||||
|
|
||||||
|
Effect.DefaultOptions = {
|
||||||
transition: Effect.Transitions.sinoidal,
|
transition: Effect.Transitions.sinoidal,
|
||||||
duration: 1.0, // seconds
|
duration: 1.0, // seconds
|
||||||
fps: 25.0, // max. 25fps due to Effect.Queue implementation
|
fps: 25.0, // max. 25fps due to Effect.Queue implementation
|
||||||
|
|
@ -222,16 +264,21 @@ Effect.Base.prototype = {
|
||||||
to: 1.0,
|
to: 1.0,
|
||||||
delay: 0.0,
|
delay: 0.0,
|
||||||
queue: 'parallel'
|
queue: 'parallel'
|
||||||
}, options || {});
|
}
|
||||||
},
|
|
||||||
|
Effect.Base = function() {};
|
||||||
|
Effect.Base.prototype = {
|
||||||
|
position: null,
|
||||||
start: function(options) {
|
start: function(options) {
|
||||||
this.setOptions(options || {});
|
this.options = Object.extend(Object.extend({},Effect.DefaultOptions), options || {});
|
||||||
this.currentFrame = 0;
|
this.currentFrame = 0;
|
||||||
this.state = 'idle';
|
this.state = 'idle';
|
||||||
this.startOn = this.options.delay*1000;
|
this.startOn = this.options.delay*1000;
|
||||||
this.finishOn = this.startOn + (this.options.duration*1000);
|
this.finishOn = this.startOn + (this.options.duration*1000);
|
||||||
this.event('beforeStart');
|
this.event('beforeStart');
|
||||||
if(!this.options.sync) Effect.Queue.add(this);
|
if(!this.options.sync)
|
||||||
|
Effect.Queues.get(typeof this.options.queue == 'string' ?
|
||||||
|
'global' : this.options.queue.scope).add(this);
|
||||||
},
|
},
|
||||||
loop: function(timePos) {
|
loop: function(timePos) {
|
||||||
if(timePos >= this.startOn) {
|
if(timePos >= this.startOn) {
|
||||||
|
|
@ -269,7 +316,9 @@ Effect.Base.prototype = {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
cancel: function() {
|
cancel: function() {
|
||||||
if(!this.options.sync) Effect.Queue.remove(this);
|
if(!this.options.sync)
|
||||||
|
Effect.Queues.get(typeof this.options.queue == 'string' ?
|
||||||
|
'global' : this.options.queue.scope).remove(this);
|
||||||
this.state = 'finished';
|
this.state = 'finished';
|
||||||
},
|
},
|
||||||
event: function(eventName) {
|
event: function(eventName) {
|
||||||
|
|
@ -307,43 +356,57 @@ Object.extend(Object.extend(Effect.Opacity.prototype, Effect.Base.prototype), {
|
||||||
this.element = $(element);
|
this.element = $(element);
|
||||||
// make this work on IE on elements without 'layout'
|
// make this work on IE on elements without 'layout'
|
||||||
if(/MSIE/.test(navigator.userAgent) && (!this.element.hasLayout))
|
if(/MSIE/.test(navigator.userAgent) && (!this.element.hasLayout))
|
||||||
Element.setStyle(this.element, {zoom: 1});
|
this.element.setStyle({zoom: 1});
|
||||||
var options = Object.extend({
|
var options = Object.extend({
|
||||||
from: Element.getOpacity(this.element) || 0.0,
|
from: this.element.getOpacity() || 0.0,
|
||||||
to: 1.0
|
to: 1.0
|
||||||
}, arguments[1] || {});
|
}, arguments[1] || {});
|
||||||
this.start(options);
|
this.start(options);
|
||||||
},
|
},
|
||||||
update: function(position) {
|
update: function(position) {
|
||||||
Element.setOpacity(this.element, position);
|
this.element.setOpacity(position);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
Effect.MoveBy = Class.create();
|
Effect.Move = Class.create();
|
||||||
Object.extend(Object.extend(Effect.MoveBy.prototype, Effect.Base.prototype), {
|
Object.extend(Object.extend(Effect.Move.prototype, Effect.Base.prototype), {
|
||||||
initialize: function(element, toTop, toLeft) {
|
initialize: function(element) {
|
||||||
this.element = $(element);
|
this.element = $(element);
|
||||||
this.toTop = toTop;
|
var options = Object.extend({
|
||||||
this.toLeft = toLeft;
|
x: 0,
|
||||||
this.start(arguments[3]);
|
y: 0,
|
||||||
|
mode: 'relative'
|
||||||
|
}, arguments[1] || {});
|
||||||
|
this.start(options);
|
||||||
},
|
},
|
||||||
setup: function() {
|
setup: function() {
|
||||||
// Bug in Opera: Opera returns the "real" position of a static element or
|
// Bug in Opera: Opera returns the "real" position of a static element or
|
||||||
// relative element that does not have top/left explicitly set.
|
// relative element that does not have top/left explicitly set.
|
||||||
// ==> Always set top and left for position relative elements in your stylesheets
|
// ==> Always set top and left for position relative elements in your stylesheets
|
||||||
// (to 0 if you do not need them)
|
// (to 0 if you do not need them)
|
||||||
Element.makePositioned(this.element);
|
this.element.makePositioned();
|
||||||
this.originalTop = parseFloat(Element.getStyle(this.element,'top') || '0');
|
this.originalLeft = parseFloat(this.element.getStyle('left') || '0');
|
||||||
this.originalLeft = parseFloat(Element.getStyle(this.element,'left') || '0');
|
this.originalTop = parseFloat(this.element.getStyle('top') || '0');
|
||||||
|
if(this.options.mode == 'absolute') {
|
||||||
|
// absolute movement, so we need to calc deltaX and deltaY
|
||||||
|
this.options.x = this.options.x - this.originalLeft;
|
||||||
|
this.options.y = this.options.y - this.originalTop;
|
||||||
|
}
|
||||||
},
|
},
|
||||||
update: function(position) {
|
update: function(position) {
|
||||||
Element.setStyle(this.element, {
|
this.element.setStyle({
|
||||||
top: this.toTop * position + this.originalTop + 'px',
|
left: this.options.x * position + this.originalLeft + 'px',
|
||||||
left: this.toLeft * position + this.originalLeft + 'px'
|
top: this.options.y * position + this.originalTop + 'px'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// for backwards compatibility
|
||||||
|
Effect.MoveBy = function(element, toTop, toLeft) {
|
||||||
|
return new Effect.Move(element,
|
||||||
|
Object.extend({ x: toLeft, y: toTop }, arguments[3] || {}));
|
||||||
|
};
|
||||||
|
|
||||||
Effect.Scale = Class.create();
|
Effect.Scale = Class.create();
|
||||||
Object.extend(Object.extend(Effect.Scale.prototype, Effect.Base.prototype), {
|
Object.extend(Object.extend(Effect.Scale.prototype, Effect.Base.prototype), {
|
||||||
initialize: function(element, percent) {
|
initialize: function(element, percent) {
|
||||||
|
|
@ -361,7 +424,7 @@ Object.extend(Object.extend(Effect.Scale.prototype, Effect.Base.prototype), {
|
||||||
},
|
},
|
||||||
setup: function() {
|
setup: function() {
|
||||||
this.restoreAfterFinish = this.options.restoreAfterFinish || false;
|
this.restoreAfterFinish = this.options.restoreAfterFinish || false;
|
||||||
this.elementPositioning = Element.getStyle(this.element,'position');
|
this.elementPositioning = this.element.getStyle('position');
|
||||||
|
|
||||||
this.originalStyle = {};
|
this.originalStyle = {};
|
||||||
['top','left','width','height','fontSize'].each( function(k) {
|
['top','left','width','height','fontSize'].each( function(k) {
|
||||||
|
|
@ -371,7 +434,7 @@ Object.extend(Object.extend(Effect.Scale.prototype, Effect.Base.prototype), {
|
||||||
this.originalTop = this.element.offsetTop;
|
this.originalTop = this.element.offsetTop;
|
||||||
this.originalLeft = this.element.offsetLeft;
|
this.originalLeft = this.element.offsetLeft;
|
||||||
|
|
||||||
var fontSize = Element.getStyle(this.element,'font-size') || '100%';
|
var fontSize = this.element.getStyle('font-size') || '100%';
|
||||||
['em','px','%'].each( function(fontSizeType) {
|
['em','px','%'].each( function(fontSizeType) {
|
||||||
if(fontSize.indexOf(fontSizeType)>0) {
|
if(fontSize.indexOf(fontSizeType)>0) {
|
||||||
this.fontSize = parseFloat(fontSize);
|
this.fontSize = parseFloat(fontSize);
|
||||||
|
|
@ -393,11 +456,11 @@ Object.extend(Object.extend(Effect.Scale.prototype, Effect.Base.prototype), {
|
||||||
update: function(position) {
|
update: function(position) {
|
||||||
var currentScale = (this.options.scaleFrom/100.0) + (this.factor * position);
|
var currentScale = (this.options.scaleFrom/100.0) + (this.factor * position);
|
||||||
if(this.options.scaleContent && this.fontSize)
|
if(this.options.scaleContent && this.fontSize)
|
||||||
Element.setStyle(this.element, {fontSize: this.fontSize * currentScale + this.fontSizeType });
|
this.element.setStyle({fontSize: this.fontSize * currentScale + this.fontSizeType });
|
||||||
this.setDimensions(this.dims[0] * currentScale, this.dims[1] * currentScale);
|
this.setDimensions(this.dims[0] * currentScale, this.dims[1] * currentScale);
|
||||||
},
|
},
|
||||||
finish: function(position) {
|
finish: function(position) {
|
||||||
if (this.restoreAfterFinish) Element.setStyle(this.element, this.originalStyle);
|
if (this.restoreAfterFinish) this.element.setStyle(this.originalStyle);
|
||||||
},
|
},
|
||||||
setDimensions: function(height, width) {
|
setDimensions: function(height, width) {
|
||||||
var d = {};
|
var d = {};
|
||||||
|
|
@ -414,7 +477,7 @@ Object.extend(Object.extend(Effect.Scale.prototype, Effect.Base.prototype), {
|
||||||
if(this.options.scaleX) d.left = -leftd + 'px';
|
if(this.options.scaleX) d.left = -leftd + 'px';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Element.setStyle(this.element, d);
|
this.element.setStyle(d);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -427,25 +490,25 @@ Object.extend(Object.extend(Effect.Highlight.prototype, Effect.Base.prototype),
|
||||||
},
|
},
|
||||||
setup: function() {
|
setup: function() {
|
||||||
// Prevent executing on elements not in the layout flow
|
// Prevent executing on elements not in the layout flow
|
||||||
if(Element.getStyle(this.element, 'display')=='none') { this.cancel(); return; }
|
if(this.element.getStyle('display')=='none') { this.cancel(); return; }
|
||||||
// Disable background image during the effect
|
// Disable background image during the effect
|
||||||
this.oldStyle = {
|
this.oldStyle = {
|
||||||
backgroundImage: Element.getStyle(this.element, 'background-image') };
|
backgroundImage: this.element.getStyle('background-image') };
|
||||||
Element.setStyle(this.element, {backgroundImage: 'none'});
|
this.element.setStyle({backgroundImage: 'none'});
|
||||||
if(!this.options.endcolor)
|
if(!this.options.endcolor)
|
||||||
this.options.endcolor = Element.getStyle(this.element, 'background-color').parseColor('#ffffff');
|
this.options.endcolor = this.element.getStyle('background-color').parseColor('#ffffff');
|
||||||
if(!this.options.restorecolor)
|
if(!this.options.restorecolor)
|
||||||
this.options.restorecolor = Element.getStyle(this.element, 'background-color');
|
this.options.restorecolor = this.element.getStyle('background-color');
|
||||||
// init color calculations
|
// init color calculations
|
||||||
this._base = $R(0,2).map(function(i){ return parseInt(this.options.startcolor.slice(i*2+1,i*2+3),16) }.bind(this));
|
this._base = $R(0,2).map(function(i){ return parseInt(this.options.startcolor.slice(i*2+1,i*2+3),16) }.bind(this));
|
||||||
this._delta = $R(0,2).map(function(i){ return parseInt(this.options.endcolor.slice(i*2+1,i*2+3),16)-this._base[i] }.bind(this));
|
this._delta = $R(0,2).map(function(i){ return parseInt(this.options.endcolor.slice(i*2+1,i*2+3),16)-this._base[i] }.bind(this));
|
||||||
},
|
},
|
||||||
update: function(position) {
|
update: function(position) {
|
||||||
Element.setStyle(this.element,{backgroundColor: $R(0,2).inject('#',function(m,v,i){
|
this.element.setStyle({backgroundColor: $R(0,2).inject('#',function(m,v,i){
|
||||||
return m+(Math.round(this._base[i]+(this._delta[i]*position)).toColorPart()); }.bind(this)) });
|
return m+(Math.round(this._base[i]+(this._delta[i]*position)).toColorPart()); }.bind(this)) });
|
||||||
},
|
},
|
||||||
finish: function() {
|
finish: function() {
|
||||||
Element.setStyle(this.element, Object.extend(this.oldStyle, {
|
this.element.setStyle(Object.extend(this.oldStyle, {
|
||||||
backgroundColor: this.options.restorecolor
|
backgroundColor: this.options.restorecolor
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
@ -479,85 +542,91 @@ Object.extend(Object.extend(Effect.ScrollTo.prototype, Effect.Base.prototype), {
|
||||||
/* ------------- combination effects ------------- */
|
/* ------------- combination effects ------------- */
|
||||||
|
|
||||||
Effect.Fade = function(element) {
|
Effect.Fade = function(element) {
|
||||||
var oldOpacity = Element.getInlineOpacity(element);
|
element = $(element);
|
||||||
|
var oldOpacity = element.getInlineOpacity();
|
||||||
var options = Object.extend({
|
var options = Object.extend({
|
||||||
from: Element.getOpacity(element) || 1.0,
|
from: element.getOpacity() || 1.0,
|
||||||
to: 0.0,
|
to: 0.0,
|
||||||
afterFinishInternal: function(effect) { with(Element) {
|
afterFinishInternal: function(effect) {
|
||||||
if(effect.options.to!=0) return;
|
if(effect.options.to!=0) return;
|
||||||
hide(effect.element);
|
effect.element.hide();
|
||||||
setStyle(effect.element, {opacity: oldOpacity}); }}
|
effect.element.setStyle({opacity: oldOpacity});
|
||||||
}, arguments[1] || {});
|
}}, arguments[1] || {});
|
||||||
return new Effect.Opacity(element,options);
|
return new Effect.Opacity(element,options);
|
||||||
}
|
}
|
||||||
|
|
||||||
Effect.Appear = function(element) {
|
Effect.Appear = function(element) {
|
||||||
|
element = $(element);
|
||||||
var options = Object.extend({
|
var options = Object.extend({
|
||||||
from: (Element.getStyle(element, 'display') == 'none' ? 0.0 : Element.getOpacity(element) || 0.0),
|
from: (element.getStyle('display') == 'none' ? 0.0 : element.getOpacity() || 0.0),
|
||||||
to: 1.0,
|
to: 1.0,
|
||||||
beforeSetup: function(effect) { with(Element) {
|
// force Safari to render floated elements properly
|
||||||
setOpacity(effect.element, effect.options.from);
|
afterFinishInternal: function(effect) {
|
||||||
show(effect.element); }}
|
effect.element.forceRerendering();
|
||||||
}, arguments[1] || {});
|
},
|
||||||
|
beforeSetup: function(effect) {
|
||||||
|
effect.element.setOpacity(effect.options.from);
|
||||||
|
effect.element.show();
|
||||||
|
}}, arguments[1] || {});
|
||||||
return new Effect.Opacity(element,options);
|
return new Effect.Opacity(element,options);
|
||||||
}
|
}
|
||||||
|
|
||||||
Effect.Puff = function(element) {
|
Effect.Puff = function(element) {
|
||||||
element = $(element);
|
element = $(element);
|
||||||
var oldStyle = { opacity: Element.getInlineOpacity(element), position: Element.getStyle(element, 'position') };
|
var oldStyle = { opacity: element.getInlineOpacity(), position: element.getStyle('position') };
|
||||||
return new Effect.Parallel(
|
return new Effect.Parallel(
|
||||||
[ new Effect.Scale(element, 200,
|
[ new Effect.Scale(element, 200,
|
||||||
{ sync: true, scaleFromCenter: true, scaleContent: true, restoreAfterFinish: true }),
|
{ sync: true, scaleFromCenter: true, scaleContent: true, restoreAfterFinish: true }),
|
||||||
new Effect.Opacity(element, { sync: true, to: 0.0 } ) ],
|
new Effect.Opacity(element, { sync: true, to: 0.0 } ) ],
|
||||||
Object.extend({ duration: 1.0,
|
Object.extend({ duration: 1.0,
|
||||||
beforeSetupInternal: function(effect) { with(Element) {
|
beforeSetupInternal: function(effect) {
|
||||||
setStyle(effect.effects[0].element, {position: 'absolute'}); }},
|
effect.effects[0].element.setStyle({position: 'absolute'}); },
|
||||||
afterFinishInternal: function(effect) { with(Element) {
|
afterFinishInternal: function(effect) {
|
||||||
hide(effect.effects[0].element);
|
effect.effects[0].element.hide();
|
||||||
setStyle(effect.effects[0].element, oldStyle); }}
|
effect.effects[0].element.setStyle(oldStyle); }
|
||||||
}, arguments[1] || {})
|
}, arguments[1] || {})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Effect.BlindUp = function(element) {
|
Effect.BlindUp = function(element) {
|
||||||
element = $(element);
|
element = $(element);
|
||||||
Element.makeClipping(element);
|
element.makeClipping();
|
||||||
return new Effect.Scale(element, 0,
|
return new Effect.Scale(element, 0,
|
||||||
Object.extend({ scaleContent: false,
|
Object.extend({ scaleContent: false,
|
||||||
scaleX: false,
|
scaleX: false,
|
||||||
restoreAfterFinish: true,
|
restoreAfterFinish: true,
|
||||||
afterFinishInternal: function(effect) { with(Element) {
|
afterFinishInternal: function(effect) {
|
||||||
[hide, undoClipping].call(effect.element); }}
|
effect.element.hide();
|
||||||
|
effect.element.undoClipping();
|
||||||
|
}
|
||||||
}, arguments[1] || {})
|
}, arguments[1] || {})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Effect.BlindDown = function(element) {
|
Effect.BlindDown = function(element) {
|
||||||
element = $(element);
|
element = $(element);
|
||||||
var oldHeight = Element.getStyle(element, 'height');
|
var elementDimensions = element.getDimensions();
|
||||||
var elementDimensions = Element.getDimensions(element);
|
|
||||||
return new Effect.Scale(element, 100,
|
return new Effect.Scale(element, 100,
|
||||||
Object.extend({ scaleContent: false,
|
Object.extend({ scaleContent: false,
|
||||||
scaleX: false,
|
scaleX: false,
|
||||||
scaleFrom: 0,
|
scaleFrom: 0,
|
||||||
scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width},
|
scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width},
|
||||||
restoreAfterFinish: true,
|
restoreAfterFinish: true,
|
||||||
afterSetup: function(effect) { with(Element) {
|
afterSetup: function(effect) {
|
||||||
makeClipping(effect.element);
|
effect.element.makeClipping();
|
||||||
setStyle(effect.element, {height: '0px'});
|
effect.element.setStyle({height: '0px'});
|
||||||
show(effect.element);
|
effect.element.show();
|
||||||
}},
|
},
|
||||||
afterFinishInternal: function(effect) { with(Element) {
|
afterFinishInternal: function(effect) {
|
||||||
undoClipping(effect.element);
|
effect.element.undoClipping();
|
||||||
setStyle(effect.element, {height: oldHeight});
|
}
|
||||||
}}
|
|
||||||
}, arguments[1] || {})
|
}, arguments[1] || {})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Effect.SwitchOff = function(element) {
|
Effect.SwitchOff = function(element) {
|
||||||
element = $(element);
|
element = $(element);
|
||||||
var oldOpacity = Element.getInlineOpacity(element);
|
var oldOpacity = element.getInlineOpacity();
|
||||||
return new Effect.Appear(element, {
|
return new Effect.Appear(element, {
|
||||||
duration: 0.4,
|
duration: 0.4,
|
||||||
from: 0,
|
from: 0,
|
||||||
|
|
@ -566,13 +635,16 @@ Effect.SwitchOff = function(element) {
|
||||||
new Effect.Scale(effect.element, 1, {
|
new Effect.Scale(effect.element, 1, {
|
||||||
duration: 0.3, scaleFromCenter: true,
|
duration: 0.3, scaleFromCenter: true,
|
||||||
scaleX: false, scaleContent: false, restoreAfterFinish: true,
|
scaleX: false, scaleContent: false, restoreAfterFinish: true,
|
||||||
beforeSetup: function(effect) { with(Element) {
|
beforeSetup: function(effect) {
|
||||||
[makePositioned,makeClipping].call(effect.element);
|
effect.element.makePositioned();
|
||||||
}},
|
effect.element.makeClipping();
|
||||||
afterFinishInternal: function(effect) { with(Element) {
|
},
|
||||||
[hide,undoClipping,undoPositioned].call(effect.element);
|
afterFinishInternal: function(effect) {
|
||||||
setStyle(effect.element, {opacity: oldOpacity});
|
effect.element.hide();
|
||||||
}}
|
effect.element.undoClipping();
|
||||||
|
effect.element.undoPositioned();
|
||||||
|
effect.element.setStyle({opacity: oldOpacity});
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
@ -581,99 +653,110 @@ Effect.SwitchOff = function(element) {
|
||||||
Effect.DropOut = function(element) {
|
Effect.DropOut = function(element) {
|
||||||
element = $(element);
|
element = $(element);
|
||||||
var oldStyle = {
|
var oldStyle = {
|
||||||
top: Element.getStyle(element, 'top'),
|
top: element.getStyle('top'),
|
||||||
left: Element.getStyle(element, 'left'),
|
left: element.getStyle('left'),
|
||||||
opacity: Element.getInlineOpacity(element) };
|
opacity: element.getInlineOpacity() };
|
||||||
return new Effect.Parallel(
|
return new Effect.Parallel(
|
||||||
[ new Effect.MoveBy(element, 100, 0, { sync: true }),
|
[ new Effect.Move(element, {x: 0, y: 100, sync: true }),
|
||||||
new Effect.Opacity(element, { sync: true, to: 0.0 }) ],
|
new Effect.Opacity(element, { sync: true, to: 0.0 }) ],
|
||||||
Object.extend(
|
Object.extend(
|
||||||
{ duration: 0.5,
|
{ duration: 0.5,
|
||||||
beforeSetup: function(effect) { with(Element) {
|
beforeSetup: function(effect) {
|
||||||
makePositioned(effect.effects[0].element); }},
|
effect.effects[0].element.makePositioned();
|
||||||
afterFinishInternal: function(effect) { with(Element) {
|
},
|
||||||
[hide, undoPositioned].call(effect.effects[0].element);
|
afterFinishInternal: function(effect) {
|
||||||
setStyle(effect.effects[0].element, oldStyle); }}
|
effect.effects[0].element.hide();
|
||||||
|
effect.effects[0].element.undoPositioned();
|
||||||
|
effect.effects[0].element.setStyle(oldStyle);
|
||||||
|
}
|
||||||
}, arguments[1] || {}));
|
}, arguments[1] || {}));
|
||||||
}
|
}
|
||||||
|
|
||||||
Effect.Shake = function(element) {
|
Effect.Shake = function(element) {
|
||||||
element = $(element);
|
element = $(element);
|
||||||
var oldStyle = {
|
var oldStyle = {
|
||||||
top: Element.getStyle(element, 'top'),
|
top: element.getStyle('top'),
|
||||||
left: Element.getStyle(element, 'left') };
|
left: element.getStyle('left') };
|
||||||
return new Effect.MoveBy(element, 0, 20,
|
return new Effect.Move(element,
|
||||||
{ duration: 0.05, afterFinishInternal: function(effect) {
|
{ x: 20, y: 0, duration: 0.05, afterFinishInternal: function(effect) {
|
||||||
new Effect.MoveBy(effect.element, 0, -40,
|
new Effect.Move(effect.element,
|
||||||
{ duration: 0.1, afterFinishInternal: function(effect) {
|
{ x: -40, y: 0, duration: 0.1, afterFinishInternal: function(effect) {
|
||||||
new Effect.MoveBy(effect.element, 0, 40,
|
new Effect.Move(effect.element,
|
||||||
{ duration: 0.1, afterFinishInternal: function(effect) {
|
{ x: 40, y: 0, duration: 0.1, afterFinishInternal: function(effect) {
|
||||||
new Effect.MoveBy(effect.element, 0, -40,
|
new Effect.Move(effect.element,
|
||||||
{ duration: 0.1, afterFinishInternal: function(effect) {
|
{ x: -40, y: 0, duration: 0.1, afterFinishInternal: function(effect) {
|
||||||
new Effect.MoveBy(effect.element, 0, 40,
|
new Effect.Move(effect.element,
|
||||||
{ duration: 0.1, afterFinishInternal: function(effect) {
|
{ x: 40, y: 0, duration: 0.1, afterFinishInternal: function(effect) {
|
||||||
new Effect.MoveBy(effect.element, 0, -20,
|
new Effect.Move(effect.element,
|
||||||
{ duration: 0.05, afterFinishInternal: function(effect) { with(Element) {
|
{ x: -20, y: 0, duration: 0.05, afterFinishInternal: function(effect) {
|
||||||
undoPositioned(effect.element);
|
effect.element.undoPositioned();
|
||||||
setStyle(effect.element, oldStyle);
|
effect.element.setStyle(oldStyle);
|
||||||
}}}) }}) }}) }}) }}) }});
|
}}) }}) }}) }}) }}) }});
|
||||||
}
|
}
|
||||||
|
|
||||||
Effect.SlideDown = function(element) {
|
Effect.SlideDown = function(element) {
|
||||||
element = $(element);
|
element = $(element);
|
||||||
Element.cleanWhitespace(element);
|
element.cleanWhitespace();
|
||||||
// SlideDown need to have the content of the element wrapped in a container element with fixed height!
|
// SlideDown need to have the content of the element wrapped in a container element with fixed height!
|
||||||
var oldInnerBottom = Element.getStyle(element.firstChild, 'bottom');
|
var oldInnerBottom = $(element.firstChild).getStyle('bottom');
|
||||||
var elementDimensions = Element.getDimensions(element);
|
var elementDimensions = element.getDimensions();
|
||||||
return new Effect.Scale(element, 100, Object.extend({
|
return new Effect.Scale(element, 100, Object.extend({
|
||||||
scaleContent: false,
|
scaleContent: false,
|
||||||
scaleX: false,
|
scaleX: false,
|
||||||
scaleFrom: 0,
|
scaleFrom: 0,
|
||||||
scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width},
|
scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width},
|
||||||
restoreAfterFinish: true,
|
restoreAfterFinish: true,
|
||||||
afterSetup: function(effect) { with(Element) {
|
afterSetup: function(effect) {
|
||||||
makePositioned(effect.element);
|
effect.element.makePositioned();
|
||||||
makePositioned(effect.element.firstChild);
|
effect.element.firstChild.makePositioned();
|
||||||
if(window.opera) setStyle(effect.element, {top: ''});
|
if(window.opera) effect.element.setStyle({top: ''});
|
||||||
makeClipping(effect.element);
|
effect.element.makeClipping();
|
||||||
setStyle(effect.element, {height: '0px'});
|
effect.element.setStyle({height: '0px'});
|
||||||
show(element); }},
|
effect.element.show(); },
|
||||||
afterUpdateInternal: function(effect) { with(Element) {
|
afterUpdateInternal: function(effect) {
|
||||||
setStyle(effect.element.firstChild, {bottom:
|
effect.element.firstChild.setStyle({bottom:
|
||||||
(effect.dims[0] - effect.element.clientHeight) + 'px' }); }},
|
(effect.dims[0] - effect.element.clientHeight) + 'px' });
|
||||||
afterFinishInternal: function(effect) { with(Element) {
|
},
|
||||||
undoClipping(effect.element);
|
afterFinishInternal: function(effect) {
|
||||||
undoPositioned(effect.element.firstChild);
|
effect.element.undoClipping();
|
||||||
undoPositioned(effect.element);
|
// IE will crash if child is undoPositioned first
|
||||||
setStyle(effect.element.firstChild, {bottom: oldInnerBottom}); }}
|
if(/MSIE/.test(navigator.userAgent)){
|
||||||
|
effect.element.undoPositioned();
|
||||||
|
effect.element.firstChild.undoPositioned();
|
||||||
|
}else{
|
||||||
|
effect.element.firstChild.undoPositioned();
|
||||||
|
effect.element.undoPositioned();
|
||||||
|
}
|
||||||
|
effect.element.firstChild.setStyle({bottom: oldInnerBottom}); }
|
||||||
}, arguments[1] || {})
|
}, arguments[1] || {})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Effect.SlideUp = function(element) {
|
Effect.SlideUp = function(element) {
|
||||||
element = $(element);
|
element = $(element);
|
||||||
Element.cleanWhitespace(element);
|
element.cleanWhitespace();
|
||||||
var oldInnerBottom = Element.getStyle(element.firstChild, 'bottom');
|
var oldInnerBottom = $(element.firstChild).getStyle('bottom');
|
||||||
return new Effect.Scale(element, 0,
|
return new Effect.Scale(element, 0,
|
||||||
Object.extend({ scaleContent: false,
|
Object.extend({ scaleContent: false,
|
||||||
scaleX: false,
|
scaleX: false,
|
||||||
scaleMode: 'box',
|
scaleMode: 'box',
|
||||||
scaleFrom: 100,
|
scaleFrom: 100,
|
||||||
restoreAfterFinish: true,
|
restoreAfterFinish: true,
|
||||||
beforeStartInternal: function(effect) { with(Element) {
|
beforeStartInternal: function(effect) {
|
||||||
makePositioned(effect.element);
|
effect.element.makePositioned();
|
||||||
makePositioned(effect.element.firstChild);
|
effect.element.firstChild.makePositioned();
|
||||||
if(window.opera) setStyle(effect.element, {top: ''});
|
if(window.opera) effect.element.setStyle({top: ''});
|
||||||
makeClipping(effect.element);
|
effect.element.makeClipping();
|
||||||
show(element); }},
|
effect.element.show(); },
|
||||||
afterUpdateInternal: function(effect) { with(Element) {
|
afterUpdateInternal: function(effect) {
|
||||||
setStyle(effect.element.firstChild, {bottom:
|
effect.element.firstChild.setStyle({bottom:
|
||||||
(effect.dims[0] - effect.element.clientHeight) + 'px' }); }},
|
(effect.dims[0] - effect.element.clientHeight) + 'px' }); },
|
||||||
afterFinishInternal: function(effect) { with(Element) {
|
afterFinishInternal: function(effect) {
|
||||||
[hide, undoClipping].call(effect.element);
|
effect.element.hide();
|
||||||
undoPositioned(effect.element.firstChild);
|
effect.element.undoClipping();
|
||||||
undoPositioned(effect.element);
|
effect.element.firstChild.undoPositioned();
|
||||||
setStyle(effect.element.firstChild, {bottom: oldInnerBottom}); }}
|
effect.element.undoPositioned();
|
||||||
|
effect.element.setStyle({bottom: oldInnerBottom}); }
|
||||||
}, arguments[1] || {})
|
}, arguments[1] || {})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -682,11 +765,11 @@ Effect.SlideUp = function(element) {
|
||||||
Effect.Squish = function(element) {
|
Effect.Squish = function(element) {
|
||||||
return new Effect.Scale(element, window.opera ? 1 : 0,
|
return new Effect.Scale(element, window.opera ? 1 : 0,
|
||||||
{ restoreAfterFinish: true,
|
{ restoreAfterFinish: true,
|
||||||
beforeSetup: function(effect) { with(Element) {
|
beforeSetup: function(effect) {
|
||||||
makeClipping(effect.element); }},
|
effect.element.makeClipping(effect.element); },
|
||||||
afterFinishInternal: function(effect) { with(Element) {
|
afterFinishInternal: function(effect) {
|
||||||
hide(effect.element);
|
effect.element.hide(effect.element);
|
||||||
undoClipping(effect.element); }}
|
effect.element.undoClipping(effect.element); }
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -694,7 +777,7 @@ Effect.Grow = function(element) {
|
||||||
element = $(element);
|
element = $(element);
|
||||||
var options = Object.extend({
|
var options = Object.extend({
|
||||||
direction: 'center',
|
direction: 'center',
|
||||||
moveTransistion: Effect.Transitions.sinoidal,
|
moveTransition: Effect.Transitions.sinoidal,
|
||||||
scaleTransition: Effect.Transitions.sinoidal,
|
scaleTransition: Effect.Transitions.sinoidal,
|
||||||
opacityTransition: Effect.Transitions.full
|
opacityTransition: Effect.Transitions.full
|
||||||
}, arguments[1] || {});
|
}, arguments[1] || {});
|
||||||
|
|
@ -703,9 +786,9 @@ Effect.Grow = function(element) {
|
||||||
left: element.style.left,
|
left: element.style.left,
|
||||||
height: element.style.height,
|
height: element.style.height,
|
||||||
width: element.style.width,
|
width: element.style.width,
|
||||||
opacity: Element.getInlineOpacity(element) };
|
opacity: element.getInlineOpacity() };
|
||||||
|
|
||||||
var dims = Element.getDimensions(element);
|
var dims = element.getDimensions();
|
||||||
var initialMoveX, initialMoveY;
|
var initialMoveX, initialMoveY;
|
||||||
var moveX, moveY;
|
var moveX, moveY;
|
||||||
|
|
||||||
|
|
@ -737,27 +820,32 @@ Effect.Grow = function(element) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
return new Effect.MoveBy(element, initialMoveY, initialMoveX, {
|
return new Effect.Move(element, {
|
||||||
|
x: initialMoveX,
|
||||||
|
y: initialMoveY,
|
||||||
duration: 0.01,
|
duration: 0.01,
|
||||||
beforeSetup: function(effect) { with(Element) {
|
beforeSetup: function(effect) {
|
||||||
hide(effect.element);
|
effect.element.hide();
|
||||||
makeClipping(effect.element);
|
effect.element.makeClipping();
|
||||||
makePositioned(effect.element);
|
effect.element.makePositioned();
|
||||||
}},
|
},
|
||||||
afterFinishInternal: function(effect) {
|
afterFinishInternal: function(effect) {
|
||||||
new Effect.Parallel(
|
new Effect.Parallel(
|
||||||
[ new Effect.Opacity(effect.element, { sync: true, to: 1.0, from: 0.0, transition: options.opacityTransition }),
|
[ new Effect.Opacity(effect.element, { sync: true, to: 1.0, from: 0.0, transition: options.opacityTransition }),
|
||||||
new Effect.MoveBy(effect.element, moveY, moveX, { sync: true, transition: options.moveTransition }),
|
new Effect.Move(effect.element, { x: moveX, y: moveY, sync: true, transition: options.moveTransition }),
|
||||||
new Effect.Scale(effect.element, 100, {
|
new Effect.Scale(effect.element, 100, {
|
||||||
scaleMode: { originalHeight: dims.height, originalWidth: dims.width },
|
scaleMode: { originalHeight: dims.height, originalWidth: dims.width },
|
||||||
sync: true, scaleFrom: window.opera ? 1 : 0, transition: options.scaleTransition, restoreAfterFinish: true})
|
sync: true, scaleFrom: window.opera ? 1 : 0, transition: options.scaleTransition, restoreAfterFinish: true})
|
||||||
], Object.extend({
|
], Object.extend({
|
||||||
beforeSetup: function(effect) { with(Element) {
|
beforeSetup: function(effect) {
|
||||||
setStyle(effect.effects[0].element, {height: '0px'});
|
effect.effects[0].element.setStyle({height: '0px'});
|
||||||
show(effect.effects[0].element); }},
|
effect.effects[0].element.show();
|
||||||
afterFinishInternal: function(effect) { with(Element) {
|
},
|
||||||
[undoClipping, undoPositioned].call(effect.effects[0].element);
|
afterFinishInternal: function(effect) {
|
||||||
setStyle(effect.effects[0].element, oldStyle); }}
|
effect.effects[0].element.undoClipping();
|
||||||
|
effect.effects[0].element.undoPositioned();
|
||||||
|
effect.effects[0].element.setStyle(oldStyle);
|
||||||
|
}
|
||||||
}, options)
|
}, options)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -768,7 +856,7 @@ Effect.Shrink = function(element) {
|
||||||
element = $(element);
|
element = $(element);
|
||||||
var options = Object.extend({
|
var options = Object.extend({
|
||||||
direction: 'center',
|
direction: 'center',
|
||||||
moveTransistion: Effect.Transitions.sinoidal,
|
moveTransition: Effect.Transitions.sinoidal,
|
||||||
scaleTransition: Effect.Transitions.sinoidal,
|
scaleTransition: Effect.Transitions.sinoidal,
|
||||||
opacityTransition: Effect.Transitions.none
|
opacityTransition: Effect.Transitions.none
|
||||||
}, arguments[1] || {});
|
}, arguments[1] || {});
|
||||||
|
|
@ -777,9 +865,9 @@ Effect.Shrink = function(element) {
|
||||||
left: element.style.left,
|
left: element.style.left,
|
||||||
height: element.style.height,
|
height: element.style.height,
|
||||||
width: element.style.width,
|
width: element.style.width,
|
||||||
opacity: Element.getInlineOpacity(element) };
|
opacity: element.getInlineOpacity() };
|
||||||
|
|
||||||
var dims = Element.getDimensions(element);
|
var dims = element.getDimensions();
|
||||||
var moveX, moveY;
|
var moveX, moveY;
|
||||||
|
|
||||||
switch (options.direction) {
|
switch (options.direction) {
|
||||||
|
|
@ -807,13 +895,16 @@ Effect.Shrink = function(element) {
|
||||||
return new Effect.Parallel(
|
return new Effect.Parallel(
|
||||||
[ new Effect.Opacity(element, { sync: true, to: 0.0, from: 1.0, transition: options.opacityTransition }),
|
[ new Effect.Opacity(element, { sync: true, to: 0.0, from: 1.0, transition: options.opacityTransition }),
|
||||||
new Effect.Scale(element, window.opera ? 1 : 0, { sync: true, transition: options.scaleTransition, restoreAfterFinish: true}),
|
new Effect.Scale(element, window.opera ? 1 : 0, { sync: true, transition: options.scaleTransition, restoreAfterFinish: true}),
|
||||||
new Effect.MoveBy(element, moveY, moveX, { sync: true, transition: options.moveTransition })
|
new Effect.Move(element, { x: moveX, y: moveY, sync: true, transition: options.moveTransition })
|
||||||
], Object.extend({
|
], Object.extend({
|
||||||
beforeStartInternal: function(effect) { with(Element) {
|
beforeStartInternal: function(effect) {
|
||||||
[makePositioned, makeClipping].call(effect.effects[0].element) }},
|
effect.effects[0].element.makePositioned();
|
||||||
afterFinishInternal: function(effect) { with(Element) {
|
effect.effects[0].element.makeClipping(); },
|
||||||
[hide, undoClipping, undoPositioned].call(effect.effects[0].element);
|
afterFinishInternal: function(effect) {
|
||||||
setStyle(effect.effects[0].element, oldStyle); }}
|
effect.effects[0].element.hide();
|
||||||
|
effect.effects[0].element.undoClipping();
|
||||||
|
effect.effects[0].element.undoPositioned();
|
||||||
|
effect.effects[0].element.setStyle(oldStyle); }
|
||||||
}, options)
|
}, options)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -821,13 +912,13 @@ Effect.Shrink = function(element) {
|
||||||
Effect.Pulsate = function(element) {
|
Effect.Pulsate = function(element) {
|
||||||
element = $(element);
|
element = $(element);
|
||||||
var options = arguments[1] || {};
|
var options = arguments[1] || {};
|
||||||
var oldOpacity = Element.getInlineOpacity(element);
|
var oldOpacity = element.getInlineOpacity();
|
||||||
var transition = options.transition || Effect.Transitions.sinoidal;
|
var transition = options.transition || Effect.Transitions.sinoidal;
|
||||||
var reverser = function(pos){ return transition(1-Effect.Transitions.pulse(pos)) };
|
var reverser = function(pos){ return transition(1-Effect.Transitions.pulse(pos)) };
|
||||||
reverser.bind(transition);
|
reverser.bind(transition);
|
||||||
return new Effect.Opacity(element,
|
return new Effect.Opacity(element,
|
||||||
Object.extend(Object.extend({ duration: 3.0, from: 0,
|
Object.extend(Object.extend({ duration: 3.0, from: 0,
|
||||||
afterFinishInternal: function(effect) { Element.setStyle(effect.element, {opacity: oldOpacity}); }
|
afterFinishInternal: function(effect) { effect.element.setStyle({opacity: oldOpacity}); }
|
||||||
}, options), {transition: reverser}));
|
}, options), {transition: reverser}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -846,9 +937,17 @@ Effect.Fold = function(element) {
|
||||||
new Effect.Scale(element, 1, {
|
new Effect.Scale(element, 1, {
|
||||||
scaleContent: false,
|
scaleContent: false,
|
||||||
scaleY: false,
|
scaleY: false,
|
||||||
afterFinishInternal: function(effect) { with(Element) {
|
afterFinishInternal: function(effect) {
|
||||||
[hide, undoClipping].call(effect.element);
|
effect.element.hide();
|
||||||
setStyle(effect.element, oldStyle);
|
effect.element.undoClipping();
|
||||||
}} });
|
effect.element.setStyle(oldStyle);
|
||||||
|
} });
|
||||||
}}, arguments[1] || {}));
|
}}, arguments[1] || {}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Element.Methods.visualEffect = function(element, effect, options) {
|
||||||
|
s = effect.gsub(/_/, '-').camelize();
|
||||||
|
effect_class = s.charAt(0).toUpperCase() + s.substring(1);
|
||||||
|
new Effect[effect_class](element, options);
|
||||||
|
return $(element);
|
||||||
|
};
|
||||||
266
tracks/public/javascripts/prototype.js
vendored
266
tracks/public/javascripts/prototype.js
vendored
|
|
@ -1,17 +1,13 @@
|
||||||
/* Prototype JavaScript framework, version 1.4.0
|
/* Prototype JavaScript framework, version 1.5.0_pre1
|
||||||
* (c) 2005 Sam Stephenson <sam@conio.net>
|
* (c) 2005 Sam Stephenson <sam@conio.net>
|
||||||
*
|
*
|
||||||
* THIS FILE IS AUTOMATICALLY GENERATED. When sending patches, please diff
|
|
||||||
* against the source tree, available from the Prototype darcs repository.
|
|
||||||
*
|
|
||||||
* Prototype is freely distributable under the terms of an MIT-style license.
|
* Prototype is freely distributable under the terms of an MIT-style license.
|
||||||
*
|
|
||||||
* For details, see the Prototype web site: http://prototype.conio.net/
|
* For details, see the Prototype web site: http://prototype.conio.net/
|
||||||
*
|
*
|
||||||
/*--------------------------------------------------------------------------*/
|
/*--------------------------------------------------------------------------*/
|
||||||
|
|
||||||
var Prototype = {
|
var Prototype = {
|
||||||
Version: '1.4.0',
|
Version: '1.5.0_pre1',
|
||||||
ScriptFragment: '(?:<script.*?>)((\n|\r|.)*?)(?:<\/script>)',
|
ScriptFragment: '(?:<script.*?>)((\n|\r|.)*?)(?:<\/script>)',
|
||||||
|
|
||||||
emptyFunction: function() {},
|
emptyFunction: function() {},
|
||||||
|
|
@ -120,26 +116,49 @@ PeriodicalExecuter.prototype = {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*--------------------------------------------------------------------------*/
|
|
||||||
|
|
||||||
function $() {
|
|
||||||
var elements = new Array();
|
|
||||||
|
|
||||||
for (var i = 0; i < arguments.length; i++) {
|
|
||||||
var element = arguments[i];
|
|
||||||
if (typeof element == 'string')
|
|
||||||
element = document.getElementById(element);
|
|
||||||
|
|
||||||
if (arguments.length == 1)
|
|
||||||
return element;
|
|
||||||
|
|
||||||
elements.push(element);
|
|
||||||
}
|
|
||||||
|
|
||||||
return elements;
|
|
||||||
}
|
|
||||||
Object.extend(String.prototype, {
|
Object.extend(String.prototype, {
|
||||||
|
gsub: function(pattern, replacement) {
|
||||||
|
var result = '', source = this, match;
|
||||||
|
replacement = arguments.callee.prepareReplacement(replacement);
|
||||||
|
|
||||||
|
while (source.length > 0) {
|
||||||
|
if (match = source.match(pattern)) {
|
||||||
|
result += source.slice(0, match.index);
|
||||||
|
result += (replacement(match) || '').toString();
|
||||||
|
source = source.slice(match.index + match[0].length);
|
||||||
|
} else {
|
||||||
|
result += source, source = '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
},
|
||||||
|
|
||||||
|
sub: function(pattern, replacement, count) {
|
||||||
|
replacement = this.gsub.prepareReplacement(replacement);
|
||||||
|
count = count === undefined ? 1 : count;
|
||||||
|
|
||||||
|
return this.gsub(pattern, function(match) {
|
||||||
|
if (--count < 0) return match[0];
|
||||||
|
return replacement(match);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
scan: function(pattern, iterator) {
|
||||||
|
this.gsub(pattern, iterator);
|
||||||
|
return this;
|
||||||
|
},
|
||||||
|
|
||||||
|
truncate: function(length, truncation) {
|
||||||
|
length = length || 30;
|
||||||
|
truncation = truncation === undefined ? '...' : truncation;
|
||||||
|
return this.length > length ?
|
||||||
|
this.slice(0, length - truncation.length) + truncation : this;
|
||||||
|
},
|
||||||
|
|
||||||
|
strip: function() {
|
||||||
|
return this.replace(/^\s+/, '').replace(/\s+$/, '');
|
||||||
|
},
|
||||||
|
|
||||||
stripTags: function() {
|
stripTags: function() {
|
||||||
return this.replace(/<\/?[^>]+>/gi, '');
|
return this.replace(/<\/?[^>]+>/gi, '');
|
||||||
},
|
},
|
||||||
|
|
@ -203,12 +222,35 @@ Object.extend(String.prototype, {
|
||||||
},
|
},
|
||||||
|
|
||||||
inspect: function() {
|
inspect: function() {
|
||||||
return "'" + this.replace('\\', '\\\\').replace("'", '\\\'') + "'";
|
return "'" + this.replace(/\\/g, '\\\\').replace(/'/g, '\\\'') + "'";
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
String.prototype.gsub.prepareReplacement = function(replacement) {
|
||||||
|
if (typeof replacement == 'function') return replacement;
|
||||||
|
var template = new Template(replacement);
|
||||||
|
return function(match) { return template.evaluate(match) };
|
||||||
|
}
|
||||||
|
|
||||||
String.prototype.parseQuery = String.prototype.toQueryParams;
|
String.prototype.parseQuery = String.prototype.toQueryParams;
|
||||||
|
|
||||||
|
var Template = Class.create();
|
||||||
|
Template.Pattern = /(^|.|\r|\n)(#\{(.*?)\})/;
|
||||||
|
Template.prototype = {
|
||||||
|
initialize: function(template, pattern) {
|
||||||
|
this.template = template.toString();
|
||||||
|
this.pattern = pattern || Template.Pattern;
|
||||||
|
},
|
||||||
|
|
||||||
|
evaluate: function(object) {
|
||||||
|
return this.template.gsub(this.pattern, function(match) {
|
||||||
|
var before = match[1];
|
||||||
|
if (before == '\\') return match[2];
|
||||||
|
return before + (object[match[3]] || '').toString();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var $break = new Object();
|
var $break = new Object();
|
||||||
var $continue = new Object();
|
var $continue = new Object();
|
||||||
|
|
||||||
|
|
@ -375,8 +417,7 @@ var Enumerable = {
|
||||||
|
|
||||||
var collections = [this].concat(args).map($A);
|
var collections = [this].concat(args).map($A);
|
||||||
return this.map(function(value, index) {
|
return this.map(function(value, index) {
|
||||||
iterator(value = collections.pluck(index));
|
return iterator(collections.pluck(index));
|
||||||
return value;
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
@ -662,7 +703,8 @@ Ajax.Request.prototype = Object.extend(new Ajax.Base(), {
|
||||||
setRequestHeaders: function() {
|
setRequestHeaders: function() {
|
||||||
var requestHeaders =
|
var requestHeaders =
|
||||||
['X-Requested-With', 'XMLHttpRequest',
|
['X-Requested-With', 'XMLHttpRequest',
|
||||||
'X-Prototype-Version', Prototype.Version];
|
'X-Prototype-Version', Prototype.Version,
|
||||||
|
'Accept', 'text/javascript, text/html, application/xml, text/xml, */*'];
|
||||||
|
|
||||||
if (this.options.method == 'post') {
|
if (this.options.method == 'post') {
|
||||||
requestHeaders.push('Content-type',
|
requestHeaders.push('Content-type',
|
||||||
|
|
@ -831,22 +873,48 @@ Ajax.PeriodicalUpdater.prototype = Object.extend(new Ajax.Base(), {
|
||||||
this.updater = new Ajax.Updater(this.container, this.url, this.options);
|
this.updater = new Ajax.Updater(this.container, this.url, this.options);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
function $() {
|
||||||
|
var results = [], element;
|
||||||
|
for (var i = 0; i < arguments.length; i++) {
|
||||||
|
element = arguments[i];
|
||||||
|
if (typeof element == 'string')
|
||||||
|
element = document.getElementById(element);
|
||||||
|
results.push(Element.extend(element));
|
||||||
|
}
|
||||||
|
return results.length < 2 ? results[0] : results;
|
||||||
|
}
|
||||||
|
|
||||||
document.getElementsByClassName = function(className, parentElement) {
|
document.getElementsByClassName = function(className, parentElement) {
|
||||||
var children = ($(parentElement) || document.body).getElementsByTagName('*');
|
var children = ($(parentElement) || document.body).getElementsByTagName('*');
|
||||||
return $A(children).inject([], function(elements, child) {
|
return $A(children).inject([], function(elements, child) {
|
||||||
if (child.className.match(new RegExp("(^|\\s)" + className + "(\\s|$)")))
|
if (child.className.match(new RegExp("(^|\\s)" + className + "(\\s|$)")))
|
||||||
elements.push(child);
|
elements.push(Element.extend(child));
|
||||||
return elements;
|
return elements;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/*--------------------------------------------------------------------------*/
|
/*--------------------------------------------------------------------------*/
|
||||||
|
|
||||||
if (!window.Element) {
|
if (!window.Element)
|
||||||
var Element = new Object();
|
var Element = new Object();
|
||||||
|
|
||||||
|
Element.extend = function(element) {
|
||||||
|
if (!element) return;
|
||||||
|
|
||||||
|
if (!element._extended && element.tagName && element != window) {
|
||||||
|
var methods = Element.Methods;
|
||||||
|
for (property in methods) {
|
||||||
|
var value = methods[property];
|
||||||
|
if (typeof value == 'function')
|
||||||
|
element[property] = value.bind(null, element);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
element._extended = true;
|
||||||
|
return element;
|
||||||
}
|
}
|
||||||
|
|
||||||
Object.extend(Element, {
|
Element.Methods = {
|
||||||
visible: function(element) {
|
visible: function(element) {
|
||||||
return $(element).style.display != 'none';
|
return $(element).style.display != 'none';
|
||||||
},
|
},
|
||||||
|
|
@ -882,6 +950,19 @@ Object.extend(Element, {
|
||||||
setTimeout(function() {html.evalScripts()}, 10);
|
setTimeout(function() {html.evalScripts()}, 10);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
replace: function(element, html) {
|
||||||
|
element = $(element);
|
||||||
|
if (element.outerHTML) {
|
||||||
|
element.outerHTML = html.stripScripts();
|
||||||
|
} else {
|
||||||
|
var range = element.ownerDocument.createRange();
|
||||||
|
range.selectNodeContents(element);
|
||||||
|
element.parentNode.replaceChild(
|
||||||
|
range.createContextualFragment(html.stripScripts()), element);
|
||||||
|
}
|
||||||
|
setTimeout(function() {html.evalScripts()}, 10);
|
||||||
|
},
|
||||||
|
|
||||||
getHeight: function(element) {
|
getHeight: function(element) {
|
||||||
element = $(element);
|
element = $(element);
|
||||||
return element.offsetHeight;
|
return element.offsetHeight;
|
||||||
|
|
@ -920,6 +1001,13 @@ Object.extend(Element, {
|
||||||
return $(element).innerHTML.match(/^\s*$/);
|
return $(element).innerHTML.match(/^\s*$/);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
childOf: function(element, ancestor) {
|
||||||
|
element = $(element), ancestor = $(ancestor);
|
||||||
|
while (element = element.parentNode)
|
||||||
|
if (element == ancestor) return true;
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
|
||||||
scrollTo: function(element) {
|
scrollTo: function(element) {
|
||||||
element = $(element);
|
element = $(element);
|
||||||
var x = element.x ? element.x : element.offsetLeft,
|
var x = element.x ? element.x : element.offsetLeft,
|
||||||
|
|
@ -1013,7 +1101,9 @@ Object.extend(Element, {
|
||||||
element.style.overflow = element._overflow;
|
element.style.overflow = element._overflow;
|
||||||
element._overflow = undefined;
|
element._overflow = undefined;
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
|
Object.extend(Element, Element.Methods);
|
||||||
|
|
||||||
var Toggle = new Object();
|
var Toggle = new Object();
|
||||||
Toggle.display = Element.toggle;
|
Toggle.display = Element.toggle;
|
||||||
|
|
@ -1148,6 +1238,116 @@ Element.ClassNames.prototype = {
|
||||||
}
|
}
|
||||||
|
|
||||||
Object.extend(Element.ClassNames.prototype, Enumerable);
|
Object.extend(Element.ClassNames.prototype, Enumerable);
|
||||||
|
var Selector = Class.create();
|
||||||
|
Selector.prototype = {
|
||||||
|
initialize: function(expression) {
|
||||||
|
this.params = {classNames: []};
|
||||||
|
this.expression = expression.toString().strip();
|
||||||
|
this.parseExpression();
|
||||||
|
this.compileMatcher();
|
||||||
|
},
|
||||||
|
|
||||||
|
parseExpression: function() {
|
||||||
|
function abort(message) { throw 'Parse error in selector: ' + message; }
|
||||||
|
|
||||||
|
if (this.expression == '') abort('empty expression');
|
||||||
|
|
||||||
|
var params = this.params, expr = this.expression, match, modifier, clause, rest;
|
||||||
|
while (match = expr.match(/^(.*)\[([a-z0-9_:-]+?)(?:([~\|!]?=)(?:"([^"]*)"|([^\]\s]*)))?\]$/i)) {
|
||||||
|
params.attributes = params.attributes || [];
|
||||||
|
params.attributes.push({name: match[2], operator: match[3], value: match[4] || match[5] || ''});
|
||||||
|
expr = match[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (expr == '*') return this.params.wildcard = true;
|
||||||
|
|
||||||
|
while (match = expr.match(/^([^a-z0-9_-])?([a-z0-9_-]+)(.*)/i)) {
|
||||||
|
modifier = match[1], clause = match[2], rest = match[3];
|
||||||
|
switch (modifier) {
|
||||||
|
case '#': params.id = clause; break;
|
||||||
|
case '.': params.classNames.push(clause); break;
|
||||||
|
case '':
|
||||||
|
case undefined: params.tagName = clause.toUpperCase(); break;
|
||||||
|
default: abort(expr.inspect());
|
||||||
|
}
|
||||||
|
expr = rest;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (expr.length > 0) abort(expr.inspect());
|
||||||
|
},
|
||||||
|
|
||||||
|
buildMatchExpression: function() {
|
||||||
|
var params = this.params, conditions = [], clause;
|
||||||
|
|
||||||
|
if (params.wildcard)
|
||||||
|
conditions.push('true');
|
||||||
|
if (clause = params.id)
|
||||||
|
conditions.push('element.id == ' + clause.inspect());
|
||||||
|
if (clause = params.tagName)
|
||||||
|
conditions.push('element.tagName.toUpperCase() == ' + clause.inspect());
|
||||||
|
if ((clause = params.classNames).length > 0)
|
||||||
|
for (var i = 0; i < clause.length; i++)
|
||||||
|
conditions.push('Element.hasClassName(element, ' + clause[i].inspect() + ')');
|
||||||
|
if (clause = params.attributes) {
|
||||||
|
clause.each(function(attribute) {
|
||||||
|
var value = 'element.getAttribute(' + attribute.name.inspect() + ')';
|
||||||
|
var splitValueBy = function(delimiter) {
|
||||||
|
return value + ' && ' + value + '.split(' + delimiter.inspect() + ')';
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (attribute.operator) {
|
||||||
|
case '=': conditions.push(value + ' == ' + attribute.value.inspect()); break;
|
||||||
|
case '~=': conditions.push(splitValueBy(' ') + '.include(' + attribute.value.inspect() + ')'); break;
|
||||||
|
case '|=': conditions.push(
|
||||||
|
splitValueBy('-') + '.first().toUpperCase() == ' + attribute.value.toUpperCase().inspect()
|
||||||
|
); break;
|
||||||
|
case '!=': conditions.push(value + ' != ' + attribute.value.inspect()); break;
|
||||||
|
case '':
|
||||||
|
case undefined: conditions.push(value + ' != null'); break;
|
||||||
|
default: throw 'Unknown operator ' + attribute.operator + ' in selector';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return conditions.join(' && ');
|
||||||
|
},
|
||||||
|
|
||||||
|
compileMatcher: function() {
|
||||||
|
this.match = new Function('element', 'if (!element.tagName) return false; \
|
||||||
|
return ' + this.buildMatchExpression());
|
||||||
|
},
|
||||||
|
|
||||||
|
findElements: function(scope) {
|
||||||
|
var element;
|
||||||
|
|
||||||
|
if (element = $(this.params.id))
|
||||||
|
if (this.match(element))
|
||||||
|
if (!scope || Element.childOf(element, scope))
|
||||||
|
return [element];
|
||||||
|
|
||||||
|
scope = (scope || document).getElementsByTagName(this.params.tagName || '*');
|
||||||
|
|
||||||
|
var results = [];
|
||||||
|
for (var i = 0; i < scope.length; i++)
|
||||||
|
if (this.match(element = scope[i]))
|
||||||
|
results.push(Element.extend(element));
|
||||||
|
|
||||||
|
return results;
|
||||||
|
},
|
||||||
|
|
||||||
|
toString: function() {
|
||||||
|
return this.expression;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function $$() {
|
||||||
|
return $A(arguments).map(function(expression) {
|
||||||
|
return expression.strip().split(/\s+/).inject([null], function(results, expr) {
|
||||||
|
var selector = new Selector(expr);
|
||||||
|
return results.map(selector.findElements.bind(selector)).flatten();
|
||||||
|
});
|
||||||
|
}).flatten();
|
||||||
|
}
|
||||||
var Field = {
|
var Field = {
|
||||||
clear: function() {
|
clear: function() {
|
||||||
for (var i = 0; i < arguments.length; i++)
|
for (var i = 0; i < arguments.length; i++)
|
||||||
|
|
|
||||||
|
|
@ -1,15 +0,0 @@
|
||||||
December 25, 2005
|
|
||||||
* Added changeset 3316: http://dev.rubyonrails.org/changeset/3316
|
|
||||||
* Added tests for javascript_generator_templates up to changeset 3116
|
|
||||||
* Added changeset 3319: http://dev.rubyonrails.org/changeset/3319
|
|
||||||
* Adds support for alert, redirect_to, call, assign, <<
|
|
||||||
* Added changeset 3329 and 3335
|
|
||||||
* Adds support for toggle, delay
|
|
||||||
|
|
||||||
December 15, 2005
|
|
||||||
* Updated prototype.js to 1.4.0
|
|
||||||
* Enabled the test test_render_file_with_locals
|
|
||||||
* Add CHANGELOG file
|
|
||||||
* Update README to reflect version 1.0 of Rails
|
|
||||||
* Added MIT-LICENSE
|
|
||||||
|
|
||||||
|
|
@ -1,20 +0,0 @@
|
||||||
Copyright (c) 2004 David Heinemeier Hansson
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining
|
|
||||||
a copy of this software and associated documentation files (the
|
|
||||||
"Software"), to deal in the Software without restriction, including
|
|
||||||
without limitation the rights to use, copy, modify, merge, publish,
|
|
||||||
distribute, sublicense, and/or sell copies of the Software, and to
|
|
||||||
permit persons to whom the Software is furnished to do so, subject to
|
|
||||||
the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be
|
|
||||||
included in all copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
||||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
||||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
||||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
||||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
||||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
||||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
||||||
|
|
@ -1,15 +0,0 @@
|
||||||
JavaScriptGeneratorTemplates
|
|
||||||
============================
|
|
||||||
|
|
||||||
This plugin allows the usage of the new RJS templates without having to run
|
|
||||||
edge rails. For more information about RJS templates please check out these
|
|
||||||
resources on the web:
|
|
||||||
|
|
||||||
http://rails.techno-weenie.net/tip/2005/11/29/ajaxed_forms_with_rjs_templates
|
|
||||||
http://www.codyfauser.com/articles/2005/11/20/rails-rjs-templates
|
|
||||||
http://dev.rubyonrails.org/changeset/3078
|
|
||||||
|
|
||||||
The RJS templates need at least version 1.4.0_rc4 of the prototype library to
|
|
||||||
function correctly. Run rake update_prototype from this source directory to
|
|
||||||
update your project's version of prototype to 1.4.0.
|
|
||||||
|
|
||||||
|
|
@ -1,27 +0,0 @@
|
||||||
require 'rake'
|
|
||||||
require 'rake/testtask'
|
|
||||||
require 'rake/rdoctask'
|
|
||||||
|
|
||||||
desc 'Default: run unit tests.'
|
|
||||||
task :default => :test
|
|
||||||
|
|
||||||
desc 'Test the javascript_generator_templates plugin.'
|
|
||||||
Rake::TestTask.new(:test) do |t|
|
|
||||||
t.libs << 'lib'
|
|
||||||
t.pattern = 'test/**/*_test.rb'
|
|
||||||
t.verbose = true
|
|
||||||
end
|
|
||||||
|
|
||||||
desc 'Generate documentation for the javascript_generator_templates plugin.'
|
|
||||||
Rake::RDocTask.new(:rdoc) do |rdoc|
|
|
||||||
rdoc.rdoc_dir = 'rdoc'
|
|
||||||
rdoc.title = 'JavaScriptGeneratorTemplates'
|
|
||||||
rdoc.options << '--line-numbers --inline-source'
|
|
||||||
rdoc.rdoc_files.include('README')
|
|
||||||
rdoc.rdoc_files.include('lib/**/*.rb')
|
|
||||||
end
|
|
||||||
|
|
||||||
desc "Install prototype.js file to public/javascripts"
|
|
||||||
task :update_prototype do
|
|
||||||
FileUtils.cp(File.dirname(__FILE__) + "/javascripts/prototype.js", File.dirname(__FILE__) + '/../../../public/javascripts/')
|
|
||||||
end
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
require 'add_rjs_to_action_view'
|
|
||||||
require 'add_rjs_to_action_controller'
|
|
||||||
require 'add_rjs_to_javascript_helper'
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -1,67 +0,0 @@
|
||||||
#--
|
|
||||||
# Copyright (c) 2004 David Heinemeier Hansson
|
|
||||||
#
|
|
||||||
# Permission is hereby granted, free of charge, to any person obtaining
|
|
||||||
# a copy of this software and associated documentation files (the
|
|
||||||
# "Software"), to deal in the Software without restriction, including
|
|
||||||
# without limitation the rights to use, copy, modify, merge, publish,
|
|
||||||
# distribute, sublicense, and/or sell copies of the Software, and to
|
|
||||||
# permit persons to whom the Software is furnished to do so, subject to
|
|
||||||
# the following conditions:
|
|
||||||
#
|
|
||||||
# The above copyright notice and this permission notice shall be
|
|
||||||
# included in all copies or substantial portions of the Software.
|
|
||||||
#
|
|
||||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
||||||
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
||||||
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
||||||
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
||||||
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
||||||
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
||||||
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
||||||
#++
|
|
||||||
module ActionController #:nodoc:
|
|
||||||
class Base
|
|
||||||
protected
|
|
||||||
def render_action(action_name, status = nil, with_layout = true)
|
|
||||||
template = default_template_name(action_name)
|
|
||||||
if with_layout && !template_exempt_from_layout?(template)
|
|
||||||
render_with_layout(template, status)
|
|
||||||
else
|
|
||||||
render_without_layout(template, status)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
def template_exempt_from_layout?(template_name = default_template_name)
|
|
||||||
@template.javascript_template_exists?(template_name)
|
|
||||||
end
|
|
||||||
|
|
||||||
def default_template_name(default_action_name = action_name)
|
|
||||||
default_action_name = default_action_name.dup
|
|
||||||
strip_out_controller!(default_action_name) if template_path_includes_controller?(default_action_name)
|
|
||||||
"#{self.class.controller_path}/#{default_action_name}"
|
|
||||||
end
|
|
||||||
|
|
||||||
def strip_out_controller!(path)
|
|
||||||
path.replace path.split('/', 2).last
|
|
||||||
end
|
|
||||||
|
|
||||||
def template_path_includes_controller?(path)
|
|
||||||
path.to_s['/'] && self.class.controller_path.split('/')[-1] == path.split('/')[0]
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
module Layout #:nodoc:
|
|
||||||
private
|
|
||||||
def apply_layout?(template_with_options, options)
|
|
||||||
template_with_options ? candidate_for_layout?(options) : !template_exempt_from_layout?
|
|
||||||
end
|
|
||||||
|
|
||||||
def candidate_for_layout?(options)
|
|
||||||
(options.has_key?(:layout) && options[:layout] != false) ||
|
|
||||||
options.values_at(:text, :file, :inline, :partial, :nothing).compact.empty? &&
|
|
||||||
!template_exempt_from_layout?(default_template_name(options[:action] || options[:template]))
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
@ -1,142 +0,0 @@
|
||||||
#--
|
|
||||||
# Copyright (c) 2004 David Heinemeier Hansson
|
|
||||||
#
|
|
||||||
# Permission is hereby granted, free of charge, to any person obtaining
|
|
||||||
# a copy of this software and associated documentation files (the
|
|
||||||
# "Software"), to deal in the Software without restriction, including
|
|
||||||
# without limitation the rights to use, copy, modify, merge, publish,
|
|
||||||
# distribute, sublicense, and/or sell copies of the Software, and to
|
|
||||||
# permit persons to whom the Software is furnished to do so, subject to
|
|
||||||
# the following conditions:
|
|
||||||
#
|
|
||||||
# The above copyright notice and this permission notice shall be
|
|
||||||
# included in all copies or substantial portions of the Software.
|
|
||||||
#
|
|
||||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
||||||
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
||||||
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
||||||
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
||||||
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
||||||
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
||||||
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
||||||
#++
|
|
||||||
module ActionView
|
|
||||||
class Base
|
|
||||||
def pick_template_extension(template_path)#:nodoc:
|
|
||||||
if match = delegate_template_exists?(template_path)
|
|
||||||
match.first
|
|
||||||
elsif erb_template_exists?(template_path): 'rhtml'
|
|
||||||
elsif builder_template_exists?(template_path): 'rxml'
|
|
||||||
elsif javascript_template_exists?(template_path): 'rjs'
|
|
||||||
else
|
|
||||||
raise ActionViewError, "No rhtml, rxml, rjs or delegate template found for #{template_path}"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def javascript_template_exists?(template_path)#:nodoc:
|
|
||||||
template_exists?(template_path, :rjs)
|
|
||||||
end
|
|
||||||
|
|
||||||
def file_exists?(template_path)#:nodoc:
|
|
||||||
%w(erb builder javascript delegate).any? do |template_type|
|
|
||||||
send("#{template_type}_template_exists?", template_path)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
# Create source code for given template
|
|
||||||
def create_template_source(extension, template, render_symbol, locals)
|
|
||||||
if template_requires_setup?(extension)
|
|
||||||
body = case extension.to_sym
|
|
||||||
when :rxml
|
|
||||||
"xml = Builder::XmlMarkup.new(:indent => 2)\n" +
|
|
||||||
"@controller.headers['Content-Type'] ||= 'text/xml'\n" +
|
|
||||||
template
|
|
||||||
when :rjs
|
|
||||||
"@controller.headers['Content-Type'] ||= 'text/javascript'\n" +
|
|
||||||
"update_page do |page|\n#{template}\nend"
|
|
||||||
end
|
|
||||||
else
|
|
||||||
body = ERB.new(template, nil, @@erb_trim_mode).src
|
|
||||||
end
|
|
||||||
|
|
||||||
@@template_args[render_symbol] ||= {}
|
|
||||||
locals_keys = @@template_args[render_symbol].keys | locals
|
|
||||||
@@template_args[render_symbol] = locals_keys.inject({}) { |h, k| h[k] = true; h }
|
|
||||||
|
|
||||||
locals_code = ""
|
|
||||||
locals_keys.each do |key|
|
|
||||||
locals_code << "#{key} = local_assigns[:#{key}] if local_assigns.has_key?(:#{key})\n"
|
|
||||||
end
|
|
||||||
|
|
||||||
"def #{render_symbol}(local_assigns)\n#{locals_code}#{body}\nend"
|
|
||||||
end
|
|
||||||
|
|
||||||
def template_requires_setup?(extension)
|
|
||||||
templates_requiring_setup.include? extension.to_s
|
|
||||||
end
|
|
||||||
|
|
||||||
def templates_requiring_setup
|
|
||||||
%w(rxml rjs)
|
|
||||||
end
|
|
||||||
|
|
||||||
def assign_method_name(extension, template, file_name)
|
|
||||||
method_name = '_run_'
|
|
||||||
method_name << "#{extension}_" if extension
|
|
||||||
|
|
||||||
if file_name
|
|
||||||
file_path = File.expand_path(file_name)
|
|
||||||
base_path = File.expand_path(@base_path)
|
|
||||||
|
|
||||||
i = file_path.index(base_path)
|
|
||||||
l = base_path.length
|
|
||||||
|
|
||||||
method_name_file_part = i ? file_path[i+l+1,file_path.length-l-1] : file_path.clone
|
|
||||||
method_name_file_part.sub!(/\.r(html|xml|js)$/,'')
|
|
||||||
method_name_file_part.tr!('/:-', '_')
|
|
||||||
method_name_file_part.gsub!(/[^a-zA-Z0-9_]/){|s| s[0].to_s}
|
|
||||||
|
|
||||||
method_name += method_name_file_part
|
|
||||||
else
|
|
||||||
@@inline_template_count += 1
|
|
||||||
method_name << @@inline_template_count.to_s
|
|
||||||
end
|
|
||||||
|
|
||||||
@@method_names[file_name || template] = method_name.intern
|
|
||||||
end
|
|
||||||
|
|
||||||
def compile_template(extension, template, file_name, local_assigns)
|
|
||||||
method_key = file_name || template
|
|
||||||
|
|
||||||
render_symbol = @@method_names[method_key] || assign_method_name(extension, template, file_name)
|
|
||||||
render_source = create_template_source(extension, template, render_symbol, local_assigns.keys)
|
|
||||||
|
|
||||||
line_offset = @@template_args[render_symbol].size
|
|
||||||
if extension
|
|
||||||
case extension.to_sym
|
|
||||||
when :rxml, :rjs
|
|
||||||
line_offset += 2
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
begin
|
|
||||||
unless file_name.blank?
|
|
||||||
CompiledTemplates.module_eval(render_source, file_name, -line_offset)
|
|
||||||
else
|
|
||||||
CompiledTemplates.module_eval(render_source, 'compiled-template', -line_offset)
|
|
||||||
end
|
|
||||||
rescue Object => e
|
|
||||||
if logger
|
|
||||||
logger.debug "ERROR: compiling #{render_symbol} RAISED #{e}"
|
|
||||||
logger.debug "Function body: #{render_source}"
|
|
||||||
logger.debug "Backtrace: #{e.backtrace.join("\n")}"
|
|
||||||
end
|
|
||||||
|
|
||||||
raise TemplateError.new(@base_path, method_key, @assigns, template, e)
|
|
||||||
end
|
|
||||||
|
|
||||||
@@compile_time[render_symbol] = Time.now
|
|
||||||
# logger.debug "Compiled template #{method_key}\n ==> #{render_symbol}" if logger
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
@ -1,204 +0,0 @@
|
||||||
#--
|
|
||||||
# Copyright (c) 2004 David Heinemeier Hansson
|
|
||||||
#
|
|
||||||
# Permission is hereby granted, free of charge, to any person obtaining
|
|
||||||
# a copy of this software and associated documentation files (the
|
|
||||||
# "Software"), to deal in the Software without restriction, including
|
|
||||||
# without limitation the rights to use, copy, modify, merge, publish,
|
|
||||||
# distribute, sublicense, and/or sell copies of the Software, and to
|
|
||||||
# permit persons to whom the Software is furnished to do so, subject to
|
|
||||||
# the following conditions:
|
|
||||||
#
|
|
||||||
# The above copyright notice and this permission notice shall be
|
|
||||||
# included in all copies or substantial portions of the Software.
|
|
||||||
#
|
|
||||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
||||||
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
||||||
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
||||||
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
||||||
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
||||||
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
||||||
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
||||||
#++
|
|
||||||
module ActionView
|
|
||||||
module Helpers
|
|
||||||
module JavaScriptHelper
|
|
||||||
# JavaScriptGenerator generates blocks of JavaScript code that allow you
|
|
||||||
# to change the content and presentation of multiple DOM elements. Use
|
|
||||||
# this in your Ajax response bodies, either in a <script> tag or as plain
|
|
||||||
# JavaScript sent with a Content-type of "text/javascript".
|
|
||||||
#
|
|
||||||
# Create new instances with PrototypeHelper#update_page, then call
|
|
||||||
# #insert_html, #replace_html, #remove, #show, or #hide on the yielded
|
|
||||||
# generator in any order you like to modify the content and appearance of
|
|
||||||
# the current page. (You can also call other helper methods which
|
|
||||||
# return JavaScript, such as
|
|
||||||
# ActionView::Helpers::ScriptaculousHelper#visual_effect.)
|
|
||||||
#
|
|
||||||
# Example:
|
|
||||||
#
|
|
||||||
# update_page do |page|
|
|
||||||
# page.insert_html :bottom, 'list', '<li>Last item</li>'
|
|
||||||
# page.visual_effect :highlight, 'list'
|
|
||||||
# page.hide 'status-indicator', 'cancel-link'
|
|
||||||
# end
|
|
||||||
#
|
|
||||||
# generates the following JavaScript:
|
|
||||||
#
|
|
||||||
# new Insertion.Bottom("list", "<li>Last item</li>");
|
|
||||||
# new Effect.Highlight("list");
|
|
||||||
# ["status-indicator", "cancel-link"].each(Element.hide);
|
|
||||||
#
|
|
||||||
# You can also use PrototypeHelper#update_page_tag instead of
|
|
||||||
# PrototypeHelper#update_page to wrap the generated JavaScript in a
|
|
||||||
# <script> tag.
|
|
||||||
class JavaScriptGenerator
|
|
||||||
def initialize(context) #:nodoc:
|
|
||||||
@context, @lines = context, []
|
|
||||||
yield self
|
|
||||||
end
|
|
||||||
|
|
||||||
def to_s #:nodoc:
|
|
||||||
@lines * $/
|
|
||||||
end
|
|
||||||
|
|
||||||
# Inserts HTML at the specified +position+ relative to the DOM element
|
|
||||||
# identified by the given +id+.
|
|
||||||
#
|
|
||||||
# +position+ may be one of:
|
|
||||||
#
|
|
||||||
# <tt>:top</tt>:: HTML is inserted inside the element, before the
|
|
||||||
# element's existing content.
|
|
||||||
# <tt>:bottom</tt>:: HTML is inserted inside the element, after the
|
|
||||||
# element's existing content.
|
|
||||||
# <tt>:before</tt>:: HTML is inserted immediately preceeding the element.
|
|
||||||
# <tt>:after</tt>:: HTML is inserted immediately following the element.
|
|
||||||
#
|
|
||||||
# +options_for_render+ may be either a string of HTML to insert, or a hash
|
|
||||||
# of options to be passed to ActionView::Base#render. For example:
|
|
||||||
#
|
|
||||||
# # Insert the rendered 'navigation' partial just before the DOM
|
|
||||||
# # element with ID 'content'.
|
|
||||||
# insert_html :before, 'content', :partial => 'navigation'
|
|
||||||
#
|
|
||||||
# # Add a list item to the bottom of the <ul> with ID 'list'.
|
|
||||||
# insert_html :bottom, 'list', '<li>Last item</li>'
|
|
||||||
#
|
|
||||||
def insert_html(position, id, *options_for_render)
|
|
||||||
insertion = position.to_s.camelize
|
|
||||||
call "new Insertion.#{insertion}", id, render(*options_for_render)
|
|
||||||
end
|
|
||||||
|
|
||||||
# Replaces the inner HTML of the DOM element with the given +id+.
|
|
||||||
#
|
|
||||||
# +options_for_render+ may be either a string of HTML to insert, or a hash
|
|
||||||
# of options to be passed to ActionView::Base#render. For example:
|
|
||||||
#
|
|
||||||
# # Replace the HTML of the DOM element having ID 'person-45' with the
|
|
||||||
# # 'person' partial for the appropriate object.
|
|
||||||
# replace_html 'person-45', :partial => 'person', :object => @person
|
|
||||||
#
|
|
||||||
def replace_html(id, *options_for_render)
|
|
||||||
call 'Element.update', id, render(*options_for_render)
|
|
||||||
end
|
|
||||||
|
|
||||||
# Removes the DOM elements with the given +ids+ from the page.
|
|
||||||
def remove(*ids)
|
|
||||||
record "#{javascript_object_for(ids)}.each(Element.remove)"
|
|
||||||
end
|
|
||||||
|
|
||||||
# Shows hidden DOM elements with the given +ids+.
|
|
||||||
def show(*ids)
|
|
||||||
call 'Element.show', *ids
|
|
||||||
end
|
|
||||||
|
|
||||||
# Hides the visible DOM elements with the given +ids+.
|
|
||||||
def hide(*ids)
|
|
||||||
call 'Element.hide', *ids
|
|
||||||
end
|
|
||||||
|
|
||||||
# Toggles the visibility of the DOM elements with the given +ids+.
|
|
||||||
def toggle(*ids)
|
|
||||||
call 'Element.toggle', *ids
|
|
||||||
end
|
|
||||||
|
|
||||||
# Displays an alert dialog with the given +message+.
|
|
||||||
def alert(message)
|
|
||||||
call 'alert', message
|
|
||||||
end
|
|
||||||
|
|
||||||
# Redirects the browser to the given +location+, in the same form as
|
|
||||||
# +url_for+.
|
|
||||||
def redirect_to(location)
|
|
||||||
assign 'window.location.href', @context.url_for(location)
|
|
||||||
end
|
|
||||||
|
|
||||||
# Calls the JavaScript +function+, optionally with the given
|
|
||||||
# +arguments+.
|
|
||||||
def call(function, *arguments)
|
|
||||||
record "#{function}(#{arguments_for_call(arguments)})"
|
|
||||||
end
|
|
||||||
|
|
||||||
# Assigns the JavaScript +variable+ the given +value+.
|
|
||||||
def assign(variable, value)
|
|
||||||
record "#{variable} = #{javascript_object_for(value)}"
|
|
||||||
end
|
|
||||||
|
|
||||||
# Writes raw JavaScript to the page.
|
|
||||||
def <<(javascript)
|
|
||||||
@lines << javascript
|
|
||||||
end
|
|
||||||
|
|
||||||
# Executes the content of the block after a delay of +seconds+. Example:
|
|
||||||
#
|
|
||||||
# page.delay(20) do
|
|
||||||
# page.visual_effect :fade, 'notice'
|
|
||||||
# end
|
|
||||||
def delay(seconds = 1)
|
|
||||||
record "setTimeout(function() {\n\n"
|
|
||||||
yield
|
|
||||||
record "}, #{(seconds * 1000).to_i})"
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
def method_missing(method, *arguments, &block)
|
|
||||||
record(@context.send(method, *arguments, &block))
|
|
||||||
end
|
|
||||||
|
|
||||||
def record(line)
|
|
||||||
returning line = "#{line.to_s.chomp.gsub /\;$/, ''};" do
|
|
||||||
self << line
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def render(*options_for_render)
|
|
||||||
Hash === options_for_render.first ?
|
|
||||||
@context.render(*options_for_render) :
|
|
||||||
options_for_render.first.to_s
|
|
||||||
end
|
|
||||||
|
|
||||||
def javascript_object_for(object)
|
|
||||||
object.respond_to?(:to_json) ? object.to_json : object.inspect
|
|
||||||
end
|
|
||||||
|
|
||||||
def arguments_for_call(arguments)
|
|
||||||
arguments.map { |argument| javascript_object_for(argument) }.join ', '
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Yields a JavaScriptGenerator and returns the generated JavaScript code.
|
|
||||||
# Use this to update multiple elements on a page in an Ajax response.
|
|
||||||
# See JavaScriptGenerator for more information.
|
|
||||||
def update_page(&block)
|
|
||||||
JavaScriptGenerator.new(@template, &block).to_s
|
|
||||||
end
|
|
||||||
|
|
||||||
# Works like update_page but wraps the generated JavaScript in a <script>
|
|
||||||
# tag. Use this to include generated JavaScript in an ERb template.
|
|
||||||
# See JavaScriptGenerator for more information.
|
|
||||||
def update_page_tag(&block)
|
|
||||||
javascript_tag update_page(&block)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
@ -1,20 +0,0 @@
|
||||||
$:.unshift(File.dirname(__FILE__) + '/..lib')
|
|
||||||
$:.unshift(File.dirname(__FILE__) + '/fixtures/helpers')
|
|
||||||
|
|
||||||
rails_dir = File.dirname(__FILE__) + '/../../../rails'
|
|
||||||
if File.directory?(rails_dir)
|
|
||||||
lib_dir = rails_dir + '/actionpack/lib'
|
|
||||||
require lib_dir + '/action_controller'
|
|
||||||
require lib_dir + '/action_controller/test_process'
|
|
||||||
else
|
|
||||||
require 'rubygems'
|
|
||||||
require 'action_controller'
|
|
||||||
require 'action_controller/test_process'
|
|
||||||
end
|
|
||||||
require 'test/unit'
|
|
||||||
require 'init'
|
|
||||||
|
|
||||||
ActionController::Base.logger = nil
|
|
||||||
ActionController::Base.ignore_missing_templates = false
|
|
||||||
ActionController::Routing::Routes.reload rescue nil
|
|
||||||
|
|
||||||
|
|
@ -1,477 +0,0 @@
|
||||||
require File.dirname(__FILE__) + '/../abstract_unit'
|
|
||||||
|
|
||||||
silence_warnings { Customer = Struct.new("Customer", :name) }
|
|
||||||
|
|
||||||
module Fun
|
|
||||||
class GamesController < ActionController::Base
|
|
||||||
def hello_world
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
class NewRenderTestController < ActionController::Base
|
|
||||||
layout :determine_layout
|
|
||||||
|
|
||||||
def self.controller_name; "test"; end
|
|
||||||
def self.controller_path; "test"; end
|
|
||||||
|
|
||||||
def hello_world
|
|
||||||
end
|
|
||||||
|
|
||||||
def render_hello_world
|
|
||||||
render :template => "test/hello_world"
|
|
||||||
end
|
|
||||||
|
|
||||||
def render_hello_world_from_variable
|
|
||||||
@person = "david"
|
|
||||||
render :text => "hello #{@person}"
|
|
||||||
end
|
|
||||||
|
|
||||||
def render_action_hello_world
|
|
||||||
render :action => "hello_world"
|
|
||||||
end
|
|
||||||
|
|
||||||
def render_text_hello_world
|
|
||||||
render :text => "hello world"
|
|
||||||
end
|
|
||||||
|
|
||||||
def render_text_hello_world_with_layout
|
|
||||||
@variable_for_layout = ", I'm here!"
|
|
||||||
render :text => "hello world", :layout => true
|
|
||||||
end
|
|
||||||
|
|
||||||
def render_custom_code
|
|
||||||
render :text => "hello world", :status => "404 Moved"
|
|
||||||
end
|
|
||||||
|
|
||||||
def render_file_with_instance_variables
|
|
||||||
@secret = 'in the sauce'
|
|
||||||
path = File.join(File.dirname(__FILE__), '../fixtures/test/render_file_with_ivar.rhtml')
|
|
||||||
render :file => path
|
|
||||||
end
|
|
||||||
|
|
||||||
def render_file_with_locals
|
|
||||||
path = File.join(File.dirname(__FILE__), '../fixtures/test/render_file_with_locals.rhtml')
|
|
||||||
render :file => path, :locals => {:secret => 'in the sauce'}
|
|
||||||
end
|
|
||||||
|
|
||||||
def render_file_not_using_full_path
|
|
||||||
@secret = 'in the sauce'
|
|
||||||
render :file => 'test/render_file_with_ivar', :use_full_path => true
|
|
||||||
end
|
|
||||||
|
|
||||||
def render_xml_hello
|
|
||||||
@name = "David"
|
|
||||||
render :template => "test/hello"
|
|
||||||
end
|
|
||||||
|
|
||||||
def greeting
|
|
||||||
# let's just rely on the template
|
|
||||||
end
|
|
||||||
|
|
||||||
def layout_test
|
|
||||||
render :action => "hello_world"
|
|
||||||
end
|
|
||||||
|
|
||||||
def layout_test_with_different_layout
|
|
||||||
render :action => "hello_world", :layout => "standard"
|
|
||||||
end
|
|
||||||
|
|
||||||
def rendering_without_layout
|
|
||||||
render :action => "hello_world", :layout => false
|
|
||||||
end
|
|
||||||
|
|
||||||
def layout_overriding_layout
|
|
||||||
render :action => "hello_world", :layout => "standard"
|
|
||||||
end
|
|
||||||
|
|
||||||
def rendering_nothing_on_layout
|
|
||||||
render :nothing => true
|
|
||||||
end
|
|
||||||
|
|
||||||
def builder_layout_test
|
|
||||||
render :action => "hello"
|
|
||||||
end
|
|
||||||
|
|
||||||
def partials_list
|
|
||||||
@test_unchanged = 'hello'
|
|
||||||
@customers = [ Customer.new("david"), Customer.new("mary") ]
|
|
||||||
render :action => "list"
|
|
||||||
end
|
|
||||||
|
|
||||||
def partial_only
|
|
||||||
render :partial => true
|
|
||||||
end
|
|
||||||
|
|
||||||
def partial_only_with_layout
|
|
||||||
render :partial => "partial_only", :layout => true
|
|
||||||
end
|
|
||||||
|
|
||||||
def partial_with_locals
|
|
||||||
render :partial => "customer", :locals => { :customer => Customer.new("david") }
|
|
||||||
end
|
|
||||||
|
|
||||||
def partial_collection
|
|
||||||
render :partial => "customer", :collection => [ Customer.new("david"), Customer.new("mary") ]
|
|
||||||
end
|
|
||||||
|
|
||||||
def partial_collection_with_locals
|
|
||||||
render :partial => "customer_greeting", :collection => [ Customer.new("david"), Customer.new("mary") ], :locals => { :greeting => "Bonjour" }
|
|
||||||
end
|
|
||||||
|
|
||||||
def empty_partial_collection
|
|
||||||
render :partial => "customer", :collection => []
|
|
||||||
end
|
|
||||||
|
|
||||||
def partial_with_hash_object
|
|
||||||
render :partial => "hash_object", :object => {:first_name => "Sam"}
|
|
||||||
end
|
|
||||||
|
|
||||||
def partial_with_implicit_local_assignment
|
|
||||||
@customer = Customer.new("Marcel")
|
|
||||||
render :partial => "customer"
|
|
||||||
end
|
|
||||||
|
|
||||||
def hello_in_a_string
|
|
||||||
@customers = [ Customer.new("david"), Customer.new("mary") ]
|
|
||||||
render :text => "How's there? #{render_to_string("test/list")}"
|
|
||||||
end
|
|
||||||
|
|
||||||
def accessing_params_in_template
|
|
||||||
render :inline => "Hello: <%= params[:name] %>"
|
|
||||||
end
|
|
||||||
|
|
||||||
def accessing_params_in_template_with_layout
|
|
||||||
render :layout => nil, :inline => "Hello: <%= params[:name] %>"
|
|
||||||
end
|
|
||||||
|
|
||||||
def render_with_explicit_template
|
|
||||||
render "test/hello_world"
|
|
||||||
end
|
|
||||||
|
|
||||||
def double_render
|
|
||||||
render :text => "hello"
|
|
||||||
render :text => "world"
|
|
||||||
end
|
|
||||||
|
|
||||||
def double_redirect
|
|
||||||
redirect_to :action => "double_render"
|
|
||||||
redirect_to :action => "double_render"
|
|
||||||
end
|
|
||||||
|
|
||||||
def render_and_redirect
|
|
||||||
render :text => "hello"
|
|
||||||
redirect_to :action => "double_render"
|
|
||||||
end
|
|
||||||
|
|
||||||
def rendering_with_conflicting_local_vars
|
|
||||||
@name = "David"
|
|
||||||
def @template.name() nil end
|
|
||||||
render :action => "potential_conflicts"
|
|
||||||
end
|
|
||||||
|
|
||||||
def delete_with_js
|
|
||||||
@project_id = 4
|
|
||||||
end
|
|
||||||
|
|
||||||
def render_js_with_explicit_template
|
|
||||||
@project_id = 4
|
|
||||||
render :template => 'test/delete_with_js'
|
|
||||||
end
|
|
||||||
|
|
||||||
def render_js_with_explicit_action_template
|
|
||||||
@project_id = 4
|
|
||||||
render :action => 'delete_with_js'
|
|
||||||
end
|
|
||||||
|
|
||||||
def action_talk_to_layout
|
|
||||||
# Action template sets variable that's picked up by layout
|
|
||||||
end
|
|
||||||
|
|
||||||
def render_text_with_assigns
|
|
||||||
@hello = "world"
|
|
||||||
render :text => "foo"
|
|
||||||
end
|
|
||||||
|
|
||||||
def yield_content_for
|
|
||||||
render :action => "content_for", :layout => "yield"
|
|
||||||
end
|
|
||||||
|
|
||||||
def rescue_action(e) raise end
|
|
||||||
|
|
||||||
private
|
|
||||||
def determine_layout
|
|
||||||
case action_name
|
|
||||||
when "hello_world", "layout_test", "rendering_without_layout",
|
|
||||||
"rendering_nothing_on_layout", "render_text_hello_world",
|
|
||||||
"render_text_hello_world_with_layout",
|
|
||||||
"partial_only", "partial_only_with_layout",
|
|
||||||
"accessing_params_in_template",
|
|
||||||
"accessing_params_in_template_with_layout",
|
|
||||||
"render_with_explicit_template",
|
|
||||||
"render_js_with_explicit_template",
|
|
||||||
"render_js_with_explicit_action_template",
|
|
||||||
"delete_with_js"
|
|
||||||
|
|
||||||
"layouts/standard"
|
|
||||||
when "builder_layout_test"
|
|
||||||
"layouts/builder"
|
|
||||||
when "action_talk_to_layout", "layout_overriding_layout"
|
|
||||||
"layouts/talk_from_action"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
NewRenderTestController.template_root = File.dirname(__FILE__) + "/../fixtures/"
|
|
||||||
Fun::GamesController.template_root = File.dirname(__FILE__) + "/../fixtures/"
|
|
||||||
|
|
||||||
class NewRenderTest < Test::Unit::TestCase
|
|
||||||
def setup
|
|
||||||
@controller = NewRenderTestController.new
|
|
||||||
|
|
||||||
# enable a logger so that (e.g.) the benchmarking stuff runs, so we can get
|
|
||||||
# a more accurate simulation of what happens in "real life".
|
|
||||||
@controller.logger = Logger.new(nil)
|
|
||||||
|
|
||||||
@request = ActionController::TestRequest.new
|
|
||||||
@response = ActionController::TestResponse.new
|
|
||||||
|
|
||||||
@request.host = "www.nextangle.com"
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_simple_show
|
|
||||||
get :hello_world
|
|
||||||
assert_response :success
|
|
||||||
assert_template "test/hello_world"
|
|
||||||
assert_equal "<html>Hello world!</html>", @response.body
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_do_with_render
|
|
||||||
get :render_hello_world
|
|
||||||
assert_template "test/hello_world"
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_do_with_render_from_variable
|
|
||||||
get :render_hello_world_from_variable
|
|
||||||
assert_equal "hello david", @response.body
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_do_with_render_action
|
|
||||||
get :render_action_hello_world
|
|
||||||
assert_template "test/hello_world"
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_do_with_render_text
|
|
||||||
get :render_text_hello_world
|
|
||||||
assert_equal "hello world", @response.body
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_do_with_render_text_and_layout
|
|
||||||
get :render_text_hello_world_with_layout
|
|
||||||
assert_equal "<html>hello world, I'm here!</html>", @response.body
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_do_with_render_custom_code
|
|
||||||
get :render_custom_code
|
|
||||||
assert_response :missing
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_render_file_with_instance_variables
|
|
||||||
get :render_file_with_instance_variables
|
|
||||||
assert_equal "The secret is in the sauce\n", @response.body
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_render_file_not_using_full_path
|
|
||||||
get :render_file_not_using_full_path
|
|
||||||
assert_equal "The secret is in the sauce\n", @response.body
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_render_file_with_locals
|
|
||||||
get :render_file_with_locals
|
|
||||||
assert_equal "The secret is in the sauce\n", @response.body
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_attempt_to_access_object_method
|
|
||||||
assert_raises(ActionController::UnknownAction, "No action responded to [clone]") { get :clone }
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_private_methods
|
|
||||||
assert_raises(ActionController::UnknownAction, "No action responded to [determine_layout]") { get :determine_layout }
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_access_to_request_in_view
|
|
||||||
view_internals_old_value = ActionController::Base.view_controller_internals
|
|
||||||
|
|
||||||
ActionController::Base.view_controller_internals = false
|
|
||||||
ActionController::Base.protected_variables_cache = nil
|
|
||||||
|
|
||||||
get :hello_world
|
|
||||||
assert_nil(assigns["request"])
|
|
||||||
|
|
||||||
ActionController::Base.view_controller_internals = true
|
|
||||||
ActionController::Base.protected_variables_cache = nil
|
|
||||||
|
|
||||||
get :hello_world
|
|
||||||
assert_kind_of ActionController::AbstractRequest, assigns["request"]
|
|
||||||
|
|
||||||
ActionController::Base.view_controller_internals = view_internals_old_value
|
|
||||||
ActionController::Base.protected_variables_cache = nil
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_render_xml
|
|
||||||
get :render_xml_hello
|
|
||||||
assert_equal "<html>\n <p>Hello David</p>\n<p>This is grand!</p>\n</html>\n", @response.body
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_render_xml_with_default
|
|
||||||
get :greeting
|
|
||||||
assert_equal "<p>This is grand!</p>\n", @response.body
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_render_rjs_with_default
|
|
||||||
get :delete_with_js
|
|
||||||
assert_equal %!["person"].each(Element.remove);\nnew Effect.Highlight('project-4',{});!, @response.body
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_render_rjs_template_explicitly
|
|
||||||
get :render_js_with_explicit_template
|
|
||||||
assert_equal %!["person"].each(Element.remove);\nnew Effect.Highlight('project-4',{});!, @response.body
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_rendering_rjs_action_explicitly
|
|
||||||
get :render_js_with_explicit_action_template
|
|
||||||
assert_equal %!["person"].each(Element.remove);\nnew Effect.Highlight('project-4',{});!, @response.body
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_layout_rendering
|
|
||||||
get :layout_test
|
|
||||||
assert_equal "<html>Hello world!</html>", @response.body
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_layout_test_with_different_layout
|
|
||||||
get :layout_test_with_different_layout
|
|
||||||
assert_equal "<html>Hello world!</html>", @response.body
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_rendering_without_layout
|
|
||||||
get :rendering_without_layout
|
|
||||||
assert_equal "Hello world!", @response.body
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_layout_overriding_layout
|
|
||||||
get :layout_overriding_layout
|
|
||||||
assert_no_match %r{<title>}, @response.body
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_rendering_nothing_on_layout
|
|
||||||
get :rendering_nothing_on_layout
|
|
||||||
assert_equal " ", @response.body
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_render_xml_with_layouts
|
|
||||||
get :builder_layout_test
|
|
||||||
assert_equal "<wrapper>\n<html>\n <p>Hello </p>\n<p>This is grand!</p>\n</html>\n</wrapper>\n", @response.body
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_partial_only
|
|
||||||
get :partial_only
|
|
||||||
assert_equal "only partial", @response.body
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_partial_only_with_layout
|
|
||||||
get :partial_only_with_layout
|
|
||||||
assert_equal "<html>only partial</html>", @response.body
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_render_to_string
|
|
||||||
get :hello_in_a_string
|
|
||||||
assert_equal "How's there? goodbyeHello: davidHello: marygoodbye\n", @response.body
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_nested_rendering
|
|
||||||
get :hello_world
|
|
||||||
assert_equal "Living in a nested world", Fun::GamesController.process(@request, @response).body
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_accessing_params_in_template
|
|
||||||
get :accessing_params_in_template, :name => "David"
|
|
||||||
assert_equal "Hello: David", @response.body
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_accessing_params_in_template_with_layout
|
|
||||||
get :accessing_params_in_template_with_layout, :name => "David"
|
|
||||||
assert_equal "<html>Hello: David</html>", @response.body
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_render_with_explicit_template
|
|
||||||
get :render_with_explicit_template
|
|
||||||
assert_response :success
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_double_render
|
|
||||||
assert_raises(ActionController::DoubleRenderError) { get :double_render }
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_double_redirect
|
|
||||||
assert_raises(ActionController::DoubleRenderError) { get :double_redirect }
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_render_and_redirect
|
|
||||||
assert_raises(ActionController::DoubleRenderError) { get :render_and_redirect }
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_rendering_with_conflicting_local_vars
|
|
||||||
get :rendering_with_conflicting_local_vars
|
|
||||||
assert_equal("First: David\nSecond: Stephan\nThird: David\nFourth: David\nFifth: ", @response.body)
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_action_talk_to_layout
|
|
||||||
get :action_talk_to_layout
|
|
||||||
assert_equal "<title>Talking to the layout</title>\nAction was here!", @response.body
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_partials_list
|
|
||||||
get :partials_list
|
|
||||||
assert_equal "goodbyeHello: davidHello: marygoodbye\n", @response.body
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_partial_with_locals
|
|
||||||
get :partial_with_locals
|
|
||||||
assert_equal "Hello: david", @response.body
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_partial_collection
|
|
||||||
get :partial_collection
|
|
||||||
assert_equal "Hello: davidHello: mary", @response.body
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_partial_collection_with_locals
|
|
||||||
get :partial_collection_with_locals
|
|
||||||
assert_equal "Bonjour: davidBonjour: mary", @response.body
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_empty_partial_collection
|
|
||||||
get :empty_partial_collection
|
|
||||||
assert_equal " ", @response.body
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_partial_with_hash_object
|
|
||||||
get :partial_with_hash_object
|
|
||||||
assert_equal "Sam", @response.body
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_partial_with_implicit_local_assignment
|
|
||||||
get :partial_with_implicit_local_assignment
|
|
||||||
assert_equal "Hello: Marcel", @response.body
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_render_text_with_assigns
|
|
||||||
get :render_text_with_assigns
|
|
||||||
assert_equal "world", assigns["hello"]
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_yield_content_for
|
|
||||||
get :yield_content_for
|
|
||||||
assert_equal "<title>Putting stuff in the title!</title>\n\nGreat stuff!\n", @response.body
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
Living in a nested world
|
|
||||||
|
|
@ -1,5 +0,0 @@
|
||||||
module AbcHelper
|
|
||||||
def bare_a() end
|
|
||||||
def bare_b() end
|
|
||||||
def bare_c() end
|
|
||||||
end
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
module Fun::GamesHelper
|
|
||||||
def stratego() "Iz guuut!" end
|
|
||||||
end
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
module Fun::PDFHelper
|
|
||||||
def foobar() 'baz' end
|
|
||||||
end
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
xml.wrapper do
|
|
||||||
xml << @content_for_layout
|
|
||||||
end
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
<html><%= @content_for_layout %><%= @variable_for_layout %></html>
|
|
||||||
|
|
@ -1,2 +0,0 @@
|
||||||
<title><%= @title || @content_for_title %></title>
|
|
||||||
<%= @content_for_layout -%>
|
|
||||||
|
|
@ -1,2 +0,0 @@
|
||||||
<title><%= yield :title %></title>
|
|
||||||
<%= yield %>
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
Hello: <%= customer.name %>
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
<%= greeting %>: <%= customer_greeting.name %>
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
<%= hash_object[:first_name] %>
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
only partial
|
|
||||||
|
|
@ -1,2 +0,0 @@
|
||||||
Second: <%= name %>
|
|
||||||
Third: <%= @name %>
|
|
||||||
|
|
@ -1,2 +0,0 @@
|
||||||
<% @title = "Talking to the layout" -%>
|
|
||||||
Action was here!
|
|
||||||
|
|
@ -1,4 +0,0 @@
|
||||||
<% days = capture do %>
|
|
||||||
Dreamy days
|
|
||||||
<% end %>
|
|
||||||
<%= days %>
|
|
||||||
|
|
@ -1,2 +0,0 @@
|
||||||
<% content_for :title do %>Putting stuff in the title!<% end %>
|
|
||||||
Great stuff!
|
|
||||||
|
|
@ -1,2 +0,0 @@
|
||||||
page.remove 'person'
|
|
||||||
page.visual_effect :highlight, "project-#{@project_id}"
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
<p>This is grand!</p>
|
|
||||||
|
|
@ -1,4 +0,0 @@
|
||||||
xml.html do
|
|
||||||
xml.p "Hello #{@name}"
|
|
||||||
xml << render_file("test/greeting")
|
|
||||||
end
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
Hello world!
|
|
||||||
|
|
@ -1,11 +0,0 @@
|
||||||
xml.html do
|
|
||||||
xml.head do
|
|
||||||
xml.title "Hello World"
|
|
||||||
end
|
|
||||||
|
|
||||||
xml.body do
|
|
||||||
xml.p "abes"
|
|
||||||
xml.p "monks"
|
|
||||||
xml.p "wiseguys"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
<%= @test_unchanged = 'goodbye' %><%= render_collection_of_partials "customer", @customers %><%= @test_unchanged %>
|
|
||||||
|
|
@ -1,4 +0,0 @@
|
||||||
First: <%= @name %>
|
|
||||||
<%= render :partial => "person", :locals => { :name => "Stephan" } -%>
|
|
||||||
Fourth: <%= @name %>
|
|
||||||
Fifth: <%= name %>
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
The secret is <%= @secret %>
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
The secret is <%= secret %>
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
The value of foo is: ::<%= @foo %>::
|
|
||||||
|
|
@ -1,9 +0,0 @@
|
||||||
<% replacement_function = update_element_function("products", :action => :update) do %>
|
|
||||||
<p>Product 1</p>
|
|
||||||
<p>Product 2</p>
|
|
||||||
<% end %>
|
|
||||||
<%= javascript_tag(replacement_function) %>
|
|
||||||
|
|
||||||
<% update_element_function("status", :action => :update, :binding => binding) do %>
|
|
||||||
<b>You bought something!</b>
|
|
||||||
<% end %>
|
|
||||||
|
|
@ -1,279 +0,0 @@
|
||||||
require File.dirname(__FILE__) + '/../abstract_unit'
|
|
||||||
|
|
||||||
module BaseTest
|
|
||||||
include ActionView::Helpers::JavaScriptHelper
|
|
||||||
|
|
||||||
include ActionView::Helpers::UrlHelper
|
|
||||||
include ActionView::Helpers::TagHelper
|
|
||||||
include ActionView::Helpers::TextHelper
|
|
||||||
include ActionView::Helpers::FormHelper
|
|
||||||
include ActionView::Helpers::CaptureHelper
|
|
||||||
|
|
||||||
def setup
|
|
||||||
@controller = Class.new do
|
|
||||||
def url_for(options, *parameters_for_method_reference)
|
|
||||||
url = "http://www.example.com/"
|
|
||||||
url << options[:action].to_s if options and options[:action]
|
|
||||||
url
|
|
||||||
end
|
|
||||||
end.new
|
|
||||||
end
|
|
||||||
|
|
||||||
protected
|
|
||||||
def create_generator
|
|
||||||
block = Proc.new { |*args| yield *args if block_given? }
|
|
||||||
JavaScriptGenerator.new self, &block
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
class JavaScriptHelperTest < Test::Unit::TestCase
|
|
||||||
include BaseTest
|
|
||||||
|
|
||||||
def test_define_javascript_functions
|
|
||||||
# check if prototype.js is included first
|
|
||||||
assert_not_nil define_javascript_functions.split("\n")[1].match(/Prototype JavaScript framework/)
|
|
||||||
|
|
||||||
# check that scriptaculous.js is not in here, only needed if loaded remotely
|
|
||||||
assert_nil define_javascript_functions.split("\n")[1].match(/var Scriptaculous = \{/)
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_escape_javascript
|
|
||||||
assert_equal %(This \\"thing\\" is really\\n netos\\'), escape_javascript(%(This "thing" is really\n netos'))
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_link_to_function
|
|
||||||
assert_dom_equal %(<a href="#" onclick="alert('Hello world!'); return false;">Greeting</a>),
|
|
||||||
link_to_function("Greeting", "alert('Hello world!')")
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_link_to_remote
|
|
||||||
assert_dom_equal %(<a class=\"fine\" href=\"#\" onclick=\"new Ajax.Request('http://www.example.com/whatnot', {asynchronous:true, evalScripts:true}); return false;\">Remote outpost</a>),
|
|
||||||
link_to_remote("Remote outpost", { :url => { :action => "whatnot" }}, { :class => "fine" })
|
|
||||||
assert_dom_equal %(<a href=\"#\" onclick=\"new Ajax.Request('http://www.example.com/whatnot', {asynchronous:true, evalScripts:true, onComplete:function(request){alert(request.reponseText)}}); return false;\">Remote outpost</a>),
|
|
||||||
link_to_remote("Remote outpost", :complete => "alert(request.reponseText)", :url => { :action => "whatnot" })
|
|
||||||
assert_dom_equal %(<a href=\"#\" onclick=\"new Ajax.Request('http://www.example.com/whatnot', {asynchronous:true, evalScripts:true, onSuccess:function(request){alert(request.reponseText)}}); return false;\">Remote outpost</a>),
|
|
||||||
link_to_remote("Remote outpost", :success => "alert(request.reponseText)", :url => { :action => "whatnot" })
|
|
||||||
assert_dom_equal %(<a href=\"#\" onclick=\"new Ajax.Request('http://www.example.com/whatnot', {asynchronous:true, evalScripts:true, onFailure:function(request){alert(request.reponseText)}}); return false;\">Remote outpost</a>),
|
|
||||||
link_to_remote("Remote outpost", :failure => "alert(request.reponseText)", :url => { :action => "whatnot" })
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_periodically_call_remote
|
|
||||||
assert_dom_equal %(<script type="text/javascript">\n//<![CDATA[\nnew PeriodicalExecuter(function() {new Ajax.Updater('schremser_bier', 'http://www.example.com/mehr_bier', {asynchronous:true, evalScripts:true})}, 10)\n//]]>\n</script>),
|
|
||||||
periodically_call_remote(:update => "schremser_bier", :url => { :action => "mehr_bier" })
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_form_remote_tag
|
|
||||||
assert_dom_equal %(<form action=\"http://www.example.com/fast\" method=\"post\" onsubmit=\"new Ajax.Updater('glass_of_beer', 'http://www.example.com/fast', {asynchronous:true, evalScripts:true, parameters:Form.serialize(this)}); return false;\">),
|
|
||||||
form_remote_tag(:update => "glass_of_beer", :url => { :action => :fast })
|
|
||||||
assert_dom_equal %(<form action=\"http://www.example.com/fast\" method=\"post\" onsubmit=\"new Ajax.Updater({success:'glass_of_beer'}, 'http://www.example.com/fast', {asynchronous:true, evalScripts:true, parameters:Form.serialize(this)}); return false;\">),
|
|
||||||
form_remote_tag(:update => { :success => "glass_of_beer" }, :url => { :action => :fast })
|
|
||||||
assert_dom_equal %(<form action=\"http://www.example.com/fast\" method=\"post\" onsubmit=\"new Ajax.Updater({failure:'glass_of_water'}, 'http://www.example.com/fast', {asynchronous:true, evalScripts:true, parameters:Form.serialize(this)}); return false;\">),
|
|
||||||
form_remote_tag(:update => { :failure => "glass_of_water" }, :url => { :action => :fast })
|
|
||||||
assert_dom_equal %(<form action=\"http://www.example.com/fast\" method=\"post\" onsubmit=\"new Ajax.Updater({success:'glass_of_beer',failure:'glass_of_water'}, 'http://www.example.com/fast', {asynchronous:true, evalScripts:true, parameters:Form.serialize(this)}); return false;\">),
|
|
||||||
form_remote_tag(:update => { :success => 'glass_of_beer', :failure => "glass_of_water" }, :url => { :action => :fast })
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_on_callbacks
|
|
||||||
callbacks = [:uninitialized, :loading, :loaded, :interactive, :complete, :success, :failure]
|
|
||||||
callbacks.each do |callback|
|
|
||||||
assert_dom_equal %(<form action=\"http://www.example.com/fast\" method=\"post\" onsubmit=\"new Ajax.Updater('glass_of_beer', 'http://www.example.com/fast', {asynchronous:true, evalScripts:true, on#{callback.to_s.capitalize}:function(request){monkeys();}, parameters:Form.serialize(this)}); return false;">),
|
|
||||||
form_remote_tag(:update => "glass_of_beer", :url => { :action => :fast }, callback=>"monkeys();")
|
|
||||||
assert_dom_equal %(<form action=\"http://www.example.com/fast\" method=\"post\" onsubmit=\"new Ajax.Updater({success:'glass_of_beer'}, 'http://www.example.com/fast', {asynchronous:true, evalScripts:true, on#{callback.to_s.capitalize}:function(request){monkeys();}, parameters:Form.serialize(this)}); return false;">),
|
|
||||||
form_remote_tag(:update => { :success => "glass_of_beer" }, :url => { :action => :fast }, callback=>"monkeys();")
|
|
||||||
assert_dom_equal %(<form action=\"http://www.example.com/fast\" method=\"post\" onsubmit=\"new Ajax.Updater({failure:'glass_of_beer'}, 'http://www.example.com/fast', {asynchronous:true, evalScripts:true, on#{callback.to_s.capitalize}:function(request){monkeys();}, parameters:Form.serialize(this)}); return false;">),
|
|
||||||
form_remote_tag(:update => { :failure => "glass_of_beer" }, :url => { :action => :fast }, callback=>"monkeys();")
|
|
||||||
assert_dom_equal %(<form action=\"http://www.example.com/fast\" method=\"post\" onsubmit=\"new Ajax.Updater({success:'glass_of_beer',failure:'glass_of_water'}, 'http://www.example.com/fast', {asynchronous:true, evalScripts:true, on#{callback.to_s.capitalize}:function(request){monkeys();}, parameters:Form.serialize(this)}); return false;">),
|
|
||||||
form_remote_tag(:update => { :success => "glass_of_beer", :failure => "glass_of_water" }, :url => { :action => :fast }, callback=>"monkeys();")
|
|
||||||
end
|
|
||||||
|
|
||||||
#HTTP status codes 200 up to 599 have callbacks
|
|
||||||
#these should work
|
|
||||||
100.upto(599) do |callback|
|
|
||||||
assert_dom_equal %(<form action=\"http://www.example.com/fast\" method=\"post\" onsubmit=\"new Ajax.Updater('glass_of_beer', 'http://www.example.com/fast', {asynchronous:true, evalScripts:true, on#{callback.to_s.capitalize}:function(request){monkeys();}, parameters:Form.serialize(this)}); return false;">),
|
|
||||||
form_remote_tag(:update => "glass_of_beer", :url => { :action => :fast }, callback=>"monkeys();")
|
|
||||||
end
|
|
||||||
|
|
||||||
#test 200 and 404
|
|
||||||
assert_dom_equal %(<form action=\"http://www.example.com/fast\" method=\"post\" onsubmit=\"new Ajax.Updater('glass_of_beer', 'http://www.example.com/fast', {asynchronous:true, evalScripts:true, on200:function(request){monkeys();}, on404:function(request){bananas();}, parameters:Form.serialize(this)}); return false;">),
|
|
||||||
form_remote_tag(:update => "glass_of_beer", :url => { :action => :fast }, 200=>"monkeys();", 404=>"bananas();")
|
|
||||||
|
|
||||||
#these shouldn't
|
|
||||||
1.upto(99) do |callback|
|
|
||||||
assert_dom_equal %(<form action=\"http://www.example.com/fast\" method=\"post\" onsubmit=\"new Ajax.Updater('glass_of_beer', 'http://www.example.com/fast', {asynchronous:true, evalScripts:true, parameters:Form.serialize(this)}); return false;">),
|
|
||||||
form_remote_tag(:update => "glass_of_beer", :url => { :action => :fast }, callback=>"monkeys();")
|
|
||||||
end
|
|
||||||
600.upto(999) do |callback|
|
|
||||||
assert_dom_equal %(<form action=\"http://www.example.com/fast\" method=\"post\" onsubmit=\"new Ajax.Updater('glass_of_beer', 'http://www.example.com/fast', {asynchronous:true, evalScripts:true, parameters:Form.serialize(this)}); return false;">),
|
|
||||||
form_remote_tag(:update => "glass_of_beer", :url => { :action => :fast }, callback=>"monkeys();")
|
|
||||||
end
|
|
||||||
|
|
||||||
#test ultimate combo
|
|
||||||
assert_dom_equal %(<form action=\"http://www.example.com/fast\" method=\"post\" onsubmit=\"new Ajax.Updater('glass_of_beer', 'http://www.example.com/fast', {asynchronous:true, evalScripts:true, on200:function(request){monkeys();}, on404:function(request){bananas();}, onComplete:function(request){c();}, onFailure:function(request){f();}, onLoading:function(request){c1()}, onSuccess:function(request){s()}, parameters:Form.serialize(this)}); return false;\">),
|
|
||||||
form_remote_tag(:update => "glass_of_beer", :url => { :action => :fast }, :loading => "c1()", :success => "s()", :failure => "f();", :complete => "c();", 200=>"monkeys();", 404=>"bananas();")
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_submit_to_remote
|
|
||||||
assert_dom_equal %(<input name=\"More beer!\" onclick=\"new Ajax.Updater('empty_bottle', 'http://www.example.com/', {asynchronous:true, evalScripts:true, parameters:Form.serialize(this.form)}); return false;\" type=\"button\" value=\"1000000\" />),
|
|
||||||
submit_to_remote("More beer!", 1_000_000, :update => "empty_bottle")
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_observe_field
|
|
||||||
assert_dom_equal %(<script type=\"text/javascript\">\n//<![CDATA[\nnew Form.Element.Observer('glass', 300, function(element, value) {new Ajax.Request('http://www.example.com/reorder_if_empty', {asynchronous:true, evalScripts:true})})\n//]]>\n</script>),
|
|
||||||
observe_field("glass", :frequency => 5.minutes, :url => { :action => "reorder_if_empty" })
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_observe_form
|
|
||||||
assert_dom_equal %(<script type=\"text/javascript\">\n//<![CDATA[\nnew Form.Observer('cart', 2, function(element, value) {new Ajax.Request('http://www.example.com/cart_changed', {asynchronous:true, evalScripts:true})})\n//]]>\n</script>),
|
|
||||||
observe_form("cart", :frequency => 2, :url => { :action => "cart_changed" })
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_effect
|
|
||||||
assert_equal "new Effect.Highlight('posts',{});", visual_effect(:highlight, "posts")
|
|
||||||
assert_equal "new Effect.Highlight('posts',{});", visual_effect("highlight", :posts)
|
|
||||||
assert_equal "new Effect.Highlight('posts',{});", visual_effect(:highlight, :posts)
|
|
||||||
assert_equal "new Effect.Fade('fademe',{duration:4.0});", visual_effect(:fade, "fademe", :duration => 4.0)
|
|
||||||
assert_equal "new Effect.Shake(element,{});", visual_effect(:shake)
|
|
||||||
assert_equal "new Effect.DropOut('dropme',{queue:'end'});", visual_effect(:drop_out, 'dropme', :queue => :end)
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_sortable_element
|
|
||||||
assert_dom_equal %(<script type=\"text/javascript\">\n//<![CDATA[\nSortable.create('mylist', {onUpdate:function(){new Ajax.Request('http://www.example.com/order', {asynchronous:true, evalScripts:true, parameters:Sortable.serialize('mylist')})}})\n//]]>\n</script>),
|
|
||||||
sortable_element("mylist", :url => { :action => "order" })
|
|
||||||
assert_equal %(<script type=\"text/javascript\">\n//<![CDATA[\nSortable.create('mylist', {constraint:'horizontal', onUpdate:function(){new Ajax.Request('http://www.example.com/order', {asynchronous:true, evalScripts:true, parameters:Sortable.serialize('mylist')})}, tag:'div'})\n//]]>\n</script>),
|
|
||||||
sortable_element("mylist", :tag => "div", :constraint => "horizontal", :url => { :action => "order" })
|
|
||||||
assert_dom_equal %|<script type=\"text/javascript\">\n//<![CDATA[\nSortable.create('mylist', {constraint:'horizontal', containment:['list1','list2'], onUpdate:function(){new Ajax.Request('http://www.example.com/order', {asynchronous:true, evalScripts:true, parameters:Sortable.serialize('mylist')})}})\n//]]>\n</script>|,
|
|
||||||
sortable_element("mylist", :containment => ['list1','list2'], :constraint => "horizontal", :url => { :action => "order" })
|
|
||||||
assert_dom_equal %(<script type=\"text/javascript\">\n//<![CDATA[\nSortable.create('mylist', {constraint:'horizontal', containment:'list1', onUpdate:function(){new Ajax.Request('http://www.example.com/order', {asynchronous:true, evalScripts:true, parameters:Sortable.serialize('mylist')})}})\n//]]>\n</script>),
|
|
||||||
sortable_element("mylist", :containment => 'list1', :constraint => "horizontal", :url => { :action => "order" })
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_draggable_element
|
|
||||||
assert_dom_equal %(<script type=\"text/javascript\">\n//<![CDATA[\nnew Draggable('product_13', {})\n//]]>\n</script>),
|
|
||||||
draggable_element('product_13')
|
|
||||||
assert_equal %(<script type=\"text/javascript\">\n//<![CDATA[\nnew Draggable('product_13', {revert:true})\n//]]>\n</script>),
|
|
||||||
draggable_element('product_13', :revert => true)
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_drop_receiving_element
|
|
||||||
assert_dom_equal %(<script type=\"text/javascript\">\n//<![CDATA[\nDroppables.add('droptarget1', {onDrop:function(element){new Ajax.Request('http://www.example.com/', {asynchronous:true, evalScripts:true, parameters:'id=' + encodeURIComponent(element.id)})}})\n//]]>\n</script>),
|
|
||||||
drop_receiving_element('droptarget1')
|
|
||||||
assert_dom_equal %(<script type=\"text/javascript\">\n//<![CDATA[\nDroppables.add('droptarget1', {accept:'products', onDrop:function(element){new Ajax.Request('http://www.example.com/', {asynchronous:true, evalScripts:true, parameters:'id=' + encodeURIComponent(element.id)})}})\n//]]>\n</script>),
|
|
||||||
drop_receiving_element('droptarget1', :accept => 'products')
|
|
||||||
assert_dom_equal %(<script type=\"text/javascript\">\n//<![CDATA[\nDroppables.add('droptarget1', {accept:'products', onDrop:function(element){new Ajax.Updater('infobox', 'http://www.example.com/', {asynchronous:true, evalScripts:true, parameters:'id=' + encodeURIComponent(element.id)})}})\n//]]>\n</script>),
|
|
||||||
drop_receiving_element('droptarget1', :accept => 'products', :update => 'infobox')
|
|
||||||
assert_dom_equal %(<script type=\"text/javascript\">\n//<![CDATA[\nDroppables.add('droptarget1', {accept:['tshirts','mugs'], onDrop:function(element){new Ajax.Updater('infobox', 'http://www.example.com/', {asynchronous:true, evalScripts:true, parameters:'id=' + encodeURIComponent(element.id)})}})\n//]]>\n</script>),
|
|
||||||
drop_receiving_element('droptarget1', :accept => ['tshirts','mugs'], :update => 'infobox')
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_update_element_function
|
|
||||||
assert_equal %($('myelement').innerHTML = 'blub';\n),
|
|
||||||
update_element_function('myelement', :content => 'blub')
|
|
||||||
assert_equal %($('myelement').innerHTML = 'blub';\n),
|
|
||||||
update_element_function('myelement', :action => :update, :content => 'blub')
|
|
||||||
assert_equal %($('myelement').innerHTML = '';\n),
|
|
||||||
update_element_function('myelement', :action => :empty)
|
|
||||||
assert_equal %(Element.remove('myelement');\n),
|
|
||||||
update_element_function('myelement', :action => :remove)
|
|
||||||
|
|
||||||
assert_equal %(new Insertion.Bottom('myelement','blub');\n),
|
|
||||||
update_element_function('myelement', :position => 'bottom', :content => 'blub')
|
|
||||||
assert_equal %(new Insertion.Bottom('myelement','blub');\n),
|
|
||||||
update_element_function('myelement', :action => :update, :position => :bottom, :content => 'blub')
|
|
||||||
|
|
||||||
_erbout = ""
|
|
||||||
assert_equal %($('myelement').innerHTML = 'test';\n),
|
|
||||||
update_element_function('myelement') { _erbout << "test" }
|
|
||||||
|
|
||||||
_erbout = ""
|
|
||||||
assert_equal %($('myelement').innerHTML = 'blockstuff';\n),
|
|
||||||
update_element_function('myelement', :content => 'paramstuff') { _erbout << "blockstuff" }
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_update_page
|
|
||||||
block = Proc.new { |page| page.replace_html('foo', 'bar') }
|
|
||||||
assert_equal create_generator(&block).to_s, update_page(&block)
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_update_page_tag
|
|
||||||
block = Proc.new { |page| page.replace_html('foo', 'bar') }
|
|
||||||
assert_equal javascript_tag(create_generator(&block).to_s), update_page_tag(&block)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
class JavaScriptGeneratorTest < Test::Unit::TestCase
|
|
||||||
include BaseTest
|
|
||||||
|
|
||||||
def setup
|
|
||||||
super
|
|
||||||
@generator = create_generator
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_insert_html_with_string
|
|
||||||
assert_equal 'new Insertion.Top("element", "<p>This is a test</p>");',
|
|
||||||
@generator.insert_html(:top, 'element', '<p>This is a test</p>')
|
|
||||||
assert_equal 'new Insertion.Bottom("element", "<p>This is a test</p>");',
|
|
||||||
@generator.insert_html(:bottom, 'element', '<p>This is a test</p>')
|
|
||||||
assert_equal 'new Insertion.Before("element", "<p>This is a test</p>");',
|
|
||||||
@generator.insert_html(:before, 'element', '<p>This is a test</p>')
|
|
||||||
assert_equal 'new Insertion.After("element", "<p>This is a test</p>");',
|
|
||||||
@generator.insert_html(:after, 'element', '<p>This is a test</p>')
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_replace_html_with_string
|
|
||||||
assert_equal 'Element.update("element", "<p>This is a test</p>");',
|
|
||||||
@generator.replace_html('element', '<p>This is a test</p>')
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_remove
|
|
||||||
assert_equal '["foo"].each(Element.remove);',
|
|
||||||
@generator.remove('foo')
|
|
||||||
assert_equal '["foo", "bar", "baz"].each(Element.remove);',
|
|
||||||
@generator.remove('foo', 'bar', 'baz')
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_show
|
|
||||||
assert_equal 'Element.show("foo");',
|
|
||||||
@generator.show('foo')
|
|
||||||
assert_equal 'Element.show("foo", "bar", "baz");',
|
|
||||||
@generator.show('foo', 'bar', 'baz')
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_hide
|
|
||||||
assert_equal 'Element.hide("foo");',
|
|
||||||
@generator.hide('foo')
|
|
||||||
assert_equal 'Element.hide("foo", "bar", "baz");',
|
|
||||||
@generator.hide('foo', 'bar', 'baz')
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_alert
|
|
||||||
assert_equal 'alert("hello");', @generator.alert('hello')
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_redirect_to
|
|
||||||
assert_equal 'window.location.href = "http://www.example.com/welcome";',
|
|
||||||
@generator.redirect_to(:action => 'welcome')
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_delay
|
|
||||||
@generator.delay(20) do
|
|
||||||
@generator.hide('foo')
|
|
||||||
end
|
|
||||||
|
|
||||||
assert_equal "setTimeout(function() {\n;\nElement.hide(\"foo\");\n}, 20000);", @generator.to_s
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_to_s
|
|
||||||
@generator.insert_html(:top, 'element', '<p>This is a test</p>')
|
|
||||||
@generator.insert_html(:bottom, 'element', '<p>This is a test</p>')
|
|
||||||
@generator.remove('foo', 'bar')
|
|
||||||
@generator.replace_html('baz', '<p>This is a test</p>')
|
|
||||||
|
|
||||||
assert_equal <<-EOS.chomp, @generator.to_s
|
|
||||||
new Insertion.Top("element", "<p>This is a test</p>");
|
|
||||||
new Insertion.Bottom("element", "<p>This is a test</p>");
|
|
||||||
["foo", "bar"].each(Element.remove);
|
|
||||||
Element.update("baz", "<p>This is a test</p>");
|
|
||||||
EOS
|
|
||||||
end
|
|
||||||
end
|
|
||||||
211
tracks/vendor/rails/actionmailer/CHANGELOG
vendored
211
tracks/vendor/rails/actionmailer/CHANGELOG
vendored
|
|
@ -1,211 +0,0 @@
|
||||||
*1.1.5* (December 13th, 2005)
|
|
||||||
|
|
||||||
* Become part of Rails 1.0
|
|
||||||
|
|
||||||
|
|
||||||
*1.1.4* (December 7th, 2005)
|
|
||||||
|
|
||||||
* Rename Version constant to VERSION. #2802 [Marcel Molina Jr.]
|
|
||||||
|
|
||||||
|
|
||||||
*1.1.3* (November 7th, 2005)
|
|
||||||
|
|
||||||
* Allow Mailers to have custom initialize methods that set default instance variables for all mail actions #2563 [mrj@bigpond.net.au]
|
|
||||||
|
|
||||||
|
|
||||||
*1.1.2* (October 26th, 2005)
|
|
||||||
|
|
||||||
* Upgraded to Action Pack 1.10.2
|
|
||||||
|
|
||||||
|
|
||||||
*1.1.1* (October 19th, 2005)
|
|
||||||
|
|
||||||
* Upgraded to Action Pack 1.10.1
|
|
||||||
|
|
||||||
|
|
||||||
*1.1.0* (October 16th, 2005)
|
|
||||||
|
|
||||||
* Update and extend documentation (rdoc)
|
|
||||||
|
|
||||||
* Minero Aoki made TMail available to Rails/ActionMailer under the MIT license (instead of LGPL) [RubyConf '05]
|
|
||||||
|
|
||||||
* Austin Ziegler made Text::Simple available to Rails/ActionMailer under a MIT-like licens [See rails ML, subject "Text::Format Licence Exception" on Oct 15, 2005]
|
|
||||||
|
|
||||||
* Fix vendor require paths to prevent files being required twice
|
|
||||||
|
|
||||||
* Don't add charset to content-type header for a part that contains subparts (for AOL compatibility) #2013 [John Long]
|
|
||||||
|
|
||||||
* Preserve underscores when unquoting message bodies #1930
|
|
||||||
|
|
||||||
* Encode multibyte characters correctly #1894
|
|
||||||
|
|
||||||
* Multipart messages specify a MIME-Version header automatically #2003 [John Long]
|
|
||||||
|
|
||||||
* Add a unified render method to ActionMailer (delegates to ActionView::Base#render)
|
|
||||||
|
|
||||||
* Move mailer initialization to a separate (overridable) method, so that subclasses may alter the various defaults #1727
|
|
||||||
|
|
||||||
* Look at content-location header (if available) to determine filename of attachments #1670
|
|
||||||
|
|
||||||
* ActionMailer::Base.deliver(email) had been accidentally removed, but was documented in the Rails book #1849
|
|
||||||
|
|
||||||
* Fix problem with sendmail delivery where headers should be delimited by \n characters instead of \r\n, which confuses some mail readers #1742 [Kent Sibilev]
|
|
||||||
|
|
||||||
|
|
||||||
*1.0.1* (11 July, 2005)
|
|
||||||
|
|
||||||
* Bind to Action Pack 1.9.1
|
|
||||||
|
|
||||||
|
|
||||||
*1.0.0* (6 July, 2005)
|
|
||||||
|
|
||||||
* Avoid adding nil header values #1392
|
|
||||||
|
|
||||||
* Better multipart support with implicit multipart/alternative and sorting of subparts [John Long]
|
|
||||||
|
|
||||||
* Allow for nested parts in multipart mails #1570 [Flurin Egger]
|
|
||||||
|
|
||||||
* Normalize line endings in outgoing mail bodies to "\n" #1536 [John Long]
|
|
||||||
|
|
||||||
* Allow template to be explicitly specified #1448 [tuxie@dekadance.se]
|
|
||||||
|
|
||||||
* Allow specific "multipart/xxx" content-type to be set on multipart messages #1412 [Flurin Egger]
|
|
||||||
|
|
||||||
* Unquoted @ characters in headers are now accepted in spite of RFC 822 #1206
|
|
||||||
|
|
||||||
* Helper support (borrowed from ActionPack)
|
|
||||||
|
|
||||||
* Silently ignore Errno::EINVAL errors when converting text.
|
|
||||||
|
|
||||||
* Don't cause an error when parsing an encoded attachment name #1340 [lon@speedymac.com]
|
|
||||||
|
|
||||||
* Nested multipart message parts are correctly processed in TMail::Mail#body
|
|
||||||
|
|
||||||
* BCC headers are removed when sending via SMTP #1402
|
|
||||||
|
|
||||||
* Added 'content_type' accessor, to allow content type to be set on a per-message basis. content_type defaults to "text/plain".
|
|
||||||
|
|
||||||
* Silently ignore Iconv::IllegalSequence errors when converting text #1341 [lon@speedymac.com]
|
|
||||||
|
|
||||||
* Support attachments and multipart messages.
|
|
||||||
|
|
||||||
* Added new accessors for the various mail properties.
|
|
||||||
|
|
||||||
* Fix to only perform the charset conversion if a 'from' and a 'to' charset are given (make no assumptions about what the charset was) #1276 [Jamis Buck]
|
|
||||||
|
|
||||||
* Fix attachments and content-type problems #1276 [Jamis Buck]
|
|
||||||
|
|
||||||
* Fixed the TMail#body method to look at the content-transfer-encoding header and unquote the body according to the rules it specifies #1265 [Jamis Buck]
|
|
||||||
|
|
||||||
* Added unquoting even if the iconv lib can't be loaded--in that case, only the charset conversion is skipped #1265 [Jamis Buck]
|
|
||||||
|
|
||||||
* Added automatic decoding of base64 bodies #1214 [Jamis Buck]
|
|
||||||
|
|
||||||
* Added that delivery errors are caught in a way so the mail is still returned whether the delivery was successful or not
|
|
||||||
|
|
||||||
* Fixed that email address like "Jamis Buck, M.D." <wild.medicine@example.net> would cause the quoter to generate emails resulting in "bad address" errors from the mail server #1220 [Jamis Buck]
|
|
||||||
|
|
||||||
|
|
||||||
*0.9.1* (20th April, 2005)
|
|
||||||
|
|
||||||
* Depend on Action Pack 1.8.1
|
|
||||||
|
|
||||||
|
|
||||||
*0.9.0* (19th April, 2005)
|
|
||||||
|
|
||||||
* Added that deliver_* will now return the email that was sent
|
|
||||||
|
|
||||||
* Added that quoting to UTF-8 only happens if the characters used are in that range #955 [Jamis Buck]
|
|
||||||
|
|
||||||
* Fixed quoting for all address headers, not just to #955 [Jamis Buck]
|
|
||||||
|
|
||||||
* Fixed unquoting of emails that doesn't have an explicit charset #1036 [wolfgang@stufenlos.net]
|
|
||||||
|
|
||||||
|
|
||||||
*0.8.1* (27th March, 2005)
|
|
||||||
|
|
||||||
* Fixed that if charset was found that the end of a mime part declaration TMail would throw an error #919 [lon@speedymac.com]
|
|
||||||
|
|
||||||
* Fixed that TMail::Unquoter would fail to recognize quoting method if it was in lowercase #919 [lon@speedymac.com]
|
|
||||||
|
|
||||||
* Fixed that TMail::Encoder would fail when it attempts to parse e-mail addresses which are encoded using something other than the messages encoding method #919 [lon@speedymac.com]
|
|
||||||
|
|
||||||
* Added rescue for missing iconv library and throws warnings if subject/body is called on a TMail object without it instead
|
|
||||||
|
|
||||||
|
|
||||||
*0.8.0* (22th March, 2005)
|
|
||||||
|
|
||||||
* Added framework support for processing incoming emails with an Action Mailer class. See example in README.
|
|
||||||
|
|
||||||
|
|
||||||
*0.7.1* (7th March, 2005)
|
|
||||||
|
|
||||||
* Bind to newest Action Pack (1.5.1)
|
|
||||||
|
|
||||||
|
|
||||||
*0.7.0* (24th February, 2005)
|
|
||||||
|
|
||||||
* Added support for charsets for both subject and body. The default charset is now UTF-8 #673 [Jamis Buck]. Examples:
|
|
||||||
|
|
||||||
def iso_charset(recipient)
|
|
||||||
@recipients = recipient
|
|
||||||
@subject = "testing iso charsets"
|
|
||||||
@from = "system@loudthinking.com"
|
|
||||||
@body = "Nothing to see here."
|
|
||||||
@charset = "iso-8859-1"
|
|
||||||
end
|
|
||||||
|
|
||||||
def unencoded_subject(recipient)
|
|
||||||
@recipients = recipient
|
|
||||||
@subject = "testing unencoded subject"
|
|
||||||
@from = "system@loudthinking.com"
|
|
||||||
@body = "Nothing to see here."
|
|
||||||
@encode_subject = false
|
|
||||||
@charset = "iso-8859-1"
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
*0.6.1* (January 18th, 2005)
|
|
||||||
|
|
||||||
* Fixed sending of emails to use Tmail#from not the deprecated Tmail#from_address
|
|
||||||
|
|
||||||
|
|
||||||
*0.6* (January 17th, 2005)
|
|
||||||
|
|
||||||
* Fixed that bcc and cc should be settable through @bcc and @cc -- not just @headers["Bcc"] and @headers["Cc"] #453 [Eric Hodel]
|
|
||||||
|
|
||||||
* Fixed Action Mailer to be "warnings safe" so you can run with ruby -w and not get framework warnings #453 [Eric Hodel]
|
|
||||||
|
|
||||||
|
|
||||||
*0.5*
|
|
||||||
|
|
||||||
* Added access to custom headers, like cc, bcc, and reply-to #268 [Andreas Schwarz]. Example:
|
|
||||||
|
|
||||||
def post_notification(recipients, post)
|
|
||||||
@recipients = recipients
|
|
||||||
@from = post.author.email_address_with_name
|
|
||||||
@headers["bcc"] = SYSTEM_ADMINISTRATOR_EMAIL
|
|
||||||
@headers["reply-to"] = "notifications@example.com"
|
|
||||||
@subject = "[#{post.account.name} #{post.title}]"
|
|
||||||
@body["post"] = post
|
|
||||||
end
|
|
||||||
|
|
||||||
*0.4* (5)
|
|
||||||
|
|
||||||
* Consolidated the server configuration options into Base#server_settings= and expanded that with controls for authentication and more [Marten]
|
|
||||||
NOTE: This is an API change that could potentially break your application if you used the old application form. Please do change!
|
|
||||||
|
|
||||||
* Added Base#deliveries as an accessor for an array of emails sent out through that ActionMailer class when using the :test delivery option. [Jeremy Kemper]
|
|
||||||
|
|
||||||
* Added Base#perform_deliveries= which can be set to false to turn off the actual delivery of the email through smtp or sendmail.
|
|
||||||
This is especially useful for functional testing that shouldn't send off real emails, but still trigger delivery_* methods.
|
|
||||||
|
|
||||||
* Added option to specify delivery method with Base#delivery_method=. Default is :smtp and :sendmail is currently the only other option.
|
|
||||||
Sendmail is assumed to be present at "/usr/sbin/sendmail" if that option is used. [Kent Sibilev]
|
|
||||||
|
|
||||||
* Dropped "include TMail" as it added to much baggage into the default namespace (like Version) [Chad Fowler]
|
|
||||||
|
|
||||||
|
|
||||||
*0.3*
|
|
||||||
|
|
||||||
* First release
|
|
||||||
21
tracks/vendor/rails/actionmailer/MIT-LICENSE
vendored
21
tracks/vendor/rails/actionmailer/MIT-LICENSE
vendored
|
|
@ -1,21 +0,0 @@
|
||||||
Copyright (c) 2004 David Heinemeier Hansson
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining
|
|
||||||
a copy of this software and associated documentation files (the
|
|
||||||
"Software"), to deal in the Software without restriction, including
|
|
||||||
without limitation the rights to use, copy, modify, merge, publish,
|
|
||||||
distribute, sublicense, and/or sell copies of the Software, and to
|
|
||||||
permit persons to whom the Software is furnished to do so, subject to
|
|
||||||
the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be
|
|
||||||
included in all copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
||||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
||||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
||||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
||||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
||||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
||||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
||||||
|
|
||||||
148
tracks/vendor/rails/actionmailer/README
vendored
148
tracks/vendor/rails/actionmailer/README
vendored
|
|
@ -1,148 +0,0 @@
|
||||||
= Action Mailer -- Easy email delivery and testing
|
|
||||||
|
|
||||||
Action Mailer is a framework for designing email-service layers. These layers
|
|
||||||
are used to consolidate code for sending out forgotten passwords, welcoming
|
|
||||||
wishes on signup, invoices for billing, and any other use case that requires
|
|
||||||
a written notification to either a person or another system.
|
|
||||||
|
|
||||||
Additionally, an Action Mailer class can be used to process incoming email,
|
|
||||||
such as allowing a weblog to accept new posts from an email (which could even
|
|
||||||
have been sent from a phone).
|
|
||||||
|
|
||||||
== Sending emails
|
|
||||||
|
|
||||||
The framework works by setting up all the email details, except the body,
|
|
||||||
in methods on the service layer. Subject, recipients, sender, and timestamp
|
|
||||||
are all set up this way. An example of such a method:
|
|
||||||
|
|
||||||
def signed_up(recipient)
|
|
||||||
recipients recipient
|
|
||||||
subject "[Signed up] Welcome #{recipient}"
|
|
||||||
from "system@loudthinking.com"
|
|
||||||
|
|
||||||
body(:recipient => recipient)
|
|
||||||
end
|
|
||||||
|
|
||||||
The body of the email is created by using an Action View template (regular
|
|
||||||
ERb) that has the content of the body hash parameter available as instance variables.
|
|
||||||
So the corresponding body template for the method above could look like this:
|
|
||||||
|
|
||||||
Hello there,
|
|
||||||
|
|
||||||
Mr. <%= @recipient %>
|
|
||||||
|
|
||||||
And if the recipient was given as "david@loudthinking.com", the email
|
|
||||||
generated would look like this:
|
|
||||||
|
|
||||||
Date: Sun, 12 Dec 2004 00:00:00 +0100
|
|
||||||
From: system@loudthinking.com
|
|
||||||
To: david@loudthinking.com
|
|
||||||
Subject: [Signed up] Welcome david@loudthinking.com
|
|
||||||
|
|
||||||
Hello there,
|
|
||||||
|
|
||||||
Mr. david@loudthinking.com
|
|
||||||
|
|
||||||
You never actually call the instance methods like signed_up directly. Instead,
|
|
||||||
you call class methods like deliver_* and create_* that are automatically
|
|
||||||
created for each instance method. So if the signed_up method sat on
|
|
||||||
ApplicationMailer, it would look like this:
|
|
||||||
|
|
||||||
ApplicationMailer.create_signed_up("david@loudthinking.com") # => tmail object for testing
|
|
||||||
ApplicationMailer.deliver_signed_up("david@loudthinking.com") # sends the email
|
|
||||||
ApplicationMailer.new.signed_up("david@loudthinking.com") # won't work!
|
|
||||||
|
|
||||||
== Receiving emails
|
|
||||||
|
|
||||||
To receive emails, you need to implement a public instance method called receive that takes a
|
|
||||||
tmail object as its single parameter. The Action Mailer framework has a corresponding class method,
|
|
||||||
which is also called receive, that accepts a raw, unprocessed email as a string, which it then turns
|
|
||||||
into the tmail object and calls the receive instance method.
|
|
||||||
|
|
||||||
Example:
|
|
||||||
|
|
||||||
class Mailman < ActionMailer::Base
|
|
||||||
def receive(email)
|
|
||||||
page = Page.find_by_address(email.to.first)
|
|
||||||
page.emails.create(
|
|
||||||
:subject => email.subject, :body => email.body
|
|
||||||
)
|
|
||||||
|
|
||||||
if email.has_attachments?
|
|
||||||
for attachment in email.attachments
|
|
||||||
page.attachments.create({
|
|
||||||
:file => attachment, :description => email.subject
|
|
||||||
})
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
This Mailman can be the target for Postfix. In Rails, you would use the runner like this:
|
|
||||||
|
|
||||||
./script/runner 'Mailman.receive(STDIN.read)'
|
|
||||||
|
|
||||||
== Configuration
|
|
||||||
|
|
||||||
The Base class has the full list of configuration options. Here's an example:
|
|
||||||
|
|
||||||
ActionMailer::Base.server_settings = {
|
|
||||||
:address=>'smtp.yourserver.com', # default: localhost
|
|
||||||
:port=>'25', # default: 25
|
|
||||||
:user_name=>'user',
|
|
||||||
:password=>'pass',
|
|
||||||
:authentication=>:plain # :plain, :login or :cram_md5
|
|
||||||
}
|
|
||||||
|
|
||||||
== Dependencies
|
|
||||||
|
|
||||||
Action Mailer requires that the Action Pack is either available to be required immediately
|
|
||||||
or is accessible as a GEM.
|
|
||||||
|
|
||||||
|
|
||||||
== Bundled software
|
|
||||||
|
|
||||||
* tmail 0.10.8 by Minero Aoki released under LGPL
|
|
||||||
Read more on http://i.loveruby.net/en/prog/tmail.html
|
|
||||||
|
|
||||||
* Text::Format 0.63 by Austin Ziegler released under OpenSource
|
|
||||||
Read more on http://www.halostatue.ca/ruby/Text__Format.html
|
|
||||||
|
|
||||||
|
|
||||||
== Download
|
|
||||||
|
|
||||||
The latest version of Action Mailer can be found at
|
|
||||||
|
|
||||||
* http://rubyforge.org/project/showfiles.php?group_id=361
|
|
||||||
|
|
||||||
Documentation can be found at
|
|
||||||
|
|
||||||
* http://actionmailer.rubyonrails.org
|
|
||||||
|
|
||||||
|
|
||||||
== Installation
|
|
||||||
|
|
||||||
You can install Action Mailer with the following command.
|
|
||||||
|
|
||||||
% [sudo] ruby install.rb
|
|
||||||
|
|
||||||
from its distribution directory.
|
|
||||||
|
|
||||||
|
|
||||||
== License
|
|
||||||
|
|
||||||
Action Mailer is released under the MIT license.
|
|
||||||
|
|
||||||
|
|
||||||
== Support
|
|
||||||
|
|
||||||
The Action Mailer homepage is http://actionmailer.rubyonrails.org. You can find
|
|
||||||
the Action Mailer RubyForge page at http://rubyforge.org/projects/actionmailer.
|
|
||||||
And as Jim from Rake says:
|
|
||||||
|
|
||||||
Feel free to submit commits or feature requests. If you send a patch,
|
|
||||||
remember to update the corresponding unit tests. If fact, I prefer
|
|
||||||
new feature to be submitted in the form of new unit tests.
|
|
||||||
|
|
||||||
For other information, feel free to ask on the ruby-talk mailing list (which
|
|
||||||
is mirrored to comp.lang.ruby) or contact mailto:david@loudthinking.com.
|
|
||||||
30
tracks/vendor/rails/actionmailer/install.rb
vendored
30
tracks/vendor/rails/actionmailer/install.rb
vendored
|
|
@ -1,30 +0,0 @@
|
||||||
require 'rbconfig'
|
|
||||||
require 'find'
|
|
||||||
require 'ftools'
|
|
||||||
|
|
||||||
include Config
|
|
||||||
|
|
||||||
# this was adapted from rdoc's install.rb by way of Log4r
|
|
||||||
|
|
||||||
$sitedir = CONFIG["sitelibdir"]
|
|
||||||
unless $sitedir
|
|
||||||
version = CONFIG["MAJOR"] + "." + CONFIG["MINOR"]
|
|
||||||
$libdir = File.join(CONFIG["libdir"], "ruby", version)
|
|
||||||
$sitedir = $:.find {|x| x =~ /site_ruby/ }
|
|
||||||
if !$sitedir
|
|
||||||
$sitedir = File.join($libdir, "site_ruby")
|
|
||||||
elsif $sitedir !~ Regexp.quote(version)
|
|
||||||
$sitedir = File.join($sitedir, version)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# the acual gruntwork
|
|
||||||
Dir.chdir("lib")
|
|
||||||
|
|
||||||
Find.find("action_mailer", "action_mailer.rb") { |f|
|
|
||||||
if f[-3..-1] == ".rb"
|
|
||||||
File::install(f, File.join($sitedir, *f.split(/\//)), 0644, true)
|
|
||||||
else
|
|
||||||
File::makedirs(File.join($sitedir, *f.split(/\//)))
|
|
||||||
end
|
|
||||||
}
|
|
||||||
|
|
@ -1,51 +0,0 @@
|
||||||
#--
|
|
||||||
# Copyright (c) 2004 David Heinemeier Hansson
|
|
||||||
#
|
|
||||||
# Permission is hereby granted, free of charge, to any person obtaining
|
|
||||||
# a copy of this software and associated documentation files (the
|
|
||||||
# "Software"), to deal in the Software without restriction, including
|
|
||||||
# without limitation the rights to use, copy, modify, merge, publish,
|
|
||||||
# distribute, sublicense, and/or sell copies of the Software, and to
|
|
||||||
# permit persons to whom the Software is furnished to do so, subject to
|
|
||||||
# the following conditions:
|
|
||||||
#
|
|
||||||
# The above copyright notice and this permission notice shall be
|
|
||||||
# included in all copies or substantial portions of the Software.
|
|
||||||
#
|
|
||||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
||||||
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
||||||
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
||||||
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
||||||
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
||||||
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
||||||
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
||||||
#++
|
|
||||||
|
|
||||||
begin
|
|
||||||
require 'action_controller'
|
|
||||||
rescue LoadError
|
|
||||||
begin
|
|
||||||
require File.dirname(__FILE__) + '/../../actionpack/lib/action_controller'
|
|
||||||
rescue LoadError
|
|
||||||
require 'rubygems'
|
|
||||||
require_gem 'actionpack', '>= 1.9.1'
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
$:.unshift(File.dirname(__FILE__) + "/action_mailer/vendor/")
|
|
||||||
|
|
||||||
require 'action_mailer/base'
|
|
||||||
require 'action_mailer/helpers'
|
|
||||||
require 'action_mailer/mail_helper'
|
|
||||||
require 'action_mailer/quoting'
|
|
||||||
require 'tmail'
|
|
||||||
require 'net/smtp'
|
|
||||||
|
|
||||||
ActionMailer::Base.class_eval do
|
|
||||||
include ActionMailer::Quoting
|
|
||||||
include ActionMailer::Helpers
|
|
||||||
|
|
||||||
helper MailHelper
|
|
||||||
end
|
|
||||||
|
|
||||||
silence_warnings { TMail::Encoder.const_set("MAX_LINE_LEN", 200) }
|
|
||||||
|
|
@ -1,27 +0,0 @@
|
||||||
module ActionMailer
|
|
||||||
module AdvAttrAccessor #:nodoc:
|
|
||||||
def self.append_features(base)
|
|
||||||
super
|
|
||||||
base.extend(ClassMethods)
|
|
||||||
end
|
|
||||||
|
|
||||||
module ClassMethods #:nodoc:
|
|
||||||
def adv_attr_accessor(*names)
|
|
||||||
names.each do |name|
|
|
||||||
define_method("#{name}=") do |value|
|
|
||||||
instance_variable_set("@#{name}", value)
|
|
||||||
end
|
|
||||||
|
|
||||||
define_method(name) do |*parameters|
|
|
||||||
raise ArgumentError, "expected 0 or 1 parameters" unless parameters.length <= 1
|
|
||||||
if parameters.empty?
|
|
||||||
instance_variable_get("@#{name}")
|
|
||||||
else
|
|
||||||
instance_variable_set("@#{name}", parameters.first)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
@ -1,453 +0,0 @@
|
||||||
require 'action_mailer/adv_attr_accessor'
|
|
||||||
require 'action_mailer/part'
|
|
||||||
require 'action_mailer/part_container'
|
|
||||||
require 'action_mailer/utils'
|
|
||||||
require 'tmail/net'
|
|
||||||
|
|
||||||
module ActionMailer
|
|
||||||
# Usage:
|
|
||||||
#
|
|
||||||
# class ApplicationMailer < ActionMailer::Base
|
|
||||||
# # Set up properties
|
|
||||||
# # Properties can also be specified via accessor methods
|
|
||||||
# # (i.e. self.subject = "foo") and instance variables (@subject = "foo").
|
|
||||||
# def signup_notification(recipient)
|
|
||||||
# recipients recipient.email_address_with_name
|
|
||||||
# subject "New account information"
|
|
||||||
# body { "account" => recipient }
|
|
||||||
# from "system@example.com"
|
|
||||||
# end
|
|
||||||
#
|
|
||||||
# # explicitly specify multipart messages
|
|
||||||
# def signup_notification(recipient)
|
|
||||||
# recipients recipient.email_address_with_name
|
|
||||||
# subject "New account information"
|
|
||||||
# from "system@example.com"
|
|
||||||
#
|
|
||||||
# part :content_type => "text/html",
|
|
||||||
# :body => render_message("signup-as-html", :account => recipient)
|
|
||||||
#
|
|
||||||
# part "text/plain" do |p|
|
|
||||||
# p.body = render_message("signup-as-plain", :account => recipient)
|
|
||||||
# p.transfer_encoding = "base64"
|
|
||||||
# end
|
|
||||||
# end
|
|
||||||
#
|
|
||||||
# # attachments
|
|
||||||
# def signup_notification(recipient)
|
|
||||||
# recipients recipient.email_address_with_name
|
|
||||||
# subject "New account information"
|
|
||||||
# from "system@example.com"
|
|
||||||
#
|
|
||||||
# attachment :content_type => "image/jpeg",
|
|
||||||
# :body => File.read("an-image.jpg")
|
|
||||||
#
|
|
||||||
# attachment "application/pdf" do |a|
|
|
||||||
# a.body = generate_your_pdf_here()
|
|
||||||
# end
|
|
||||||
# end
|
|
||||||
#
|
|
||||||
# # implicitly multipart messages
|
|
||||||
# def signup_notification(recipient)
|
|
||||||
# recipients recipient.email_address_with_name
|
|
||||||
# subject "New account information"
|
|
||||||
# from "system@example.com"
|
|
||||||
# body(:account => "recipient")
|
|
||||||
#
|
|
||||||
# # ActionMailer will automatically detect and use multipart templates,
|
|
||||||
# # where each template is named after the name of the action, followed
|
|
||||||
# # by the content type. Each such detected template will be added as
|
|
||||||
# # a separate part to the message.
|
|
||||||
# #
|
|
||||||
# # for example, if the following templates existed:
|
|
||||||
# # * signup_notification.text.plain.rhtml
|
|
||||||
# # * signup_notification.text.html.rhtml
|
|
||||||
# # * signup_notification.text.xml.rxml
|
|
||||||
# # * signup_notification.text.x-yaml.rhtml
|
|
||||||
# #
|
|
||||||
# # Each would be rendered and added as a separate part to the message,
|
|
||||||
# # with the corresponding content type. The same body hash is passed to
|
|
||||||
# # each template.
|
|
||||||
# end
|
|
||||||
# end
|
|
||||||
#
|
|
||||||
# # After this, post_notification will look for "templates/application_mailer/post_notification.rhtml"
|
|
||||||
# ApplicationMailer.template_root = "templates"
|
|
||||||
#
|
|
||||||
# ApplicationMailer.create_comment_notification(david, hello_world) # => a tmail object
|
|
||||||
# ApplicationMailer.deliver_comment_notification(david, hello_world) # sends the email
|
|
||||||
#
|
|
||||||
# = Configuration options
|
|
||||||
#
|
|
||||||
# These options are specified on the class level, like <tt>ActionMailer::Base.template_root = "/my/templates"</tt>
|
|
||||||
#
|
|
||||||
# * <tt>template_root</tt> - template root determines the base from which template references will be made.
|
|
||||||
#
|
|
||||||
# * <tt>logger</tt> - the logger is used for generating information on the mailing run if available.
|
|
||||||
# Can be set to nil for no logging. Compatible with both Ruby's own Logger and Log4r loggers.
|
|
||||||
#
|
|
||||||
# * <tt>server_settings</tt> - Allows detailed configuration of the server:
|
|
||||||
# * <tt>:address</tt> Allows you to use a remote mail server. Just change it from its default "localhost" setting.
|
|
||||||
# * <tt>:port</tt> On the off chance that your mail server doesn't run on port 25, you can change it.
|
|
||||||
# * <tt>:domain</tt> If you need to specify a HELO domain, you can do it here.
|
|
||||||
# * <tt>:user_name</tt> If your mail server requires authentication, set the username in this setting.
|
|
||||||
# * <tt>:password</tt> If your mail server requires authentication, set the password in this setting.
|
|
||||||
# * <tt>:authentication</tt> If your mail server requires authentication, you need to specify the authentication type here.
|
|
||||||
# This is a symbol and one of :plain, :login, :cram_md5
|
|
||||||
#
|
|
||||||
# * <tt>raise_delivery_errors</tt> - whether or not errors should be raised if the email fails to be delivered.
|
|
||||||
#
|
|
||||||
# * <tt>delivery_method</tt> - Defines a delivery method. Possible values are :smtp (default), :sendmail, and :test.
|
|
||||||
# Sendmail is assumed to be present at "/usr/sbin/sendmail".
|
|
||||||
#
|
|
||||||
# * <tt>perform_deliveries</tt> - Determines whether deliver_* methods are actually carried out. By default they are,
|
|
||||||
# but this can be turned off to help functional testing.
|
|
||||||
#
|
|
||||||
# * <tt>deliveries</tt> - Keeps an array of all the emails sent out through the Action Mailer with delivery_method :test. Most useful
|
|
||||||
# for unit and functional testing.
|
|
||||||
#
|
|
||||||
# * <tt>default_charset</tt> - The default charset used for the body and to encode the subject. Defaults to UTF-8. You can also
|
|
||||||
# pick a different charset from inside a method with <tt>@charset</tt>.
|
|
||||||
# * <tt>default_content_type</tt> - The default content type used for the main part of the message. Defaults to "text/plain". You
|
|
||||||
# can also pick a different content type from inside a method with <tt>@content_type</tt>.
|
|
||||||
# * <tt>default_mime_version</tt> - The default mime version used for the message. Defaults to nil. You
|
|
||||||
# can also pick a different value from inside a method with <tt>@mime_version</tt>. When multipart messages are in
|
|
||||||
# use, <tt>@mime_version</tt> will be set to "1.0" if it is not set inside a method.
|
|
||||||
# * <tt>default_implicit_parts_order</tt> - When a message is built implicitly (i.e. multiple parts are assembled from templates
|
|
||||||
# which specify the content type in their filenames) this variable controls how the parts are ordered. Defaults to
|
|
||||||
# ["text/html", "text/enriched", "text/plain"]. Items that appear first in the array have higher priority in the mail client
|
|
||||||
# and appear last in the mime encoded message. You can also pick a different order from inside a method with
|
|
||||||
# <tt>@implicit_parts_order</tt>.
|
|
||||||
class Base
|
|
||||||
include AdvAttrAccessor, PartContainer
|
|
||||||
|
|
||||||
private_class_method :new #:nodoc:
|
|
||||||
|
|
||||||
cattr_accessor :template_root
|
|
||||||
cattr_accessor :logger
|
|
||||||
|
|
||||||
@@server_settings = {
|
|
||||||
:address => "localhost",
|
|
||||||
:port => 25,
|
|
||||||
:domain => 'localhost.localdomain',
|
|
||||||
:user_name => nil,
|
|
||||||
:password => nil,
|
|
||||||
:authentication => nil
|
|
||||||
}
|
|
||||||
cattr_accessor :server_settings
|
|
||||||
|
|
||||||
@@raise_delivery_errors = true
|
|
||||||
cattr_accessor :raise_delivery_errors
|
|
||||||
|
|
||||||
@@delivery_method = :smtp
|
|
||||||
cattr_accessor :delivery_method
|
|
||||||
|
|
||||||
@@perform_deliveries = true
|
|
||||||
cattr_accessor :perform_deliveries
|
|
||||||
|
|
||||||
@@deliveries = []
|
|
||||||
cattr_accessor :deliveries
|
|
||||||
|
|
||||||
@@default_charset = "utf-8"
|
|
||||||
cattr_accessor :default_charset
|
|
||||||
|
|
||||||
@@default_content_type = "text/plain"
|
|
||||||
cattr_accessor :default_content_type
|
|
||||||
|
|
||||||
@@default_mime_version = nil
|
|
||||||
cattr_accessor :default_mime_version
|
|
||||||
|
|
||||||
@@default_implicit_parts_order = [ "text/html", "text/enriched", "text/plain" ]
|
|
||||||
cattr_accessor :default_implicit_parts_order
|
|
||||||
|
|
||||||
# Specify the BCC addresses for the message
|
|
||||||
adv_attr_accessor :bcc
|
|
||||||
|
|
||||||
# Define the body of the message. This is either a Hash (in which case it
|
|
||||||
# specifies the variables to pass to the template when it is rendered),
|
|
||||||
# or a string, in which case it specifies the actual text of the message.
|
|
||||||
adv_attr_accessor :body
|
|
||||||
|
|
||||||
# Specify the CC addresses for the message.
|
|
||||||
adv_attr_accessor :cc
|
|
||||||
|
|
||||||
# Specify the charset to use for the message. This defaults to the
|
|
||||||
# +default_charset+ specified for ActionMailer::Base.
|
|
||||||
adv_attr_accessor :charset
|
|
||||||
|
|
||||||
# Specify the content type for the message. This defaults to <tt>text/plain</tt>
|
|
||||||
# in most cases, but can be automatically set in some situations.
|
|
||||||
adv_attr_accessor :content_type
|
|
||||||
|
|
||||||
# Specify the from address for the message.
|
|
||||||
adv_attr_accessor :from
|
|
||||||
|
|
||||||
# Specify additional headers to be added to the message.
|
|
||||||
adv_attr_accessor :headers
|
|
||||||
|
|
||||||
# Specify the order in which parts should be sorted, based on content-type.
|
|
||||||
# This defaults to the value for the +default_implicit_parts_order+.
|
|
||||||
adv_attr_accessor :implicit_parts_order
|
|
||||||
|
|
||||||
# Override the mailer name, which defaults to an inflected version of the
|
|
||||||
# mailer's class name. If you want to use a template in a non-standard
|
|
||||||
# location, you can use this to specify that location.
|
|
||||||
adv_attr_accessor :mailer_name
|
|
||||||
|
|
||||||
# Defaults to "1.0", but may be explicitly given if needed.
|
|
||||||
adv_attr_accessor :mime_version
|
|
||||||
|
|
||||||
# The recipient addresses for the message, either as a string (for a single
|
|
||||||
# address) or an array (for multiple addresses).
|
|
||||||
adv_attr_accessor :recipients
|
|
||||||
|
|
||||||
# The date on which the message was sent. If not set (the default), the
|
|
||||||
# header will be set by the delivery agent.
|
|
||||||
adv_attr_accessor :sent_on
|
|
||||||
|
|
||||||
# Specify the subject of the message.
|
|
||||||
adv_attr_accessor :subject
|
|
||||||
|
|
||||||
# Specify the template name to use for current message. This is the "base"
|
|
||||||
# template name, without the extension or directory, and may be used to
|
|
||||||
# have multiple mailer methods share the same template.
|
|
||||||
adv_attr_accessor :template
|
|
||||||
|
|
||||||
# The mail object instance referenced by this mailer.
|
|
||||||
attr_reader :mail
|
|
||||||
|
|
||||||
class << self
|
|
||||||
def method_missing(method_symbol, *parameters)#:nodoc:
|
|
||||||
case method_symbol.id2name
|
|
||||||
when /^create_([_a-z]\w*)/ then new($1, *parameters).mail
|
|
||||||
when /^deliver_([_a-z]\w*)/ then new($1, *parameters).deliver!
|
|
||||||
when "new" then nil
|
|
||||||
else super
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Receives a raw email, parses it into an email object, decodes it,
|
|
||||||
# instantiates a new mailer, and passes the email object to the mailer
|
|
||||||
# object's #receive method. If you want your mailer to be able to
|
|
||||||
# process incoming messages, you'll need to implement a #receive
|
|
||||||
# method that accepts the email object as a parameter:
|
|
||||||
#
|
|
||||||
# class MyMailer < ActionMailer::Base
|
|
||||||
# def receive(mail)
|
|
||||||
# ...
|
|
||||||
# end
|
|
||||||
# end
|
|
||||||
def receive(raw_email)
|
|
||||||
logger.info "Received mail:\n #{raw_email}" unless logger.nil?
|
|
||||||
mail = TMail::Mail.parse(raw_email)
|
|
||||||
mail.base64_decode
|
|
||||||
new.receive(mail)
|
|
||||||
end
|
|
||||||
|
|
||||||
# Deliver the given mail object directly. This can be used to deliver
|
|
||||||
# a preconstructed mail object, like:
|
|
||||||
#
|
|
||||||
# email = MyMailer.create_some_mail(parameters)
|
|
||||||
# email.set_some_obscure_header "frobnicate"
|
|
||||||
# MyMailer.deliver(email)
|
|
||||||
def deliver(mail)
|
|
||||||
new.deliver!(mail)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Instantiate a new mailer object. If +method_name+ is not +nil+, the mailer
|
|
||||||
# will be initialized according to the named method. If not, the mailer will
|
|
||||||
# remain uninitialized (useful when you only need to invoke the "receive"
|
|
||||||
# method, for instance).
|
|
||||||
def initialize(method_name=nil, *parameters) #:nodoc:
|
|
||||||
create!(method_name, *parameters) if method_name
|
|
||||||
end
|
|
||||||
|
|
||||||
# Initialize the mailer via the given +method_name+. The body will be
|
|
||||||
# rendered and a new TMail::Mail object created.
|
|
||||||
def create!(method_name, *parameters) #:nodoc:
|
|
||||||
initialize_defaults(method_name)
|
|
||||||
send(method_name, *parameters)
|
|
||||||
|
|
||||||
# If an explicit, textual body has not been set, we check assumptions.
|
|
||||||
unless String === @body
|
|
||||||
# First, we look to see if there are any likely templates that match,
|
|
||||||
# which include the content-type in their file name (i.e.,
|
|
||||||
# "the_template_file.text.html.rhtml", etc.). Only do this if parts
|
|
||||||
# have not already been specified manually.
|
|
||||||
if @parts.empty?
|
|
||||||
templates = Dir.glob("#{template_path}/#{@template}.*")
|
|
||||||
templates.each do |path|
|
|
||||||
type = (File.basename(path).split(".")[1..-2] || []).join("/")
|
|
||||||
next if type.empty?
|
|
||||||
@parts << Part.new(:content_type => type,
|
|
||||||
:disposition => "inline", :charset => charset,
|
|
||||||
:body => render_message(File.basename(path).split(".")[0..-2].join('.'), @body))
|
|
||||||
end
|
|
||||||
unless @parts.empty?
|
|
||||||
@content_type = "multipart/alternative"
|
|
||||||
@charset = nil
|
|
||||||
@parts = sort_parts(@parts, @implicit_parts_order)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Then, if there were such templates, we check to see if we ought to
|
|
||||||
# also render a "normal" template (without the content type). If a
|
|
||||||
# normal template exists (or if there were no implicit parts) we render
|
|
||||||
# it.
|
|
||||||
template_exists = @parts.empty?
|
|
||||||
template_exists ||= Dir.glob("#{template_path}/#{@template}.*").any? { |i| i.split(".").length == 2 }
|
|
||||||
@body = render_message(@template, @body) if template_exists
|
|
||||||
|
|
||||||
# Finally, if there are other message parts and a textual body exists,
|
|
||||||
# we shift it onto the front of the parts and set the body to nil (so
|
|
||||||
# that create_mail doesn't try to render it in addition to the parts).
|
|
||||||
if !@parts.empty? && String === @body
|
|
||||||
@parts.unshift Part.new(:charset => charset, :body => @body)
|
|
||||||
@body = nil
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# If this is a multipart e-mail add the mime_version if it is not
|
|
||||||
# already set.
|
|
||||||
@mime_version ||= "1.0" if !@parts.empty?
|
|
||||||
|
|
||||||
# build the mail object itself
|
|
||||||
@mail = create_mail
|
|
||||||
end
|
|
||||||
|
|
||||||
# Delivers a TMail::Mail object. By default, it delivers the cached mail
|
|
||||||
# object (from the #create! method). If no cached mail object exists, and
|
|
||||||
# no alternate has been given as the parameter, this will fail.
|
|
||||||
def deliver!(mail = @mail)
|
|
||||||
raise "no mail object available for delivery!" unless mail
|
|
||||||
logger.info "Sent mail:\n #{mail.encoded}" unless logger.nil?
|
|
||||||
|
|
||||||
begin
|
|
||||||
send("perform_delivery_#{delivery_method}", mail) if perform_deliveries
|
|
||||||
rescue Object => e
|
|
||||||
raise e if raise_delivery_errors
|
|
||||||
end
|
|
||||||
|
|
||||||
return mail
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
# Set up the default values for the various instance variables of this
|
|
||||||
# mailer. Subclasses may override this method to provide different
|
|
||||||
# defaults.
|
|
||||||
def initialize_defaults(method_name)
|
|
||||||
@charset ||= @@default_charset.dup
|
|
||||||
@content_type ||= @@default_content_type.dup
|
|
||||||
@implicit_parts_order ||= @@default_implicit_parts_order.dup
|
|
||||||
@template ||= method_name
|
|
||||||
@mailer_name ||= Inflector.underscore(self.class.name)
|
|
||||||
@parts ||= []
|
|
||||||
@headers ||= {}
|
|
||||||
@body ||= {}
|
|
||||||
@mime_version = @@default_mime_version.dup if @@default_mime_version
|
|
||||||
end
|
|
||||||
|
|
||||||
def render_message(method_name, body)
|
|
||||||
render :file => method_name, :body => body
|
|
||||||
end
|
|
||||||
|
|
||||||
def render(opts)
|
|
||||||
body = opts.delete(:body)
|
|
||||||
initialize_template_class(body).render(opts)
|
|
||||||
end
|
|
||||||
|
|
||||||
def template_path
|
|
||||||
"#{template_root}/#{mailer_name}"
|
|
||||||
end
|
|
||||||
|
|
||||||
def initialize_template_class(assigns)
|
|
||||||
ActionView::Base.new(template_path, assigns, self)
|
|
||||||
end
|
|
||||||
|
|
||||||
def sort_parts(parts, order = [])
|
|
||||||
order = order.collect { |s| s.downcase }
|
|
||||||
|
|
||||||
parts = parts.sort do |a, b|
|
|
||||||
a_ct = a.content_type.downcase
|
|
||||||
b_ct = b.content_type.downcase
|
|
||||||
|
|
||||||
a_in = order.include? a_ct
|
|
||||||
b_in = order.include? b_ct
|
|
||||||
|
|
||||||
s = case
|
|
||||||
when a_in && b_in
|
|
||||||
order.index(a_ct) <=> order.index(b_ct)
|
|
||||||
when a_in
|
|
||||||
-1
|
|
||||||
when b_in
|
|
||||||
1
|
|
||||||
else
|
|
||||||
a_ct <=> b_ct
|
|
||||||
end
|
|
||||||
|
|
||||||
# reverse the ordering because parts that come last are displayed
|
|
||||||
# first in mail clients
|
|
||||||
(s * -1)
|
|
||||||
end
|
|
||||||
|
|
||||||
parts
|
|
||||||
end
|
|
||||||
|
|
||||||
def create_mail
|
|
||||||
m = TMail::Mail.new
|
|
||||||
|
|
||||||
m.subject, = quote_any_if_necessary(charset, subject)
|
|
||||||
m.to, m.from = quote_any_address_if_necessary(charset, recipients, from)
|
|
||||||
m.bcc = quote_address_if_necessary(bcc, charset) unless bcc.nil?
|
|
||||||
m.cc = quote_address_if_necessary(cc, charset) unless cc.nil?
|
|
||||||
|
|
||||||
m.mime_version = mime_version unless mime_version.nil?
|
|
||||||
m.date = sent_on.to_time rescue sent_on if sent_on
|
|
||||||
headers.each { |k, v| m[k] = v }
|
|
||||||
|
|
||||||
if @parts.empty?
|
|
||||||
m.set_content_type content_type, nil, { "charset" => charset }
|
|
||||||
m.body = Utils.normalize_new_lines(body)
|
|
||||||
else
|
|
||||||
if String === body
|
|
||||||
part = TMail::Mail.new
|
|
||||||
part.body = Utils.normalize_new_lines(body)
|
|
||||||
part.set_content_type content_type, nil, { "charset" => charset }
|
|
||||||
part.set_content_disposition "inline"
|
|
||||||
m.parts << part
|
|
||||||
end
|
|
||||||
|
|
||||||
@parts.each do |p|
|
|
||||||
part = (TMail::Mail === p ? p : p.to_mail(self))
|
|
||||||
m.parts << part
|
|
||||||
end
|
|
||||||
|
|
||||||
m.set_content_type(content_type, nil, { "charset" => charset }) if content_type =~ /multipart/
|
|
||||||
end
|
|
||||||
|
|
||||||
@mail = m
|
|
||||||
end
|
|
||||||
|
|
||||||
def perform_delivery_smtp(mail)
|
|
||||||
destinations = mail.destinations
|
|
||||||
mail.ready_to_send
|
|
||||||
|
|
||||||
Net::SMTP.start(server_settings[:address], server_settings[:port], server_settings[:domain],
|
|
||||||
server_settings[:user_name], server_settings[:password], server_settings[:authentication]) do |smtp|
|
|
||||||
smtp.sendmail(mail.encoded, mail.from, destinations)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def perform_delivery_sendmail(mail)
|
|
||||||
IO.popen("/usr/sbin/sendmail -i -t","w+") do |sm|
|
|
||||||
sm.print(mail.encoded.gsub(/\r/, ''))
|
|
||||||
sm.flush
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def perform_delivery_test(mail)
|
|
||||||
deliveries << mail
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
@ -1,115 +0,0 @@
|
||||||
module ActionMailer
|
|
||||||
module Helpers #:nodoc:
|
|
||||||
def self.append_features(base) #:nodoc:
|
|
||||||
super
|
|
||||||
|
|
||||||
# Initialize the base module to aggregate its helpers.
|
|
||||||
base.class_inheritable_accessor :master_helper_module
|
|
||||||
base.master_helper_module = Module.new
|
|
||||||
|
|
||||||
# Extend base with class methods to declare helpers.
|
|
||||||
base.extend(ClassMethods)
|
|
||||||
|
|
||||||
base.class_eval do
|
|
||||||
# Wrap inherited to create a new master helper module for subclasses.
|
|
||||||
class << self
|
|
||||||
alias_method :inherited_without_helper, :inherited
|
|
||||||
alias_method :inherited, :inherited_with_helper
|
|
||||||
end
|
|
||||||
|
|
||||||
# Wrap initialize_template_class to extend new template class
|
|
||||||
# instances with the master helper module.
|
|
||||||
alias_method :initialize_template_class_without_helper, :initialize_template_class
|
|
||||||
alias_method :initialize_template_class, :initialize_template_class_with_helper
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
module ClassMethods
|
|
||||||
# Makes all the (instance) methods in the helper module available to templates rendered through this controller.
|
|
||||||
# See ActionView::Helpers (link:classes/ActionView/Helpers.html) for more about making your own helper modules
|
|
||||||
# available to the templates.
|
|
||||||
def add_template_helper(helper_module) #:nodoc:
|
|
||||||
master_helper_module.module_eval "include #{helper_module}"
|
|
||||||
end
|
|
||||||
|
|
||||||
# Declare a helper:
|
|
||||||
# helper :foo
|
|
||||||
# requires 'foo_helper' and includes FooHelper in the template class.
|
|
||||||
# helper FooHelper
|
|
||||||
# includes FooHelper in the template class.
|
|
||||||
# helper { def foo() "#{bar} is the very best" end }
|
|
||||||
# evaluates the block in the template class, adding method #foo.
|
|
||||||
# helper(:three, BlindHelper) { def mice() 'mice' end }
|
|
||||||
# does all three.
|
|
||||||
def helper(*args, &block)
|
|
||||||
args.flatten.each do |arg|
|
|
||||||
case arg
|
|
||||||
when Module
|
|
||||||
add_template_helper(arg)
|
|
||||||
when String, Symbol
|
|
||||||
file_name = arg.to_s.underscore + '_helper'
|
|
||||||
class_name = file_name.camelize
|
|
||||||
|
|
||||||
begin
|
|
||||||
require_dependency(file_name)
|
|
||||||
rescue LoadError => load_error
|
|
||||||
requiree = / -- (.*?)(\.rb)?$/.match(load_error).to_a[1]
|
|
||||||
msg = (requiree == file_name) ? "Missing helper file helpers/#{file_name}.rb" : "Can't load file: #{requiree}"
|
|
||||||
raise LoadError.new(msg).copy_blame!(load_error)
|
|
||||||
end
|
|
||||||
|
|
||||||
add_template_helper(class_name.constantize)
|
|
||||||
else
|
|
||||||
raise ArgumentError, 'helper expects String, Symbol, or Module argument'
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Evaluate block in template class if given.
|
|
||||||
master_helper_module.module_eval(&block) if block_given?
|
|
||||||
end
|
|
||||||
|
|
||||||
# Declare a controller method as a helper. For example,
|
|
||||||
# helper_method :link_to
|
|
||||||
# def link_to(name, options) ... end
|
|
||||||
# makes the link_to controller method available in the view.
|
|
||||||
def helper_method(*methods)
|
|
||||||
methods.flatten.each do |method|
|
|
||||||
master_helper_module.module_eval <<-end_eval
|
|
||||||
def #{method}(*args, &block)
|
|
||||||
controller.send(%(#{method}), *args, &block)
|
|
||||||
end
|
|
||||||
end_eval
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Declare a controller attribute as a helper. For example,
|
|
||||||
# helper_attr :name
|
|
||||||
# attr_accessor :name
|
|
||||||
# makes the name and name= controller methods available in the view.
|
|
||||||
# The is a convenience wrapper for helper_method.
|
|
||||||
def helper_attr(*attrs)
|
|
||||||
attrs.flatten.each { |attr| helper_method(attr, "#{attr}=") }
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
def inherited_with_helper(child)
|
|
||||||
inherited_without_helper(child)
|
|
||||||
begin
|
|
||||||
child.master_helper_module = Module.new
|
|
||||||
child.master_helper_module.send :include, master_helper_module
|
|
||||||
child.helper child.name.underscore
|
|
||||||
rescue MissingSourceFile => e
|
|
||||||
raise unless e.is_missing?("helpers/#{child.name.underscore}_helper")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
# Extend the template class instance with our controller's helper module.
|
|
||||||
def initialize_template_class_with_helper(assigns)
|
|
||||||
returning(template = initialize_template_class_without_helper(assigns)) do
|
|
||||||
template.extend self.class.master_helper_module
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
@ -1,19 +0,0 @@
|
||||||
require 'text/format'
|
|
||||||
|
|
||||||
module MailHelper
|
|
||||||
# Uses Text::Format to take the text and format it, indented two spaces for
|
|
||||||
# each line, and wrapped at 72 columns.
|
|
||||||
def block_format(text)
|
|
||||||
formatted = text.split(/\n\r\n/).collect { |paragraph|
|
|
||||||
Text::Format.new(
|
|
||||||
:columns => 72, :first_indent => 2, :body_indent => 2, :text => paragraph
|
|
||||||
).format
|
|
||||||
}.join("\n")
|
|
||||||
|
|
||||||
# Make list points stand on their own line
|
|
||||||
formatted.gsub!(/[ ]*([*]+) ([^*]*)/) { |s| " #{$1} #{$2.strip}\n" }
|
|
||||||
formatted.gsub!(/[ ]*([#]+) ([^#]*)/) { |s| " #{$1} #{$2.strip}\n" }
|
|
||||||
|
|
||||||
formatted
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
@ -1,108 +0,0 @@
|
||||||
require 'action_mailer/adv_attr_accessor'
|
|
||||||
require 'action_mailer/part_container'
|
|
||||||
require 'action_mailer/utils'
|
|
||||||
|
|
||||||
module ActionMailer
|
|
||||||
# Represents a subpart of an email message. It shares many similar
|
|
||||||
# attributes of ActionMailer::Base. Although you can create parts manually
|
|
||||||
# and add them to the #parts list of the mailer, it is easier
|
|
||||||
# to use the helper methods in ActionMailer::PartContainer.
|
|
||||||
class Part
|
|
||||||
include ActionMailer::AdvAttrAccessor
|
|
||||||
include ActionMailer::PartContainer
|
|
||||||
|
|
||||||
# Represents the body of the part, as a string. This should not be a
|
|
||||||
# Hash (like ActionMailer::Base), but if you want a template to be rendered
|
|
||||||
# into the body of a subpart you can do it with the mailer's #render method
|
|
||||||
# and assign the result here.
|
|
||||||
adv_attr_accessor :body
|
|
||||||
|
|
||||||
# Specify the charset for this subpart. By default, it will be the charset
|
|
||||||
# of the containing part or mailer.
|
|
||||||
adv_attr_accessor :charset
|
|
||||||
|
|
||||||
# The content disposition of this part, typically either "inline" or
|
|
||||||
# "attachment".
|
|
||||||
adv_attr_accessor :content_disposition
|
|
||||||
|
|
||||||
# The content type of the part.
|
|
||||||
adv_attr_accessor :content_type
|
|
||||||
|
|
||||||
# The filename to use for this subpart (usually for attachments).
|
|
||||||
adv_attr_accessor :filename
|
|
||||||
|
|
||||||
# Accessor for specifying additional headers to include with this part.
|
|
||||||
adv_attr_accessor :headers
|
|
||||||
|
|
||||||
# The transfer encoding to use for this subpart, like "base64" or
|
|
||||||
# "quoted-printable".
|
|
||||||
adv_attr_accessor :transfer_encoding
|
|
||||||
|
|
||||||
# Create a new part from the given +params+ hash. The valid params keys
|
|
||||||
# correspond to the accessors.
|
|
||||||
def initialize(params)
|
|
||||||
@content_type = params[:content_type]
|
|
||||||
@content_disposition = params[:disposition] || "inline"
|
|
||||||
@charset = params[:charset]
|
|
||||||
@body = params[:body]
|
|
||||||
@filename = params[:filename]
|
|
||||||
@transfer_encoding = params[:transfer_encoding] || "quoted-printable"
|
|
||||||
@headers = params[:headers] || {}
|
|
||||||
@parts = []
|
|
||||||
end
|
|
||||||
|
|
||||||
# Convert the part to a mail object which can be included in the parts
|
|
||||||
# list of another mail object.
|
|
||||||
def to_mail(defaults)
|
|
||||||
part = TMail::Mail.new
|
|
||||||
|
|
||||||
if @parts.empty?
|
|
||||||
part.content_transfer_encoding = transfer_encoding || "quoted-printable"
|
|
||||||
case (transfer_encoding || "").downcase
|
|
||||||
when "base64" then
|
|
||||||
part.body = TMail::Base64.folding_encode(body)
|
|
||||||
when "quoted-printable"
|
|
||||||
part.body = [Utils.normalize_new_lines(body)].pack("M*")
|
|
||||||
else
|
|
||||||
part.body = body
|
|
||||||
end
|
|
||||||
|
|
||||||
# Always set the content_type after setting the body and or parts!
|
|
||||||
# Also don't set filename and name when there is none (like in
|
|
||||||
# non-attachment parts)
|
|
||||||
if content_disposition == "attachment"
|
|
||||||
part.set_content_type(content_type || defaults.content_type, nil,
|
|
||||||
squish("charset" => nil, "name" => filename))
|
|
||||||
part.set_content_disposition(content_disposition,
|
|
||||||
squish("filename" => filename))
|
|
||||||
else
|
|
||||||
part.set_content_type(content_type || defaults.content_type, nil,
|
|
||||||
"charset" => (charset || defaults.charset))
|
|
||||||
part.set_content_disposition(content_disposition)
|
|
||||||
end
|
|
||||||
else
|
|
||||||
if String === body
|
|
||||||
part = TMail::Mail.new
|
|
||||||
part.body = body
|
|
||||||
part.set_content_type content_type, nil, { "charset" => charset }
|
|
||||||
part.set_content_disposition "inline"
|
|
||||||
m.parts << part
|
|
||||||
end
|
|
||||||
|
|
||||||
@parts.each do |p|
|
|
||||||
prt = (TMail::Mail === p ? p : p.to_mail(defaults))
|
|
||||||
part.parts << prt
|
|
||||||
end
|
|
||||||
|
|
||||||
part.set_content_type(content_type, nil, { "charset" => charset }) if content_type =~ /multipart/
|
|
||||||
end
|
|
||||||
|
|
||||||
part
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
def squish(values={})
|
|
||||||
values.delete_if { |k,v| v.nil? }
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
@ -1,42 +0,0 @@
|
||||||
module ActionMailer
|
|
||||||
# Accessors and helpers that ActionMailer::Base and ActionMailer::Part have
|
|
||||||
# in common. Using these helpers you can easily add subparts or attachments
|
|
||||||
# to your message:
|
|
||||||
#
|
|
||||||
# def my_mail_message(...)
|
|
||||||
# ...
|
|
||||||
# part "text/plain" do |p|
|
|
||||||
# p.body "hello, world"
|
|
||||||
# p.transfer_encoding "base64"
|
|
||||||
# end
|
|
||||||
#
|
|
||||||
# attachment "image/jpg" do |a|
|
|
||||||
# a.body = File.read("hello.jpg")
|
|
||||||
# a.filename = "hello.jpg"
|
|
||||||
# end
|
|
||||||
# end
|
|
||||||
module PartContainer
|
|
||||||
# The list of subparts of this container
|
|
||||||
attr_reader :parts
|
|
||||||
|
|
||||||
# Add a part to a multipart message, with the given content-type. The
|
|
||||||
# part itself is yielded to the block so that other properties (charset,
|
|
||||||
# body, headers, etc.) can be set on it.
|
|
||||||
def part(params)
|
|
||||||
params = {:content_type => params} if String === params
|
|
||||||
part = Part.new(params)
|
|
||||||
yield part if block_given?
|
|
||||||
@parts << part
|
|
||||||
end
|
|
||||||
|
|
||||||
# Add an attachment to a multipart message. This is simply a part with the
|
|
||||||
# content-disposition set to "attachment".
|
|
||||||
def attachment(params, &block)
|
|
||||||
params = { :content_type => params } if String === params
|
|
||||||
params = { :disposition => "attachment",
|
|
||||||
:transfer_encoding => "base64" }.merge(params)
|
|
||||||
part(params, &block)
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
@ -1,59 +0,0 @@
|
||||||
module ActionMailer
|
|
||||||
module Quoting #:nodoc:
|
|
||||||
# Convert the given text into quoted printable format, with an instruction
|
|
||||||
# that the text be eventually interpreted in the given charset.
|
|
||||||
def quoted_printable(text, charset)
|
|
||||||
text = text.gsub( /[^a-z ]/i ) { quoted_printable_encode($&) }.
|
|
||||||
gsub( / /, "_" )
|
|
||||||
"=?#{charset}?Q?#{text}?="
|
|
||||||
end
|
|
||||||
|
|
||||||
# Convert the given character to quoted printable format, taking into
|
|
||||||
# account multi-byte characters (if executing with $KCODE="u", for instance)
|
|
||||||
def quoted_printable_encode(character)
|
|
||||||
result = ""
|
|
||||||
character.each_byte { |b| result << "=%02x" % b }
|
|
||||||
result
|
|
||||||
end
|
|
||||||
|
|
||||||
# A quick-and-dirty regexp for determining whether a string contains any
|
|
||||||
# characters that need escaping.
|
|
||||||
if !defined?(CHARS_NEEDING_QUOTING)
|
|
||||||
CHARS_NEEDING_QUOTING = /[\000-\011\013\014\016-\037\177-\377]/
|
|
||||||
end
|
|
||||||
|
|
||||||
# Quote the given text if it contains any "illegal" characters
|
|
||||||
def quote_if_necessary(text, charset)
|
|
||||||
(text =~ CHARS_NEEDING_QUOTING) ?
|
|
||||||
quoted_printable(text, charset) :
|
|
||||||
text
|
|
||||||
end
|
|
||||||
|
|
||||||
# Quote any of the given strings if they contain any "illegal" characters
|
|
||||||
def quote_any_if_necessary(charset, *args)
|
|
||||||
args.map { |v| quote_if_necessary(v, charset) }
|
|
||||||
end
|
|
||||||
|
|
||||||
# Quote the given address if it needs to be. The address may be a
|
|
||||||
# regular email address, or it can be a phrase followed by an address in
|
|
||||||
# brackets. The phrase is the only part that will be quoted, and only if
|
|
||||||
# it needs to be. This allows extended characters to be used in the
|
|
||||||
# "to", "from", "cc", and "bcc" headers.
|
|
||||||
def quote_address_if_necessary(address, charset)
|
|
||||||
if Array === address
|
|
||||||
address.map { |a| quote_address_if_necessary(a, charset) }
|
|
||||||
elsif address =~ /^(\S.*)\s+(<.*>)$/
|
|
||||||
address = $2
|
|
||||||
phrase = quote_if_necessary($1.gsub(/^['"](.*)['"]$/, '\1'), charset)
|
|
||||||
"\"#{phrase}\" #{address}"
|
|
||||||
else
|
|
||||||
address
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Quote any of the given addresses, if they need to be.
|
|
||||||
def quote_any_address_if_necessary(charset, *args)
|
|
||||||
args.map { |v| quote_address_if_necessary(v, charset) }
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
@ -1,8 +0,0 @@
|
||||||
module ActionMailer
|
|
||||||
module Utils #:nodoc:
|
|
||||||
def normalize_new_lines(text)
|
|
||||||
text.to_s.gsub(/\r\n?/, "\n")
|
|
||||||
end
|
|
||||||
module_function :normalize_new_lines
|
|
||||||
end
|
|
||||||
end
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -1,3 +0,0 @@
|
||||||
require 'tmail/info'
|
|
||||||
require 'tmail/mail'
|
|
||||||
require 'tmail/mailbox'
|
|
||||||
|
|
@ -1,242 +0,0 @@
|
||||||
#
|
|
||||||
# address.rb
|
|
||||||
#
|
|
||||||
#--
|
|
||||||
# Copyright (c) 1998-2003 Minero Aoki <aamine@loveruby.net>
|
|
||||||
#
|
|
||||||
# Permission is hereby granted, free of charge, to any person obtaining
|
|
||||||
# a copy of this software and associated documentation files (the
|
|
||||||
# "Software"), to deal in the Software without restriction, including
|
|
||||||
# without limitation the rights to use, copy, modify, merge, publish,
|
|
||||||
# distribute, sublicense, and/or sell copies of the Software, and to
|
|
||||||
# permit persons to whom the Software is furnished to do so, subject to
|
|
||||||
# the following conditions:
|
|
||||||
#
|
|
||||||
# The above copyright notice and this permission notice shall be
|
|
||||||
# included in all copies or substantial portions of the Software.
|
|
||||||
#
|
|
||||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
||||||
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
||||||
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
||||||
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
||||||
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
||||||
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
||||||
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
||||||
#
|
|
||||||
# Note: Originally licensed under LGPL v2+. Using MIT license for Rails
|
|
||||||
# with permission of Minero Aoki.
|
|
||||||
#++
|
|
||||||
|
|
||||||
require 'tmail/encode'
|
|
||||||
require 'tmail/parser'
|
|
||||||
|
|
||||||
|
|
||||||
module TMail
|
|
||||||
|
|
||||||
class Address
|
|
||||||
|
|
||||||
include TextUtils
|
|
||||||
|
|
||||||
def Address.parse( str )
|
|
||||||
Parser.parse :ADDRESS, str
|
|
||||||
end
|
|
||||||
|
|
||||||
def address_group?
|
|
||||||
false
|
|
||||||
end
|
|
||||||
|
|
||||||
def initialize( local, domain )
|
|
||||||
if domain
|
|
||||||
domain.each do |s|
|
|
||||||
raise SyntaxError, 'empty word in domain' if s.empty?
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@local = local
|
|
||||||
@domain = domain
|
|
||||||
@name = nil
|
|
||||||
@routes = []
|
|
||||||
end
|
|
||||||
|
|
||||||
attr_reader :name
|
|
||||||
|
|
||||||
def name=( str )
|
|
||||||
@name = str
|
|
||||||
@name = nil if str and str.empty?
|
|
||||||
end
|
|
||||||
|
|
||||||
alias phrase name
|
|
||||||
alias phrase= name=
|
|
||||||
|
|
||||||
attr_reader :routes
|
|
||||||
|
|
||||||
def inspect
|
|
||||||
"#<#{self.class} #{address()}>"
|
|
||||||
end
|
|
||||||
|
|
||||||
def local
|
|
||||||
return nil unless @local
|
|
||||||
return '""' if @local.size == 1 and @local[0].empty?
|
|
||||||
@local.map {|i| quote_atom(i) }.join('.')
|
|
||||||
end
|
|
||||||
|
|
||||||
def domain
|
|
||||||
return nil unless @domain
|
|
||||||
join_domain(@domain)
|
|
||||||
end
|
|
||||||
|
|
||||||
def spec
|
|
||||||
s = self.local
|
|
||||||
d = self.domain
|
|
||||||
if s and d
|
|
||||||
s + '@' + d
|
|
||||||
else
|
|
||||||
s
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
alias address spec
|
|
||||||
|
|
||||||
|
|
||||||
def ==( other )
|
|
||||||
other.respond_to? :spec and self.spec == other.spec
|
|
||||||
end
|
|
||||||
|
|
||||||
alias eql? ==
|
|
||||||
|
|
||||||
def hash
|
|
||||||
@local.hash ^ @domain.hash
|
|
||||||
end
|
|
||||||
|
|
||||||
def dup
|
|
||||||
obj = self.class.new(@local.dup, @domain.dup)
|
|
||||||
obj.name = @name.dup if @name
|
|
||||||
obj.routes.replace @routes
|
|
||||||
obj
|
|
||||||
end
|
|
||||||
|
|
||||||
include StrategyInterface
|
|
||||||
|
|
||||||
def accept( strategy, dummy1 = nil, dummy2 = nil )
|
|
||||||
unless @local
|
|
||||||
strategy.meta '<>' # empty return-path
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
spec_p = (not @name and @routes.empty?)
|
|
||||||
if @name
|
|
||||||
strategy.phrase @name
|
|
||||||
strategy.space
|
|
||||||
end
|
|
||||||
tmp = spec_p ? '' : '<'
|
|
||||||
unless @routes.empty?
|
|
||||||
tmp << @routes.map {|i| '@' + i }.join(',') << ':'
|
|
||||||
end
|
|
||||||
tmp << self.spec
|
|
||||||
tmp << '>' unless spec_p
|
|
||||||
strategy.meta tmp
|
|
||||||
strategy.lwsp ''
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
class AddressGroup
|
|
||||||
|
|
||||||
include Enumerable
|
|
||||||
|
|
||||||
def address_group?
|
|
||||||
true
|
|
||||||
end
|
|
||||||
|
|
||||||
def initialize( name, addrs )
|
|
||||||
@name = name
|
|
||||||
@addresses = addrs
|
|
||||||
end
|
|
||||||
|
|
||||||
attr_reader :name
|
|
||||||
|
|
||||||
def ==( other )
|
|
||||||
other.respond_to? :to_a and @addresses == other.to_a
|
|
||||||
end
|
|
||||||
|
|
||||||
alias eql? ==
|
|
||||||
|
|
||||||
def hash
|
|
||||||
map {|i| i.hash }.hash
|
|
||||||
end
|
|
||||||
|
|
||||||
def []( idx )
|
|
||||||
@addresses[idx]
|
|
||||||
end
|
|
||||||
|
|
||||||
def size
|
|
||||||
@addresses.size
|
|
||||||
end
|
|
||||||
|
|
||||||
def empty?
|
|
||||||
@addresses.empty?
|
|
||||||
end
|
|
||||||
|
|
||||||
def each( &block )
|
|
||||||
@addresses.each(&block)
|
|
||||||
end
|
|
||||||
|
|
||||||
def to_a
|
|
||||||
@addresses.dup
|
|
||||||
end
|
|
||||||
|
|
||||||
alias to_ary to_a
|
|
||||||
|
|
||||||
def include?( a )
|
|
||||||
@addresses.include? a
|
|
||||||
end
|
|
||||||
|
|
||||||
def flatten
|
|
||||||
set = []
|
|
||||||
@addresses.each do |a|
|
|
||||||
if a.respond_to? :flatten
|
|
||||||
set.concat a.flatten
|
|
||||||
else
|
|
||||||
set.push a
|
|
||||||
end
|
|
||||||
end
|
|
||||||
set
|
|
||||||
end
|
|
||||||
|
|
||||||
def each_address( &block )
|
|
||||||
flatten.each(&block)
|
|
||||||
end
|
|
||||||
|
|
||||||
def add( a )
|
|
||||||
@addresses.push a
|
|
||||||
end
|
|
||||||
|
|
||||||
alias push add
|
|
||||||
|
|
||||||
def delete( a )
|
|
||||||
@addresses.delete a
|
|
||||||
end
|
|
||||||
|
|
||||||
include StrategyInterface
|
|
||||||
|
|
||||||
def accept( strategy, dummy1 = nil, dummy2 = nil )
|
|
||||||
strategy.phrase @name
|
|
||||||
strategy.meta ':'
|
|
||||||
strategy.space
|
|
||||||
first = true
|
|
||||||
each do |mbox|
|
|
||||||
if first
|
|
||||||
first = false
|
|
||||||
else
|
|
||||||
strategy.meta ','
|
|
||||||
end
|
|
||||||
strategy.space
|
|
||||||
mbox.accept strategy
|
|
||||||
end
|
|
||||||
strategy.meta ';'
|
|
||||||
strategy.lwsp ''
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
||||||
end # module TMail
|
|
||||||
|
|
@ -1,34 +0,0 @@
|
||||||
require 'stringio'
|
|
||||||
|
|
||||||
module TMail
|
|
||||||
class Attachment < StringIO
|
|
||||||
attr_accessor :original_filename, :content_type
|
|
||||||
end
|
|
||||||
|
|
||||||
class Mail
|
|
||||||
def has_attachments?
|
|
||||||
multipart? && parts.any? { |part| part.header["content-type"].main_type != "text" }
|
|
||||||
end
|
|
||||||
|
|
||||||
def attachments
|
|
||||||
if multipart?
|
|
||||||
parts.collect { |part|
|
|
||||||
if part.header["content-type"].main_type != "text"
|
|
||||||
content = part.body # unquoted automatically by TMail#body
|
|
||||||
file_name = (part['content-location'] &&
|
|
||||||
part['content-location'].body) ||
|
|
||||||
part.sub_header("content-type", "name") ||
|
|
||||||
part.sub_header("content-disposition", "filename")
|
|
||||||
|
|
||||||
next if file_name.blank? || content.blank?
|
|
||||||
|
|
||||||
attachment = Attachment.new(content)
|
|
||||||
attachment.original_filename = file_name.strip
|
|
||||||
attachment.content_type = part.content_type
|
|
||||||
attachment
|
|
||||||
end
|
|
||||||
}.compact
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
@ -1,71 +0,0 @@
|
||||||
#
|
|
||||||
# base64.rb
|
|
||||||
#
|
|
||||||
#--
|
|
||||||
# Copyright (c) 1998-2003 Minero Aoki <aamine@loveruby.net>
|
|
||||||
#
|
|
||||||
# Permission is hereby granted, free of charge, to any person obtaining
|
|
||||||
# a copy of this software and associated documentation files (the
|
|
||||||
# "Software"), to deal in the Software without restriction, including
|
|
||||||
# without limitation the rights to use, copy, modify, merge, publish,
|
|
||||||
# distribute, sublicense, and/or sell copies of the Software, and to
|
|
||||||
# permit persons to whom the Software is furnished to do so, subject to
|
|
||||||
# the following conditions:
|
|
||||||
#
|
|
||||||
# The above copyright notice and this permission notice shall be
|
|
||||||
# included in all copies or substantial portions of the Software.
|
|
||||||
#
|
|
||||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
||||||
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
||||||
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
||||||
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
||||||
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
||||||
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
||||||
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
||||||
#
|
|
||||||
# Note: Originally licensed under LGPL v2+. Using MIT license for Rails
|
|
||||||
# with permission of Minero Aoki.
|
|
||||||
#++
|
|
||||||
|
|
||||||
module TMail
|
|
||||||
|
|
||||||
module Base64
|
|
||||||
|
|
||||||
module_function
|
|
||||||
|
|
||||||
def rb_folding_encode( str, eol = "\n", limit = 60 )
|
|
||||||
[str].pack('m')
|
|
||||||
end
|
|
||||||
|
|
||||||
def rb_encode( str )
|
|
||||||
[str].pack('m').tr( "\r\n", '' )
|
|
||||||
end
|
|
||||||
|
|
||||||
def rb_decode( str, strict = false )
|
|
||||||
str.unpack('m')
|
|
||||||
end
|
|
||||||
|
|
||||||
begin
|
|
||||||
require 'tmail/base64.so'
|
|
||||||
alias folding_encode c_folding_encode
|
|
||||||
alias encode c_encode
|
|
||||||
alias decode c_decode
|
|
||||||
class << self
|
|
||||||
alias folding_encode c_folding_encode
|
|
||||||
alias encode c_encode
|
|
||||||
alias decode c_decode
|
|
||||||
end
|
|
||||||
rescue LoadError
|
|
||||||
alias folding_encode rb_folding_encode
|
|
||||||
alias encode rb_encode
|
|
||||||
alias decode rb_decode
|
|
||||||
class << self
|
|
||||||
alias folding_encode rb_folding_encode
|
|
||||||
alias encode rb_encode
|
|
||||||
alias decode rb_decode
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
@ -1,69 +0,0 @@
|
||||||
#
|
|
||||||
# config.rb
|
|
||||||
#
|
|
||||||
#--
|
|
||||||
# Copyright (c) 1998-2003 Minero Aoki <aamine@loveruby.net>
|
|
||||||
#
|
|
||||||
# Permission is hereby granted, free of charge, to any person obtaining
|
|
||||||
# a copy of this software and associated documentation files (the
|
|
||||||
# "Software"), to deal in the Software without restriction, including
|
|
||||||
# without limitation the rights to use, copy, modify, merge, publish,
|
|
||||||
# distribute, sublicense, and/or sell copies of the Software, and to
|
|
||||||
# permit persons to whom the Software is furnished to do so, subject to
|
|
||||||
# the following conditions:
|
|
||||||
#
|
|
||||||
# The above copyright notice and this permission notice shall be
|
|
||||||
# included in all copies or substantial portions of the Software.
|
|
||||||
#
|
|
||||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
||||||
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
||||||
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
||||||
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
||||||
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
||||||
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
||||||
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
||||||
#
|
|
||||||
# Note: Originally licensed under LGPL v2+. Using MIT license for Rails
|
|
||||||
# with permission of Minero Aoki.
|
|
||||||
#++
|
|
||||||
|
|
||||||
module TMail
|
|
||||||
|
|
||||||
class Config
|
|
||||||
|
|
||||||
def initialize( strict )
|
|
||||||
@strict_parse = strict
|
|
||||||
@strict_base64decode = strict
|
|
||||||
end
|
|
||||||
|
|
||||||
def strict_parse?
|
|
||||||
@strict_parse
|
|
||||||
end
|
|
||||||
|
|
||||||
attr_writer :strict_parse
|
|
||||||
|
|
||||||
def strict_base64decode?
|
|
||||||
@strict_base64decode
|
|
||||||
end
|
|
||||||
|
|
||||||
attr_writer :strict_base64decode
|
|
||||||
|
|
||||||
def new_body_port( mail )
|
|
||||||
StringPort.new
|
|
||||||
end
|
|
||||||
|
|
||||||
alias new_preamble_port new_body_port
|
|
||||||
alias new_part_port new_body_port
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
||||||
DEFAULT_CONFIG = Config.new(false)
|
|
||||||
DEFAULT_STRICT_CONFIG = Config.new(true)
|
|
||||||
|
|
||||||
def Config.to_config( arg )
|
|
||||||
return DEFAULT_STRICT_CONFIG if arg == true
|
|
||||||
return DEFAULT_CONFIG if arg == false
|
|
||||||
arg or DEFAULT_CONFIG
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
@ -1,467 +0,0 @@
|
||||||
#
|
|
||||||
# encode.rb
|
|
||||||
#
|
|
||||||
#--
|
|
||||||
# Copyright (c) 1998-2003 Minero Aoki <aamine@loveruby.net>
|
|
||||||
#
|
|
||||||
# Permission is hereby granted, free of charge, to any person obtaining
|
|
||||||
# a copy of this software and associated documentation files (the
|
|
||||||
# "Software"), to deal in the Software without restriction, including
|
|
||||||
# without limitation the rights to use, copy, modify, merge, publish,
|
|
||||||
# distribute, sublicense, and/or sell copies of the Software, and to
|
|
||||||
# permit persons to whom the Software is furnished to do so, subject to
|
|
||||||
# the following conditions:
|
|
||||||
#
|
|
||||||
# The above copyright notice and this permission notice shall be
|
|
||||||
# included in all copies or substantial portions of the Software.
|
|
||||||
#
|
|
||||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
||||||
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
||||||
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
||||||
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
||||||
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
||||||
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
||||||
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
||||||
#
|
|
||||||
# Note: Originally licensed under LGPL v2+. Using MIT license for Rails
|
|
||||||
# with permission of Minero Aoki.
|
|
||||||
#++
|
|
||||||
|
|
||||||
require 'nkf'
|
|
||||||
require 'tmail/base64.rb'
|
|
||||||
require 'tmail/stringio'
|
|
||||||
require 'tmail/utils'
|
|
||||||
|
|
||||||
|
|
||||||
module TMail
|
|
||||||
|
|
||||||
module StrategyInterface
|
|
||||||
|
|
||||||
def create_dest( obj )
|
|
||||||
case obj
|
|
||||||
when nil
|
|
||||||
StringOutput.new
|
|
||||||
when String
|
|
||||||
StringOutput.new(obj)
|
|
||||||
when IO, StringOutput
|
|
||||||
obj
|
|
||||||
else
|
|
||||||
raise TypeError, 'cannot handle this type of object for dest'
|
|
||||||
end
|
|
||||||
end
|
|
||||||
module_function :create_dest
|
|
||||||
|
|
||||||
def encoded( eol = "\r\n", charset = 'j', dest = nil )
|
|
||||||
accept_strategy Encoder, eol, charset, dest
|
|
||||||
end
|
|
||||||
|
|
||||||
def decoded( eol = "\n", charset = 'e', dest = nil )
|
|
||||||
accept_strategy Decoder, eol, charset, dest
|
|
||||||
end
|
|
||||||
|
|
||||||
alias to_s decoded
|
|
||||||
|
|
||||||
def accept_strategy( klass, eol, charset, dest = nil )
|
|
||||||
dest ||= ''
|
|
||||||
accept klass.new(create_dest(dest), charset, eol)
|
|
||||||
dest
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
###
|
|
||||||
### MIME B encoding decoder
|
|
||||||
###
|
|
||||||
|
|
||||||
class Decoder
|
|
||||||
|
|
||||||
include TextUtils
|
|
||||||
|
|
||||||
encoded = '=\?(?:iso-2022-jp|euc-jp|shift_jis)\?[QB]\?[a-z0-9+/=]+\?='
|
|
||||||
ENCODED_WORDS = /#{encoded}(?:\s+#{encoded})*/i
|
|
||||||
|
|
||||||
OUTPUT_ENCODING = {
|
|
||||||
'EUC' => 'e',
|
|
||||||
'SJIS' => 's',
|
|
||||||
}
|
|
||||||
|
|
||||||
def self.decode( str, encoding = nil )
|
|
||||||
encoding ||= (OUTPUT_ENCODING[$KCODE] || 'j')
|
|
||||||
opt = '-m' + encoding
|
|
||||||
str.gsub(ENCODED_WORDS) {|s| NKF.nkf(opt, s) }
|
|
||||||
end
|
|
||||||
|
|
||||||
def initialize( dest, encoding = nil, eol = "\n" )
|
|
||||||
@f = StrategyInterface.create_dest(dest)
|
|
||||||
@encoding = (/\A[ejs]/ === encoding) ? encoding[0,1] : nil
|
|
||||||
@eol = eol
|
|
||||||
end
|
|
||||||
|
|
||||||
def decode( str )
|
|
||||||
self.class.decode(str, @encoding)
|
|
||||||
end
|
|
||||||
private :decode
|
|
||||||
|
|
||||||
def terminate
|
|
||||||
end
|
|
||||||
|
|
||||||
def header_line( str )
|
|
||||||
@f << decode(str)
|
|
||||||
end
|
|
||||||
|
|
||||||
def header_name( nm )
|
|
||||||
@f << nm << ': '
|
|
||||||
end
|
|
||||||
|
|
||||||
def header_body( str )
|
|
||||||
@f << decode(str)
|
|
||||||
end
|
|
||||||
|
|
||||||
def space
|
|
||||||
@f << ' '
|
|
||||||
end
|
|
||||||
|
|
||||||
alias spc space
|
|
||||||
|
|
||||||
def lwsp( str )
|
|
||||||
@f << str
|
|
||||||
end
|
|
||||||
|
|
||||||
def meta( str )
|
|
||||||
@f << str
|
|
||||||
end
|
|
||||||
|
|
||||||
def text( str )
|
|
||||||
@f << decode(str)
|
|
||||||
end
|
|
||||||
|
|
||||||
def phrase( str )
|
|
||||||
@f << quote_phrase(decode(str))
|
|
||||||
end
|
|
||||||
|
|
||||||
def kv_pair( k, v )
|
|
||||||
@f << k << '=' << v
|
|
||||||
end
|
|
||||||
|
|
||||||
def puts( str = nil )
|
|
||||||
@f << str if str
|
|
||||||
@f << @eol
|
|
||||||
end
|
|
||||||
|
|
||||||
def write( str )
|
|
||||||
@f << str
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
###
|
|
||||||
### MIME B-encoding encoder
|
|
||||||
###
|
|
||||||
|
|
||||||
#
|
|
||||||
# FIXME: This class can handle only (euc-jp/shift_jis -> iso-2022-jp).
|
|
||||||
#
|
|
||||||
class Encoder
|
|
||||||
|
|
||||||
include TextUtils
|
|
||||||
|
|
||||||
BENCODE_DEBUG = false unless defined?(BENCODE_DEBUG)
|
|
||||||
|
|
||||||
def Encoder.encode( str )
|
|
||||||
e = new()
|
|
||||||
e.header_body str
|
|
||||||
e.terminate
|
|
||||||
e.dest.string
|
|
||||||
end
|
|
||||||
|
|
||||||
SPACER = "\t"
|
|
||||||
MAX_LINE_LEN = 70
|
|
||||||
|
|
||||||
OPTIONS = {
|
|
||||||
'EUC' => '-Ej -m0',
|
|
||||||
'SJIS' => '-Sj -m0',
|
|
||||||
'UTF8' => nil, # FIXME
|
|
||||||
'NONE' => nil
|
|
||||||
}
|
|
||||||
|
|
||||||
def initialize( dest = nil, encoding = nil, eol = "\r\n", limit = nil )
|
|
||||||
@f = StrategyInterface.create_dest(dest)
|
|
||||||
@opt = OPTIONS[$KCODE]
|
|
||||||
@eol = eol
|
|
||||||
reset
|
|
||||||
end
|
|
||||||
|
|
||||||
def normalize_encoding( str )
|
|
||||||
if @opt
|
|
||||||
then NKF.nkf(@opt, str)
|
|
||||||
else str
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def reset
|
|
||||||
@text = ''
|
|
||||||
@lwsp = ''
|
|
||||||
@curlen = 0
|
|
||||||
end
|
|
||||||
|
|
||||||
def terminate
|
|
||||||
add_lwsp ''
|
|
||||||
reset
|
|
||||||
end
|
|
||||||
|
|
||||||
def dest
|
|
||||||
@f
|
|
||||||
end
|
|
||||||
|
|
||||||
def puts( str = nil )
|
|
||||||
@f << str if str
|
|
||||||
@f << @eol
|
|
||||||
end
|
|
||||||
|
|
||||||
def write( str )
|
|
||||||
@f << str
|
|
||||||
end
|
|
||||||
|
|
||||||
#
|
|
||||||
# add
|
|
||||||
#
|
|
||||||
|
|
||||||
def header_line( line )
|
|
||||||
scanadd line
|
|
||||||
end
|
|
||||||
|
|
||||||
def header_name( name )
|
|
||||||
add_text name.split(/-/).map {|i| i.capitalize }.join('-')
|
|
||||||
add_text ':'
|
|
||||||
add_lwsp ' '
|
|
||||||
end
|
|
||||||
|
|
||||||
def header_body( str )
|
|
||||||
scanadd normalize_encoding(str)
|
|
||||||
end
|
|
||||||
|
|
||||||
def space
|
|
||||||
add_lwsp ' '
|
|
||||||
end
|
|
||||||
|
|
||||||
alias spc space
|
|
||||||
|
|
||||||
def lwsp( str )
|
|
||||||
add_lwsp str.sub(/[\r\n]+[^\r\n]*\z/, '')
|
|
||||||
end
|
|
||||||
|
|
||||||
def meta( str )
|
|
||||||
add_text str
|
|
||||||
end
|
|
||||||
|
|
||||||
def text( str )
|
|
||||||
scanadd normalize_encoding(str)
|
|
||||||
end
|
|
||||||
|
|
||||||
def phrase( str )
|
|
||||||
str = normalize_encoding(str)
|
|
||||||
if CONTROL_CHAR === str
|
|
||||||
scanadd str
|
|
||||||
else
|
|
||||||
add_text quote_phrase(str)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# FIXME: implement line folding
|
|
||||||
#
|
|
||||||
def kv_pair( k, v )
|
|
||||||
return if v.nil?
|
|
||||||
v = normalize_encoding(v)
|
|
||||||
if token_safe?(v)
|
|
||||||
add_text k + '=' + v
|
|
||||||
elsif not CONTROL_CHAR === v
|
|
||||||
add_text k + '=' + quote_token(v)
|
|
||||||
else
|
|
||||||
# apply RFC2231 encoding
|
|
||||||
kv = k + '*=' + "iso-2022-jp'ja'" + encode_value(v)
|
|
||||||
add_text kv
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def encode_value( str )
|
|
||||||
str.gsub(TOKEN_UNSAFE) {|s| '%%%02x' % s[0] }
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def scanadd( str, force = false )
|
|
||||||
types = ''
|
|
||||||
strs = []
|
|
||||||
|
|
||||||
until str.empty?
|
|
||||||
if m = /\A[^\e\t\r\n ]+/.match(str)
|
|
||||||
types << (force ? 'j' : 'a')
|
|
||||||
strs.push m[0]
|
|
||||||
|
|
||||||
elsif m = /\A[\t\r\n ]+/.match(str)
|
|
||||||
types << 's'
|
|
||||||
strs.push m[0]
|
|
||||||
|
|
||||||
elsif m = /\A\e../.match(str)
|
|
||||||
esc = m[0]
|
|
||||||
str = m.post_match
|
|
||||||
if esc != "\e(B" and m = /\A[^\e]+/.match(str)
|
|
||||||
types << 'j'
|
|
||||||
strs.push m[0]
|
|
||||||
end
|
|
||||||
|
|
||||||
else
|
|
||||||
raise 'TMail FATAL: encoder scan fail'
|
|
||||||
end
|
|
||||||
(str = m.post_match) unless m.nil?
|
|
||||||
end
|
|
||||||
|
|
||||||
do_encode types, strs
|
|
||||||
end
|
|
||||||
|
|
||||||
def do_encode( types, strs )
|
|
||||||
#
|
|
||||||
# result : (A|E)(S(A|E))*
|
|
||||||
# E : W(SW)*
|
|
||||||
# W : (J|A)+ but must contain J # (J|A)*J(J|A)*
|
|
||||||
# A : <<A character string not to be encoded>>
|
|
||||||
# J : <<A character string to be encoded>>
|
|
||||||
# S : <<LWSP>>
|
|
||||||
#
|
|
||||||
# An encoding unit is `E'.
|
|
||||||
# Input (parameter `types') is (J|A)(J|A|S)*(J|A)
|
|
||||||
#
|
|
||||||
if BENCODE_DEBUG
|
|
||||||
puts
|
|
||||||
puts '-- do_encode ------------'
|
|
||||||
puts types.split(//).join(' ')
|
|
||||||
p strs
|
|
||||||
end
|
|
||||||
|
|
||||||
e = /[ja]*j[ja]*(?:s[ja]*j[ja]*)*/
|
|
||||||
|
|
||||||
while m = e.match(types)
|
|
||||||
pre = m.pre_match
|
|
||||||
concat_A_S pre, strs[0, pre.size] unless pre.empty?
|
|
||||||
concat_E m[0], strs[m.begin(0) ... m.end(0)]
|
|
||||||
types = m.post_match
|
|
||||||
strs.slice! 0, m.end(0)
|
|
||||||
end
|
|
||||||
concat_A_S types, strs
|
|
||||||
end
|
|
||||||
|
|
||||||
def concat_A_S( types, strs )
|
|
||||||
i = 0
|
|
||||||
types.each_byte do |t|
|
|
||||||
case t
|
|
||||||
when ?a then add_text strs[i]
|
|
||||||
when ?s then add_lwsp strs[i]
|
|
||||||
else
|
|
||||||
raise "TMail FATAL: unknown flag: #{t.chr}"
|
|
||||||
end
|
|
||||||
i += 1
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
METHOD_ID = {
|
|
||||||
?j => :extract_J,
|
|
||||||
?e => :extract_E,
|
|
||||||
?a => :extract_A,
|
|
||||||
?s => :extract_S
|
|
||||||
}
|
|
||||||
|
|
||||||
def concat_E( types, strs )
|
|
||||||
if BENCODE_DEBUG
|
|
||||||
puts '---- concat_E'
|
|
||||||
puts "types=#{types.split(//).join(' ')}"
|
|
||||||
puts "strs =#{strs.inspect}"
|
|
||||||
end
|
|
||||||
|
|
||||||
flush() unless @text.empty?
|
|
||||||
|
|
||||||
chunk = ''
|
|
||||||
strs.each_with_index do |s,i|
|
|
||||||
mid = METHOD_ID[types[i]]
|
|
||||||
until s.empty?
|
|
||||||
unless c = __send__(mid, chunk.size, s)
|
|
||||||
add_with_encode chunk unless chunk.empty?
|
|
||||||
flush
|
|
||||||
chunk = ''
|
|
||||||
fold
|
|
||||||
c = __send__(mid, 0, s)
|
|
||||||
raise 'TMail FATAL: extract fail' unless c
|
|
||||||
end
|
|
||||||
chunk << c
|
|
||||||
end
|
|
||||||
end
|
|
||||||
add_with_encode chunk unless chunk.empty?
|
|
||||||
end
|
|
||||||
|
|
||||||
def extract_J( chunksize, str )
|
|
||||||
size = max_bytes(chunksize, str.size) - 6
|
|
||||||
size = (size % 2 == 0) ? (size) : (size - 1)
|
|
||||||
return nil if size <= 0
|
|
||||||
"\e$B#{str.slice!(0, size)}\e(B"
|
|
||||||
end
|
|
||||||
|
|
||||||
def extract_A( chunksize, str )
|
|
||||||
size = max_bytes(chunksize, str.size)
|
|
||||||
return nil if size <= 0
|
|
||||||
str.slice!(0, size)
|
|
||||||
end
|
|
||||||
|
|
||||||
alias extract_S extract_A
|
|
||||||
|
|
||||||
def max_bytes( chunksize, ssize )
|
|
||||||
(restsize() - '=?iso-2022-jp?B??='.size) / 4 * 3 - chunksize
|
|
||||||
end
|
|
||||||
|
|
||||||
#
|
|
||||||
# free length buffer
|
|
||||||
#
|
|
||||||
|
|
||||||
def add_text( str )
|
|
||||||
@text << str
|
|
||||||
# puts '---- text -------------------------------------'
|
|
||||||
# puts "+ #{str.inspect}"
|
|
||||||
# puts "txt >>>#{@text.inspect}<<<"
|
|
||||||
end
|
|
||||||
|
|
||||||
def add_with_encode( str )
|
|
||||||
@text << "=?iso-2022-jp?B?#{Base64.encode(str)}?="
|
|
||||||
end
|
|
||||||
|
|
||||||
def add_lwsp( lwsp )
|
|
||||||
# puts '---- lwsp -------------------------------------'
|
|
||||||
# puts "+ #{lwsp.inspect}"
|
|
||||||
fold if restsize() <= 0
|
|
||||||
flush
|
|
||||||
@lwsp = lwsp
|
|
||||||
end
|
|
||||||
|
|
||||||
def flush
|
|
||||||
# puts '---- flush ----'
|
|
||||||
# puts "spc >>>#{@lwsp.inspect}<<<"
|
|
||||||
# puts "txt >>>#{@text.inspect}<<<"
|
|
||||||
@f << @lwsp << @text
|
|
||||||
@curlen += (@lwsp.size + @text.size)
|
|
||||||
@text = ''
|
|
||||||
@lwsp = ''
|
|
||||||
end
|
|
||||||
|
|
||||||
def fold
|
|
||||||
# puts '---- fold ----'
|
|
||||||
@f << @eol
|
|
||||||
@curlen = 0
|
|
||||||
@lwsp = SPACER
|
|
||||||
end
|
|
||||||
|
|
||||||
def restsize
|
|
||||||
MAX_LINE_LEN - (@curlen + @lwsp.size + @text.size)
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
||||||
end # module TMail
|
|
||||||
|
|
@ -1,552 +0,0 @@
|
||||||
#
|
|
||||||
# facade.rb
|
|
||||||
#
|
|
||||||
#--
|
|
||||||
# Copyright (c) 1998-2003 Minero Aoki <aamine@loveruby.net>
|
|
||||||
#
|
|
||||||
# Permission is hereby granted, free of charge, to any person obtaining
|
|
||||||
# a copy of this software and associated documentation files (the
|
|
||||||
# "Software"), to deal in the Software without restriction, including
|
|
||||||
# without limitation the rights to use, copy, modify, merge, publish,
|
|
||||||
# distribute, sublicense, and/or sell copies of the Software, and to
|
|
||||||
# permit persons to whom the Software is furnished to do so, subject to
|
|
||||||
# the following conditions:
|
|
||||||
#
|
|
||||||
# The above copyright notice and this permission notice shall be
|
|
||||||
# included in all copies or substantial portions of the Software.
|
|
||||||
#
|
|
||||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
||||||
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
||||||
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
||||||
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
||||||
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
||||||
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
||||||
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
||||||
#
|
|
||||||
# Note: Originally licensed under LGPL v2+. Using MIT license for Rails
|
|
||||||
# with permission of Minero Aoki.
|
|
||||||
#++
|
|
||||||
|
|
||||||
require 'tmail/utils'
|
|
||||||
|
|
||||||
|
|
||||||
module TMail
|
|
||||||
|
|
||||||
class Mail
|
|
||||||
|
|
||||||
def header_string( name, default = nil )
|
|
||||||
h = @header[name.downcase] or return default
|
|
||||||
h.to_s
|
|
||||||
end
|
|
||||||
|
|
||||||
###
|
|
||||||
### attributes
|
|
||||||
###
|
|
||||||
|
|
||||||
include TextUtils
|
|
||||||
|
|
||||||
def set_string_array_attr( key, strs )
|
|
||||||
strs.flatten!
|
|
||||||
if strs.empty?
|
|
||||||
@header.delete key.downcase
|
|
||||||
else
|
|
||||||
store key, strs.join(', ')
|
|
||||||
end
|
|
||||||
strs
|
|
||||||
end
|
|
||||||
private :set_string_array_attr
|
|
||||||
|
|
||||||
def set_string_attr( key, str )
|
|
||||||
if str
|
|
||||||
store key, str
|
|
||||||
else
|
|
||||||
@header.delete key.downcase
|
|
||||||
end
|
|
||||||
str
|
|
||||||
end
|
|
||||||
private :set_string_attr
|
|
||||||
|
|
||||||
def set_addrfield( name, arg )
|
|
||||||
if arg
|
|
||||||
h = HeaderField.internal_new(name, @config)
|
|
||||||
h.addrs.replace [arg].flatten
|
|
||||||
@header[name] = h
|
|
||||||
else
|
|
||||||
@header.delete name
|
|
||||||
end
|
|
||||||
arg
|
|
||||||
end
|
|
||||||
private :set_addrfield
|
|
||||||
|
|
||||||
def addrs2specs( addrs )
|
|
||||||
return nil unless addrs
|
|
||||||
list = addrs.map {|addr|
|
|
||||||
if addr.address_group?
|
|
||||||
then addr.map {|a| a.spec }
|
|
||||||
else addr.spec
|
|
||||||
end
|
|
||||||
}.flatten
|
|
||||||
return nil if list.empty?
|
|
||||||
list
|
|
||||||
end
|
|
||||||
private :addrs2specs
|
|
||||||
|
|
||||||
|
|
||||||
#
|
|
||||||
# date time
|
|
||||||
#
|
|
||||||
|
|
||||||
def date( default = nil )
|
|
||||||
if h = @header['date']
|
|
||||||
h.date
|
|
||||||
else
|
|
||||||
default
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def date=( time )
|
|
||||||
if time
|
|
||||||
store 'Date', time2str(time)
|
|
||||||
else
|
|
||||||
@header.delete 'date'
|
|
||||||
end
|
|
||||||
time
|
|
||||||
end
|
|
||||||
|
|
||||||
def strftime( fmt, default = nil )
|
|
||||||
if t = date
|
|
||||||
t.strftime(fmt)
|
|
||||||
else
|
|
||||||
default
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
#
|
|
||||||
# destination
|
|
||||||
#
|
|
||||||
|
|
||||||
def to_addrs( default = nil )
|
|
||||||
if h = @header['to']
|
|
||||||
h.addrs
|
|
||||||
else
|
|
||||||
default
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def cc_addrs( default = nil )
|
|
||||||
if h = @header['cc']
|
|
||||||
h.addrs
|
|
||||||
else
|
|
||||||
default
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def bcc_addrs( default = nil )
|
|
||||||
if h = @header['bcc']
|
|
||||||
h.addrs
|
|
||||||
else
|
|
||||||
default
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def to_addrs=( arg )
|
|
||||||
set_addrfield 'to', arg
|
|
||||||
end
|
|
||||||
|
|
||||||
def cc_addrs=( arg )
|
|
||||||
set_addrfield 'cc', arg
|
|
||||||
end
|
|
||||||
|
|
||||||
def bcc_addrs=( arg )
|
|
||||||
set_addrfield 'bcc', arg
|
|
||||||
end
|
|
||||||
|
|
||||||
def to( default = nil )
|
|
||||||
addrs2specs(to_addrs(nil)) || default
|
|
||||||
end
|
|
||||||
|
|
||||||
def cc( default = nil )
|
|
||||||
addrs2specs(cc_addrs(nil)) || default
|
|
||||||
end
|
|
||||||
|
|
||||||
def bcc( default = nil )
|
|
||||||
addrs2specs(bcc_addrs(nil)) || default
|
|
||||||
end
|
|
||||||
|
|
||||||
def to=( *strs )
|
|
||||||
set_string_array_attr 'To', strs
|
|
||||||
end
|
|
||||||
|
|
||||||
def cc=( *strs )
|
|
||||||
set_string_array_attr 'Cc', strs
|
|
||||||
end
|
|
||||||
|
|
||||||
def bcc=( *strs )
|
|
||||||
set_string_array_attr 'Bcc', strs
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
#
|
|
||||||
# originator
|
|
||||||
#
|
|
||||||
|
|
||||||
def from_addrs( default = nil )
|
|
||||||
if h = @header['from']
|
|
||||||
h.addrs
|
|
||||||
else
|
|
||||||
default
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def from_addrs=( arg )
|
|
||||||
set_addrfield 'from', arg
|
|
||||||
end
|
|
||||||
|
|
||||||
def from( default = nil )
|
|
||||||
addrs2specs(from_addrs(nil)) || default
|
|
||||||
end
|
|
||||||
|
|
||||||
def from=( *strs )
|
|
||||||
set_string_array_attr 'From', strs
|
|
||||||
end
|
|
||||||
|
|
||||||
def friendly_from( default = nil )
|
|
||||||
h = @header['from']
|
|
||||||
a, = h.addrs
|
|
||||||
return default unless a
|
|
||||||
return a.phrase if a.phrase
|
|
||||||
return h.comments.join(' ') unless h.comments.empty?
|
|
||||||
a.spec
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
def reply_to_addrs( default = nil )
|
|
||||||
if h = @header['reply-to']
|
|
||||||
h.addrs
|
|
||||||
else
|
|
||||||
default
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def reply_to_addrs=( arg )
|
|
||||||
set_addrfield 'reply-to', arg
|
|
||||||
end
|
|
||||||
|
|
||||||
def reply_to( default = nil )
|
|
||||||
addrs2specs(reply_to_addrs(nil)) || default
|
|
||||||
end
|
|
||||||
|
|
||||||
def reply_to=( *strs )
|
|
||||||
set_string_array_attr 'Reply-To', strs
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
def sender_addr( default = nil )
|
|
||||||
f = @header['sender'] or return default
|
|
||||||
f.addr or return default
|
|
||||||
end
|
|
||||||
|
|
||||||
def sender_addr=( addr )
|
|
||||||
if addr
|
|
||||||
h = HeaderField.internal_new('sender', @config)
|
|
||||||
h.addr = addr
|
|
||||||
@header['sender'] = h
|
|
||||||
else
|
|
||||||
@header.delete 'sender'
|
|
||||||
end
|
|
||||||
addr
|
|
||||||
end
|
|
||||||
|
|
||||||
def sender( default )
|
|
||||||
f = @header['sender'] or return default
|
|
||||||
a = f.addr or return default
|
|
||||||
a.spec
|
|
||||||
end
|
|
||||||
|
|
||||||
def sender=( str )
|
|
||||||
set_string_attr 'Sender', str
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
#
|
|
||||||
# subject
|
|
||||||
#
|
|
||||||
|
|
||||||
def subject( default = nil )
|
|
||||||
if h = @header['subject']
|
|
||||||
h.body
|
|
||||||
else
|
|
||||||
default
|
|
||||||
end
|
|
||||||
end
|
|
||||||
alias quoted_subject subject
|
|
||||||
|
|
||||||
def subject=( str )
|
|
||||||
set_string_attr 'Subject', str
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
#
|
|
||||||
# identity & threading
|
|
||||||
#
|
|
||||||
|
|
||||||
def message_id( default = nil )
|
|
||||||
if h = @header['message-id']
|
|
||||||
h.id || default
|
|
||||||
else
|
|
||||||
default
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def message_id=( str )
|
|
||||||
set_string_attr 'Message-Id', str
|
|
||||||
end
|
|
||||||
|
|
||||||
def in_reply_to( default = nil )
|
|
||||||
if h = @header['in-reply-to']
|
|
||||||
h.ids
|
|
||||||
else
|
|
||||||
default
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def in_reply_to=( *idstrs )
|
|
||||||
set_string_array_attr 'In-Reply-To', idstrs
|
|
||||||
end
|
|
||||||
|
|
||||||
def references( default = nil )
|
|
||||||
if h = @header['references']
|
|
||||||
h.refs
|
|
||||||
else
|
|
||||||
default
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def references=( *strs )
|
|
||||||
set_string_array_attr 'References', strs
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
#
|
|
||||||
# MIME headers
|
|
||||||
#
|
|
||||||
|
|
||||||
def mime_version( default = nil )
|
|
||||||
if h = @header['mime-version']
|
|
||||||
h.version || default
|
|
||||||
else
|
|
||||||
default
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def mime_version=( m, opt = nil )
|
|
||||||
if opt
|
|
||||||
if h = @header['mime-version']
|
|
||||||
h.major = m
|
|
||||||
h.minor = opt
|
|
||||||
else
|
|
||||||
store 'Mime-Version', "#{m}.#{opt}"
|
|
||||||
end
|
|
||||||
else
|
|
||||||
store 'Mime-Version', m
|
|
||||||
end
|
|
||||||
m
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
def content_type( default = nil )
|
|
||||||
if h = @header['content-type']
|
|
||||||
h.content_type || default
|
|
||||||
else
|
|
||||||
default
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def main_type( default = nil )
|
|
||||||
if h = @header['content-type']
|
|
||||||
h.main_type || default
|
|
||||||
else
|
|
||||||
default
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def sub_type( default = nil )
|
|
||||||
if h = @header['content-type']
|
|
||||||
h.sub_type || default
|
|
||||||
else
|
|
||||||
default
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def set_content_type( str, sub = nil, param = nil )
|
|
||||||
if sub
|
|
||||||
main, sub = str, sub
|
|
||||||
else
|
|
||||||
main, sub = str.split(%r</>, 2)
|
|
||||||
raise ArgumentError, "sub type missing: #{str.inspect}" unless sub
|
|
||||||
end
|
|
||||||
if h = @header['content-type']
|
|
||||||
h.main_type = main
|
|
||||||
h.sub_type = sub
|
|
||||||
h.params.clear
|
|
||||||
else
|
|
||||||
store 'Content-Type', "#{main}/#{sub}"
|
|
||||||
end
|
|
||||||
@header['content-type'].params.replace param if param
|
|
||||||
|
|
||||||
str
|
|
||||||
end
|
|
||||||
|
|
||||||
alias content_type= set_content_type
|
|
||||||
|
|
||||||
def type_param( name, default = nil )
|
|
||||||
if h = @header['content-type']
|
|
||||||
h[name] || default
|
|
||||||
else
|
|
||||||
default
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def charset( default = nil )
|
|
||||||
if h = @header['content-type']
|
|
||||||
h['charset'] or default
|
|
||||||
else
|
|
||||||
default
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def charset=( str )
|
|
||||||
if str
|
|
||||||
if h = @header[ 'content-type' ]
|
|
||||||
h['charset'] = str
|
|
||||||
else
|
|
||||||
store 'Content-Type', "text/plain; charset=#{str}"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
str
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
def transfer_encoding( default = nil )
|
|
||||||
if h = @header['content-transfer-encoding']
|
|
||||||
h.encoding || default
|
|
||||||
else
|
|
||||||
default
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def transfer_encoding=( str )
|
|
||||||
set_string_attr 'Content-Transfer-Encoding', str
|
|
||||||
end
|
|
||||||
|
|
||||||
alias encoding transfer_encoding
|
|
||||||
alias encoding= transfer_encoding=
|
|
||||||
alias content_transfer_encoding transfer_encoding
|
|
||||||
alias content_transfer_encoding= transfer_encoding=
|
|
||||||
|
|
||||||
|
|
||||||
def disposition( default = nil )
|
|
||||||
if h = @header['content-disposition']
|
|
||||||
h.disposition || default
|
|
||||||
else
|
|
||||||
default
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
alias content_disposition disposition
|
|
||||||
|
|
||||||
def set_disposition( str, params = nil )
|
|
||||||
if h = @header['content-disposition']
|
|
||||||
h.disposition = str
|
|
||||||
h.params.clear
|
|
||||||
else
|
|
||||||
store('Content-Disposition', str)
|
|
||||||
h = @header['content-disposition']
|
|
||||||
end
|
|
||||||
h.params.replace params if params
|
|
||||||
end
|
|
||||||
|
|
||||||
alias disposition= set_disposition
|
|
||||||
alias set_content_disposition set_disposition
|
|
||||||
alias content_disposition= set_disposition
|
|
||||||
|
|
||||||
def disposition_param( name, default = nil )
|
|
||||||
if h = @header['content-disposition']
|
|
||||||
h[name] || default
|
|
||||||
else
|
|
||||||
default
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
###
|
|
||||||
### utils
|
|
||||||
###
|
|
||||||
|
|
||||||
def create_reply
|
|
||||||
mail = TMail::Mail.parse('')
|
|
||||||
mail.subject = 'Re: ' + subject('').sub(/\A(?:\[[^\]]+\])?(?:\s*Re:)*\s*/i, '')
|
|
||||||
mail.to_addrs = reply_addresses([])
|
|
||||||
mail.in_reply_to = [message_id(nil)].compact
|
|
||||||
mail.references = references([]) + [message_id(nil)].compact
|
|
||||||
mail.mime_version = '1.0'
|
|
||||||
mail
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
def base64_encode
|
|
||||||
store 'Content-Transfer-Encoding', 'Base64'
|
|
||||||
self.body = Base64.folding_encode(self.body)
|
|
||||||
end
|
|
||||||
|
|
||||||
def base64_decode
|
|
||||||
if /base64/i === self.transfer_encoding('')
|
|
||||||
store 'Content-Transfer-Encoding', '8bit'
|
|
||||||
self.body = Base64.decode(self.body, @config.strict_base64decode?)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
def destinations( default = nil )
|
|
||||||
ret = []
|
|
||||||
%w( to cc bcc ).each do |nm|
|
|
||||||
if h = @header[nm]
|
|
||||||
h.addrs.each {|i| ret.push i.address }
|
|
||||||
end
|
|
||||||
end
|
|
||||||
ret.empty? ? default : ret
|
|
||||||
end
|
|
||||||
|
|
||||||
def each_destination( &block )
|
|
||||||
destinations([]).each do |i|
|
|
||||||
if Address === i
|
|
||||||
yield i
|
|
||||||
else
|
|
||||||
i.each(&block)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
alias each_dest each_destination
|
|
||||||
|
|
||||||
|
|
||||||
def reply_addresses( default = nil )
|
|
||||||
reply_to_addrs(nil) or from_addrs(nil) or default
|
|
||||||
end
|
|
||||||
|
|
||||||
def error_reply_addresses( default = nil )
|
|
||||||
if s = sender(nil)
|
|
||||||
[s]
|
|
||||||
else
|
|
||||||
from_addrs(default)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
def multipart?
|
|
||||||
main_type('').downcase == 'multipart'
|
|
||||||
end
|
|
||||||
|
|
||||||
end # class Mail
|
|
||||||
|
|
||||||
end # module TMail
|
|
||||||
|
|
@ -1,914 +0,0 @@
|
||||||
#
|
|
||||||
# header.rb
|
|
||||||
#
|
|
||||||
#--
|
|
||||||
# Copyright (c) 1998-2003 Minero Aoki <aamine@loveruby.net>
|
|
||||||
#
|
|
||||||
# Permission is hereby granted, free of charge, to any person obtaining
|
|
||||||
# a copy of this software and associated documentation files (the
|
|
||||||
# "Software"), to deal in the Software without restriction, including
|
|
||||||
# without limitation the rights to use, copy, modify, merge, publish,
|
|
||||||
# distribute, sublicense, and/or sell copies of the Software, and to
|
|
||||||
# permit persons to whom the Software is furnished to do so, subject to
|
|
||||||
# the following conditions:
|
|
||||||
#
|
|
||||||
# The above copyright notice and this permission notice shall be
|
|
||||||
# included in all copies or substantial portions of the Software.
|
|
||||||
#
|
|
||||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
||||||
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
||||||
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
||||||
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
||||||
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
||||||
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
||||||
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
||||||
#
|
|
||||||
# Note: Originally licensed under LGPL v2+. Using MIT license for Rails
|
|
||||||
# with permission of Minero Aoki.
|
|
||||||
#++
|
|
||||||
|
|
||||||
require 'tmail/encode'
|
|
||||||
require 'tmail/address'
|
|
||||||
require 'tmail/parser'
|
|
||||||
require 'tmail/config'
|
|
||||||
require 'tmail/utils'
|
|
||||||
|
|
||||||
|
|
||||||
module TMail
|
|
||||||
|
|
||||||
class HeaderField
|
|
||||||
|
|
||||||
include TextUtils
|
|
||||||
|
|
||||||
class << self
|
|
||||||
|
|
||||||
alias newobj new
|
|
||||||
|
|
||||||
def new( name, body, conf = DEFAULT_CONFIG )
|
|
||||||
klass = FNAME_TO_CLASS[name.downcase] || UnstructuredHeader
|
|
||||||
klass.newobj body, conf
|
|
||||||
end
|
|
||||||
|
|
||||||
def new_from_port( port, name, conf = DEFAULT_CONFIG )
|
|
||||||
re = Regep.new('\A(' + Regexp.quote(name) + '):', 'i')
|
|
||||||
str = nil
|
|
||||||
port.ropen {|f|
|
|
||||||
f.each do |line|
|
|
||||||
if m = re.match(line) then str = m.post_match.strip
|
|
||||||
elsif str and /\A[\t ]/ === line then str << ' ' << line.strip
|
|
||||||
elsif /\A-*\s*\z/ === line then break
|
|
||||||
elsif str then break
|
|
||||||
end
|
|
||||||
end
|
|
||||||
}
|
|
||||||
new(name, str, Config.to_config(conf))
|
|
||||||
end
|
|
||||||
|
|
||||||
def internal_new( name, conf )
|
|
||||||
FNAME_TO_CLASS[name].newobj('', conf, true)
|
|
||||||
end
|
|
||||||
|
|
||||||
end # class << self
|
|
||||||
|
|
||||||
def initialize( body, conf, intern = false )
|
|
||||||
@body = body
|
|
||||||
@config = conf
|
|
||||||
|
|
||||||
@illegal = false
|
|
||||||
@parsed = false
|
|
||||||
if intern
|
|
||||||
@parsed = true
|
|
||||||
parse_init
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def inspect
|
|
||||||
"#<#{self.class} #{@body.inspect}>"
|
|
||||||
end
|
|
||||||
|
|
||||||
def illegal?
|
|
||||||
@illegal
|
|
||||||
end
|
|
||||||
|
|
||||||
def empty?
|
|
||||||
ensure_parsed
|
|
||||||
return true if @illegal
|
|
||||||
isempty?
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def ensure_parsed
|
|
||||||
return if @parsed
|
|
||||||
@parsed = true
|
|
||||||
parse
|
|
||||||
end
|
|
||||||
|
|
||||||
# defabstract parse
|
|
||||||
# end
|
|
||||||
|
|
||||||
def clear_parse_status
|
|
||||||
@parsed = false
|
|
||||||
@illegal = false
|
|
||||||
end
|
|
||||||
|
|
||||||
public
|
|
||||||
|
|
||||||
def body
|
|
||||||
ensure_parsed
|
|
||||||
v = Decoder.new(s = '')
|
|
||||||
do_accept v
|
|
||||||
v.terminate
|
|
||||||
s
|
|
||||||
end
|
|
||||||
|
|
||||||
def body=( str )
|
|
||||||
@body = str
|
|
||||||
clear_parse_status
|
|
||||||
end
|
|
||||||
|
|
||||||
include StrategyInterface
|
|
||||||
|
|
||||||
def accept( strategy, dummy1 = nil, dummy2 = nil )
|
|
||||||
ensure_parsed
|
|
||||||
do_accept strategy
|
|
||||||
strategy.terminate
|
|
||||||
end
|
|
||||||
|
|
||||||
# abstract do_accept
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
class UnstructuredHeader < HeaderField
|
|
||||||
|
|
||||||
def body
|
|
||||||
ensure_parsed
|
|
||||||
@body
|
|
||||||
end
|
|
||||||
|
|
||||||
def body=( arg )
|
|
||||||
ensure_parsed
|
|
||||||
@body = arg
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def parse_init
|
|
||||||
end
|
|
||||||
|
|
||||||
def parse
|
|
||||||
@body = Decoder.decode(@body.gsub(/\n|\r\n|\r/, ''))
|
|
||||||
end
|
|
||||||
|
|
||||||
def isempty?
|
|
||||||
not @body
|
|
||||||
end
|
|
||||||
|
|
||||||
def do_accept( strategy )
|
|
||||||
strategy.text @body
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
class StructuredHeader < HeaderField
|
|
||||||
|
|
||||||
def comments
|
|
||||||
ensure_parsed
|
|
||||||
@comments
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def parse
|
|
||||||
save = nil
|
|
||||||
|
|
||||||
begin
|
|
||||||
parse_init
|
|
||||||
do_parse
|
|
||||||
rescue SyntaxError
|
|
||||||
if not save and mime_encoded? @body
|
|
||||||
save = @body
|
|
||||||
@body = Decoder.decode(save)
|
|
||||||
retry
|
|
||||||
elsif save
|
|
||||||
@body = save
|
|
||||||
end
|
|
||||||
|
|
||||||
@illegal = true
|
|
||||||
raise if @config.strict_parse?
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def parse_init
|
|
||||||
@comments = []
|
|
||||||
init
|
|
||||||
end
|
|
||||||
|
|
||||||
def do_parse
|
|
||||||
obj = Parser.parse(self.class::PARSE_TYPE, @body, @comments)
|
|
||||||
set obj if obj
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
class DateTimeHeader < StructuredHeader
|
|
||||||
|
|
||||||
PARSE_TYPE = :DATETIME
|
|
||||||
|
|
||||||
def date
|
|
||||||
ensure_parsed
|
|
||||||
@date
|
|
||||||
end
|
|
||||||
|
|
||||||
def date=( arg )
|
|
||||||
ensure_parsed
|
|
||||||
@date = arg
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def init
|
|
||||||
@date = nil
|
|
||||||
end
|
|
||||||
|
|
||||||
def set( t )
|
|
||||||
@date = t
|
|
||||||
end
|
|
||||||
|
|
||||||
def isempty?
|
|
||||||
not @date
|
|
||||||
end
|
|
||||||
|
|
||||||
def do_accept( strategy )
|
|
||||||
strategy.meta time2str(@date)
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
class AddressHeader < StructuredHeader
|
|
||||||
|
|
||||||
PARSE_TYPE = :MADDRESS
|
|
||||||
|
|
||||||
def addrs
|
|
||||||
ensure_parsed
|
|
||||||
@addrs
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def init
|
|
||||||
@addrs = []
|
|
||||||
end
|
|
||||||
|
|
||||||
def set( a )
|
|
||||||
@addrs = a
|
|
||||||
end
|
|
||||||
|
|
||||||
def isempty?
|
|
||||||
@addrs.empty?
|
|
||||||
end
|
|
||||||
|
|
||||||
def do_accept( strategy )
|
|
||||||
first = true
|
|
||||||
@addrs.each do |a|
|
|
||||||
if first
|
|
||||||
first = false
|
|
||||||
else
|
|
||||||
strategy.meta ','
|
|
||||||
strategy.space
|
|
||||||
end
|
|
||||||
a.accept strategy
|
|
||||||
end
|
|
||||||
|
|
||||||
@comments.each do |c|
|
|
||||||
strategy.space
|
|
||||||
strategy.meta '('
|
|
||||||
strategy.text c
|
|
||||||
strategy.meta ')'
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
class ReturnPathHeader < AddressHeader
|
|
||||||
|
|
||||||
PARSE_TYPE = :RETPATH
|
|
||||||
|
|
||||||
def addr
|
|
||||||
addrs()[0]
|
|
||||||
end
|
|
||||||
|
|
||||||
def spec
|
|
||||||
a = addr() or return nil
|
|
||||||
a.spec
|
|
||||||
end
|
|
||||||
|
|
||||||
def routes
|
|
||||||
a = addr() or return nil
|
|
||||||
a.routes
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def do_accept( strategy )
|
|
||||||
a = addr()
|
|
||||||
|
|
||||||
strategy.meta '<'
|
|
||||||
unless a.routes.empty?
|
|
||||||
strategy.meta a.routes.map {|i| '@' + i }.join(',')
|
|
||||||
strategy.meta ':'
|
|
||||||
end
|
|
||||||
spec = a.spec
|
|
||||||
strategy.meta spec if spec
|
|
||||||
strategy.meta '>'
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
class SingleAddressHeader < AddressHeader
|
|
||||||
|
|
||||||
def addr
|
|
||||||
addrs()[0]
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def do_accept( strategy )
|
|
||||||
a = addr()
|
|
||||||
a.accept strategy
|
|
||||||
@comments.each do |c|
|
|
||||||
strategy.space
|
|
||||||
strategy.meta '('
|
|
||||||
strategy.text c
|
|
||||||
strategy.meta ')'
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
class MessageIdHeader < StructuredHeader
|
|
||||||
|
|
||||||
def id
|
|
||||||
ensure_parsed
|
|
||||||
@id
|
|
||||||
end
|
|
||||||
|
|
||||||
def id=( arg )
|
|
||||||
ensure_parsed
|
|
||||||
@id = arg
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def init
|
|
||||||
@id = nil
|
|
||||||
end
|
|
||||||
|
|
||||||
def isempty?
|
|
||||||
not @id
|
|
||||||
end
|
|
||||||
|
|
||||||
def do_parse
|
|
||||||
@id = @body.slice(MESSAGE_ID) or
|
|
||||||
raise SyntaxError, "wrong Message-ID format: #{@body}"
|
|
||||||
end
|
|
||||||
|
|
||||||
def do_accept( strategy )
|
|
||||||
strategy.meta @id
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
class ReferencesHeader < StructuredHeader
|
|
||||||
|
|
||||||
def refs
|
|
||||||
ensure_parsed
|
|
||||||
@refs
|
|
||||||
end
|
|
||||||
|
|
||||||
def each_id
|
|
||||||
self.refs.each do |i|
|
|
||||||
yield i if MESSAGE_ID === i
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def ids
|
|
||||||
ensure_parsed
|
|
||||||
@ids
|
|
||||||
end
|
|
||||||
|
|
||||||
def each_phrase
|
|
||||||
self.refs.each do |i|
|
|
||||||
yield i unless MESSAGE_ID === i
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def phrases
|
|
||||||
ret = []
|
|
||||||
each_phrase {|i| ret.push i }
|
|
||||||
ret
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def init
|
|
||||||
@refs = []
|
|
||||||
@ids = []
|
|
||||||
end
|
|
||||||
|
|
||||||
def isempty?
|
|
||||||
@ids.empty?
|
|
||||||
end
|
|
||||||
|
|
||||||
def do_parse
|
|
||||||
str = @body
|
|
||||||
while m = MESSAGE_ID.match(str)
|
|
||||||
pre = m.pre_match.strip
|
|
||||||
@refs.push pre unless pre.empty?
|
|
||||||
@refs.push s = m[0]
|
|
||||||
@ids.push s
|
|
||||||
str = m.post_match
|
|
||||||
end
|
|
||||||
str = str.strip
|
|
||||||
@refs.push str unless str.empty?
|
|
||||||
end
|
|
||||||
|
|
||||||
def do_accept( strategy )
|
|
||||||
first = true
|
|
||||||
@ids.each do |i|
|
|
||||||
if first
|
|
||||||
first = false
|
|
||||||
else
|
|
||||||
strategy.space
|
|
||||||
end
|
|
||||||
strategy.meta i
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
class ReceivedHeader < StructuredHeader
|
|
||||||
|
|
||||||
PARSE_TYPE = :RECEIVED
|
|
||||||
|
|
||||||
def from
|
|
||||||
ensure_parsed
|
|
||||||
@from
|
|
||||||
end
|
|
||||||
|
|
||||||
def from=( arg )
|
|
||||||
ensure_parsed
|
|
||||||
@from = arg
|
|
||||||
end
|
|
||||||
|
|
||||||
def by
|
|
||||||
ensure_parsed
|
|
||||||
@by
|
|
||||||
end
|
|
||||||
|
|
||||||
def by=( arg )
|
|
||||||
ensure_parsed
|
|
||||||
@by = arg
|
|
||||||
end
|
|
||||||
|
|
||||||
def via
|
|
||||||
ensure_parsed
|
|
||||||
@via
|
|
||||||
end
|
|
||||||
|
|
||||||
def via=( arg )
|
|
||||||
ensure_parsed
|
|
||||||
@via = arg
|
|
||||||
end
|
|
||||||
|
|
||||||
def with
|
|
||||||
ensure_parsed
|
|
||||||
@with
|
|
||||||
end
|
|
||||||
|
|
||||||
def id
|
|
||||||
ensure_parsed
|
|
||||||
@id
|
|
||||||
end
|
|
||||||
|
|
||||||
def id=( arg )
|
|
||||||
ensure_parsed
|
|
||||||
@id = arg
|
|
||||||
end
|
|
||||||
|
|
||||||
def _for
|
|
||||||
ensure_parsed
|
|
||||||
@_for
|
|
||||||
end
|
|
||||||
|
|
||||||
def _for=( arg )
|
|
||||||
ensure_parsed
|
|
||||||
@_for = arg
|
|
||||||
end
|
|
||||||
|
|
||||||
def date
|
|
||||||
ensure_parsed
|
|
||||||
@date
|
|
||||||
end
|
|
||||||
|
|
||||||
def date=( arg )
|
|
||||||
ensure_parsed
|
|
||||||
@date = arg
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def init
|
|
||||||
@from = @by = @via = @with = @id = @_for = nil
|
|
||||||
@with = []
|
|
||||||
@date = nil
|
|
||||||
end
|
|
||||||
|
|
||||||
def set( args )
|
|
||||||
@from, @by, @via, @with, @id, @_for, @date = *args
|
|
||||||
end
|
|
||||||
|
|
||||||
def isempty?
|
|
||||||
@with.empty? and not (@from or @by or @via or @id or @_for or @date)
|
|
||||||
end
|
|
||||||
|
|
||||||
def do_accept( strategy )
|
|
||||||
list = []
|
|
||||||
list.push 'from ' + @from if @from
|
|
||||||
list.push 'by ' + @by if @by
|
|
||||||
list.push 'via ' + @via if @via
|
|
||||||
@with.each do |i|
|
|
||||||
list.push 'with ' + i
|
|
||||||
end
|
|
||||||
list.push 'id ' + @id if @id
|
|
||||||
list.push 'for <' + @_for + '>' if @_for
|
|
||||||
|
|
||||||
first = true
|
|
||||||
list.each do |i|
|
|
||||||
strategy.space unless first
|
|
||||||
strategy.meta i
|
|
||||||
first = false
|
|
||||||
end
|
|
||||||
if @date
|
|
||||||
strategy.meta ';'
|
|
||||||
strategy.space
|
|
||||||
strategy.meta time2str(@date)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
class KeywordsHeader < StructuredHeader
|
|
||||||
|
|
||||||
PARSE_TYPE = :KEYWORDS
|
|
||||||
|
|
||||||
def keys
|
|
||||||
ensure_parsed
|
|
||||||
@keys
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def init
|
|
||||||
@keys = []
|
|
||||||
end
|
|
||||||
|
|
||||||
def set( a )
|
|
||||||
@keys = a
|
|
||||||
end
|
|
||||||
|
|
||||||
def isempty?
|
|
||||||
@keys.empty?
|
|
||||||
end
|
|
||||||
|
|
||||||
def do_accept( strategy )
|
|
||||||
first = true
|
|
||||||
@keys.each do |i|
|
|
||||||
if first
|
|
||||||
first = false
|
|
||||||
else
|
|
||||||
strategy.meta ','
|
|
||||||
end
|
|
||||||
strategy.meta i
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
class EncryptedHeader < StructuredHeader
|
|
||||||
|
|
||||||
PARSE_TYPE = :ENCRYPTED
|
|
||||||
|
|
||||||
def encrypter
|
|
||||||
ensure_parsed
|
|
||||||
@encrypter
|
|
||||||
end
|
|
||||||
|
|
||||||
def encrypter=( arg )
|
|
||||||
ensure_parsed
|
|
||||||
@encrypter = arg
|
|
||||||
end
|
|
||||||
|
|
||||||
def keyword
|
|
||||||
ensure_parsed
|
|
||||||
@keyword
|
|
||||||
end
|
|
||||||
|
|
||||||
def keyword=( arg )
|
|
||||||
ensure_parsed
|
|
||||||
@keyword = arg
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def init
|
|
||||||
@encrypter = nil
|
|
||||||
@keyword = nil
|
|
||||||
end
|
|
||||||
|
|
||||||
def set( args )
|
|
||||||
@encrypter, @keyword = args
|
|
||||||
end
|
|
||||||
|
|
||||||
def isempty?
|
|
||||||
not (@encrypter or @keyword)
|
|
||||||
end
|
|
||||||
|
|
||||||
def do_accept( strategy )
|
|
||||||
if @key
|
|
||||||
strategy.meta @encrypter + ','
|
|
||||||
strategy.space
|
|
||||||
strategy.meta @keyword
|
|
||||||
else
|
|
||||||
strategy.meta @encrypter
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
class MimeVersionHeader < StructuredHeader
|
|
||||||
|
|
||||||
PARSE_TYPE = :MIMEVERSION
|
|
||||||
|
|
||||||
def major
|
|
||||||
ensure_parsed
|
|
||||||
@major
|
|
||||||
end
|
|
||||||
|
|
||||||
def major=( arg )
|
|
||||||
ensure_parsed
|
|
||||||
@major = arg
|
|
||||||
end
|
|
||||||
|
|
||||||
def minor
|
|
||||||
ensure_parsed
|
|
||||||
@minor
|
|
||||||
end
|
|
||||||
|
|
||||||
def minor=( arg )
|
|
||||||
ensure_parsed
|
|
||||||
@minor = arg
|
|
||||||
end
|
|
||||||
|
|
||||||
def version
|
|
||||||
sprintf('%d.%d', major, minor)
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def init
|
|
||||||
@major = nil
|
|
||||||
@minor = nil
|
|
||||||
end
|
|
||||||
|
|
||||||
def set( args )
|
|
||||||
@major, @minor = *args
|
|
||||||
end
|
|
||||||
|
|
||||||
def isempty?
|
|
||||||
not (@major or @minor)
|
|
||||||
end
|
|
||||||
|
|
||||||
def do_accept( strategy )
|
|
||||||
strategy.meta sprintf('%d.%d', @major, @minor)
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
class ContentTypeHeader < StructuredHeader
|
|
||||||
|
|
||||||
PARSE_TYPE = :CTYPE
|
|
||||||
|
|
||||||
def main_type
|
|
||||||
ensure_parsed
|
|
||||||
@main
|
|
||||||
end
|
|
||||||
|
|
||||||
def main_type=( arg )
|
|
||||||
ensure_parsed
|
|
||||||
@main = arg.downcase
|
|
||||||
end
|
|
||||||
|
|
||||||
def sub_type
|
|
||||||
ensure_parsed
|
|
||||||
@sub
|
|
||||||
end
|
|
||||||
|
|
||||||
def sub_type=( arg )
|
|
||||||
ensure_parsed
|
|
||||||
@sub = arg.downcase
|
|
||||||
end
|
|
||||||
|
|
||||||
def content_type
|
|
||||||
ensure_parsed
|
|
||||||
@sub ? sprintf('%s/%s', @main, @sub) : @main
|
|
||||||
end
|
|
||||||
|
|
||||||
def params
|
|
||||||
ensure_parsed
|
|
||||||
@params
|
|
||||||
end
|
|
||||||
|
|
||||||
def []( key )
|
|
||||||
ensure_parsed
|
|
||||||
@params and @params[key]
|
|
||||||
end
|
|
||||||
|
|
||||||
def []=( key, val )
|
|
||||||
ensure_parsed
|
|
||||||
(@params ||= {})[key] = val
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def init
|
|
||||||
@main = @sub = @params = nil
|
|
||||||
end
|
|
||||||
|
|
||||||
def set( args )
|
|
||||||
@main, @sub, @params = *args
|
|
||||||
end
|
|
||||||
|
|
||||||
def isempty?
|
|
||||||
not (@main or @sub)
|
|
||||||
end
|
|
||||||
|
|
||||||
def do_accept( strategy )
|
|
||||||
if @sub
|
|
||||||
strategy.meta sprintf('%s/%s', @main, @sub)
|
|
||||||
else
|
|
||||||
strategy.meta @main
|
|
||||||
end
|
|
||||||
@params.each do |k,v|
|
|
||||||
if v
|
|
||||||
strategy.meta ';'
|
|
||||||
strategy.space
|
|
||||||
strategy.kv_pair k, v
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
class ContentTransferEncodingHeader < StructuredHeader
|
|
||||||
|
|
||||||
PARSE_TYPE = :CENCODING
|
|
||||||
|
|
||||||
def encoding
|
|
||||||
ensure_parsed
|
|
||||||
@encoding
|
|
||||||
end
|
|
||||||
|
|
||||||
def encoding=( arg )
|
|
||||||
ensure_parsed
|
|
||||||
@encoding = arg
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def init
|
|
||||||
@encoding = nil
|
|
||||||
end
|
|
||||||
|
|
||||||
def set( s )
|
|
||||||
@encoding = s
|
|
||||||
end
|
|
||||||
|
|
||||||
def isempty?
|
|
||||||
not @encoding
|
|
||||||
end
|
|
||||||
|
|
||||||
def do_accept( strategy )
|
|
||||||
strategy.meta @encoding.capitalize
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
class ContentDispositionHeader < StructuredHeader
|
|
||||||
|
|
||||||
PARSE_TYPE = :CDISPOSITION
|
|
||||||
|
|
||||||
def disposition
|
|
||||||
ensure_parsed
|
|
||||||
@disposition
|
|
||||||
end
|
|
||||||
|
|
||||||
def disposition=( str )
|
|
||||||
ensure_parsed
|
|
||||||
@disposition = str.downcase
|
|
||||||
end
|
|
||||||
|
|
||||||
def params
|
|
||||||
ensure_parsed
|
|
||||||
@params
|
|
||||||
end
|
|
||||||
|
|
||||||
def []( key )
|
|
||||||
ensure_parsed
|
|
||||||
@params and @params[key]
|
|
||||||
end
|
|
||||||
|
|
||||||
def []=( key, val )
|
|
||||||
ensure_parsed
|
|
||||||
(@params ||= {})[key] = val
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def init
|
|
||||||
@disposition = @params = nil
|
|
||||||
end
|
|
||||||
|
|
||||||
def set( args )
|
|
||||||
@disposition, @params = *args
|
|
||||||
end
|
|
||||||
|
|
||||||
def isempty?
|
|
||||||
not @disposition and (not @params or @params.empty?)
|
|
||||||
end
|
|
||||||
|
|
||||||
def do_accept( strategy )
|
|
||||||
strategy.meta @disposition
|
|
||||||
@params.each do |k,v|
|
|
||||||
strategy.meta ';'
|
|
||||||
strategy.space
|
|
||||||
strategy.kv_pair k, v
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
class HeaderField # redefine
|
|
||||||
|
|
||||||
FNAME_TO_CLASS = {
|
|
||||||
'date' => DateTimeHeader,
|
|
||||||
'resent-date' => DateTimeHeader,
|
|
||||||
'to' => AddressHeader,
|
|
||||||
'cc' => AddressHeader,
|
|
||||||
'bcc' => AddressHeader,
|
|
||||||
'from' => AddressHeader,
|
|
||||||
'reply-to' => AddressHeader,
|
|
||||||
'resent-to' => AddressHeader,
|
|
||||||
'resent-cc' => AddressHeader,
|
|
||||||
'resent-bcc' => AddressHeader,
|
|
||||||
'resent-from' => AddressHeader,
|
|
||||||
'resent-reply-to' => AddressHeader,
|
|
||||||
'sender' => SingleAddressHeader,
|
|
||||||
'resent-sender' => SingleAddressHeader,
|
|
||||||
'return-path' => ReturnPathHeader,
|
|
||||||
'message-id' => MessageIdHeader,
|
|
||||||
'resent-message-id' => MessageIdHeader,
|
|
||||||
'in-reply-to' => ReferencesHeader,
|
|
||||||
'received' => ReceivedHeader,
|
|
||||||
'references' => ReferencesHeader,
|
|
||||||
'keywords' => KeywordsHeader,
|
|
||||||
'encrypted' => EncryptedHeader,
|
|
||||||
'mime-version' => MimeVersionHeader,
|
|
||||||
'content-type' => ContentTypeHeader,
|
|
||||||
'content-transfer-encoding' => ContentTransferEncodingHeader,
|
|
||||||
'content-disposition' => ContentDispositionHeader,
|
|
||||||
'content-id' => MessageIdHeader,
|
|
||||||
'subject' => UnstructuredHeader,
|
|
||||||
'comments' => UnstructuredHeader,
|
|
||||||
'content-description' => UnstructuredHeader
|
|
||||||
}
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
||||||
end # module TMail
|
|
||||||
|
|
@ -1,35 +0,0 @@
|
||||||
#
|
|
||||||
# info.rb
|
|
||||||
#
|
|
||||||
#--
|
|
||||||
# Copyright (c) 1998-2003 Minero Aoki <aamine@loveruby.net>
|
|
||||||
#
|
|
||||||
# Permission is hereby granted, free of charge, to any person obtaining
|
|
||||||
# a copy of this software and associated documentation files (the
|
|
||||||
# "Software"), to deal in the Software without restriction, including
|
|
||||||
# without limitation the rights to use, copy, modify, merge, publish,
|
|
||||||
# distribute, sublicense, and/or sell copies of the Software, and to
|
|
||||||
# permit persons to whom the Software is furnished to do so, subject to
|
|
||||||
# the following conditions:
|
|
||||||
#
|
|
||||||
# The above copyright notice and this permission notice shall be
|
|
||||||
# included in all copies or substantial portions of the Software.
|
|
||||||
#
|
|
||||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
||||||
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
||||||
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
||||||
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
||||||
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
||||||
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
||||||
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
||||||
#
|
|
||||||
# Note: Originally licensed under LGPL v2+. Using MIT license for Rails
|
|
||||||
# with permission of Minero Aoki.
|
|
||||||
#++
|
|
||||||
|
|
||||||
module TMail
|
|
||||||
|
|
||||||
Version = '0.10.7'
|
|
||||||
Copyright = 'Copyright (c) 1998-2002 Minero Aoki'
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
require 'tmail/mailbox'
|
|
||||||
|
|
@ -1,447 +0,0 @@
|
||||||
#
|
|
||||||
# mail.rb
|
|
||||||
#
|
|
||||||
#--
|
|
||||||
# Copyright (c) 1998-2003 Minero Aoki <aamine@loveruby.net>
|
|
||||||
#
|
|
||||||
# Permission is hereby granted, free of charge, to any person obtaining
|
|
||||||
# a copy of this software and associated documentation files (the
|
|
||||||
# "Software"), to deal in the Software without restriction, including
|
|
||||||
# without limitation the rights to use, copy, modify, merge, publish,
|
|
||||||
# distribute, sublicense, and/or sell copies of the Software, and to
|
|
||||||
# permit persons to whom the Software is furnished to do so, subject to
|
|
||||||
# the following conditions:
|
|
||||||
#
|
|
||||||
# The above copyright notice and this permission notice shall be
|
|
||||||
# included in all copies or substantial portions of the Software.
|
|
||||||
#
|
|
||||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
||||||
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
||||||
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
||||||
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
||||||
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
||||||
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
||||||
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
||||||
#
|
|
||||||
# Note: Originally licensed under LGPL v2+. Using MIT license for Rails
|
|
||||||
# with permission of Minero Aoki.
|
|
||||||
#++
|
|
||||||
|
|
||||||
require 'tmail/facade'
|
|
||||||
require 'tmail/encode'
|
|
||||||
require 'tmail/header'
|
|
||||||
require 'tmail/port'
|
|
||||||
require 'tmail/config'
|
|
||||||
require 'tmail/utils'
|
|
||||||
require 'tmail/attachments'
|
|
||||||
require 'tmail/quoting'
|
|
||||||
require 'socket'
|
|
||||||
|
|
||||||
|
|
||||||
module TMail
|
|
||||||
|
|
||||||
class Mail
|
|
||||||
|
|
||||||
class << self
|
|
||||||
def load( fname )
|
|
||||||
new(FilePort.new(fname))
|
|
||||||
end
|
|
||||||
|
|
||||||
alias load_from load
|
|
||||||
alias loadfrom load
|
|
||||||
|
|
||||||
def parse( str )
|
|
||||||
new(StringPort.new(str))
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def initialize( port = nil, conf = DEFAULT_CONFIG )
|
|
||||||
@port = port || StringPort.new
|
|
||||||
@config = Config.to_config(conf)
|
|
||||||
|
|
||||||
@header = {}
|
|
||||||
@body_port = nil
|
|
||||||
@body_parsed = false
|
|
||||||
@epilogue = ''
|
|
||||||
@parts = []
|
|
||||||
|
|
||||||
@port.ropen {|f|
|
|
||||||
parse_header f
|
|
||||||
parse_body f unless @port.reproducible?
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
attr_reader :port
|
|
||||||
|
|
||||||
def inspect
|
|
||||||
"\#<#{self.class} port=#{@port.inspect} bodyport=#{@body_port.inspect}>"
|
|
||||||
end
|
|
||||||
|
|
||||||
#
|
|
||||||
# to_s interfaces
|
|
||||||
#
|
|
||||||
|
|
||||||
public
|
|
||||||
|
|
||||||
include StrategyInterface
|
|
||||||
|
|
||||||
def write_back( eol = "\n", charset = 'e' )
|
|
||||||
parse_body
|
|
||||||
@port.wopen {|stream| encoded eol, charset, stream }
|
|
||||||
end
|
|
||||||
|
|
||||||
def accept( strategy )
|
|
||||||
with_multipart_encoding(strategy) {
|
|
||||||
ordered_each do |name, field|
|
|
||||||
next if field.empty?
|
|
||||||
strategy.header_name canonical(name)
|
|
||||||
field.accept strategy
|
|
||||||
strategy.puts
|
|
||||||
end
|
|
||||||
strategy.puts
|
|
||||||
body_port().ropen {|r|
|
|
||||||
strategy.write r.read
|
|
||||||
}
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def canonical( name )
|
|
||||||
name.split(/-/).map {|s| s.capitalize }.join('-')
|
|
||||||
end
|
|
||||||
|
|
||||||
def with_multipart_encoding( strategy )
|
|
||||||
if parts().empty? # DO NOT USE @parts
|
|
||||||
yield
|
|
||||||
|
|
||||||
else
|
|
||||||
bound = ::TMail.new_boundary
|
|
||||||
if @header.key? 'content-type'
|
|
||||||
@header['content-type'].params['boundary'] = bound
|
|
||||||
else
|
|
||||||
store 'Content-Type', %<multipart/mixed; boundary="#{bound}">
|
|
||||||
end
|
|
||||||
|
|
||||||
yield
|
|
||||||
|
|
||||||
parts().each do |tm|
|
|
||||||
strategy.puts
|
|
||||||
strategy.puts '--' + bound
|
|
||||||
tm.accept strategy
|
|
||||||
end
|
|
||||||
strategy.puts
|
|
||||||
strategy.puts '--' + bound + '--'
|
|
||||||
strategy.write epilogue()
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
###
|
|
||||||
### header
|
|
||||||
###
|
|
||||||
|
|
||||||
public
|
|
||||||
|
|
||||||
ALLOW_MULTIPLE = {
|
|
||||||
'received' => true,
|
|
||||||
'resent-date' => true,
|
|
||||||
'resent-from' => true,
|
|
||||||
'resent-sender' => true,
|
|
||||||
'resent-to' => true,
|
|
||||||
'resent-cc' => true,
|
|
||||||
'resent-bcc' => true,
|
|
||||||
'resent-message-id' => true,
|
|
||||||
'comments' => true,
|
|
||||||
'keywords' => true
|
|
||||||
}
|
|
||||||
USE_ARRAY = ALLOW_MULTIPLE
|
|
||||||
|
|
||||||
def header
|
|
||||||
@header.dup
|
|
||||||
end
|
|
||||||
|
|
||||||
def []( key )
|
|
||||||
@header[key.downcase]
|
|
||||||
end
|
|
||||||
|
|
||||||
def sub_header(key, param)
|
|
||||||
(hdr = self[key]) ? hdr[param] : nil
|
|
||||||
end
|
|
||||||
|
|
||||||
alias fetch []
|
|
||||||
|
|
||||||
def []=( key, val )
|
|
||||||
dkey = key.downcase
|
|
||||||
|
|
||||||
if val.nil?
|
|
||||||
@header.delete dkey
|
|
||||||
return nil
|
|
||||||
end
|
|
||||||
|
|
||||||
case val
|
|
||||||
when String
|
|
||||||
header = new_hf(key, val)
|
|
||||||
when HeaderField
|
|
||||||
;
|
|
||||||
when Array
|
|
||||||
ALLOW_MULTIPLE.include? dkey or
|
|
||||||
raise ArgumentError, "#{key}: Header must not be multiple"
|
|
||||||
@header[dkey] = val
|
|
||||||
return val
|
|
||||||
else
|
|
||||||
header = new_hf(key, val.to_s)
|
|
||||||
end
|
|
||||||
if ALLOW_MULTIPLE.include? dkey
|
|
||||||
(@header[dkey] ||= []).push header
|
|
||||||
else
|
|
||||||
@header[dkey] = header
|
|
||||||
end
|
|
||||||
|
|
||||||
val
|
|
||||||
end
|
|
||||||
|
|
||||||
alias store []=
|
|
||||||
|
|
||||||
def each_header
|
|
||||||
@header.each do |key, val|
|
|
||||||
[val].flatten.each {|v| yield key, v }
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
alias each_pair each_header
|
|
||||||
|
|
||||||
def each_header_name( &block )
|
|
||||||
@header.each_key(&block)
|
|
||||||
end
|
|
||||||
|
|
||||||
alias each_key each_header_name
|
|
||||||
|
|
||||||
def each_field( &block )
|
|
||||||
@header.values.flatten.each(&block)
|
|
||||||
end
|
|
||||||
|
|
||||||
alias each_value each_field
|
|
||||||
|
|
||||||
FIELD_ORDER = %w(
|
|
||||||
return-path received
|
|
||||||
resent-date resent-from resent-sender resent-to
|
|
||||||
resent-cc resent-bcc resent-message-id
|
|
||||||
date from sender reply-to to cc bcc
|
|
||||||
message-id in-reply-to references
|
|
||||||
subject comments keywords
|
|
||||||
mime-version content-type content-transfer-encoding
|
|
||||||
content-disposition content-description
|
|
||||||
)
|
|
||||||
|
|
||||||
def ordered_each
|
|
||||||
list = @header.keys
|
|
||||||
FIELD_ORDER.each do |name|
|
|
||||||
if list.delete(name)
|
|
||||||
[@header[name]].flatten.each {|v| yield name, v }
|
|
||||||
end
|
|
||||||
end
|
|
||||||
list.each do |name|
|
|
||||||
[@header[name]].flatten.each {|v| yield name, v }
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def clear
|
|
||||||
@header.clear
|
|
||||||
end
|
|
||||||
|
|
||||||
def delete( key )
|
|
||||||
@header.delete key.downcase
|
|
||||||
end
|
|
||||||
|
|
||||||
def delete_if
|
|
||||||
@header.delete_if do |key,val|
|
|
||||||
if Array === val
|
|
||||||
val.delete_if {|v| yield key, v }
|
|
||||||
val.empty?
|
|
||||||
else
|
|
||||||
yield key, val
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def keys
|
|
||||||
@header.keys
|
|
||||||
end
|
|
||||||
|
|
||||||
def key?( key )
|
|
||||||
@header.key? key.downcase
|
|
||||||
end
|
|
||||||
|
|
||||||
def values_at( *args )
|
|
||||||
args.map {|k| @header[k.downcase] }.flatten
|
|
||||||
end
|
|
||||||
|
|
||||||
alias indexes values_at
|
|
||||||
alias indices values_at
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def parse_header( f )
|
|
||||||
name = field = nil
|
|
||||||
unixfrom = nil
|
|
||||||
|
|
||||||
while line = f.gets
|
|
||||||
case line
|
|
||||||
when /\A[ \t]/ # continue from prev line
|
|
||||||
raise SyntaxError, 'mail is began by space' unless field
|
|
||||||
field << ' ' << line.strip
|
|
||||||
|
|
||||||
when /\A([^\: \t]+):\s*/ # new header line
|
|
||||||
add_hf name, field if field
|
|
||||||
name = $1
|
|
||||||
field = $' #.strip
|
|
||||||
|
|
||||||
when /\A\-*\s*\z/ # end of header
|
|
||||||
add_hf name, field if field
|
|
||||||
name = field = nil
|
|
||||||
break
|
|
||||||
|
|
||||||
when /\AFrom (\S+)/
|
|
||||||
unixfrom = $1
|
|
||||||
|
|
||||||
when /^charset=.*/
|
|
||||||
|
|
||||||
else
|
|
||||||
raise SyntaxError, "wrong mail header: '#{line.inspect}'"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
add_hf name, field if name
|
|
||||||
|
|
||||||
if unixfrom
|
|
||||||
add_hf 'Return-Path', "<#{unixfrom}>" unless @header['return-path']
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def add_hf( name, field )
|
|
||||||
key = name.downcase
|
|
||||||
field = new_hf(name, field)
|
|
||||||
|
|
||||||
if ALLOW_MULTIPLE.include? key
|
|
||||||
(@header[key] ||= []).push field
|
|
||||||
else
|
|
||||||
@header[key] = field
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def new_hf( name, field )
|
|
||||||
HeaderField.new(name, field, @config)
|
|
||||||
end
|
|
||||||
|
|
||||||
###
|
|
||||||
### body
|
|
||||||
###
|
|
||||||
|
|
||||||
public
|
|
||||||
|
|
||||||
def body_port
|
|
||||||
parse_body
|
|
||||||
@body_port
|
|
||||||
end
|
|
||||||
|
|
||||||
def each( &block )
|
|
||||||
body_port().ropen {|f| f.each(&block) }
|
|
||||||
end
|
|
||||||
|
|
||||||
def quoted_body
|
|
||||||
parse_body
|
|
||||||
@body_port.ropen {|f|
|
|
||||||
return f.read
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
def body=( str )
|
|
||||||
parse_body
|
|
||||||
@body_port.wopen {|f| f.write str }
|
|
||||||
str
|
|
||||||
end
|
|
||||||
|
|
||||||
alias preamble body
|
|
||||||
alias preamble= body=
|
|
||||||
|
|
||||||
def epilogue
|
|
||||||
parse_body
|
|
||||||
@epilogue.dup
|
|
||||||
end
|
|
||||||
|
|
||||||
def epilogue=( str )
|
|
||||||
parse_body
|
|
||||||
@epilogue = str
|
|
||||||
str
|
|
||||||
end
|
|
||||||
|
|
||||||
def parts
|
|
||||||
parse_body
|
|
||||||
@parts
|
|
||||||
end
|
|
||||||
|
|
||||||
def each_part( &block )
|
|
||||||
parts().each(&block)
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def parse_body( f = nil )
|
|
||||||
return if @body_parsed
|
|
||||||
if f
|
|
||||||
parse_body_0 f
|
|
||||||
else
|
|
||||||
@port.ropen {|f|
|
|
||||||
skip_header f
|
|
||||||
parse_body_0 f
|
|
||||||
}
|
|
||||||
end
|
|
||||||
@body_parsed = true
|
|
||||||
end
|
|
||||||
|
|
||||||
def skip_header( f )
|
|
||||||
while line = f.gets
|
|
||||||
return if /\A[\r\n]*\z/ === line
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def parse_body_0( f )
|
|
||||||
if multipart?
|
|
||||||
read_multipart f
|
|
||||||
else
|
|
||||||
@body_port = @config.new_body_port(self)
|
|
||||||
@body_port.wopen {|w|
|
|
||||||
w.write f.read
|
|
||||||
}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def read_multipart( src )
|
|
||||||
bound = @header['content-type'].params['boundary']
|
|
||||||
is_sep = /\A--#{Regexp.quote bound}(?:--)?[ \t]*(?:\n|\r\n|\r)/
|
|
||||||
lastbound = "--#{bound}--"
|
|
||||||
|
|
||||||
ports = [ @config.new_preamble_port(self) ]
|
|
||||||
begin
|
|
||||||
f = ports.last.wopen
|
|
||||||
while line = src.gets
|
|
||||||
if is_sep === line
|
|
||||||
f.close
|
|
||||||
break if line.strip == lastbound
|
|
||||||
ports.push @config.new_part_port(self)
|
|
||||||
f = ports.last.wopen
|
|
||||||
else
|
|
||||||
f << line
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@epilogue = (src.read || '')
|
|
||||||
ensure
|
|
||||||
f.close if f and not f.closed?
|
|
||||||
end
|
|
||||||
|
|
||||||
@body_port = ports.shift
|
|
||||||
@parts = ports.map {|p| self.class.new(p, @config) }
|
|
||||||
end
|
|
||||||
|
|
||||||
end # class Mail
|
|
||||||
|
|
||||||
end # module TMail
|
|
||||||
|
|
@ -1,433 +0,0 @@
|
||||||
#
|
|
||||||
# mailbox.rb
|
|
||||||
#
|
|
||||||
#--
|
|
||||||
# Copyright (c) 1998-2003 Minero Aoki <aamine@loveruby.net>
|
|
||||||
#
|
|
||||||
# Permission is hereby granted, free of charge, to any person obtaining
|
|
||||||
# a copy of this software and associated documentation files (the
|
|
||||||
# "Software"), to deal in the Software without restriction, including
|
|
||||||
# without limitation the rights to use, copy, modify, merge, publish,
|
|
||||||
# distribute, sublicense, and/or sell copies of the Software, and to
|
|
||||||
# permit persons to whom the Software is furnished to do so, subject to
|
|
||||||
# the following conditions:
|
|
||||||
#
|
|
||||||
# The above copyright notice and this permission notice shall be
|
|
||||||
# included in all copies or substantial portions of the Software.
|
|
||||||
#
|
|
||||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
||||||
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
||||||
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
||||||
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
||||||
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
||||||
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
||||||
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
||||||
#
|
|
||||||
# Note: Originally licensed under LGPL v2+. Using MIT license for Rails
|
|
||||||
# with permission of Minero Aoki.
|
|
||||||
#++
|
|
||||||
|
|
||||||
require 'tmail/port'
|
|
||||||
require 'socket'
|
|
||||||
require 'mutex_m'
|
|
||||||
|
|
||||||
|
|
||||||
unless [].respond_to?(:sort_by)
|
|
||||||
module Enumerable#:nodoc:
|
|
||||||
def sort_by
|
|
||||||
map {|i| [yield(i), i] }.sort {|a,b| a.first <=> b.first }.map {|i| i[1] }
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
module TMail
|
|
||||||
|
|
||||||
class MhMailbox
|
|
||||||
|
|
||||||
PORT_CLASS = MhPort
|
|
||||||
|
|
||||||
def initialize( dir )
|
|
||||||
edir = File.expand_path(dir)
|
|
||||||
raise ArgumentError, "not directory: #{dir}"\
|
|
||||||
unless FileTest.directory? edir
|
|
||||||
@dirname = edir
|
|
||||||
@last_file = nil
|
|
||||||
@last_atime = nil
|
|
||||||
end
|
|
||||||
|
|
||||||
def directory
|
|
||||||
@dirname
|
|
||||||
end
|
|
||||||
|
|
||||||
alias dirname directory
|
|
||||||
|
|
||||||
attr_accessor :last_atime
|
|
||||||
|
|
||||||
def inspect
|
|
||||||
"#<#{self.class} #{@dirname}>"
|
|
||||||
end
|
|
||||||
|
|
||||||
def close
|
|
||||||
end
|
|
||||||
|
|
||||||
def new_port
|
|
||||||
PORT_CLASS.new(next_file_name())
|
|
||||||
end
|
|
||||||
|
|
||||||
def each_port
|
|
||||||
mail_files().each do |path|
|
|
||||||
yield PORT_CLASS.new(path)
|
|
||||||
end
|
|
||||||
@last_atime = Time.now
|
|
||||||
end
|
|
||||||
|
|
||||||
alias each each_port
|
|
||||||
|
|
||||||
def reverse_each_port
|
|
||||||
mail_files().reverse_each do |path|
|
|
||||||
yield PORT_CLASS.new(path)
|
|
||||||
end
|
|
||||||
@last_atime = Time.now
|
|
||||||
end
|
|
||||||
|
|
||||||
alias reverse_each reverse_each_port
|
|
||||||
|
|
||||||
# old #each_mail returns Port
|
|
||||||
#def each_mail
|
|
||||||
# each_port do |port|
|
|
||||||
# yield Mail.new(port)
|
|
||||||
# end
|
|
||||||
#end
|
|
||||||
|
|
||||||
def each_new_port( mtime = nil, &block )
|
|
||||||
mtime ||= @last_atime
|
|
||||||
return each_port(&block) unless mtime
|
|
||||||
return unless File.mtime(@dirname) >= mtime
|
|
||||||
|
|
||||||
mail_files().each do |path|
|
|
||||||
yield PORT_CLASS.new(path) if File.mtime(path) > mtime
|
|
||||||
end
|
|
||||||
@last_atime = Time.now
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def mail_files
|
|
||||||
Dir.entries(@dirname)\
|
|
||||||
.select {|s| /\A\d+\z/ === s }\
|
|
||||||
.map {|s| s.to_i }\
|
|
||||||
.sort\
|
|
||||||
.map {|i| "#{@dirname}/#{i}" }\
|
|
||||||
.select {|path| FileTest.file? path }
|
|
||||||
end
|
|
||||||
|
|
||||||
def next_file_name
|
|
||||||
unless n = @last_file
|
|
||||||
n = 0
|
|
||||||
Dir.entries(@dirname)\
|
|
||||||
.select {|s| /\A\d+\z/ === s }\
|
|
||||||
.map {|s| s.to_i }.sort\
|
|
||||||
.each do |i|
|
|
||||||
next unless FileTest.file? "#{@dirname}/#{i}"
|
|
||||||
n = i
|
|
||||||
end
|
|
||||||
end
|
|
||||||
begin
|
|
||||||
n += 1
|
|
||||||
end while FileTest.exist? "#{@dirname}/#{n}"
|
|
||||||
@last_file = n
|
|
||||||
|
|
||||||
"#{@dirname}/#{n}"
|
|
||||||
end
|
|
||||||
|
|
||||||
end # MhMailbox
|
|
||||||
|
|
||||||
MhLoader = MhMailbox
|
|
||||||
|
|
||||||
|
|
||||||
class UNIXMbox
|
|
||||||
|
|
||||||
def UNIXMbox.lock( fname )
|
|
||||||
begin
|
|
||||||
f = File.open(fname)
|
|
||||||
f.flock File::LOCK_EX
|
|
||||||
yield f
|
|
||||||
ensure
|
|
||||||
f.flock File::LOCK_UN
|
|
||||||
f.close if f and not f.closed?
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
class << self
|
|
||||||
alias newobj new
|
|
||||||
end
|
|
||||||
|
|
||||||
def UNIXMbox.new( fname, tmpdir = nil, readonly = false )
|
|
||||||
tmpdir = ENV['TEMP'] || ENV['TMP'] || '/tmp'
|
|
||||||
newobj(fname, "#{tmpdir}/ruby_tmail_#{$$}_#{rand()}", readonly, false)
|
|
||||||
end
|
|
||||||
|
|
||||||
def UNIXMbox.static_new( fname, dir, readonly = false )
|
|
||||||
newobj(fname, dir, readonly, true)
|
|
||||||
end
|
|
||||||
|
|
||||||
def initialize( fname, mhdir, readonly, static )
|
|
||||||
@filename = fname
|
|
||||||
@readonly = readonly
|
|
||||||
@closed = false
|
|
||||||
|
|
||||||
Dir.mkdir mhdir
|
|
||||||
@real = MhMailbox.new(mhdir)
|
|
||||||
@finalizer = UNIXMbox.mkfinal(@real, @filename, !@readonly, !static)
|
|
||||||
ObjectSpace.define_finalizer self, @finalizer
|
|
||||||
end
|
|
||||||
|
|
||||||
def UNIXMbox.mkfinal( mh, mboxfile, writeback_p, cleanup_p )
|
|
||||||
lambda {
|
|
||||||
if writeback_p
|
|
||||||
lock(mboxfile) {|f|
|
|
||||||
mh.each_port do |port|
|
|
||||||
f.puts create_from_line(port)
|
|
||||||
port.ropen {|r|
|
|
||||||
f.puts r.read
|
|
||||||
}
|
|
||||||
end
|
|
||||||
}
|
|
||||||
end
|
|
||||||
if cleanup_p
|
|
||||||
Dir.foreach(mh.dirname) do |fname|
|
|
||||||
next if /\A\.\.?\z/ === fname
|
|
||||||
File.unlink "#{mh.dirname}/#{fname}"
|
|
||||||
end
|
|
||||||
Dir.rmdir mh.dirname
|
|
||||||
end
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
# make _From line
|
|
||||||
def UNIXMbox.create_from_line( port )
|
|
||||||
sprintf 'From %s %s',
|
|
||||||
fromaddr(), TextUtils.time2str(File.mtime(port.filename))
|
|
||||||
end
|
|
||||||
|
|
||||||
def UNIXMbox.fromaddr
|
|
||||||
h = HeaderField.new_from_port(port, 'Return-Path') ||
|
|
||||||
HeaderField.new_from_port(port, 'From') or return 'nobody'
|
|
||||||
a = h.addrs[0] or return 'nobody'
|
|
||||||
a.spec
|
|
||||||
end
|
|
||||||
private_class_method :fromaddr
|
|
||||||
|
|
||||||
def close
|
|
||||||
return if @closed
|
|
||||||
|
|
||||||
ObjectSpace.undefine_finalizer self
|
|
||||||
@finalizer.call
|
|
||||||
@finalizer = nil
|
|
||||||
@real = nil
|
|
||||||
@closed = true
|
|
||||||
@updated = nil
|
|
||||||
end
|
|
||||||
|
|
||||||
def each_port( &block )
|
|
||||||
close_check
|
|
||||||
update
|
|
||||||
@real.each_port(&block)
|
|
||||||
end
|
|
||||||
|
|
||||||
alias each each_port
|
|
||||||
|
|
||||||
def reverse_each_port( &block )
|
|
||||||
close_check
|
|
||||||
update
|
|
||||||
@real.reverse_each_port(&block)
|
|
||||||
end
|
|
||||||
|
|
||||||
alias reverse_each reverse_each_port
|
|
||||||
|
|
||||||
# old #each_mail returns Port
|
|
||||||
#def each_mail( &block )
|
|
||||||
# each_port do |port|
|
|
||||||
# yield Mail.new(port)
|
|
||||||
# end
|
|
||||||
#end
|
|
||||||
|
|
||||||
def each_new_port( mtime = nil )
|
|
||||||
close_check
|
|
||||||
update
|
|
||||||
@real.each_new_port(mtime) {|p| yield p }
|
|
||||||
end
|
|
||||||
|
|
||||||
def new_port
|
|
||||||
close_check
|
|
||||||
@real.new_port
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def close_check
|
|
||||||
@closed and raise ArgumentError, 'accessing already closed mbox'
|
|
||||||
end
|
|
||||||
|
|
||||||
def update
|
|
||||||
return if FileTest.zero?(@filename)
|
|
||||||
return if @updated and File.mtime(@filename) < @updated
|
|
||||||
w = nil
|
|
||||||
port = nil
|
|
||||||
time = nil
|
|
||||||
UNIXMbox.lock(@filename) {|f|
|
|
||||||
begin
|
|
||||||
f.each do |line|
|
|
||||||
if /\AFrom / === line
|
|
||||||
w.close if w
|
|
||||||
File.utime time, time, port.filename if time
|
|
||||||
|
|
||||||
port = @real.new_port
|
|
||||||
w = port.wopen
|
|
||||||
time = fromline2time(line)
|
|
||||||
else
|
|
||||||
w.print line if w
|
|
||||||
end
|
|
||||||
end
|
|
||||||
ensure
|
|
||||||
if w and not w.closed?
|
|
||||||
w.close
|
|
||||||
File.utime time, time, port.filename if time
|
|
||||||
end
|
|
||||||
end
|
|
||||||
f.truncate(0) unless @readonly
|
|
||||||
@updated = Time.now
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
def fromline2time( line )
|
|
||||||
m = /\AFrom \S+ \w+ (\w+) (\d+) (\d+):(\d+):(\d+) (\d+)/.match(line) \
|
|
||||||
or return nil
|
|
||||||
Time.local(m[6].to_i, m[1], m[2].to_i, m[3].to_i, m[4].to_i, m[5].to_i)
|
|
||||||
end
|
|
||||||
|
|
||||||
end # UNIXMbox
|
|
||||||
|
|
||||||
MboxLoader = UNIXMbox
|
|
||||||
|
|
||||||
|
|
||||||
class Maildir
|
|
||||||
|
|
||||||
extend Mutex_m
|
|
||||||
|
|
||||||
PORT_CLASS = MaildirPort
|
|
||||||
|
|
||||||
@seq = 0
|
|
||||||
def Maildir.unique_number
|
|
||||||
synchronize {
|
|
||||||
@seq += 1
|
|
||||||
return @seq
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
def initialize( dir = nil )
|
|
||||||
@dirname = dir || ENV['MAILDIR']
|
|
||||||
raise ArgumentError, "not directory: #{@dirname}"\
|
|
||||||
unless FileTest.directory? @dirname
|
|
||||||
@new = "#{@dirname}/new"
|
|
||||||
@tmp = "#{@dirname}/tmp"
|
|
||||||
@cur = "#{@dirname}/cur"
|
|
||||||
end
|
|
||||||
|
|
||||||
def directory
|
|
||||||
@dirname
|
|
||||||
end
|
|
||||||
|
|
||||||
def inspect
|
|
||||||
"#<#{self.class} #{@dirname}>"
|
|
||||||
end
|
|
||||||
|
|
||||||
def close
|
|
||||||
end
|
|
||||||
|
|
||||||
def each_port
|
|
||||||
mail_files(@cur).each do |path|
|
|
||||||
yield PORT_CLASS.new(path)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
alias each each_port
|
|
||||||
|
|
||||||
def reverse_each_port
|
|
||||||
mail_files(@cur).reverse_each do |path|
|
|
||||||
yield PORT_CLASS.new(path)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
alias reverse_each reverse_each_port
|
|
||||||
|
|
||||||
def new_port
|
|
||||||
fname = nil
|
|
||||||
tmpfname = nil
|
|
||||||
newfname = nil
|
|
||||||
|
|
||||||
begin
|
|
||||||
fname = "#{Time.now.to_i}.#{$$}_#{Maildir.unique_number}.#{Socket.gethostname}"
|
|
||||||
|
|
||||||
tmpfname = "#{@tmp}/#{fname}"
|
|
||||||
newfname = "#{@new}/#{fname}"
|
|
||||||
end while FileTest.exist? tmpfname
|
|
||||||
|
|
||||||
if block_given?
|
|
||||||
File.open(tmpfname, 'w') {|f| yield f }
|
|
||||||
File.rename tmpfname, newfname
|
|
||||||
PORT_CLASS.new(newfname)
|
|
||||||
else
|
|
||||||
File.open(tmpfname, 'w') {|f| f.write "\n\n" }
|
|
||||||
PORT_CLASS.new(tmpfname)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def each_new_port
|
|
||||||
mail_files(@new).each do |path|
|
|
||||||
dest = @cur + '/' + File.basename(path)
|
|
||||||
File.rename path, dest
|
|
||||||
yield PORT_CLASS.new(dest)
|
|
||||||
end
|
|
||||||
|
|
||||||
check_tmp
|
|
||||||
end
|
|
||||||
|
|
||||||
TOO_OLD = 60 * 60 * 36 # 36 hour
|
|
||||||
|
|
||||||
def check_tmp
|
|
||||||
old = Time.now.to_i - TOO_OLD
|
|
||||||
|
|
||||||
each_filename(@tmp) do |full, fname|
|
|
||||||
if FileTest.file? full and
|
|
||||||
File.stat(full).mtime.to_i < old
|
|
||||||
File.unlink full
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def mail_files( dir )
|
|
||||||
Dir.entries(dir)\
|
|
||||||
.select {|s| s[0] != ?. }\
|
|
||||||
.sort_by {|s| s.slice(/\A\d+/).to_i }\
|
|
||||||
.map {|s| "#{dir}/#{s}" }\
|
|
||||||
.select {|path| FileTest.file? path }
|
|
||||||
end
|
|
||||||
|
|
||||||
def each_filename( dir )
|
|
||||||
Dir.foreach(dir) do |fname|
|
|
||||||
path = "#{dir}/#{fname}"
|
|
||||||
if fname[0] != ?. and FileTest.file? path
|
|
||||||
yield path, fname
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
end # Maildir
|
|
||||||
|
|
||||||
MaildirLoader = Maildir
|
|
||||||
|
|
||||||
end # module TMail
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
require 'tmail/mailbox'
|
|
||||||
|
|
@ -1,280 +0,0 @@
|
||||||
#
|
|
||||||
# net.rb
|
|
||||||
#
|
|
||||||
#--
|
|
||||||
# Copyright (c) 1998-2003 Minero Aoki <aamine@loveruby.net>
|
|
||||||
#
|
|
||||||
# Permission is hereby granted, free of charge, to any person obtaining
|
|
||||||
# a copy of this software and associated documentation files (the
|
|
||||||
# "Software"), to deal in the Software without restriction, including
|
|
||||||
# without limitation the rights to use, copy, modify, merge, publish,
|
|
||||||
# distribute, sublicense, and/or sell copies of the Software, and to
|
|
||||||
# permit persons to whom the Software is furnished to do so, subject to
|
|
||||||
# the following conditions:
|
|
||||||
#
|
|
||||||
# The above copyright notice and this permission notice shall be
|
|
||||||
# included in all copies or substantial portions of the Software.
|
|
||||||
#
|
|
||||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
||||||
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
||||||
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
||||||
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
||||||
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
||||||
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
||||||
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
||||||
#
|
|
||||||
# Note: Originally licensed under LGPL v2+. Using MIT license for Rails
|
|
||||||
# with permission of Minero Aoki.
|
|
||||||
#++
|
|
||||||
|
|
||||||
require 'nkf'
|
|
||||||
|
|
||||||
|
|
||||||
module TMail
|
|
||||||
|
|
||||||
class Mail
|
|
||||||
|
|
||||||
def send_to( smtp )
|
|
||||||
do_send_to(smtp) do
|
|
||||||
ready_to_send
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def send_text_to( smtp )
|
|
||||||
do_send_to(smtp) do
|
|
||||||
ready_to_send
|
|
||||||
mime_encode
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def do_send_to( smtp )
|
|
||||||
from = from_address or raise ArgumentError, 'no from address'
|
|
||||||
(dests = destinations).empty? and raise ArgumentError, 'no receipient'
|
|
||||||
yield
|
|
||||||
send_to_0 smtp, from, dests
|
|
||||||
end
|
|
||||||
private :do_send_to
|
|
||||||
|
|
||||||
def send_to_0( smtp, from, to )
|
|
||||||
smtp.ready(from, to) do |f|
|
|
||||||
encoded "\r\n", 'j', f, ''
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def ready_to_send
|
|
||||||
delete_no_send_fields
|
|
||||||
add_message_id
|
|
||||||
add_date
|
|
||||||
end
|
|
||||||
|
|
||||||
NOSEND_FIELDS = %w(
|
|
||||||
received
|
|
||||||
bcc
|
|
||||||
)
|
|
||||||
|
|
||||||
def delete_no_send_fields
|
|
||||||
NOSEND_FIELDS.each do |nm|
|
|
||||||
delete nm
|
|
||||||
end
|
|
||||||
delete_if {|n,v| v.empty? }
|
|
||||||
end
|
|
||||||
|
|
||||||
def add_message_id( fqdn = nil )
|
|
||||||
self.message_id = ::TMail::new_message_id(fqdn)
|
|
||||||
end
|
|
||||||
|
|
||||||
def add_date
|
|
||||||
self.date = Time.now
|
|
||||||
end
|
|
||||||
|
|
||||||
def mime_encode
|
|
||||||
if parts.empty?
|
|
||||||
mime_encode_singlepart
|
|
||||||
else
|
|
||||||
mime_encode_multipart true
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def mime_encode_singlepart
|
|
||||||
self.mime_version = '1.0'
|
|
||||||
b = body
|
|
||||||
if NKF.guess(b) != NKF::BINARY
|
|
||||||
mime_encode_text b
|
|
||||||
else
|
|
||||||
mime_encode_binary b
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def mime_encode_text( body )
|
|
||||||
self.body = NKF.nkf('-j -m0', body)
|
|
||||||
self.set_content_type 'text', 'plain', {'charset' => 'iso-2022-jp'}
|
|
||||||
self.encoding = '7bit'
|
|
||||||
end
|
|
||||||
|
|
||||||
def mime_encode_binary( body )
|
|
||||||
self.body = [body].pack('m')
|
|
||||||
self.set_content_type 'application', 'octet-stream'
|
|
||||||
self.encoding = 'Base64'
|
|
||||||
end
|
|
||||||
|
|
||||||
def mime_encode_multipart( top = true )
|
|
||||||
self.mime_version = '1.0' if top
|
|
||||||
self.set_content_type 'multipart', 'mixed'
|
|
||||||
e = encoding(nil)
|
|
||||||
if e and not /\A(?:7bit|8bit|binary)\z/i === e
|
|
||||||
raise ArgumentError,
|
|
||||||
'using C.T.Encoding with multipart mail is not permitted'
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def create_empty_mail
|
|
||||||
self.class.new(StringPort.new(''), @config)
|
|
||||||
end
|
|
||||||
|
|
||||||
def create_reply
|
|
||||||
setup_reply create_empty_mail()
|
|
||||||
end
|
|
||||||
|
|
||||||
def setup_reply( m )
|
|
||||||
if tmp = reply_addresses(nil)
|
|
||||||
m.to_addrs = tmp
|
|
||||||
end
|
|
||||||
|
|
||||||
mid = message_id(nil)
|
|
||||||
tmp = references(nil) || []
|
|
||||||
tmp.push mid if mid
|
|
||||||
m.in_reply_to = [mid] if mid
|
|
||||||
m.references = tmp unless tmp.empty?
|
|
||||||
m.subject = 'Re: ' + subject('').sub(/\A(?:\s*re:)+/i, '')
|
|
||||||
|
|
||||||
m
|
|
||||||
end
|
|
||||||
|
|
||||||
def create_forward
|
|
||||||
setup_forward create_empty_mail()
|
|
||||||
end
|
|
||||||
|
|
||||||
def setup_forward( mail )
|
|
||||||
m = Mail.new(StringPort.new(''))
|
|
||||||
m.body = decoded
|
|
||||||
m.set_content_type 'message', 'rfc822'
|
|
||||||
m.encoding = encoding('7bit')
|
|
||||||
mail.parts.push m
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
class DeleteFields
|
|
||||||
|
|
||||||
NOSEND_FIELDS = %w(
|
|
||||||
received
|
|
||||||
bcc
|
|
||||||
)
|
|
||||||
|
|
||||||
def initialize( nosend = nil, delempty = true )
|
|
||||||
@no_send_fields = nosend || NOSEND_FIELDS.dup
|
|
||||||
@delete_empty_fields = delempty
|
|
||||||
end
|
|
||||||
|
|
||||||
attr :no_send_fields
|
|
||||||
attr :delete_empty_fields, true
|
|
||||||
|
|
||||||
def exec( mail )
|
|
||||||
@no_send_fields.each do |nm|
|
|
||||||
delete nm
|
|
||||||
end
|
|
||||||
delete_if {|n,v| v.empty? } if @delete_empty_fields
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
class AddMessageId
|
|
||||||
|
|
||||||
def initialize( fqdn = nil )
|
|
||||||
@fqdn = fqdn
|
|
||||||
end
|
|
||||||
|
|
||||||
attr :fqdn, true
|
|
||||||
|
|
||||||
def exec( mail )
|
|
||||||
mail.message_id = ::TMail::new_msgid(@fqdn)
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
class AddDate
|
|
||||||
|
|
||||||
def exec( mail )
|
|
||||||
mail.date = Time.now
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
class MimeEncodeAuto
|
|
||||||
|
|
||||||
def initialize( s = nil, m = nil )
|
|
||||||
@singlepart_composer = s || MimeEncodeSingle.new
|
|
||||||
@multipart_composer = m || MimeEncodeMulti.new
|
|
||||||
end
|
|
||||||
|
|
||||||
attr :singlepart_composer
|
|
||||||
attr :multipart_composer
|
|
||||||
|
|
||||||
def exec( mail )
|
|
||||||
if mail._builtin_multipart?
|
|
||||||
then @multipart_composer
|
|
||||||
else @singlepart_composer end.exec mail
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
class MimeEncodeSingle
|
|
||||||
|
|
||||||
def exec( mail )
|
|
||||||
mail.mime_version = '1.0'
|
|
||||||
b = mail.body
|
|
||||||
if NKF.guess(b) != NKF::BINARY
|
|
||||||
on_text b
|
|
||||||
else
|
|
||||||
on_binary b
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def on_text( body )
|
|
||||||
mail.body = NKF.nkf('-j -m0', body)
|
|
||||||
mail.set_content_type 'text', 'plain', {'charset' => 'iso-2022-jp'}
|
|
||||||
mail.encoding = '7bit'
|
|
||||||
end
|
|
||||||
|
|
||||||
def on_binary( body )
|
|
||||||
mail.body = [body].pack('m')
|
|
||||||
mail.set_content_type 'application', 'octet-stream'
|
|
||||||
mail.encoding = 'Base64'
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
class MimeEncodeMulti
|
|
||||||
|
|
||||||
def exec( mail, top = true )
|
|
||||||
mail.mime_version = '1.0' if top
|
|
||||||
mail.set_content_type 'multipart', 'mixed'
|
|
||||||
e = encoding(nil)
|
|
||||||
if e and not /\A(?:7bit|8bit|binary)\z/i === e
|
|
||||||
raise ArgumentError,
|
|
||||||
'using C.T.Encoding with multipart mail is not permitted'
|
|
||||||
end
|
|
||||||
mail.parts.each do |m|
|
|
||||||
exec m, false if m._builtin_multipart?
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
||||||
end # module TMail
|
|
||||||
|
|
@ -1,135 +0,0 @@
|
||||||
#
|
|
||||||
# obsolete.rb
|
|
||||||
#
|
|
||||||
#--
|
|
||||||
# Copyright (c) 1998-2003 Minero Aoki <aamine@loveruby.net>
|
|
||||||
#
|
|
||||||
# Permission is hereby granted, free of charge, to any person obtaining
|
|
||||||
# a copy of this software and associated documentation files (the
|
|
||||||
# "Software"), to deal in the Software without restriction, including
|
|
||||||
# without limitation the rights to use, copy, modify, merge, publish,
|
|
||||||
# distribute, sublicense, and/or sell copies of the Software, and to
|
|
||||||
# permit persons to whom the Software is furnished to do so, subject to
|
|
||||||
# the following conditions:
|
|
||||||
#
|
|
||||||
# The above copyright notice and this permission notice shall be
|
|
||||||
# included in all copies or substantial portions of the Software.
|
|
||||||
#
|
|
||||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
||||||
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
||||||
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
||||||
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
||||||
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
||||||
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
||||||
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
||||||
#
|
|
||||||
# Note: Originally licensed under LGPL v2+. Using MIT license for Rails
|
|
||||||
# with permission of Minero Aoki.
|
|
||||||
#++
|
|
||||||
|
|
||||||
module TMail
|
|
||||||
|
|
||||||
# mail.rb
|
|
||||||
class Mail
|
|
||||||
alias include? key?
|
|
||||||
alias has_key? key?
|
|
||||||
|
|
||||||
def values
|
|
||||||
ret = []
|
|
||||||
each_field {|v| ret.push v }
|
|
||||||
ret
|
|
||||||
end
|
|
||||||
|
|
||||||
def value?( val )
|
|
||||||
HeaderField === val or return false
|
|
||||||
|
|
||||||
[ @header[val.name.downcase] ].flatten.include? val
|
|
||||||
end
|
|
||||||
|
|
||||||
alias has_value? value?
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
# facade.rb
|
|
||||||
class Mail
|
|
||||||
def from_addr( default = nil )
|
|
||||||
addr, = from_addrs(nil)
|
|
||||||
addr || default
|
|
||||||
end
|
|
||||||
|
|
||||||
def from_address( default = nil )
|
|
||||||
if a = from_addr(nil)
|
|
||||||
a.spec
|
|
||||||
else
|
|
||||||
default
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
alias from_address= from_addrs=
|
|
||||||
|
|
||||||
def from_phrase( default = nil )
|
|
||||||
if a = from_addr(nil)
|
|
||||||
a.phrase
|
|
||||||
else
|
|
||||||
default
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
alias msgid message_id
|
|
||||||
alias msgid= message_id=
|
|
||||||
|
|
||||||
alias each_dest each_destination
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
# address.rb
|
|
||||||
class Address
|
|
||||||
alias route routes
|
|
||||||
alias addr spec
|
|
||||||
|
|
||||||
def spec=( str )
|
|
||||||
@local, @domain = str.split(/@/,2).map {|s| s.split(/\./) }
|
|
||||||
end
|
|
||||||
|
|
||||||
alias addr= spec=
|
|
||||||
alias address= spec=
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
# mbox.rb
|
|
||||||
class MhMailbox
|
|
||||||
alias new_mail new_port
|
|
||||||
alias each_mail each_port
|
|
||||||
alias each_newmail each_new_port
|
|
||||||
end
|
|
||||||
class UNIXMbox
|
|
||||||
alias new_mail new_port
|
|
||||||
alias each_mail each_port
|
|
||||||
alias each_newmail each_new_port
|
|
||||||
end
|
|
||||||
class Maildir
|
|
||||||
alias new_mail new_port
|
|
||||||
alias each_mail each_port
|
|
||||||
alias each_newmail each_new_port
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
# utils.rb
|
|
||||||
extend TextUtils
|
|
||||||
|
|
||||||
class << self
|
|
||||||
alias msgid? message_id?
|
|
||||||
alias boundary new_boundary
|
|
||||||
alias msgid new_message_id
|
|
||||||
alias new_msgid new_message_id
|
|
||||||
end
|
|
||||||
|
|
||||||
def Mail.boundary
|
|
||||||
::TMail.new_boundary
|
|
||||||
end
|
|
||||||
|
|
||||||
def Mail.msgid
|
|
||||||
::TMail.new_message_id
|
|
||||||
end
|
|
||||||
|
|
||||||
end # module TMail
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -1,377 +0,0 @@
|
||||||
#
|
|
||||||
# port.rb
|
|
||||||
#
|
|
||||||
#--
|
|
||||||
# Copyright (c) 1998-2003 Minero Aoki <aamine@loveruby.net>
|
|
||||||
#
|
|
||||||
# Permission is hereby granted, free of charge, to any person obtaining
|
|
||||||
# a copy of this software and associated documentation files (the
|
|
||||||
# "Software"), to deal in the Software without restriction, including
|
|
||||||
# without limitation the rights to use, copy, modify, merge, publish,
|
|
||||||
# distribute, sublicense, and/or sell copies of the Software, and to
|
|
||||||
# permit persons to whom the Software is furnished to do so, subject to
|
|
||||||
# the following conditions:
|
|
||||||
#
|
|
||||||
# The above copyright notice and this permission notice shall be
|
|
||||||
# included in all copies or substantial portions of the Software.
|
|
||||||
#
|
|
||||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
||||||
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
||||||
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
||||||
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
||||||
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
||||||
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
||||||
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
||||||
#
|
|
||||||
# Note: Originally licensed under LGPL v2+. Using MIT license for Rails
|
|
||||||
# with permission of Minero Aoki.
|
|
||||||
#++
|
|
||||||
|
|
||||||
require 'tmail/stringio'
|
|
||||||
|
|
||||||
|
|
||||||
module TMail
|
|
||||||
|
|
||||||
class Port
|
|
||||||
def reproducible?
|
|
||||||
false
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
###
|
|
||||||
### FilePort
|
|
||||||
###
|
|
||||||
|
|
||||||
class FilePort < Port
|
|
||||||
|
|
||||||
def initialize( fname )
|
|
||||||
@filename = File.expand_path(fname)
|
|
||||||
super()
|
|
||||||
end
|
|
||||||
|
|
||||||
attr_reader :filename
|
|
||||||
|
|
||||||
alias ident filename
|
|
||||||
|
|
||||||
def ==( other )
|
|
||||||
other.respond_to?(:filename) and @filename == other.filename
|
|
||||||
end
|
|
||||||
|
|
||||||
alias eql? ==
|
|
||||||
|
|
||||||
def hash
|
|
||||||
@filename.hash
|
|
||||||
end
|
|
||||||
|
|
||||||
def inspect
|
|
||||||
"#<#{self.class}:#{@filename}>"
|
|
||||||
end
|
|
||||||
|
|
||||||
def reproducible?
|
|
||||||
true
|
|
||||||
end
|
|
||||||
|
|
||||||
def size
|
|
||||||
File.size @filename
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
def ropen( &block )
|
|
||||||
File.open(@filename, &block)
|
|
||||||
end
|
|
||||||
|
|
||||||
def wopen( &block )
|
|
||||||
File.open(@filename, 'w', &block)
|
|
||||||
end
|
|
||||||
|
|
||||||
def aopen( &block )
|
|
||||||
File.open(@filename, 'a', &block)
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
def read_all
|
|
||||||
ropen {|f|
|
|
||||||
return f.read
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
def remove
|
|
||||||
File.unlink @filename
|
|
||||||
end
|
|
||||||
|
|
||||||
def move_to( port )
|
|
||||||
begin
|
|
||||||
File.link @filename, port.filename
|
|
||||||
rescue Errno::EXDEV
|
|
||||||
copy_to port
|
|
||||||
end
|
|
||||||
File.unlink @filename
|
|
||||||
end
|
|
||||||
|
|
||||||
alias mv move_to
|
|
||||||
|
|
||||||
def copy_to( port )
|
|
||||||
if FilePort === port
|
|
||||||
copy_file @filename, port.filename
|
|
||||||
else
|
|
||||||
File.open(@filename) {|r|
|
|
||||||
port.wopen {|w|
|
|
||||||
while s = r.sysread(4096)
|
|
||||||
w.write << s
|
|
||||||
end
|
|
||||||
} }
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
alias cp copy_to
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
# from fileutils.rb
|
|
||||||
def copy_file( src, dest )
|
|
||||||
st = r = w = nil
|
|
||||||
|
|
||||||
File.open(src, 'rb') {|r|
|
|
||||||
File.open(dest, 'wb') {|w|
|
|
||||||
st = r.stat
|
|
||||||
begin
|
|
||||||
while true
|
|
||||||
w.write r.sysread(st.blksize)
|
|
||||||
end
|
|
||||||
rescue EOFError
|
|
||||||
end
|
|
||||||
} }
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
module MailFlags
|
|
||||||
|
|
||||||
def seen=( b )
|
|
||||||
set_status 'S', b
|
|
||||||
end
|
|
||||||
|
|
||||||
def seen?
|
|
||||||
get_status 'S'
|
|
||||||
end
|
|
||||||
|
|
||||||
def replied=( b )
|
|
||||||
set_status 'R', b
|
|
||||||
end
|
|
||||||
|
|
||||||
def replied?
|
|
||||||
get_status 'R'
|
|
||||||
end
|
|
||||||
|
|
||||||
def flagged=( b )
|
|
||||||
set_status 'F', b
|
|
||||||
end
|
|
||||||
|
|
||||||
def flagged?
|
|
||||||
get_status 'F'
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def procinfostr( str, tag, true_p )
|
|
||||||
a = str.upcase.split(//)
|
|
||||||
a.push true_p ? tag : nil
|
|
||||||
a.delete tag unless true_p
|
|
||||||
a.compact.sort.join('').squeeze
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
class MhPort < FilePort
|
|
||||||
|
|
||||||
include MailFlags
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def set_status( tag, flag )
|
|
||||||
begin
|
|
||||||
tmpfile = @filename + '.tmailtmp.' + $$.to_s
|
|
||||||
File.open(tmpfile, 'w') {|f|
|
|
||||||
write_status f, tag, flag
|
|
||||||
}
|
|
||||||
File.unlink @filename
|
|
||||||
File.link tmpfile, @filename
|
|
||||||
ensure
|
|
||||||
File.unlink tmpfile
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def write_status( f, tag, flag )
|
|
||||||
stat = ''
|
|
||||||
File.open(@filename) {|r|
|
|
||||||
while line = r.gets
|
|
||||||
if line.strip.empty?
|
|
||||||
break
|
|
||||||
elsif m = /\AX-TMail-Status:/i.match(line)
|
|
||||||
stat = m.post_match.strip
|
|
||||||
else
|
|
||||||
f.print line
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
s = procinfostr(stat, tag, flag)
|
|
||||||
f.puts 'X-TMail-Status: ' + s unless s.empty?
|
|
||||||
f.puts
|
|
||||||
|
|
||||||
while s = r.read(2048)
|
|
||||||
f.write s
|
|
||||||
end
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
def get_status( tag )
|
|
||||||
File.foreach(@filename) {|line|
|
|
||||||
return false if line.strip.empty?
|
|
||||||
if m = /\AX-TMail-Status:/i.match(line)
|
|
||||||
return m.post_match.strip.include?(tag[0])
|
|
||||||
end
|
|
||||||
}
|
|
||||||
false
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
class MaildirPort < FilePort
|
|
||||||
|
|
||||||
def move_to_new
|
|
||||||
new = replace_dir(@filename, 'new')
|
|
||||||
File.rename @filename, new
|
|
||||||
@filename = new
|
|
||||||
end
|
|
||||||
|
|
||||||
def move_to_cur
|
|
||||||
new = replace_dir(@filename, 'cur')
|
|
||||||
File.rename @filename, new
|
|
||||||
@filename = new
|
|
||||||
end
|
|
||||||
|
|
||||||
def replace_dir( path, dir )
|
|
||||||
"#{File.dirname File.dirname(path)}/#{dir}/#{File.basename path}"
|
|
||||||
end
|
|
||||||
private :replace_dir
|
|
||||||
|
|
||||||
|
|
||||||
include MailFlags
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
MAIL_FILE = /\A(\d+\.[\d_]+\.[^:]+)(?:\:(\d),(\w+)?)?\z/
|
|
||||||
|
|
||||||
def set_status( tag, flag )
|
|
||||||
if m = MAIL_FILE.match(File.basename(@filename))
|
|
||||||
s, uniq, type, info, = m.to_a
|
|
||||||
return if type and type != '2' # do not change anything
|
|
||||||
newname = File.dirname(@filename) + '/' +
|
|
||||||
uniq + ':2,' + procinfostr(info.to_s, tag, flag)
|
|
||||||
else
|
|
||||||
newname = @filename + ':2,' + tag
|
|
||||||
end
|
|
||||||
|
|
||||||
File.link @filename, newname
|
|
||||||
File.unlink @filename
|
|
||||||
@filename = newname
|
|
||||||
end
|
|
||||||
|
|
||||||
def get_status( tag )
|
|
||||||
m = MAIL_FILE.match(File.basename(@filename)) or return false
|
|
||||||
m[2] == '2' and m[3].to_s.include?(tag[0])
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
###
|
|
||||||
### StringPort
|
|
||||||
###
|
|
||||||
|
|
||||||
class StringPort < Port
|
|
||||||
|
|
||||||
def initialize( str = '' )
|
|
||||||
@buffer = str
|
|
||||||
super()
|
|
||||||
end
|
|
||||||
|
|
||||||
def string
|
|
||||||
@buffer
|
|
||||||
end
|
|
||||||
|
|
||||||
def to_s
|
|
||||||
@buffer.dup
|
|
||||||
end
|
|
||||||
|
|
||||||
alias read_all to_s
|
|
||||||
|
|
||||||
def size
|
|
||||||
@buffer.size
|
|
||||||
end
|
|
||||||
|
|
||||||
def ==( other )
|
|
||||||
StringPort === other and @buffer.equal? other.string
|
|
||||||
end
|
|
||||||
|
|
||||||
alias eql? ==
|
|
||||||
|
|
||||||
def hash
|
|
||||||
@buffer.object_id.hash
|
|
||||||
end
|
|
||||||
|
|
||||||
def inspect
|
|
||||||
"#<#{self.class}:id=#{sprintf '0x%x', @buffer.object_id}>"
|
|
||||||
end
|
|
||||||
|
|
||||||
def reproducible?
|
|
||||||
true
|
|
||||||
end
|
|
||||||
|
|
||||||
def ropen( &block )
|
|
||||||
@buffer or raise Errno::ENOENT, "#{inspect} is already removed"
|
|
||||||
StringInput.open(@buffer, &block)
|
|
||||||
end
|
|
||||||
|
|
||||||
def wopen( &block )
|
|
||||||
@buffer = ''
|
|
||||||
StringOutput.new(@buffer, &block)
|
|
||||||
end
|
|
||||||
|
|
||||||
def aopen( &block )
|
|
||||||
@buffer ||= ''
|
|
||||||
StringOutput.new(@buffer, &block)
|
|
||||||
end
|
|
||||||
|
|
||||||
def remove
|
|
||||||
@buffer = nil
|
|
||||||
end
|
|
||||||
|
|
||||||
alias rm remove
|
|
||||||
|
|
||||||
def copy_to( port )
|
|
||||||
port.wopen {|f|
|
|
||||||
f.write @buffer
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
alias cp copy_to
|
|
||||||
|
|
||||||
def move_to( port )
|
|
||||||
if StringPort === port
|
|
||||||
str = @buffer
|
|
||||||
port.instance_eval { @buffer = str }
|
|
||||||
else
|
|
||||||
copy_to port
|
|
||||||
end
|
|
||||||
remove
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
||||||
end # module TMail
|
|
||||||
|
|
@ -1,125 +0,0 @@
|
||||||
module TMail
|
|
||||||
class Mail
|
|
||||||
def subject(to_charset = 'utf-8')
|
|
||||||
Unquoter.unquote_and_convert_to(quoted_subject, to_charset)
|
|
||||||
end
|
|
||||||
|
|
||||||
def unquoted_body(to_charset = 'utf-8')
|
|
||||||
from_charset = sub_header("content-type", "charset")
|
|
||||||
case (content_transfer_encoding || "7bit").downcase
|
|
||||||
when "quoted-printable"
|
|
||||||
Unquoter.unquote_quoted_printable_and_convert_to(quoted_body,
|
|
||||||
to_charset, from_charset, true)
|
|
||||||
when "base64"
|
|
||||||
Unquoter.unquote_base64_and_convert_to(quoted_body, to_charset,
|
|
||||||
from_charset)
|
|
||||||
when "7bit", "8bit"
|
|
||||||
Unquoter.convert_to(quoted_body, to_charset, from_charset)
|
|
||||||
when "binary"
|
|
||||||
quoted_body
|
|
||||||
else
|
|
||||||
quoted_body
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def body(to_charset = 'utf-8', &block)
|
|
||||||
attachment_presenter = block || Proc.new { |file_name| "Attachment: #{file_name}\n" }
|
|
||||||
|
|
||||||
if multipart?
|
|
||||||
parts.collect { |part|
|
|
||||||
header = part["content-type"]
|
|
||||||
|
|
||||||
if part.multipart?
|
|
||||||
part.body(to_charset, &attachment_presenter)
|
|
||||||
elsif header.nil?
|
|
||||||
""
|
|
||||||
elsif header.main_type == "text"
|
|
||||||
part.unquoted_body(to_charset)
|
|
||||||
else
|
|
||||||
attachment_presenter.call(header["name"] || "(unnamed)")
|
|
||||||
end
|
|
||||||
}.join
|
|
||||||
else
|
|
||||||
unquoted_body(to_charset)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
class Unquoter
|
|
||||||
class << self
|
|
||||||
def unquote_and_convert_to(text, to_charset, from_charset = "iso-8859-1", preserve_underscores=false)
|
|
||||||
return "" if text.nil?
|
|
||||||
if text =~ /^=\?(.*?)\?(.)\?(.*)\?=$/
|
|
||||||
from_charset = $1
|
|
||||||
quoting_method = $2
|
|
||||||
text = $3
|
|
||||||
case quoting_method.upcase
|
|
||||||
when "Q" then
|
|
||||||
unquote_quoted_printable_and_convert_to(text, to_charset, from_charset, preserve_underscores)
|
|
||||||
when "B" then
|
|
||||||
unquote_base64_and_convert_to(text, to_charset, from_charset)
|
|
||||||
else
|
|
||||||
raise "unknown quoting method #{quoting_method.inspect}"
|
|
||||||
end
|
|
||||||
else
|
|
||||||
convert_to(text, to_charset, from_charset)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def unquote_quoted_printable_and_convert_to(text, to, from, preserve_underscores=false)
|
|
||||||
text = text.gsub(/_/, " ") unless preserve_underscores
|
|
||||||
convert_to(text.unpack("M*").first, to, from)
|
|
||||||
end
|
|
||||||
|
|
||||||
def unquote_base64_and_convert_to(text, to, from)
|
|
||||||
convert_to(Base64.decode(text).first, to, from)
|
|
||||||
end
|
|
||||||
|
|
||||||
begin
|
|
||||||
require 'iconv'
|
|
||||||
def convert_to(text, to, from)
|
|
||||||
return text unless to && from
|
|
||||||
text ? Iconv.iconv(to, from, text).first : ""
|
|
||||||
rescue Iconv::IllegalSequence, Errno::EINVAL
|
|
||||||
# the 'from' parameter specifies a charset other than what the text
|
|
||||||
# actually is...not much we can do in this case but just return the
|
|
||||||
# unconverted text.
|
|
||||||
#
|
|
||||||
# Ditto if either parameter represents an unknown charset, like
|
|
||||||
# X-UNKNOWN.
|
|
||||||
text
|
|
||||||
end
|
|
||||||
rescue LoadError
|
|
||||||
# Not providing quoting support
|
|
||||||
def convert_to(text, to, from)
|
|
||||||
warn "Action Mailer: iconv not loaded; ignoring conversion from #{from} to #{to} (#{__FILE__}:#{__LINE__})"
|
|
||||||
text
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
if __FILE__ == $0
|
|
||||||
require 'test/unit'
|
|
||||||
|
|
||||||
class TC_Unquoter < Test::Unit::TestCase
|
|
||||||
def test_unquote_quoted_printable
|
|
||||||
a ="=?ISO-8859-1?Q?[166417]_Bekr=E6ftelse_fra_Rejsefeber?="
|
|
||||||
b = TMail::Unquoter.unquote_and_convert_to(a, 'utf-8')
|
|
||||||
assert_equal "[166417] Bekr\303\246ftelse fra Rejsefeber", b
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_unquote_base64
|
|
||||||
a ="=?ISO-8859-1?B?WzE2NjQxN10gQmVrcuZmdGVsc2UgZnJhIFJlanNlZmViZXI=?="
|
|
||||||
b = TMail::Unquoter.unquote_and_convert_to(a, 'utf-8')
|
|
||||||
assert_equal "[166417] Bekr\303\246ftelse fra Rejsefeber", b
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_unquote_without_charset
|
|
||||||
a ="[166417]_Bekr=E6ftelse_fra_Rejsefeber"
|
|
||||||
b = TMail::Unquoter.unquote_and_convert_to(a, 'utf-8')
|
|
||||||
assert_equal "[166417]_Bekr=E6ftelse_fra_Rejsefeber", b
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
@ -1,41 +0,0 @@
|
||||||
#
|
|
||||||
# scanner.rb
|
|
||||||
#
|
|
||||||
#--
|
|
||||||
# Copyright (c) 1998-2003 Minero Aoki <aamine@loveruby.net>
|
|
||||||
#
|
|
||||||
# Permission is hereby granted, free of charge, to any person obtaining
|
|
||||||
# a copy of this software and associated documentation files (the
|
|
||||||
# "Software"), to deal in the Software without restriction, including
|
|
||||||
# without limitation the rights to use, copy, modify, merge, publish,
|
|
||||||
# distribute, sublicense, and/or sell copies of the Software, and to
|
|
||||||
# permit persons to whom the Software is furnished to do so, subject to
|
|
||||||
# the following conditions:
|
|
||||||
#
|
|
||||||
# The above copyright notice and this permission notice shall be
|
|
||||||
# included in all copies or substantial portions of the Software.
|
|
||||||
#
|
|
||||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
||||||
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
||||||
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
||||||
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
||||||
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
||||||
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
||||||
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
||||||
#
|
|
||||||
# Note: Originally licensed under LGPL v2+. Using MIT license for Rails
|
|
||||||
# with permission of Minero Aoki.
|
|
||||||
#++
|
|
||||||
|
|
||||||
require 'tmail/utils'
|
|
||||||
|
|
||||||
module TMail
|
|
||||||
require 'tmail/scanner_r.rb'
|
|
||||||
begin
|
|
||||||
raise LoadError, 'Turn off Ruby extention by user choice' if ENV['NORUBYEXT']
|
|
||||||
require 'tmail/scanner_c.so'
|
|
||||||
Scanner = Scanner_C
|
|
||||||
rescue LoadError
|
|
||||||
Scanner = Scanner_R
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
@ -1,263 +0,0 @@
|
||||||
#
|
|
||||||
# scanner_r.rb
|
|
||||||
#
|
|
||||||
#--
|
|
||||||
# Copyright (c) 1998-2003 Minero Aoki <aamine@loveruby.net>
|
|
||||||
#
|
|
||||||
# Permission is hereby granted, free of charge, to any person obtaining
|
|
||||||
# a copy of this software and associated documentation files (the
|
|
||||||
# "Software"), to deal in the Software without restriction, including
|
|
||||||
# without limitation the rights to use, copy, modify, merge, publish,
|
|
||||||
# distribute, sublicense, and/or sell copies of the Software, and to
|
|
||||||
# permit persons to whom the Software is furnished to do so, subject to
|
|
||||||
# the following conditions:
|
|
||||||
#
|
|
||||||
# The above copyright notice and this permission notice shall be
|
|
||||||
# included in all copies or substantial portions of the Software.
|
|
||||||
#
|
|
||||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
||||||
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
||||||
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
||||||
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
||||||
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
||||||
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
||||||
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
||||||
#
|
|
||||||
# Note: Originally licensed under LGPL v2+. Using MIT license for Rails
|
|
||||||
# with permission of Minero Aoki.
|
|
||||||
#++
|
|
||||||
|
|
||||||
require 'tmail/config'
|
|
||||||
|
|
||||||
|
|
||||||
module TMail
|
|
||||||
|
|
||||||
class Scanner_R
|
|
||||||
|
|
||||||
Version = '0.10.7'
|
|
||||||
Version.freeze
|
|
||||||
|
|
||||||
MIME_HEADERS = {
|
|
||||||
:CTYPE => true,
|
|
||||||
:CENCODING => true,
|
|
||||||
:CDISPOSITION => true
|
|
||||||
}
|
|
||||||
|
|
||||||
alnum = 'a-zA-Z0-9'
|
|
||||||
atomsyms = %q[ _#!$%&`'*+-{|}~^@/=? ].strip
|
|
||||||
tokensyms = %q[ _#!$%&`'*+-{|}~^@. ].strip
|
|
||||||
|
|
||||||
atomchars = alnum + Regexp.quote(atomsyms)
|
|
||||||
tokenchars = alnum + Regexp.quote(tokensyms)
|
|
||||||
iso2022str = '\e(?!\(B)..(?:[^\e]+|\e(?!\(B)..)*\e\(B'
|
|
||||||
|
|
||||||
eucstr = '(?:[\xa1-\xfe][\xa1-\xfe])+'
|
|
||||||
sjisstr = '(?:[\x81-\x9f\xe0-\xef][\x40-\x7e\x80-\xfc])+'
|
|
||||||
utf8str = '(?:[\xc0-\xdf][\x80-\xbf]|[\xe0-\xef][\x80-\xbf][\x80-\xbf])+'
|
|
||||||
|
|
||||||
quoted_with_iso2022 = /\A(?:[^\\\e"]+|#{iso2022str})+/n
|
|
||||||
domlit_with_iso2022 = /\A(?:[^\\\e\]]+|#{iso2022str})+/n
|
|
||||||
comment_with_iso2022 = /\A(?:[^\\\e()]+|#{iso2022str})+/n
|
|
||||||
|
|
||||||
quoted_without_iso2022 = /\A[^\\"]+/n
|
|
||||||
domlit_without_iso2022 = /\A[^\\\]]+/n
|
|
||||||
comment_without_iso2022 = /\A[^\\()]+/n
|
|
||||||
|
|
||||||
PATTERN_TABLE = {}
|
|
||||||
PATTERN_TABLE['EUC'] =
|
|
||||||
[
|
|
||||||
/\A(?:[#{atomchars}]+|#{iso2022str}|#{eucstr})+/n,
|
|
||||||
/\A(?:[#{tokenchars}]+|#{iso2022str}|#{eucstr})+/n,
|
|
||||||
quoted_with_iso2022,
|
|
||||||
domlit_with_iso2022,
|
|
||||||
comment_with_iso2022
|
|
||||||
]
|
|
||||||
PATTERN_TABLE['SJIS'] =
|
|
||||||
[
|
|
||||||
/\A(?:[#{atomchars}]+|#{iso2022str}|#{sjisstr})+/n,
|
|
||||||
/\A(?:[#{tokenchars}]+|#{iso2022str}|#{sjisstr})+/n,
|
|
||||||
quoted_with_iso2022,
|
|
||||||
domlit_with_iso2022,
|
|
||||||
comment_with_iso2022
|
|
||||||
]
|
|
||||||
PATTERN_TABLE['UTF8'] =
|
|
||||||
[
|
|
||||||
/\A(?:[#{atomchars}]+|#{utf8str})+/n,
|
|
||||||
/\A(?:[#{tokenchars}]+|#{utf8str})+/n,
|
|
||||||
quoted_without_iso2022,
|
|
||||||
domlit_without_iso2022,
|
|
||||||
comment_without_iso2022
|
|
||||||
]
|
|
||||||
PATTERN_TABLE['NONE'] =
|
|
||||||
[
|
|
||||||
/\A[#{atomchars}]+/n,
|
|
||||||
/\A[#{tokenchars}]+/n,
|
|
||||||
quoted_without_iso2022,
|
|
||||||
domlit_without_iso2022,
|
|
||||||
comment_without_iso2022
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
def initialize( str, scantype, comments )
|
|
||||||
init_scanner str
|
|
||||||
@comments = comments || []
|
|
||||||
@debug = false
|
|
||||||
|
|
||||||
# fix scanner mode
|
|
||||||
@received = (scantype == :RECEIVED)
|
|
||||||
@is_mime_header = MIME_HEADERS[scantype]
|
|
||||||
|
|
||||||
atom, token, @quoted_re, @domlit_re, @comment_re = PATTERN_TABLE[$KCODE]
|
|
||||||
@word_re = (MIME_HEADERS[scantype] ? token : atom)
|
|
||||||
end
|
|
||||||
|
|
||||||
attr_accessor :debug
|
|
||||||
|
|
||||||
def scan( &block )
|
|
||||||
if @debug
|
|
||||||
scan_main do |arr|
|
|
||||||
s, v = arr
|
|
||||||
printf "%7d %-10s %s\n",
|
|
||||||
rest_size(),
|
|
||||||
s.respond_to?(:id2name) ? s.id2name : s.inspect,
|
|
||||||
v.inspect
|
|
||||||
yield arr
|
|
||||||
end
|
|
||||||
else
|
|
||||||
scan_main(&block)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
RECV_TOKEN = {
|
|
||||||
'from' => :FROM,
|
|
||||||
'by' => :BY,
|
|
||||||
'via' => :VIA,
|
|
||||||
'with' => :WITH,
|
|
||||||
'id' => :ID,
|
|
||||||
'for' => :FOR
|
|
||||||
}
|
|
||||||
|
|
||||||
def scan_main
|
|
||||||
until eof?
|
|
||||||
if skip(/\A[\n\r\t ]+/n) # LWSP
|
|
||||||
break if eof?
|
|
||||||
end
|
|
||||||
|
|
||||||
if s = readstr(@word_re)
|
|
||||||
if @is_mime_header
|
|
||||||
yield :TOKEN, s
|
|
||||||
else
|
|
||||||
# atom
|
|
||||||
if /\A\d+\z/ === s
|
|
||||||
yield :DIGIT, s
|
|
||||||
elsif @received
|
|
||||||
yield RECV_TOKEN[s.downcase] || :ATOM, s
|
|
||||||
else
|
|
||||||
yield :ATOM, s
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
elsif skip(/\A"/)
|
|
||||||
yield :QUOTED, scan_quoted_word()
|
|
||||||
|
|
||||||
elsif skip(/\A\[/)
|
|
||||||
yield :DOMLIT, scan_domain_literal()
|
|
||||||
|
|
||||||
elsif skip(/\A\(/)
|
|
||||||
@comments.push scan_comment()
|
|
||||||
|
|
||||||
else
|
|
||||||
c = readchar()
|
|
||||||
yield c, c
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
yield false, '$'
|
|
||||||
end
|
|
||||||
|
|
||||||
def scan_quoted_word
|
|
||||||
scan_qstr(@quoted_re, /\A"/, 'quoted-word')
|
|
||||||
end
|
|
||||||
|
|
||||||
def scan_domain_literal
|
|
||||||
'[' + scan_qstr(@domlit_re, /\A\]/, 'domain-literal') + ']'
|
|
||||||
end
|
|
||||||
|
|
||||||
def scan_qstr( pattern, terminal, type )
|
|
||||||
result = ''
|
|
||||||
until eof?
|
|
||||||
if s = readstr(pattern) then result << s
|
|
||||||
elsif skip(terminal) then return result
|
|
||||||
elsif skip(/\A\\/) then result << readchar()
|
|
||||||
else
|
|
||||||
raise "TMail FATAL: not match in #{type}"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
scan_error! "found unterminated #{type}"
|
|
||||||
end
|
|
||||||
|
|
||||||
def scan_comment
|
|
||||||
result = ''
|
|
||||||
nest = 1
|
|
||||||
content = @comment_re
|
|
||||||
|
|
||||||
until eof?
|
|
||||||
if s = readstr(content) then result << s
|
|
||||||
elsif skip(/\A\)/) then nest -= 1
|
|
||||||
return result if nest == 0
|
|
||||||
result << ')'
|
|
||||||
elsif skip(/\A\(/) then nest += 1
|
|
||||||
result << '('
|
|
||||||
elsif skip(/\A\\/) then result << readchar()
|
|
||||||
else
|
|
||||||
raise 'TMail FATAL: not match in comment'
|
|
||||||
end
|
|
||||||
end
|
|
||||||
scan_error! 'found unterminated comment'
|
|
||||||
end
|
|
||||||
|
|
||||||
# string scanner
|
|
||||||
|
|
||||||
def init_scanner( str )
|
|
||||||
@src = str
|
|
||||||
end
|
|
||||||
|
|
||||||
def eof?
|
|
||||||
@src.empty?
|
|
||||||
end
|
|
||||||
|
|
||||||
def rest_size
|
|
||||||
@src.size
|
|
||||||
end
|
|
||||||
|
|
||||||
def readstr( re )
|
|
||||||
if m = re.match(@src)
|
|
||||||
@src = m.post_match
|
|
||||||
m[0]
|
|
||||||
else
|
|
||||||
nil
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def readchar
|
|
||||||
readstr(/\A./)
|
|
||||||
end
|
|
||||||
|
|
||||||
def skip( re )
|
|
||||||
if m = re.match(@src)
|
|
||||||
@src = m.post_match
|
|
||||||
true
|
|
||||||
else
|
|
||||||
false
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def scan_error!( msg )
|
|
||||||
raise SyntaxError, msg
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
||||||
end # module TMail
|
|
||||||
|
|
@ -1,277 +0,0 @@
|
||||||
#
|
|
||||||
# stringio.rb
|
|
||||||
#
|
|
||||||
#--
|
|
||||||
# Copyright (c) 1998-2003 Minero Aoki <aamine@loveruby.net>
|
|
||||||
#
|
|
||||||
# Permission is hereby granted, free of charge, to any person obtaining
|
|
||||||
# a copy of this software and associated documentation files (the
|
|
||||||
# "Software"), to deal in the Software without restriction, including
|
|
||||||
# without limitation the rights to use, copy, modify, merge, publish,
|
|
||||||
# distribute, sublicense, and/or sell copies of the Software, and to
|
|
||||||
# permit persons to whom the Software is furnished to do so, subject to
|
|
||||||
# the following conditions:
|
|
||||||
#
|
|
||||||
# The above copyright notice and this permission notice shall be
|
|
||||||
# included in all copies or substantial portions of the Software.
|
|
||||||
#
|
|
||||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
||||||
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
||||||
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
||||||
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
||||||
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
||||||
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
||||||
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
||||||
#
|
|
||||||
# Note: Originally licensed under LGPL v2+. Using MIT license for Rails
|
|
||||||
# with permission of Minero Aoki.
|
|
||||||
#++
|
|
||||||
|
|
||||||
class StringInput#:nodoc:
|
|
||||||
|
|
||||||
include Enumerable
|
|
||||||
|
|
||||||
class << self
|
|
||||||
|
|
||||||
def new( str )
|
|
||||||
if block_given?
|
|
||||||
begin
|
|
||||||
f = super
|
|
||||||
yield f
|
|
||||||
ensure
|
|
||||||
f.close if f
|
|
||||||
end
|
|
||||||
else
|
|
||||||
super
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
alias open new
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
||||||
def initialize( str )
|
|
||||||
@src = str
|
|
||||||
@pos = 0
|
|
||||||
@closed = false
|
|
||||||
@lineno = 0
|
|
||||||
end
|
|
||||||
|
|
||||||
attr_reader :lineno
|
|
||||||
|
|
||||||
def string
|
|
||||||
@src
|
|
||||||
end
|
|
||||||
|
|
||||||
def inspect
|
|
||||||
"#<#{self.class}:#{@closed ? 'closed' : 'open'},src=#{@src[0,30].inspect}>"
|
|
||||||
end
|
|
||||||
|
|
||||||
def close
|
|
||||||
stream_check!
|
|
||||||
@pos = nil
|
|
||||||
@closed = true
|
|
||||||
end
|
|
||||||
|
|
||||||
def closed?
|
|
||||||
@closed
|
|
||||||
end
|
|
||||||
|
|
||||||
def pos
|
|
||||||
stream_check!
|
|
||||||
[@pos, @src.size].min
|
|
||||||
end
|
|
||||||
|
|
||||||
alias tell pos
|
|
||||||
|
|
||||||
def seek( offset, whence = IO::SEEK_SET )
|
|
||||||
stream_check!
|
|
||||||
case whence
|
|
||||||
when IO::SEEK_SET
|
|
||||||
@pos = offset
|
|
||||||
when IO::SEEK_CUR
|
|
||||||
@pos += offset
|
|
||||||
when IO::SEEK_END
|
|
||||||
@pos = @src.size - offset
|
|
||||||
else
|
|
||||||
raise ArgumentError, "unknown seek flag: #{whence}"
|
|
||||||
end
|
|
||||||
@pos = 0 if @pos < 0
|
|
||||||
@pos = [@pos, @src.size + 1].min
|
|
||||||
offset
|
|
||||||
end
|
|
||||||
|
|
||||||
def rewind
|
|
||||||
stream_check!
|
|
||||||
@pos = 0
|
|
||||||
end
|
|
||||||
|
|
||||||
def eof?
|
|
||||||
stream_check!
|
|
||||||
@pos > @src.size
|
|
||||||
end
|
|
||||||
|
|
||||||
def each( &block )
|
|
||||||
stream_check!
|
|
||||||
begin
|
|
||||||
@src.each(&block)
|
|
||||||
ensure
|
|
||||||
@pos = 0
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def gets
|
|
||||||
stream_check!
|
|
||||||
if idx = @src.index(?\n, @pos)
|
|
||||||
idx += 1 # "\n".size
|
|
||||||
line = @src[ @pos ... idx ]
|
|
||||||
@pos = idx
|
|
||||||
@pos += 1 if @pos == @src.size
|
|
||||||
else
|
|
||||||
line = @src[ @pos .. -1 ]
|
|
||||||
@pos = @src.size + 1
|
|
||||||
end
|
|
||||||
@lineno += 1
|
|
||||||
|
|
||||||
line
|
|
||||||
end
|
|
||||||
|
|
||||||
def getc
|
|
||||||
stream_check!
|
|
||||||
ch = @src[@pos]
|
|
||||||
@pos += 1
|
|
||||||
@pos += 1 if @pos == @src.size
|
|
||||||
ch
|
|
||||||
end
|
|
||||||
|
|
||||||
def read( len = nil )
|
|
||||||
stream_check!
|
|
||||||
return read_all unless len
|
|
||||||
str = @src[@pos, len]
|
|
||||||
@pos += len
|
|
||||||
@pos += 1 if @pos == @src.size
|
|
||||||
str
|
|
||||||
end
|
|
||||||
|
|
||||||
alias sysread read
|
|
||||||
|
|
||||||
def read_all
|
|
||||||
stream_check!
|
|
||||||
return nil if eof?
|
|
||||||
rest = @src[@pos ... @src.size]
|
|
||||||
@pos = @src.size + 1
|
|
||||||
rest
|
|
||||||
end
|
|
||||||
|
|
||||||
def stream_check!
|
|
||||||
@closed and raise IOError, 'closed stream'
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
class StringOutput#:nodoc:
|
|
||||||
|
|
||||||
class << self
|
|
||||||
|
|
||||||
def new( str = '' )
|
|
||||||
if block_given?
|
|
||||||
begin
|
|
||||||
f = super
|
|
||||||
yield f
|
|
||||||
ensure
|
|
||||||
f.close if f
|
|
||||||
end
|
|
||||||
else
|
|
||||||
super
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
alias open new
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
||||||
def initialize( str = '' )
|
|
||||||
@dest = str
|
|
||||||
@closed = false
|
|
||||||
end
|
|
||||||
|
|
||||||
def close
|
|
||||||
@closed = true
|
|
||||||
end
|
|
||||||
|
|
||||||
def closed?
|
|
||||||
@closed
|
|
||||||
end
|
|
||||||
|
|
||||||
def string
|
|
||||||
@dest
|
|
||||||
end
|
|
||||||
|
|
||||||
alias value string
|
|
||||||
alias to_str string
|
|
||||||
|
|
||||||
def size
|
|
||||||
@dest.size
|
|
||||||
end
|
|
||||||
|
|
||||||
alias pos size
|
|
||||||
|
|
||||||
def inspect
|
|
||||||
"#<#{self.class}:#{@dest ? 'open' : 'closed'},#{id}>"
|
|
||||||
end
|
|
||||||
|
|
||||||
def print( *args )
|
|
||||||
stream_check!
|
|
||||||
raise ArgumentError, 'wrong # of argument (0 for >1)' if args.empty?
|
|
||||||
args.each do |s|
|
|
||||||
raise ArgumentError, 'nil not allowed' if s.nil?
|
|
||||||
@dest << s.to_s
|
|
||||||
end
|
|
||||||
nil
|
|
||||||
end
|
|
||||||
|
|
||||||
def puts( *args )
|
|
||||||
stream_check!
|
|
||||||
args.each do |str|
|
|
||||||
@dest << (s = str.to_s)
|
|
||||||
@dest << "\n" unless s[-1] == ?\n
|
|
||||||
end
|
|
||||||
@dest << "\n" if args.empty?
|
|
||||||
nil
|
|
||||||
end
|
|
||||||
|
|
||||||
def putc( ch )
|
|
||||||
stream_check!
|
|
||||||
@dest << ch.chr
|
|
||||||
nil
|
|
||||||
end
|
|
||||||
|
|
||||||
def printf( *args )
|
|
||||||
stream_check!
|
|
||||||
@dest << sprintf(*args)
|
|
||||||
nil
|
|
||||||
end
|
|
||||||
|
|
||||||
def write( str )
|
|
||||||
stream_check!
|
|
||||||
s = str.to_s
|
|
||||||
@dest << s
|
|
||||||
s.size
|
|
||||||
end
|
|
||||||
|
|
||||||
alias syswrite write
|
|
||||||
|
|
||||||
def <<( str )
|
|
||||||
stream_check!
|
|
||||||
@dest << str.to_s
|
|
||||||
self
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def stream_check!
|
|
||||||
@closed and raise IOError, 'closed stream'
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
require 'tmail'
|
|
||||||
|
|
@ -1,238 +0,0 @@
|
||||||
#
|
|
||||||
# utils.rb
|
|
||||||
#
|
|
||||||
#--
|
|
||||||
# Copyright (c) 1998-2003 Minero Aoki <aamine@loveruby.net>
|
|
||||||
#
|
|
||||||
# Permission is hereby granted, free of charge, to any person obtaining
|
|
||||||
# a copy of this software and associated documentation files (the
|
|
||||||
# "Software"), to deal in the Software without restriction, including
|
|
||||||
# without limitation the rights to use, copy, modify, merge, publish,
|
|
||||||
# distribute, sublicense, and/or sell copies of the Software, and to
|
|
||||||
# permit persons to whom the Software is furnished to do so, subject to
|
|
||||||
# the following conditions:
|
|
||||||
#
|
|
||||||
# The above copyright notice and this permission notice shall be
|
|
||||||
# included in all copies or substantial portions of the Software.
|
|
||||||
#
|
|
||||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
||||||
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
||||||
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
||||||
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
||||||
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
||||||
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
||||||
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
||||||
#
|
|
||||||
# Note: Originally licensed under LGPL v2+. Using MIT license for Rails
|
|
||||||
# with permission of Minero Aoki.
|
|
||||||
#++
|
|
||||||
|
|
||||||
module TMail
|
|
||||||
|
|
||||||
class SyntaxError < StandardError; end
|
|
||||||
|
|
||||||
|
|
||||||
def TMail.new_boundary
|
|
||||||
'mimepart_' + random_tag
|
|
||||||
end
|
|
||||||
|
|
||||||
def TMail.new_message_id( fqdn = nil )
|
|
||||||
fqdn ||= ::Socket.gethostname
|
|
||||||
"<#{random_tag()}@#{fqdn}.tmail>"
|
|
||||||
end
|
|
||||||
|
|
||||||
def TMail.random_tag
|
|
||||||
@uniq += 1
|
|
||||||
t = Time.now
|
|
||||||
sprintf('%x%x_%x%x%d%x',
|
|
||||||
t.to_i, t.tv_usec,
|
|
||||||
$$, Thread.current.object_id, @uniq, rand(255))
|
|
||||||
end
|
|
||||||
private_class_method :random_tag
|
|
||||||
|
|
||||||
@uniq = 0
|
|
||||||
|
|
||||||
|
|
||||||
module TextUtils
|
|
||||||
|
|
||||||
aspecial = '()<>[]:;.\\,"'
|
|
||||||
tspecial = '()<>[];:\\,"/?='
|
|
||||||
lwsp = " \t\r\n"
|
|
||||||
control = '\x00-\x1f\x7f-\xff'
|
|
||||||
|
|
||||||
ATOM_UNSAFE = /[#{Regexp.quote aspecial}#{control}#{lwsp}]/n
|
|
||||||
PHRASE_UNSAFE = /[#{Regexp.quote aspecial}#{control}]/n
|
|
||||||
TOKEN_UNSAFE = /[#{Regexp.quote tspecial}#{control}#{lwsp}]/n
|
|
||||||
CONTROL_CHAR = /[#{control}]/n
|
|
||||||
|
|
||||||
def atom_safe?( str )
|
|
||||||
not ATOM_UNSAFE === str
|
|
||||||
end
|
|
||||||
|
|
||||||
def quote_atom( str )
|
|
||||||
(ATOM_UNSAFE === str) ? dquote(str) : str
|
|
||||||
end
|
|
||||||
|
|
||||||
def quote_phrase( str )
|
|
||||||
(PHRASE_UNSAFE === str) ? dquote(str) : str
|
|
||||||
end
|
|
||||||
|
|
||||||
def token_safe?( str )
|
|
||||||
not TOKEN_UNSAFE === str
|
|
||||||
end
|
|
||||||
|
|
||||||
def quote_token( str )
|
|
||||||
(TOKEN_UNSAFE === str) ? dquote(str) : str
|
|
||||||
end
|
|
||||||
|
|
||||||
def dquote( str )
|
|
||||||
'"' + str.gsub(/["\\]/n) {|s| '\\' + s } + '"'
|
|
||||||
end
|
|
||||||
private :dquote
|
|
||||||
|
|
||||||
|
|
||||||
def join_domain( arr )
|
|
||||||
arr.map {|i|
|
|
||||||
if /\A\[.*\]\z/ === i
|
|
||||||
i
|
|
||||||
else
|
|
||||||
quote_atom(i)
|
|
||||||
end
|
|
||||||
}.join('.')
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
ZONESTR_TABLE = {
|
|
||||||
'jst' => 9 * 60,
|
|
||||||
'eet' => 2 * 60,
|
|
||||||
'bst' => 1 * 60,
|
|
||||||
'met' => 1 * 60,
|
|
||||||
'gmt' => 0,
|
|
||||||
'utc' => 0,
|
|
||||||
'ut' => 0,
|
|
||||||
'nst' => -(3 * 60 + 30),
|
|
||||||
'ast' => -4 * 60,
|
|
||||||
'edt' => -4 * 60,
|
|
||||||
'est' => -5 * 60,
|
|
||||||
'cdt' => -5 * 60,
|
|
||||||
'cst' => -6 * 60,
|
|
||||||
'mdt' => -6 * 60,
|
|
||||||
'mst' => -7 * 60,
|
|
||||||
'pdt' => -7 * 60,
|
|
||||||
'pst' => -8 * 60,
|
|
||||||
'a' => -1 * 60,
|
|
||||||
'b' => -2 * 60,
|
|
||||||
'c' => -3 * 60,
|
|
||||||
'd' => -4 * 60,
|
|
||||||
'e' => -5 * 60,
|
|
||||||
'f' => -6 * 60,
|
|
||||||
'g' => -7 * 60,
|
|
||||||
'h' => -8 * 60,
|
|
||||||
'i' => -9 * 60,
|
|
||||||
# j not use
|
|
||||||
'k' => -10 * 60,
|
|
||||||
'l' => -11 * 60,
|
|
||||||
'm' => -12 * 60,
|
|
||||||
'n' => 1 * 60,
|
|
||||||
'o' => 2 * 60,
|
|
||||||
'p' => 3 * 60,
|
|
||||||
'q' => 4 * 60,
|
|
||||||
'r' => 5 * 60,
|
|
||||||
's' => 6 * 60,
|
|
||||||
't' => 7 * 60,
|
|
||||||
'u' => 8 * 60,
|
|
||||||
'v' => 9 * 60,
|
|
||||||
'w' => 10 * 60,
|
|
||||||
'x' => 11 * 60,
|
|
||||||
'y' => 12 * 60,
|
|
||||||
'z' => 0 * 60
|
|
||||||
}
|
|
||||||
|
|
||||||
def timezone_string_to_unixtime( str )
|
|
||||||
if m = /([\+\-])(\d\d?)(\d\d)/.match(str)
|
|
||||||
sec = (m[2].to_i * 60 + m[3].to_i) * 60
|
|
||||||
m[1] == '-' ? -sec : sec
|
|
||||||
else
|
|
||||||
min = ZONESTR_TABLE[str.downcase] or
|
|
||||||
raise SyntaxError, "wrong timezone format '#{str}'"
|
|
||||||
min * 60
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
WDAY = %w( Sun Mon Tue Wed Thu Fri Sat TMailBUG )
|
|
||||||
MONTH = %w( TMailBUG Jan Feb Mar Apr May Jun
|
|
||||||
Jul Aug Sep Oct Nov Dec TMailBUG )
|
|
||||||
|
|
||||||
def time2str( tm )
|
|
||||||
# [ruby-list:7928]
|
|
||||||
gmt = Time.at(tm.to_i)
|
|
||||||
gmt.gmtime
|
|
||||||
offset = tm.to_i - Time.local(*gmt.to_a[0,6].reverse).to_i
|
|
||||||
|
|
||||||
# DO NOT USE strftime: setlocale() breaks it
|
|
||||||
sprintf '%s, %s %s %d %02d:%02d:%02d %+.2d%.2d',
|
|
||||||
WDAY[tm.wday], tm.mday, MONTH[tm.month],
|
|
||||||
tm.year, tm.hour, tm.min, tm.sec,
|
|
||||||
*(offset / 60).divmod(60)
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
MESSAGE_ID = /<[^\@>]+\@[^>\@]+>/
|
|
||||||
|
|
||||||
def message_id?( str )
|
|
||||||
MESSAGE_ID === str
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
MIME_ENCODED = /=\?[^\s?=]+\?[QB]\?[^\s?=]+\?=/i
|
|
||||||
|
|
||||||
def mime_encoded?( str )
|
|
||||||
MIME_ENCODED === str
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
def decode_params( hash )
|
|
||||||
new = Hash.new
|
|
||||||
encoded = nil
|
|
||||||
hash.each do |key, value|
|
|
||||||
if m = /\*(?:(\d+)\*)?\z/.match(key)
|
|
||||||
((encoded ||= {})[m.pre_match] ||= [])[(m[1] || 0).to_i] = value
|
|
||||||
else
|
|
||||||
new[key] = to_kcode(value)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
if encoded
|
|
||||||
encoded.each do |key, strings|
|
|
||||||
new[key] = decode_RFC2231(strings.join(''))
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
new
|
|
||||||
end
|
|
||||||
|
|
||||||
NKF_FLAGS = {
|
|
||||||
'EUC' => '-e -m',
|
|
||||||
'SJIS' => '-s -m'
|
|
||||||
}
|
|
||||||
|
|
||||||
def to_kcode( str )
|
|
||||||
flag = NKF_FLAGS[$KCODE] or return str
|
|
||||||
NKF.nkf(flag, str)
|
|
||||||
end
|
|
||||||
|
|
||||||
RFC2231_ENCODED = /\A(?:iso-2022-jp|euc-jp|shift_jis|us-ascii)?'[a-z]*'/in
|
|
||||||
|
|
||||||
def decode_RFC2231( str )
|
|
||||||
m = RFC2231_ENCODED.match(str) or return str
|
|
||||||
begin
|
|
||||||
NKF.nkf(NKF_FLAGS[$KCODE],
|
|
||||||
m.post_match.gsub(/%[\da-f]{2}/in) {|s| s[1,2].hex.chr })
|
|
||||||
rescue
|
|
||||||
m.post_match.gsub(/%[\da-f]{2}/in, "")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
@ -1,9 +0,0 @@
|
||||||
module ActionMailer
|
|
||||||
module VERSION #:nodoc:
|
|
||||||
MAJOR = 1
|
|
||||||
MINOR = 1
|
|
||||||
TINY = 5
|
|
||||||
|
|
||||||
STRING = [MAJOR, MINOR, TINY].join('.')
|
|
||||||
end
|
|
||||||
end
|
|
||||||
198
tracks/vendor/rails/actionmailer/rakefile
vendored
198
tracks/vendor/rails/actionmailer/rakefile
vendored
|
|
@ -1,198 +0,0 @@
|
||||||
require 'rubygems'
|
|
||||||
require 'rake'
|
|
||||||
require 'rake/testtask'
|
|
||||||
require 'rake/rdoctask'
|
|
||||||
require 'rake/packagetask'
|
|
||||||
require 'rake/gempackagetask'
|
|
||||||
require 'rake/contrib/rubyforgepublisher'
|
|
||||||
require File.join(File.dirname(__FILE__), 'lib', 'action_mailer', 'version')
|
|
||||||
|
|
||||||
PKG_BUILD = ENV['PKG_BUILD'] ? '.' + ENV['PKG_BUILD'] : ''
|
|
||||||
PKG_NAME = 'actionmailer'
|
|
||||||
PKG_VERSION = ActionMailer::VERSION::STRING + PKG_BUILD
|
|
||||||
PKG_FILE_NAME = "#{PKG_NAME}-#{PKG_VERSION}"
|
|
||||||
|
|
||||||
RELEASE_NAME = "REL #{PKG_VERSION}"
|
|
||||||
|
|
||||||
RUBY_FORGE_PROJECT = "actionmailer"
|
|
||||||
RUBY_FORGE_USER = "webster132"
|
|
||||||
|
|
||||||
desc "Default Task"
|
|
||||||
task :default => [ :test ]
|
|
||||||
|
|
||||||
# Run the unit tests
|
|
||||||
Rake::TestTask.new { |t|
|
|
||||||
t.libs << "test"
|
|
||||||
t.pattern = 'test/*_test.rb'
|
|
||||||
t.verbose = true
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
# Genereate the RDoc documentation
|
|
||||||
Rake::RDocTask.new { |rdoc|
|
|
||||||
rdoc.rdoc_dir = 'doc'
|
|
||||||
rdoc.title = "Action Mailer -- Easy email delivery and testing"
|
|
||||||
rdoc.options << '--line-numbers --inline-source --main README --accessor adv_attr_accessor=M'
|
|
||||||
rdoc.template = "#{ENV['template']}.rb" if ENV['template']
|
|
||||||
rdoc.rdoc_files.include('README', 'CHANGELOG')
|
|
||||||
rdoc.rdoc_files.include('lib/action_mailer.rb')
|
|
||||||
rdoc.rdoc_files.include('lib/action_mailer/*.rb')
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
# Create compressed packages
|
|
||||||
spec = Gem::Specification.new do |s|
|
|
||||||
s.platform = Gem::Platform::RUBY
|
|
||||||
s.name = PKG_NAME
|
|
||||||
s.summary = "Service layer for easy email delivery and testing."
|
|
||||||
s.description = %q{Makes it trivial to test and deliver emails sent from a single service layer.}
|
|
||||||
s.version = PKG_VERSION
|
|
||||||
|
|
||||||
s.author = "David Heinemeier Hansson"
|
|
||||||
s.email = "david@loudthinking.com"
|
|
||||||
s.rubyforge_project = "actionmailer"
|
|
||||||
s.homepage = "http://www.rubyonrails.org"
|
|
||||||
|
|
||||||
s.add_dependency('actionpack', '= 1.11.2' + PKG_BUILD)
|
|
||||||
|
|
||||||
s.has_rdoc = true
|
|
||||||
s.requirements << 'none'
|
|
||||||
s.require_path = 'lib'
|
|
||||||
s.autorequire = 'action_mailer'
|
|
||||||
|
|
||||||
s.files = [ "rakefile", "install.rb", "README", "CHANGELOG", "MIT-LICENSE" ]
|
|
||||||
s.files = s.files + Dir.glob( "lib/**/*" ).delete_if { |item| item.include?( "\.svn" ) }
|
|
||||||
s.files = s.files + Dir.glob( "test/**/*" ).delete_if { |item| item.include?( "\.svn" ) }
|
|
||||||
end
|
|
||||||
|
|
||||||
Rake::GemPackageTask.new(spec) do |p|
|
|
||||||
p.gem_spec = spec
|
|
||||||
p.need_tar = true
|
|
||||||
p.need_zip = true
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
desc "Publish the API documentation"
|
|
||||||
task :pgem => [:package] do
|
|
||||||
Rake::SshFilePublisher.new("davidhh@wrath.rubyonrails.org", "public_html/gems/gems", "pkg", "#{PKG_FILE_NAME}.gem").upload
|
|
||||||
end
|
|
||||||
|
|
||||||
desc "Publish the API documentation"
|
|
||||||
task :pdoc => [:rdoc] do
|
|
||||||
Rake::SshDirPublisher.new("davidhh@wrath.rubyonrails.org", "public_html/am", "doc").upload
|
|
||||||
end
|
|
||||||
|
|
||||||
desc "Publish the release files to RubyForge."
|
|
||||||
task :release => [:package] do
|
|
||||||
files = ["gem", "tgz", "zip"].map { |ext| "pkg/#{PKG_FILE_NAME}.#{ext}" }
|
|
||||||
|
|
||||||
if RUBY_FORGE_PROJECT then
|
|
||||||
require 'net/http'
|
|
||||||
require 'open-uri'
|
|
||||||
|
|
||||||
project_uri = "http://rubyforge.org/projects/#{RUBY_FORGE_PROJECT}/"
|
|
||||||
project_data = open(project_uri) { |data| data.read }
|
|
||||||
group_id = project_data[/[?&]group_id=(\d+)/, 1]
|
|
||||||
raise "Couldn't get group id" unless group_id
|
|
||||||
|
|
||||||
# This echos password to shell which is a bit sucky
|
|
||||||
if ENV["RUBY_FORGE_PASSWORD"]
|
|
||||||
password = ENV["RUBY_FORGE_PASSWORD"]
|
|
||||||
else
|
|
||||||
print "#{RUBY_FORGE_USER}@rubyforge.org's password: "
|
|
||||||
password = STDIN.gets.chomp
|
|
||||||
end
|
|
||||||
|
|
||||||
login_response = Net::HTTP.start("rubyforge.org", 80) do |http|
|
|
||||||
data = [
|
|
||||||
"login=1",
|
|
||||||
"form_loginname=#{RUBY_FORGE_USER}",
|
|
||||||
"form_pw=#{password}"
|
|
||||||
].join("&")
|
|
||||||
http.post("/account/login.php", data)
|
|
||||||
end
|
|
||||||
|
|
||||||
cookie = login_response["set-cookie"]
|
|
||||||
raise "Login failed" unless cookie
|
|
||||||
headers = { "Cookie" => cookie }
|
|
||||||
|
|
||||||
release_uri = "http://rubyforge.org/frs/admin/?group_id=#{group_id}"
|
|
||||||
release_data = open(release_uri, headers) { |data| data.read }
|
|
||||||
package_id = release_data[/[?&]package_id=(\d+)/, 1]
|
|
||||||
raise "Couldn't get package id" unless package_id
|
|
||||||
|
|
||||||
first_file = true
|
|
||||||
release_id = ""
|
|
||||||
|
|
||||||
files.each do |filename|
|
|
||||||
basename = File.basename(filename)
|
|
||||||
file_ext = File.extname(filename)
|
|
||||||
file_data = File.open(filename, "rb") { |file| file.read }
|
|
||||||
|
|
||||||
puts "Releasing #{basename}..."
|
|
||||||
|
|
||||||
release_response = Net::HTTP.start("rubyforge.org", 80) do |http|
|
|
||||||
release_date = Time.now.strftime("%Y-%m-%d %H:%M")
|
|
||||||
type_map = {
|
|
||||||
".zip" => "3000",
|
|
||||||
".tgz" => "3110",
|
|
||||||
".gz" => "3110",
|
|
||||||
".gem" => "1400"
|
|
||||||
}; type_map.default = "9999"
|
|
||||||
type = type_map[file_ext]
|
|
||||||
boundary = "rubyqMY6QN9bp6e4kS21H4y0zxcvoor"
|
|
||||||
|
|
||||||
query_hash = if first_file then
|
|
||||||
{
|
|
||||||
"group_id" => group_id,
|
|
||||||
"package_id" => package_id,
|
|
||||||
"release_name" => RELEASE_NAME,
|
|
||||||
"release_date" => release_date,
|
|
||||||
"type_id" => type,
|
|
||||||
"processor_id" => "8000", # Any
|
|
||||||
"release_notes" => "",
|
|
||||||
"release_changes" => "",
|
|
||||||
"preformatted" => "1",
|
|
||||||
"submit" => "1"
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
"group_id" => group_id,
|
|
||||||
"release_id" => release_id,
|
|
||||||
"package_id" => package_id,
|
|
||||||
"step2" => "1",
|
|
||||||
"type_id" => type,
|
|
||||||
"processor_id" => "8000", # Any
|
|
||||||
"submit" => "Add This File"
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
query = "?" + query_hash.map do |(name, value)|
|
|
||||||
[name, URI.encode(value)].join("=")
|
|
||||||
end.join("&")
|
|
||||||
|
|
||||||
data = [
|
|
||||||
"--" + boundary,
|
|
||||||
"Content-Disposition: form-data; name=\"userfile\"; filename=\"#{basename}\"",
|
|
||||||
"Content-Type: application/octet-stream",
|
|
||||||
"Content-Transfer-Encoding: binary",
|
|
||||||
"", file_data, ""
|
|
||||||
].join("\x0D\x0A")
|
|
||||||
|
|
||||||
release_headers = headers.merge(
|
|
||||||
"Content-Type" => "multipart/form-data; boundary=#{boundary}"
|
|
||||||
)
|
|
||||||
|
|
||||||
target = first_file ? "/frs/admin/qrs.php" : "/frs/admin/editrelease.php"
|
|
||||||
http.post(target + query, data, release_headers)
|
|
||||||
end
|
|
||||||
|
|
||||||
if first_file then
|
|
||||||
release_id = release_response.body[/release_id=(\d+)/, 1]
|
|
||||||
raise("Couldn't get release id") unless release_id
|
|
||||||
end
|
|
||||||
|
|
||||||
first_file = false
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
Hello, <%= person_name %>. Thanks for registering!
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
This message brought to you by <%= name_of_the_mailer_class %>.
|
|
||||||
|
|
@ -1,5 +0,0 @@
|
||||||
From "Romeo and Juliet":
|
|
||||||
|
|
||||||
<%= block_format @text %>
|
|
||||||
|
|
||||||
Good ol' Shakespeare.
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
So, <%= test_format(@text) %>
|
|
||||||
|
|
@ -1,5 +0,0 @@
|
||||||
module TestHelper
|
|
||||||
def test_format(text)
|
|
||||||
"<em><strong><small>#{text}</small></strong></em>"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
@ -1,14 +0,0 @@
|
||||||
From jamis_buck@byu.edu Mon May 2 16:07:05 2005
|
|
||||||
Mime-Version: 1.0 (Apple Message framework v622)
|
|
||||||
Content-Transfer-Encoding: base64
|
|
||||||
Message-Id: <d3b8cf8e49f04480850c28713a1f473e@37signals.com>
|
|
||||||
Content-Type: text/plain;
|
|
||||||
charset=EUC-KR;
|
|
||||||
format=flowed
|
|
||||||
To: willard15georgina@jamis.backpackit.com
|
|
||||||
From: Jamis Buck <jamis@37signals.com>
|
|
||||||
Subject: =?EUC-KR?Q?NOTE:_=C7=D1=B1=B9=B8=BB=B7=CE_=C7=CF=B4=C2_=B0=CD?=
|
|
||||||
Date: Mon, 2 May 2005 16:07:05 -0600
|
|
||||||
|
|
||||||
tOu6zrrQwMcguLbC+bChwfa3ziwgv+y4rrTCIMfPs6q01MC7ILnPvcC0z7TZLg0KDQrBpiDAzLin
|
|
||||||
wLogSmFtaXPA1LTPtNku
|
|
||||||
|
|
@ -1,20 +0,0 @@
|
||||||
Return-Path: <xxx@xxxx.xxx>
|
|
||||||
Received: from xxx.xxxx.xxx by xxx.xxxx.xxx with ESMTP id C1B953B4CB6 for <xxxxx@Exxx.xxxx.xxx>; Tue, 10 May 2005 15:27:05 -0500
|
|
||||||
Received: from SMS-GTYxxx.xxxx.xxx by xxx.xxxx.xxx with ESMTP id ca for <xxxxx@Exxx.xxxx.xxx>; Tue, 10 May 2005 15:27:04 -0500
|
|
||||||
Received: from xxx.xxxx.xxx by SMS-GTYxxx.xxxx.xxx with ESMTP id j4AKR3r23323 for <xxxxx@Exxx.xxxx.xxx>; Tue, 10 May 2005 15:27:03 -0500
|
|
||||||
Date: Tue, 10 May 2005 15:27:03 -0500
|
|
||||||
From: xxx@xxxx.xxx
|
|
||||||
Sender: xxx@xxxx.xxx
|
|
||||||
To: xxxxxxxxxxx@xxxx.xxxx.xxx
|
|
||||||
Message-Id: <xxx@xxxx.xxx>
|
|
||||||
X-Original-To: xxxxxxxxxxx@xxxx.xxxx.xxx
|
|
||||||
Delivered-To: xxx@xxxx.xxx
|
|
||||||
Importance: normal
|
|
||||||
Content-Type: text/plain; charset=X-UNKNOWN
|
|
||||||
|
|
||||||
Test test. Hi. Waving. m
|
|
||||||
|
|
||||||
----------------------------------------------------------------
|
|
||||||
Sent via Bell Mobility's Text Messaging service.
|
|
||||||
Envoyé par le service de messagerie texte de Bell Mobilité.
|
|
||||||
----------------------------------------------------------------
|
|
||||||
|
|
@ -1,34 +0,0 @@
|
||||||
From xxx@xxxx.com Wed Apr 27 14:15:31 2005
|
|
||||||
Mime-Version: 1.0 (Apple Message framework v619.2)
|
|
||||||
To: xxxxx@xxxxx <matmail>
|
|
||||||
Message-Id: <416eaebec6d333ec6939eaf8a7d80724@xxxxx>
|
|
||||||
Content-Type: multipart/alternative;
|
|
||||||
boundary=Apple-Mail-5-1037861608
|
|
||||||
From: xxxxx@xxxxx <xxxxx@xxxxx>
|
|
||||||
Subject: worse when you use them.
|
|
||||||
Date: Wed, 27 Apr 2005 14:15:31 -0700
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
--Apple-Mail-5-1037861608
|
|
||||||
Content-Transfer-Encoding: 7bit
|
|
||||||
Content-Type: text/plain;
|
|
||||||
charset=US-ASCII;
|
|
||||||
format=flowed
|
|
||||||
|
|
||||||
|
|
||||||
XXXXX Xxxxx
|
|
||||||
|
|
||||||
--Apple-Mail-5-1037861608
|
|
||||||
Content-Transfer-Encoding: 7bit
|
|
||||||
Content-Type: text/enriched;
|
|
||||||
charset=US-ASCII
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<bold>XXXXX Xxxxx</bold>
|
|
||||||
|
|
||||||
|
|
||||||
--Apple-Mail-5-1037861608--
|
|
||||||
|
|
||||||
|
|
@ -1,32 +0,0 @@
|
||||||
Mime-Version: 1.0 (Apple Message framework v730)
|
|
||||||
Content-Type: multipart/mixed; boundary=Apple-Mail-13-196941151
|
|
||||||
Message-Id: <9169D984-4E0B-45EF-82D4-8F5E53AD7012@example.com>
|
|
||||||
From: foo@example.com
|
|
||||||
Subject: testing
|
|
||||||
Date: Mon, 6 Jun 2005 22:21:22 +0200
|
|
||||||
To: blah@example.com
|
|
||||||
|
|
||||||
|
|
||||||
--Apple-Mail-13-196941151
|
|
||||||
Content-Transfer-Encoding: quoted-printable
|
|
||||||
Content-Type: text/plain;
|
|
||||||
charset=ISO-8859-1;
|
|
||||||
delsp=yes;
|
|
||||||
format=flowed
|
|
||||||
|
|
||||||
This is the first part.
|
|
||||||
|
|
||||||
--Apple-Mail-13-196941151
|
|
||||||
Content-Type: image/jpeg
|
|
||||||
Content-Transfer-Encoding: base64
|
|
||||||
Content-Location: Photo25.jpg
|
|
||||||
Content-ID: <qbFGyPQAS8>
|
|
||||||
Content-Disposition: inline
|
|
||||||
|
|
||||||
jamisSqGSIb3DQEHAqCAMIjamisxCzAJBgUrDgMCGgUAMIAGCSqGSjamisEHAQAAoIIFSjCCBUYw
|
|
||||||
ggQujamisQICBD++ukQwDQYJKojamisNAQEFBQAwMTELMAkGA1UEBhMCRjamisAKBgNVBAoTA1RE
|
|
||||||
QzEUMBIGjamisxMLVERDIE9DRVMgQ0jamisNMDQwMjI5MTE1OTAxWhcNMDYwMjamisIyOTAxWjCB
|
|
||||||
gDELMAkGA1UEjamisEsxKTAnBgNVBAoTIEjamisuIG9yZ2FuaXNhdG9yaXNrIHRpbjamisRuaW5=
|
|
||||||
|
|
||||||
--Apple-Mail-13-196941151--
|
|
||||||
|
|
||||||
|
|
@ -1,114 +0,0 @@
|
||||||
From xxxxxxxxx.xxxxxxx@gmail.com Sun May 8 19:07:09 2005
|
|
||||||
Return-Path: <xxxxxxxxx.xxxxxxx@gmail.com>
|
|
||||||
X-Original-To: xxxxx@xxxxx.xxxxxxxxx.com
|
|
||||||
Delivered-To: xxxxx@xxxxx.xxxxxxxxx.com
|
|
||||||
Received: from localhost (localhost [127.0.0.1])
|
|
||||||
by xxxxx.xxxxxxxxx.com (Postfix) with ESMTP id 06C9DA98D
|
|
||||||
for <xxxxx@xxxxx.xxxxxxxxx.com>; Sun, 8 May 2005 19:09:13 +0000 (GMT)
|
|
||||||
Received: from xxxxx.xxxxxxxxx.com ([127.0.0.1])
|
|
||||||
by localhost (xxxxx.xxxxxxxxx.com [127.0.0.1]) (amavisd-new, port 10024)
|
|
||||||
with LMTP id 88783-08 for <xxxxx@xxxxx.xxxxxxxxx.com>;
|
|
||||||
Sun, 8 May 2005 19:09:12 +0000 (GMT)
|
|
||||||
Received: from xxxxxxx.xxxxxxxxx.com (xxxxxxx.xxxxxxxxx.com [69.36.39.150])
|
|
||||||
by xxxxx.xxxxxxxxx.com (Postfix) with ESMTP id 10D8BA960
|
|
||||||
for <xxxxx@xxxxxxxxx.org>; Sun, 8 May 2005 19:09:12 +0000 (GMT)
|
|
||||||
Received: from zproxy.gmail.com (zproxy.gmail.com [64.233.162.199])
|
|
||||||
by xxxxxxx.xxxxxxxxx.com (Postfix) with ESMTP id 9EBC4148EAB
|
|
||||||
for <xxxxx@xxxxxxxxx.com>; Sun, 8 May 2005 14:09:11 -0500 (CDT)
|
|
||||||
Received: by zproxy.gmail.com with SMTP id 13so1233405nzp
|
|
||||||
for <xxxxx@xxxxxxxxx.com>; Sun, 08 May 2005 12:09:11 -0700 (PDT)
|
|
||||||
DomainKey-Signature: a=rsa-sha1; q=dns; c=nofws;
|
|
||||||
s=beta; d=gmail.com;
|
|
||||||
h=received:message-id:date:from:reply-to:to:subject:in-reply-to:mime-version:content-type:references;
|
|
||||||
b=cid1mzGEFa3gtRa06oSrrEYfKca2CTKu9sLMkWxjbvCsWMtp9RGEILjUz0L5RySdH5iO661LyNUoHRFQIa57bylAbXM3g2DTEIIKmuASDG3x3rIQ4sHAKpNxP7Pul+mgTaOKBv+spcH7af++QEJ36gHFXD2O/kx9RePs3JNf/K8=
|
|
||||||
Received: by 10.36.10.16 with SMTP id 16mr1012493nzj;
|
|
||||||
Sun, 08 May 2005 12:09:11 -0700 (PDT)
|
|
||||||
Received: by 10.36.5.10 with HTTP; Sun, 8 May 2005 12:09:11 -0700 (PDT)
|
|
||||||
Message-ID: <e85734b90505081209eaaa17b@mail.gmail.com>
|
|
||||||
Date: Sun, 8 May 2005 14:09:11 -0500
|
|
||||||
From: xxxxxxxxx xxxxxxx <xxxxxxxxx.xxxxxxx@gmail.com>
|
|
||||||
Reply-To: xxxxxxxxx xxxxxxx <xxxxxxxxx.xxxxxxx@gmail.com>
|
|
||||||
To: xxxxx xxxx <xxxxx@xxxxxxxxx.com>
|
|
||||||
Subject: Fwd: Signed email causes file attachments
|
|
||||||
In-Reply-To: <F6E2D0B4-CC35-4A91-BA4C-C7C712B10C13@mac.com>
|
|
||||||
Mime-Version: 1.0
|
|
||||||
Content-Type: multipart/mixed;
|
|
||||||
boundary="----=_Part_5028_7368284.1115579351471"
|
|
||||||
References: <F6E2D0B4-CC35-4A91-BA4C-C7C712B10C13@mac.com>
|
|
||||||
|
|
||||||
------=_Part_5028_7368284.1115579351471
|
|
||||||
Content-Type: text/plain; charset=ISO-8859-1
|
|
||||||
Content-Transfer-Encoding: quoted-printable
|
|
||||||
Content-Disposition: inline
|
|
||||||
|
|
||||||
We should not include these files or vcards as attachments.
|
|
||||||
|
|
||||||
---------- Forwarded message ----------
|
|
||||||
From: xxxxx xxxxxx <xxxxxxxx@xxx.com>
|
|
||||||
Date: May 8, 2005 1:17 PM
|
|
||||||
Subject: Signed email causes file attachments
|
|
||||||
To: xxxxxxx@xxxxxxxxxx.com
|
|
||||||
|
|
||||||
|
|
||||||
Hi,
|
|
||||||
|
|
||||||
Just started to use my xxxxxxxx account (to set-up a GTD system,
|
|
||||||
natch) and noticed that when I send content via email the signature/
|
|
||||||
certificate from my email account gets added as a file (e.g.
|
|
||||||
"smime.p7s").
|
|
||||||
|
|
||||||
Obviously I can uncheck the signature option in the Mail compose
|
|
||||||
window but how often will I remember to do that?
|
|
||||||
|
|
||||||
Is there any way these kind of files could be ignored, e.g. via some
|
|
||||||
sort of exclusions list?
|
|
||||||
|
|
||||||
------=_Part_5028_7368284.1115579351471
|
|
||||||
Content-Type: application/pkcs7-signature; name=smime.p7s
|
|
||||||
Content-Transfer-Encoding: base64
|
|
||||||
Content-Disposition: attachment; filename="smime.p7s"
|
|
||||||
|
|
||||||
MIAGCSqGSIb3DQEHAqCAMIACAQExCzAJBgUrDgMCGgUAMIAGCSqGSIb3DQEHAQAAoIIGFDCCAs0w
|
|
||||||
ggI2oAMCAQICAw5c+TANBgkqhkiG9w0BAQQFADBiMQswCQYDVQQGEwJaQTElMCMGA1UEChMcVGhh
|
|
||||||
d3RlIENvbnN1bHRpbmcgKFB0eSkgTHRkLjEsMCoGA1UEAxMjVGhhd3RlIFBlcnNvbmFsIEZyZWVt
|
|
||||||
YWlsIElzc3VpbmcgQ0EwHhcNMDUwMzI5MDkzOTEwWhcNMDYwMzI5MDkzOTEwWjBCMR8wHQYDVQQD
|
|
||||||
ExZUaGF3dGUgRnJlZW1haWwgTWVtYmVyMR8wHQYJKoZIhvcNAQkBFhBzbWhhdW5jaEBtYWMuY29t
|
|
||||||
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAn90dPsYS3LjfMY211OSYrDQLzwNYPlAL
|
|
||||||
7+/0XA+kdy8/rRnyEHFGwhNCDmg0B6pxC7z3xxJD/8GfCd+IYUUNUQV5m9MkxfP9pTVXZVIYLaBw
|
|
||||||
o8xS3A0a1LXealcmlEbJibmKkEaoXci3MhryLgpaa+Kk/sH02SNatDO1vS28bPsibZpcc6deFrla
|
|
||||||
hSYnL+PW54mDTGHIcCN2fbx/Y6qspzqmtKaXrv75NBtuy9cB6KzU4j2xXbTkAwz3pRSghJJaAwdp
|
|
||||||
+yIivAD3vr0kJE3p+Ez34HMh33EXEpFoWcN+MCEQZD9WnmFViMrvfvMXLGVFQfAAcC060eGFSRJ1
|
|
||||||
ZQ9UVQIDAQABoy0wKzAbBgNVHREEFDASgRBzbWhhdW5jaEBtYWMuY29tMAwGA1UdEwEB/wQCMAAw
|
|
||||||
DQYJKoZIhvcNAQEEBQADgYEAQMrg1n2pXVWteP7BBj+Pk3UfYtbuHb42uHcLJjfjnRlH7AxnSwrd
|
|
||||||
L3HED205w3Cq8T7tzVxIjRRLO/ljq0GedSCFBky7eYo1PrXhztGHCTSBhsiWdiyLWxKlOxGAwJc/
|
|
||||||
lMMnwqLOdrQcoF/YgbjeaUFOQbUh94w9VDNpWZYCZwcwggM/MIICqKADAgECAgENMA0GCSqGSIb3
|
|
||||||
DQEBBQUAMIHRMQswCQYDVQQGEwJaQTEVMBMGA1UECBMMV2VzdGVybiBDYXBlMRIwEAYDVQQHEwlD
|
|
||||||
YXBlIFRvd24xGjAYBgNVBAoTEVRoYXd0ZSBDb25zdWx0aW5nMSgwJgYDVQQLEx9DZXJ0aWZpY2F0
|
|
||||||
aW9uIFNlcnZpY2VzIERpdmlzaW9uMSQwIgYDVQQDExtUaGF3dGUgUGVyc29uYWwgRnJlZW1haWwg
|
|
||||||
Q0ExKzApBgkqhkiG9w0BCQEWHHBlcnNvbmFsLWZyZWVtYWlsQHRoYXd0ZS5jb20wHhcNMDMwNzE3
|
|
||||||
MDAwMDAwWhcNMTMwNzE2MjM1OTU5WjBiMQswCQYDVQQGEwJaQTElMCMGA1UEChMcVGhhd3RlIENv
|
|
||||||
bnN1bHRpbmcgKFB0eSkgTHRkLjEsMCoGA1UEAxMjVGhhd3RlIFBlcnNvbmFsIEZyZWVtYWlsIElz
|
|
||||||
c3VpbmcgQ0EwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAMSmPFVzVftOucqZWh5owHUEcJ3f
|
|
||||||
6f+jHuy9zfVb8hp2vX8MOmHyv1HOAdTlUAow1wJjWiyJFXCO3cnwK4Vaqj9xVsuvPAsH5/EfkTYk
|
|
||||||
KhPPK9Xzgnc9A74r/rsYPge/QIACZNenprufZdHFKlSFD0gEf6e20TxhBEAeZBlyYLf7AgMBAAGj
|
|
||||||
gZQwgZEwEgYDVR0TAQH/BAgwBgEB/wIBADBDBgNVHR8EPDA6MDigNqA0hjJodHRwOi8vY3JsLnRo
|
|
||||||
YXd0ZS5jb20vVGhhd3RlUGVyc29uYWxGcmVlbWFpbENBLmNybDALBgNVHQ8EBAMCAQYwKQYDVR0R
|
|
||||||
BCIwIKQeMBwxGjAYBgNVBAMTEVByaXZhdGVMYWJlbDItMTM4MA0GCSqGSIb3DQEBBQUAA4GBAEiM
|
|
||||||
0VCD6gsuzA2jZqxnD3+vrL7CF6FDlpSdf0whuPg2H6otnzYvwPQcUCCTcDz9reFhYsPZOhl+hLGZ
|
|
||||||
GwDFGguCdJ4lUJRix9sncVcljd2pnDmOjCBPZV+V2vf3h9bGCE6u9uo05RAaWzVNd+NWIXiC3CEZ
|
|
||||||
Nd4ksdMdRv9dX2VPMYIC5zCCAuMCAQEwaTBiMQswCQYDVQQGEwJaQTElMCMGA1UEChMcVGhhd3Rl
|
|
||||||
IENvbnN1bHRpbmcgKFB0eSkgTHRkLjEsMCoGA1UEAxMjVGhhd3RlIFBlcnNvbmFsIEZyZWVtYWls
|
|
||||||
IElzc3VpbmcgQ0ECAw5c+TAJBgUrDgMCGgUAoIIBUzAYBgkqhkiG9w0BCQMxCwYJKoZIhvcNAQcB
|
|
||||||
MBwGCSqGSIb3DQEJBTEPFw0wNTA1MDgxODE3NDZaMCMGCSqGSIb3DQEJBDEWBBQSkG9j6+hB0pKp
|
|
||||||
fV9tCi/iP59sNTB4BgkrBgEEAYI3EAQxazBpMGIxCzAJBgNVBAYTAlpBMSUwIwYDVQQKExxUaGF3
|
|
||||||
dGUgQ29uc3VsdGluZyAoUHR5KSBMdGQuMSwwKgYDVQQDEyNUaGF3dGUgUGVyc29uYWwgRnJlZW1h
|
|
||||||
aWwgSXNzdWluZyBDQQIDDlz5MHoGCyqGSIb3DQEJEAILMWugaTBiMQswCQYDVQQGEwJaQTElMCMG
|
|
||||||
A1UEChMcVGhhd3RlIENvbnN1bHRpbmcgKFB0eSkgTHRkLjEsMCoGA1UEAxMjVGhhd3RlIFBlcnNv
|
|
||||||
bmFsIEZyZWVtYWlsIElzc3VpbmcgQ0ECAw5c+TANBgkqhkiG9w0BAQEFAASCAQAm1GeF7dWfMvrW
|
|
||||||
8yMPjkhE+R8D1DsiCoWSCp+5gAQm7lcK7V3KrZh5howfpI3TmCZUbbaMxOH+7aKRKpFemxoBY5Q8
|
|
||||||
rnCkbpg/++/+MI01T69hF/rgMmrGcrv2fIYy8EaARLG0xUVFSZHSP+NQSYz0TTmh4cAESHMzY3JA
|
|
||||||
nHOoUkuPyl8RXrimY1zn0lceMXlweZRouiPGuPNl1hQKw8P+GhOC5oLlM71UtStnrlk3P9gqX5v7
|
|
||||||
Tj7Hx057oVfY8FMevjxGwU3EK5TczHezHbWWgTyum9l2ZQbUQsDJxSniD3BM46C1VcbDLPaotAZ0
|
|
||||||
fTYLZizQfm5hcWEbfYVzkSzLAAAAAAAA
|
|
||||||
------=_Part_5028_7368284.1115579351471--
|
|
||||||
|
|
||||||
|
|
@ -1,70 +0,0 @@
|
||||||
From xxxx@xxxx.com Tue May 10 11:28:07 2005
|
|
||||||
Return-Path: <xxxx@xxxx.com>
|
|
||||||
X-Original-To: xxxx@xxxx.com
|
|
||||||
Delivered-To: xxxx@xxxx.com
|
|
||||||
Received: from localhost (localhost [127.0.0.1])
|
|
||||||
by xxx.xxxxx.com (Postfix) with ESMTP id 50FD3A96F
|
|
||||||
for <xxxx@xxxx.com>; Tue, 10 May 2005 17:26:50 +0000 (GMT)
|
|
||||||
Received: from xxx.xxxxx.com ([127.0.0.1])
|
|
||||||
by localhost (xxx.xxxxx.com [127.0.0.1]) (amavisd-new, port 10024)
|
|
||||||
with LMTP id 70060-03 for <xxxx@xxxx.com>;
|
|
||||||
Tue, 10 May 2005 17:26:49 +0000 (GMT)
|
|
||||||
Received: from xxx.xxxxx.com (xxx.xxxxx.com [69.36.39.150])
|
|
||||||
by xxx.xxxxx.com (Postfix) with ESMTP id 8B957A94B
|
|
||||||
for <xxxx@xxxx.com>; Tue, 10 May 2005 17:26:48 +0000 (GMT)
|
|
||||||
Received: from xxx.xxxxx.com (xxx.xxxxx.com [64.233.184.203])
|
|
||||||
by xxx.xxxxx.com (Postfix) with ESMTP id 9972514824C
|
|
||||||
for <xxxx@xxxx.com>; Tue, 10 May 2005 12:26:40 -0500 (CDT)
|
|
||||||
Received: by xxx.xxxxx.com with SMTP id 68so1694448wri
|
|
||||||
for <xxxx@xxxx.com>; Tue, 10 May 2005 10:26:40 -0700 (PDT)
|
|
||||||
DomainKey-Signature: a=rsa-sha1; q=dns; c=nofws;
|
|
||||||
s=beta; d=xxxxx.com;
|
|
||||||
h=received:message-id:date:from:reply-to:to:subject:mime-version:content-type;
|
|
||||||
b=g8ZO5ttS6GPEMAz9WxrRk9+9IXBUfQIYsZLL6T88+ECbsXqGIgfGtzJJFn6o9CE3/HMrrIGkN5AisxVFTGXWxWci5YA/7PTVWwPOhJff5BRYQDVNgRKqMl/SMttNrrRElsGJjnD1UyQ/5kQmcBxq2PuZI5Zc47u6CILcuoBcM+A=
|
|
||||||
Received: by 10.54.96.19 with SMTP id t19mr621017wrb;
|
|
||||||
Tue, 10 May 2005 10:26:39 -0700 (PDT)
|
|
||||||
Received: by 10.54.110.5 with HTTP; Tue, 10 May 2005 10:26:39 -0700 (PDT)
|
|
||||||
Message-ID: <xxxx@xxxx.com>
|
|
||||||
Date: Tue, 10 May 2005 11:26:39 -0600
|
|
||||||
From: Test Tester <xxxx@xxxx.com>
|
|
||||||
Reply-To: Test Tester <xxxx@xxxx.com>
|
|
||||||
To: xxxx@xxxx.com, xxxx@xxxx.com
|
|
||||||
Subject: Another PDF
|
|
||||||
Mime-Version: 1.0
|
|
||||||
Content-Type: multipart/mixed;
|
|
||||||
boundary="----=_Part_2192_32400445.1115745999735"
|
|
||||||
X-Virus-Scanned: amavisd-new at textdrive.com
|
|
||||||
|
|
||||||
------=_Part_2192_32400445.1115745999735
|
|
||||||
Content-Type: text/plain; charset=ISO-8859-1
|
|
||||||
Content-Transfer-Encoding: quoted-printable
|
|
||||||
Content-Disposition: inline
|
|
||||||
|
|
||||||
Just attaching another PDF, here, to see what the message looks like,
|
|
||||||
and to see if I can figure out what is going wrong here.
|
|
||||||
|
|
||||||
------=_Part_2192_32400445.1115745999735
|
|
||||||
Content-Type: application/pdf; name="broken.pdf"
|
|
||||||
Content-Transfer-Encoding: base64
|
|
||||||
Content-Disposition: attachment; filename="broken.pdf"
|
|
||||||
|
|
||||||
JVBERi0xLjQNCiXk9tzfDQoxIDAgb2JqDQo8PCAvTGVuZ3RoIDIgMCBSDQogICAvRmlsdGVyIC9G
|
|
||||||
bGF0ZURlY29kZQ0KPj4NCnN0cmVhbQ0KeJy9Wt2KJbkNvm/od6jrhZxYln9hWEh2p+8HBvICySaE
|
|
||||||
ycLuTV4/1ifJ9qnq09NpSBimu76yLUuy/qzqcPz7+em3Ixx/CDc6CsXxs3b5+fvfjr/8cPz6/BRu
|
|
||||||
rbfAx/n3739/fuJylJ5u5fjX81OuDr4deK4Bz3z/aDP+8fz0yw8g0Ofq7ktr1Mn+u28rvhy/jVeD
|
|
||||||
QSa+9YNKHP/pxjvDNfVAx/m3MFz54FhvTbaseaxiDoN2LeMVMw+yA7RbHSCDzxZuaYB2E1Yay7QU
|
|
||||||
x89vz0+tyFDKMlAHK5yqLmnjF+c4RjEiQIUeKwblXMe+AsZjN1J5yGQL5DHpDHksurM81rF6PKab
|
|
||||||
gK6zAarIDzIiUY23rJsN9iorAE816aIu6lsgAdQFsuhhkHOUFgVjp2GjMqSewITXNQ27jrMeamkg
|
|
||||||
1rPI3iLWG2CIaSBB+V1245YVRICGbbpYKHc2USFDl6M09acQVQYhlwIrkBNLISvXhGlF1wi5FHCw
|
|
||||||
wxZkoGNJlVeJCEsqKA+3YAV5AMb6KkeaqEJQmFKKQU8T1pRi2ihE1Y4CDrqoYFFXYjJJOatsyzuI
|
|
||||||
8SIlykuxKTMibWK8H1PgEvqYgs4GmQSrEjJAalgGirIhik+p4ZQN9E3ETFPAHE1b8pp1l/0Rc1gl
|
|
||||||
fQs0ABWvyoZZzU8VnPXwVVcO9BEsyjEJaO6eBoZRyKGlrKoYoOygA8BGIzgwN3RQ15ouigG5idZQ
|
|
||||||
fx2U4Db2CqiLO0WHAZoylGiCAqhniNQjFjQPSkmjwfNTgQ6M1Ih+eWo36wFmjIxDJZiGUBiWsAyR
|
|
||||||
xX3EekGOizkGI96Ol9zVZTAivikURhRsHh2E3JhWMpSTZCnnonrLhMCodgrNcgo4uyJUJc6qnVss
|
|
||||||
nrGd1Ptr0YwisCOYyIbUwVjV4xBUNLbguSO2YHujonAMJkMdSI7bIw91Akq2AUlMUWGFTMAOamjU
|
|
||||||
OvZQCxIkY2pCpMFo/IwLdVLHs6nddwTRrgoVbvLU9eB0G4EMndV0TNoxHbt3JBWwK6hhv3iHfDtF
|
|
||||||
yokB302IpEBTnWICde4uYc/1khDbSIkQopO6lcqamGBu1OSE3N5IPSsZX00CkSHRiiyx6HQIShsS
|
|
||||||
HSVNswdVsaOUSAWq9aYhDtGDaoG5a3lBGkYt/lFlBFt1UqrYnzVtUpUQnLiZeouKgf1KhRBViRRk
|
|
||||||
ExepJCzTwEmFDalIRbLEGtw0gfpESOpIAF/NnpPzcVCG86s0g2DuSyd41uhNGbEgaSrWEXORErbw
|
|
||||||
------=_Part_2192_32400445.1115745999735--
|
|
||||||
|
|
||||||
|
|
@ -1,59 +0,0 @@
|
||||||
Return-Path: <xxx@xxxx.xxx>
|
|
||||||
Received: from xxx.xxxx.xxx by xxx.xxxx.xxx with ESMTP id 6AAEE3B4D23 for <xxx@xxxx.xxx>; Sun, 8 May 2005 12:30:23 -0500
|
|
||||||
Received: from xxx.xxxx.xxx by xxx.xxxx.xxx with ESMTP id j48HUC213279 for <xxx@xxxx.xxx>; Sun, 8 May 2005 12:30:13 -0500
|
|
||||||
Received: from conversion-xxx.xxxx.xxx.net by xxx.xxxx.xxx id <0IG600901LQ64I@xxx.xxxx.xxx> for <xxx@xxxx.xxx>; Sun, 8 May 2005 12:30:12 -0500
|
|
||||||
Received: from agw1 by xxx.xxxx.xxx with ESMTP id <0IG600JFYLYCAxxx@xxxx.xxx> for <xxx@xxxx.xxx>; Sun, 8 May 2005 12:30:12 -0500
|
|
||||||
Date: Sun, 8 May 2005 12:30:08 -0500
|
|
||||||
From: xxx@xxxx.xxx
|
|
||||||
To: xxx@xxxx.xxx
|
|
||||||
Message-Id: <7864245.1115573412626.JavaMxxx@xxxx.xxx>
|
|
||||||
Subject: Filth
|
|
||||||
Mime-Version: 1.0
|
|
||||||
Content-Type: multipart/mixed; boundary=mimepart_427e4cb4ca329_133ae40413c81ef
|
|
||||||
X-Mms-Priority: 1
|
|
||||||
X-Mms-Transaction-Id: 3198421808-0
|
|
||||||
X-Mms-Message-Type: 0
|
|
||||||
X-Mms-Sender-Visibility: 1
|
|
||||||
X-Mms-Read-Reply: 1
|
|
||||||
X-Original-To: xxx@xxxx.xxx
|
|
||||||
X-Mms-Message-Class: 0
|
|
||||||
X-Mms-Delivery-Report: 0
|
|
||||||
X-Mms-Mms-Version: 16
|
|
||||||
Delivered-To: xxx@xxxx.xxx
|
|
||||||
X-Nokia-Ag-Version: 2.0
|
|
||||||
|
|
||||||
This is a multi-part message in MIME format.
|
|
||||||
|
|
||||||
--mimepart_427e4cb4ca329_133ae40413c81ef
|
|
||||||
Content-Type: multipart/mixed; boundary=mimepart_427e4cb4cbd97_133ae40413c8217
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
--mimepart_427e4cb4cbd97_133ae40413c8217
|
|
||||||
Content-Type: text/plain; charset=utf-8
|
|
||||||
Content-Transfer-Encoding: 7bit
|
|
||||||
Content-Disposition: inline
|
|
||||||
Content-Location: text.txt
|
|
||||||
|
|
||||||
Some text
|
|
||||||
|
|
||||||
--mimepart_427e4cb4cbd97_133ae40413c8217--
|
|
||||||
|
|
||||||
--mimepart_427e4cb4ca329_133ae40413c81ef
|
|
||||||
Content-Type: text/plain; charset=us-ascii
|
|
||||||
Content-Transfer-Encoding: 7bit
|
|
||||||
|
|
||||||
|
|
||||||
--
|
|
||||||
This Orange Multi Media Message was sent wirefree from an Orange
|
|
||||||
MMS phone. If you would like to reply, please text or phone the
|
|
||||||
sender directly by using the phone number listed in the sender's
|
|
||||||
address. To learn more about Orange's Multi Media Messaging
|
|
||||||
Service, find us on the Web at xxx.xxxx.xxx.uk/mms
|
|
||||||
|
|
||||||
|
|
||||||
--mimepart_427e4cb4ca329_133ae40413c81ef
|
|
||||||
|
|
||||||
|
|
||||||
--mimepart_427e4cb4ca329_133ae40413c81ef-
|
|
||||||
|
|
||||||
|
|
@ -1,19 +0,0 @@
|
||||||
Return-Path: <xxx@xxxx.xxx>
|
|
||||||
Received: from xxx.xxxx.xxx by xxx.xxxx.xxx with ESMTP id C1B953B4CB6 for <xxxxx@Exxx.xxxx.xxx>; Tue, 10 May 2005 15:27:05 -0500
|
|
||||||
Received: from SMS-GTYxxx.xxxx.xxx by xxx.xxxx.xxx with ESMTP id ca for <xxxxx@Exxx.xxxx.xxx>; Tue, 10 May 2005 15:27:04 -0500
|
|
||||||
Received: from xxx.xxxx.xxx by SMS-GTYxxx.xxxx.xxx with ESMTP id j4AKR3r23323 for <xxxxx@Exxx.xxxx.xxx>; Tue, 10 May 2005 15:27:03 -0500
|
|
||||||
Date: Tue, 10 May 2005 15:27:03 -0500
|
|
||||||
From: xxx@xxxx.xxx
|
|
||||||
Sender: xxx@xxxx.xxx
|
|
||||||
To: xxxxxxxxxxx@xxxx.xxxx.xxx
|
|
||||||
Message-Id: <xxx@xxxx.xxx>
|
|
||||||
X-Original-To: xxxxxxxxxxx@xxxx.xxxx.xxx
|
|
||||||
Delivered-To: xxx@xxxx.xxx
|
|
||||||
Importance: normal
|
|
||||||
|
|
||||||
Test test. Hi. Waving. m
|
|
||||||
|
|
||||||
----------------------------------------------------------------
|
|
||||||
Sent via Bell Mobility's Text Messaging service.
|
|
||||||
Envoyé par le service de messagerie texte de Bell Mobilité.
|
|
||||||
----------------------------------------------------------------
|
|
||||||
|
|
@ -1,20 +0,0 @@
|
||||||
Return-Path: <xxx@xxxx.xxx>
|
|
||||||
Received: from xxx.xxxx.xxx by xxx.xxxx.xxx with ESMTP id C1B953B4CB6 for <xxxxx@Exxx.xxxx.xxx>; Tue, 10 May 2005 15:27:05 -0500
|
|
||||||
Received: from SMS-GTYxxx.xxxx.xxx by xxx.xxxx.xxx with ESMTP id ca for <xxxxx@Exxx.xxxx.xxx>; Tue, 10 May 2005 15:27:04 -0500
|
|
||||||
Received: from xxx.xxxx.xxx by SMS-GTYxxx.xxxx.xxx with ESMTP id j4AKR3r23323 for <xxxxx@Exxx.xxxx.xxx>; Tue, 10 May 2005 15:27:03 -0500
|
|
||||||
Date: Tue, 10 May 2005 15:27:03 -0500
|
|
||||||
From: xxx@xxxx.xxx
|
|
||||||
Sender: xxx@xxxx.xxx
|
|
||||||
To: xxxxxxxxxxx@xxxx.xxxx.xxx
|
|
||||||
Message-Id: <xxx@xxxx.xxx>
|
|
||||||
X-Original-To: xxxxxxxxxxx@xxxx.xxxx.xxx
|
|
||||||
Delivered-To: xxx@xxxx.xxx
|
|
||||||
Importance: normal
|
|
||||||
Content-Type: text/plain; charset=us-ascii
|
|
||||||
|
|
||||||
Test test. Hi. Waving. m
|
|
||||||
|
|
||||||
----------------------------------------------------------------
|
|
||||||
Sent via Bell Mobility's Text Messaging service.
|
|
||||||
Envoyé par le service de messagerie texte de Bell Mobilité.
|
|
||||||
----------------------------------------------------------------
|
|
||||||
|
|
@ -1,56 +0,0 @@
|
||||||
Mime-Version: 1.0 (Apple Message framework v730)
|
|
||||||
Content-Type: multipart/mixed; boundary=Apple-Mail-13-196941151
|
|
||||||
Message-Id: <9169D984-4E0B-45EF-82D4-8F5E53AD7012@example.com>
|
|
||||||
From: foo@example.com
|
|
||||||
Subject: testing
|
|
||||||
Date: Mon, 6 Jun 2005 22:21:22 +0200
|
|
||||||
To: blah@example.com
|
|
||||||
|
|
||||||
|
|
||||||
--Apple-Mail-13-196941151
|
|
||||||
Content-Type: multipart/mixed;
|
|
||||||
boundary=Apple-Mail-12-196940926
|
|
||||||
|
|
||||||
|
|
||||||
--Apple-Mail-12-196940926
|
|
||||||
Content-Transfer-Encoding: quoted-printable
|
|
||||||
Content-Type: text/plain;
|
|
||||||
charset=ISO-8859-1;
|
|
||||||
delsp=yes;
|
|
||||||
format=flowed
|
|
||||||
|
|
||||||
This is the first part.
|
|
||||||
|
|
||||||
--Apple-Mail-12-196940926
|
|
||||||
Content-Transfer-Encoding: base64
|
|
||||||
Content-Type: application/pdf;
|
|
||||||
x-unix-mode=0666;
|
|
||||||
name="test.pdf"
|
|
||||||
Content-Disposition: inline;
|
|
||||||
filename=test.pdf
|
|
||||||
|
|
||||||
YmxhaCBibGFoIGJsYWg=
|
|
||||||
|
|
||||||
--Apple-Mail-12-196940926
|
|
||||||
Content-Transfer-Encoding: 7bit
|
|
||||||
Content-Type: text/plain;
|
|
||||||
charset=US-ASCII;
|
|
||||||
format=flowed
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
--Apple-Mail-12-196940926--
|
|
||||||
|
|
||||||
--Apple-Mail-13-196941151
|
|
||||||
Content-Transfer-Encoding: base64
|
|
||||||
Content-Type: application/pkcs7-signature;
|
|
||||||
name=smime.p7s
|
|
||||||
Content-Disposition: attachment;
|
|
||||||
filename=smime.p7s
|
|
||||||
|
|
||||||
jamisSqGSIb3DQEHAqCAMIjamisxCzAJBgUrDgMCGgUAMIAGCSqGSjamisEHAQAAoIIFSjCCBUYw
|
|
||||||
ggQujamisQICBD++ukQwDQYJKojamisNAQEFBQAwMTELMAkGA1UEBhMCRjamisAKBgNVBAoTA1RE
|
|
||||||
QzEUMBIGjamisxMLVERDIE9DRVMgQ0jamisNMDQwMjI5MTE1OTAxWhcNMDYwMjamisIyOTAxWjCB
|
|
||||||
gDELMAkGA1UEjamisEsxKTAnBgNVBAoTIEjamisuIG9yZ2FuaXNhdG9yaXNrIHRpbjamisRuaW5=
|
|
||||||
|
|
||||||
--Apple-Mail-13-196941151--
|
|
||||||
|
|
@ -1,47 +0,0 @@
|
||||||
From xxxxxxxxx.xxxxxxx@gmail.com Sun May 8 19:07:09 2005
|
|
||||||
Return-Path: <xxxxxxxxx.xxxxxxx@gmail.com>
|
|
||||||
Message-ID: <e85734b90505081209eaaa17b@mail.gmail.com>
|
|
||||||
Date: Sun, 8 May 2005 14:09:11 -0500
|
|
||||||
From: xxxxxxxxx xxxxxxx <xxxxxxxxx.xxxxxxx@gmail.com>
|
|
||||||
Reply-To: xxxxxxxxx xxxxxxx <xxxxxxxxx.xxxxxxx@gmail.com>
|
|
||||||
To: xxxxx xxxx <xxxxx@xxxxxxxxx.com>
|
|
||||||
Subject: Fwd: Signed email causes file attachments
|
|
||||||
In-Reply-To: <F6E2D0B4-CC35-4A91-BA4C-C7C712B10C13@mac.com>
|
|
||||||
Mime-Version: 1.0
|
|
||||||
Content-Type: multipart/mixed;
|
|
||||||
boundary="----=_Part_5028_7368284.1115579351471"
|
|
||||||
References: <F6E2D0B4-CC35-4A91-BA4C-C7C712B10C13@mac.com>
|
|
||||||
|
|
||||||
------=_Part_5028_7368284.1115579351471
|
|
||||||
Content-Type: text/plain; charset=ISO-8859-1
|
|
||||||
Content-Transfer-Encoding: quoted-printable
|
|
||||||
Content-Disposition: inline
|
|
||||||
|
|
||||||
We should not include these files or vcards as attachments.
|
|
||||||
|
|
||||||
---------- Forwarded message ----------
|
|
||||||
From: xxxxx xxxxxx <xxxxxxxx@xxx.com>
|
|
||||||
Date: May 8, 2005 1:17 PM
|
|
||||||
Subject: Signed email causes file attachments
|
|
||||||
To: xxxxxxx@xxxxxxxxxx.com
|
|
||||||
|
|
||||||
|
|
||||||
Hi,
|
|
||||||
|
|
||||||
Test attachments oddly encoded with japanese charset.
|
|
||||||
|
|
||||||
|
|
||||||
------=_Part_5028_7368284.1115579351471
|
|
||||||
Content-Type: application/octet-stream; name*=iso-2022-jp'ja'01%20Quien%20Te%20Dij%8aat.%20Pitbull.mp3
|
|
||||||
Content-Transfer-Encoding: base64
|
|
||||||
Content-Disposition: attachment
|
|
||||||
|
|
||||||
MIAGCSqGSIb3DQEHAqCAMIACAQExCzAJBgUrDgMCGgUAMIAGCSqGSIb3DQEHAQAAoIIGFDCCAs0w
|
|
||||||
ggI2oAMCAQICAw5c+TANBgkqhkiG9w0BAQQFADBiMQswCQYDVQQGEwJaQTElMCMGA1UEChMcVGhh
|
|
||||||
d3RlIENvbnN1bHRpbmcgKFB0eSkgTHRkLjEsMCoGA1UEAxMjVGhhd3RlIFBlcnNvbmFsIEZyZWVt
|
|
||||||
YWlsIElzc3VpbmcgQ0EwHhcNMDUwMzI5MDkzOTEwWhcNMDYwMzI5MDkzOTEwWjBCMR8wHQYDVQQD
|
|
||||||
ExZUaGF3dGUgRnJlZW1haWwgTWVtYmVyMR8wHQYJKoZIhvcNAQkBFhBzbWhhdW5jaEBtYWMuY29t
|
|
||||||
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAn90dPsYS3LjfMY211OSYrDQLzwNYPlAL
|
|
||||||
7+/0XA+kdy8/rRnyEHFGwhNCDmg0B6pxC7z3xxJD/8GfCd+IYUUNUQV5m9MkxfP9pTVXZVIYLaBw
|
|
||||||
------=_Part_5028_7368284.1115579351471--
|
|
||||||
|
|
||||||
|
|
@ -1,28 +0,0 @@
|
||||||
Received: from xxx.xxx.xxx ([xxx.xxx.xxx.xxx] verified)
|
|
||||||
by xxx.com (CommuniGate Pro SMTP 4.2.8)
|
|
||||||
with SMTP id 2532598 for xxx@xxx.com; Wed, 23 Feb 2005 17:51:49 -0500
|
|
||||||
Received-SPF: softfail
|
|
||||||
receiver=xxx.com; client-ip=xxx.xxx.xxx.xxx; envelope-from=xxx@xxx.xxx
|
|
||||||
quite Delivered-To: xxx@xxx.xxx
|
|
||||||
Received: by xxx.xxx.xxx (Wostfix, from userid xxx)
|
|
||||||
id 0F87F333; Wed, 23 Feb 2005 16:16:17 -0600
|
|
||||||
Date: Wed, 23 Feb 2005 18:20:17 -0400
|
|
||||||
From: "xxx xxx" <xxx@xxx.xxx>
|
|
||||||
Message-ID: <4D6AA7EB.6490534@xxx.xxx>
|
|
||||||
To: xxx@xxx.com
|
|
||||||
Subject: Stop adware/spyware once and for all.
|
|
||||||
X-Scanned-By: MIMEDefang 2.11 (www dot roaringpenguin dot com slash mimedefang)
|
|
||||||
|
|
||||||
You are infected with:
|
|
||||||
Ad Ware and Spy Ware
|
|
||||||
|
|
||||||
Get your free scan and removal download now,
|
|
||||||
before it gets any worse.
|
|
||||||
|
|
||||||
http://xxx.xxx.info?aid=3D13&?stat=3D4327kdzt
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
no more? (you will still be infected)
|
|
||||||
http://xxx.xxx.info/discon/?xxx@xxx.com
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
Hello there,
|
|
||||||
|
|
||||||
Mr. <%= @recipient %>
|
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue