Publish Vulpes Preview 15 sources for desktop, Android and Tundra

Share Gaia assets and adapters, keep private profiles and development documents local, and update both public READMEs.
This commit is contained in:
OverSu 2026-09-30 23:13:03 +02:00
commit b38dea0597
25901 changed files with 9982925 additions and 369 deletions

View file

@ -0,0 +1,122 @@
/* Copyright 2012 Mozilla Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
(function (root, factory) {
if (typeof exports === 'object') {
define = function(deps, factory) {
deps = deps.map.forEach(function(id) {
return require(id);
});
module.exports = factory(deps);
};
define.amd = {};
}
if (typeof define === 'function' && define.amd) {
define([
'wbxml',
'./codepages/Common',
'./codepages/AirSync',
'./codepages/Contacts',
'./codepages/Email',
'./codepages/Calendar',
'./codepages/Move',
'./codepages/ItemEstimate',
'./codepages/FolderHierarchy',
'./codepages/MeetingResponse',
'./codepages/Tasks',
'./codepages/ResolveRecipients',
'./codepages/ValidateCert',
'./codepages/Contacts2',
'./codepages/Ping',
'./codepages/Provision',
'./codepages/Search',
'./codepages/GAL',
'./codepages/AirSyncBase',
'./codepages/Settings',
'./codepages/DocumentLibrary',
'./codepages/ItemOperations',
'./codepages/ComposeMail',
'./codepages/Email2',
'./codepages/Notes',
'./codepages/RightsManagement'
], factory);
} else {
root.ActiveSyncCodepages = factory(WBXML,
ASCPCommon,
ASCPAirSync,
ASCPContacts,
ASCPEmail,
ASCPCalendar,
ASCPMove,
ASCPItemEstimate,
ASCPHierarchy,
ASCPMeetingResponse,
ASCPTasks,
ASCPResolveRecipients,
ASCPValidateCert,
ASCPContacts2,
ASCPPing,
ASCPProvision,
ASCPSearch,
ASCPGAL,
ASCPAirSyncBase,
ASCPSettings,
ASCPDocumentLibrary,
ASCPItemOperations,
ASCPComposeMail,
ASCPEmail2,
ASCPNotes,
ASCPRightsManagement);
}
}(this, function(WBXML, Common, AirSync, Contacts, Email, Calendar, Move,
ItemEstimate, FolderHierarchy, MeetingResponse, Tasks,
ResolveRecipients, ValidateCert, Contacts2, Ping, Provision,
Search, GAL, AirSyncBase, Settings, DocumentLibrary,
ItemOperations, ComposeMail, Email2, Notes, RightsManagement) {
'use strict';
var codepages = {
Common: Common,
AirSync: AirSync,
Contacts: Contacts,
Email: Email,
Calendar: Calendar,
Move: Move,
ItemEstimate: ItemEstimate,
FolderHierarchy: FolderHierarchy,
MeetingResponse: MeetingResponse,
Tasks: Tasks,
ResolveRecipients: ResolveRecipients,
ValidateCert: ValidateCert,
Contacts2: Contacts2,
Ping: Ping,
Provision: Provision,
Search: Search,
GAL: GAL,
AirSyncBase: AirSyncBase,
Settings: Settings,
DocumentLibrary: DocumentLibrary,
ItemOperations: ItemOperations,
ComposeMail: ComposeMail,
Email2: Email2,
Notes: Notes,
RightsManagement: RightsManagement
};
WBXML.CompileCodepages(codepages);
return codepages;
}));

View file

@ -0,0 +1,115 @@
/* Copyright 2012 Mozilla Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
(function (root, factory) {
if (typeof exports === 'object')
module.exports = factory();
else if (typeof define === 'function' && define.amd)
define([], factory);
else
root.ASCPAirSync = factory();
}(this, function() {
'use strict';
return {
Tags: {
Sync: 0x0005,
Responses: 0x0006,
Add: 0x0007,
Change: 0x0008,
Delete: 0x0009,
Fetch: 0x000A,
SyncKey: 0x000B,
ClientId: 0x000C,
ServerId: 0x000D,
Status: 0x000E,
Collection: 0x000F,
Class: 0x0010,
Version: 0x0011,
CollectionId: 0x0012,
GetChanges: 0x0013,
MoreAvailable: 0x0014,
WindowSize: 0x0015,
Commands: 0x0016,
Options: 0x0017,
FilterType: 0x0018,
Truncation: 0x0019,
RtfTruncation: 0x001A,
Conflict: 0x001B,
Collections: 0x001C,
ApplicationData: 0x001D,
DeletesAsMoves: 0x001E,
NotifyGUID: 0x001F,
Supported: 0x0020,
SoftDelete: 0x0021,
MIMESupport: 0x0022,
MIMETruncation: 0x0023,
Wait: 0x0024,
Limit: 0x0025,
Partial: 0x0026,
ConversationMode: 0x0027,
MaxItems: 0x0028,
HeartbeatInterval: 0x0029,
},
Enums: {
Status: {
Success: '1',
InvalidSyncKey: '3',
ProtocolError: '4',
ServerError: '5',
ConversionError: '6',
MatchingConflict: '7',
ObjectNotFound: '8',
OutOfSpace: '9',
HierarchyChanged: '12',
IncompleteRequest: '13',
InvalidInterval: '14',
InvalidRequest: '15',
Retry: '16',
},
FilterType: {
NoFilter: '0',
OneDayBack: '1',
ThreeDaysBack: '2',
OneWeekBack: '3',
TwoWeeksBack: '4',
OneMonthBack: '5',
ThreeMonthsBack: '6',
SixMonthsBack: '7',
IncompleteTasks: '8',
},
Conflict: {
ClientReplacesServer: '0',
ServerReplacesClient: '1',
},
MIMESupport: {
Never: '0',
SMIMEOnly: '1',
Always: '2',
},
MIMETruncation: {
TruncateAll: '0',
Truncate4K: '1',
Truncate5K: '2',
Truncate7K: '3',
Truncate10K: '4',
Truncate20K: '5',
Truncate50K: '6',
Truncate100K: '7',
NoTruncate: '8',
},
},
};
}));

View file

@ -0,0 +1,74 @@
/* Copyright 2012 Mozilla Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
(function (root, factory) {
if (typeof exports === 'object')
module.exports = factory();
else if (typeof define === 'function' && define.amd)
define([], factory);
else
root.ASCPAirSyncBase = factory();
}(this, function() {
'use strict';
return {
Tags: {
BodyPreference: 0x1105,
Type: 0x1106,
TruncationSize: 0x1107,
AllOrNone: 0x1108,
Reserved: 0x1109,
Body: 0x110A,
Data: 0x110B,
EstimatedDataSize: 0x110C,
Truncated: 0x110D,
Attachments: 0x110E,
Attachment: 0x110F,
DisplayName: 0x1110,
FileReference: 0x1111,
Method: 0x1112,
ContentId: 0x1113,
ContentLocation: 0x1114,
IsInline: 0x1115,
NativeBodyType: 0x1116,
ContentType: 0x1117,
Preview: 0x1118,
BodyPartPreference: 0x1119,
BodyPart: 0x111A,
Status: 0x111B,
},
Enums: {
Type: {
PlainText: '1',
HTML: '2',
RTF: '3',
MIME: '4',
},
Method: {
Normal: '1',
EmbeddedMessage: '5',
AttachOLE: '6',
},
NativeBodyType: {
PlainText: '1',
HTML: '2',
RTF: '3',
},
Status: {
Success: '1',
}
}
};
}));

View file

@ -0,0 +1,85 @@
/* Copyright 2012 Mozilla Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
(function (root, factory) {
if (typeof exports === 'object')
module.exports = factory();
else if (typeof define === 'function' && define.amd)
define([], factory);
else
root.ASCPCalendar = factory();
}(this, function() {
'use strict';
return {
Tags: {
TimeZone: 0x0405,
AllDayEvent: 0x0406,
Attendees: 0x0407,
Attendee: 0x0408,
Email: 0x0409,
Name: 0x040A,
Body: 0x040B,
BodyTruncated: 0x040C,
BusyStatus: 0x040D,
Categories: 0x040E,
Category: 0x040F,
CompressedRTF: 0x0410,
DtStamp: 0x0411,
EndTime: 0x0412,
Exception: 0x0413,
Exceptions: 0x0414,
Deleted: 0x0415,
ExceptionStartTime: 0x0416,
Location: 0x0417,
MeetingStatus: 0x0418,
OrganizerEmail: 0x0419,
OrganizerName: 0x041A,
Recurrence: 0x041B,
Type: 0x041C,
Until: 0x041D,
Occurrences: 0x041E,
Interval: 0x041F,
DayOfWeek: 0x0420,
DayOfMonth: 0x0421,
WeekOfMonth: 0x0422,
MonthOfYear: 0x0423,
Reminder: 0x0424,
Sensitivity: 0x0425,
Subject: 0x0426,
StartTime: 0x0427,
UID: 0x0428,
AttendeeStatus: 0x0429,
AttendeeType: 0x042A,
Attachment: 0x042B,
Attachments: 0x042C,
AttName: 0x042D,
AttSize: 0x042E,
AttOid: 0x042F,
AttMethod: 0x0430,
AttRemoved: 0x0431,
DisplayName: 0x0432,
DisallowNewTimeProposal: 0x0433,
ResponseRequested: 0x0434,
AppointmentReplyTime: 0x0435,
ResponseType: 0x0436,
CalendarType: 0x0437,
IsLeapMonth: 0x0438,
FirstDayOfWeek: 0x0439,
OnlineMeetingConfLink: 0x043A,
OnlineMeetingExternalLink: 0x043B,
},
};
}));

View file

@ -0,0 +1,106 @@
/* Copyright 2012 Mozilla Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
(function (root, factory) {
if (typeof exports === 'object')
module.exports = factory();
else if (typeof define === 'function' && define.amd)
define([], factory);
else
root.ASCPCommon = factory();
}(this, function() {
'use strict';
return {
Enums: {
Status: {
InvalidContent: '101',
InvalidWBXML: '102',
InvalidXML: '103',
InvalidDateTime: '104',
InvalidCombinationOfIDs: '105',
InvalidIDs: '106',
InvalidMIME: '107',
DeviceIdMissingOrInvalid: '108',
DeviceTypeMissingOrInvalid: '109',
ServerError: '110',
ServerErrorRetryLater: '111',
ActiveDirectoryAccessDenied: '112',
MailboxQuotaExceeded: '113',
MailboxServerOffline: '114',
SendQuotaExceeded: '115',
MessageRecipientUnresolved: '116',
MessageReplyNotAllowed: '117',
MessagePreviouslySent: '118',
MessageHasNoRecipient: '119',
MailSubmissionFailed: '120',
MessageReplyFailed: '121',
AttachmentIsTooLarge: '122',
UserHasNoMailbox: '123',
UserCannotBeAnonymous: '124',
UserPrincipalCouldNotBeFound: '125',
UserDisabledForSync: '126',
UserOnNewMailboxCannotSync: '127',
UserOnLegacyMailboxCannotSync: '128',
DeviceIsBlockedForThisUser: '129',
AccessDenied: '130',
AccountDisabled: '131',
SyncStateNotFound: '132',
SyncStateLocked: '133',
SyncStateCorrupt: '134',
SyncStateAlreadyExists: '135',
SyncStateVersionInvalid: '136',
CommandNotSupported: '137',
VersionNotSupported: '138',
DeviceNotFullyProvisionable: '139',
RemoteWipeRequested: '140',
LegacyDeviceOnStrictPolicy: '141',
DeviceNotProvisioned: '142',
PolicyRefresh: '143',
InvalidPolicyKey: '144',
ExternallyManagedDevicesNotAllowed: '145',
NoRecurrenceInCalendar: '146',
UnexpectedItemClass: '147',
RemoteServerHasNoSSL: '148',
InvalidStoredRequest: '149',
ItemNotFound: '150',
TooManyFolders: '151',
NoFoldersFounds: '152',
ItemsLostAfterMove: '153',
FailureInMoveOperation: '154',
MoveCommandDisallowedForNonPersistentMoveAction: '155',
MoveCommandInvalidDestinationFolder: '156',
AvailabilityTooManyRecipients: '160',
AvailabilityDLLimitReached: '161',
AvailabilityTransientFailure: '162',
AvailabilityFailure: '163',
BodyPartPreferenceTypeNotSupported: '164',
DeviceInformationRequired: '165',
InvalidAccountId: '166',
AccountSendDisabled: '167',
IRM_FeatureDisabled: '168',
IRM_TransientError: '169',
IRM_PermanentError: '170',
IRM_InvalidTemplateID: '171',
IRM_OperationNotPermitted: '172',
NoPicture: '173',
PictureTooLarge: '174',
PictureLimitReached: '175',
BodyPart_ConversationTooLarge: '176',
MaximumDevicesReached: '177',
},
},
};
}));

View file

@ -0,0 +1,45 @@
/* Copyright 2012 Mozilla Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
(function (root, factory) {
if (typeof exports === 'object')
module.exports = factory();
else if (typeof define === 'function' && define.amd)
define([], factory);
else
root.ASCPComposeMail = factory();
}(this, function() {
'use strict';
return {
Tags: {
SendMail: 0x1505,
SmartForward: 0x1506,
SmartReply: 0x1507,
SaveInSentItems: 0x1508,
ReplaceMime: 0x1509,
/* Missing tag value 0x150A */
Source: 0x150B,
FolderId: 0x150C,
ItemId: 0x150D,
LongId: 0x150E,
InstanceId: 0x150F,
Mime: 0x1510,
ClientId: 0x1511,
Status: 0x1512,
AccountId: 0x1513,
}
};
}));

View file

@ -0,0 +1,88 @@
/* Copyright 2012 Mozilla Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
(function (root, factory) {
if (typeof exports === 'object')
module.exports = factory();
else if (typeof define === 'function' && define.amd)
define([], factory);
else
root.ASCPContacts = factory();
}(this, function() {
'use strict';
return {
Tags: {
Anniversary: 0x0105,
AssistantName: 0x0106,
AssistantPhoneNumber: 0x0107,
Birthday: 0x0108,
Body: 0x0109,
BodySize: 0x010A,
BodyTruncated: 0x010B,
Business2PhoneNumber: 0x010C,
BusinessAddressCity: 0x010D,
BusinessAddressCountry: 0x010E,
BusinessAddressPostalCode: 0x010F,
BusinessAddressState: 0x0110,
BusinessAddressStreet: 0x0111,
BusinessFaxNumber: 0x0112,
BusinessPhoneNumber: 0x0113,
CarPhoneNumber: 0x0114,
Categories: 0x0115,
Category: 0x0116,
Children: 0x0117,
Child: 0x0118,
CompanyName: 0x0119,
Department: 0x011A,
Email1Address: 0x011B,
Email2Address: 0x011C,
Email3Address: 0x011D,
FileAs: 0x011E,
FirstName: 0x011F,
Home2PhoneNumber: 0x0120,
HomeAddressCity: 0x0121,
HomeAddressCountry: 0x0122,
HomeAddressPostalCode: 0x0123,
HomeAddressState: 0x0124,
HomeAddressStreet: 0x0125,
HomeFaxNumber: 0x0126,
HomePhoneNumber: 0x0127,
JobTitle: 0x0128,
LastName: 0x0129,
MiddleName: 0x012A,
MobilePhoneNumber: 0x012B,
OfficeLocation: 0x012C,
OtherAddressCity: 0x012D,
OtherAddressCountry: 0x012E,
OtherAddressPostalCode: 0x012F,
OtherAddressState: 0x0130,
OtherAddressStreet: 0x0131,
PagerNumber: 0x0132,
RadioPhoneNumber: 0x0133,
Spouse: 0x0134,
Suffix: 0x0135,
Title: 0x0136,
WebPage: 0x0137,
YomiCompanyName: 0x0138,
YomiFirstName: 0x0139,
YomiLastName: 0x013A,
CompressedRTF: 0x013B,
Picture: 0x013C,
Alias: 0x013D,
WeightedRank: 0x013E,
},
};
}));

View file

@ -0,0 +1,40 @@
/* Copyright 2012 Mozilla Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
(function (root, factory) {
if (typeof exports === 'object')
module.exports = factory();
else if (typeof define === 'function' && define.amd)
define([], factory);
else
root.ASCPContacts2 = factory();
}(this, function() {
'use strict';
return {
Tags: {
CustomerId: 0x0C05,
GovernmentId: 0x0C06,
IMAddress: 0x0C07,
IMAddress2: 0x0C08,
IMAddress3: 0x0C09,
ManagerName: 0x0C0A,
CompanyMainPhone: 0x0C0B,
AccountName: 0x0C0C,
NickName: 0x0C0D,
MMS: 0x0C0E,
}
};
}));

View file

@ -0,0 +1,38 @@
/* Copyright 2012 Mozilla Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
(function (root, factory) {
if (typeof exports === 'object')
module.exports = factory();
else if (typeof define === 'function' && define.amd)
define([], factory);
else
root.ASCPDocumentLibrary = factory();
}(this, function() {
'use strict';
return {
Tags: {
LinkId: 0x1305,
DisplayName: 0x1306,
IsFolder: 0x1307,
CreationDate: 0x1308,
LastModifiedDate: 0x1309,
IsHidden: 0x130A,
ContentLength: 0x130B,
ContentType: 0x130C,
},
};
}));

View file

@ -0,0 +1,128 @@
/* Copyright 2012 Mozilla Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
(function (root, factory) {
if (typeof exports === 'object')
module.exports = factory();
else if (typeof define === 'function' && define.amd)
define([], factory);
else
root.ASCPEmail = factory();
}(this, function() {
'use strict';
return {
Tags: {
Attachment: 0x0205,
Attachments: 0x0206,
AttName: 0x0207,
AttSize: 0x0208,
Att0Id: 0x0209,
AttMethod: 0x020A,
AttRemoved: 0x020B,
Body: 0x020C,
BodySize: 0x020D,
BodyTruncated: 0x020E,
DateReceived: 0x020F,
DisplayName: 0x0210,
DisplayTo: 0x0211,
Importance: 0x0212,
MessageClass: 0x0213,
Subject: 0x0214,
Read: 0x0215,
To: 0x0216,
Cc: 0x0217,
From: 0x0218,
ReplyTo: 0x0219,
AllDayEvent: 0x021A,
Categories: 0x021B,
Category: 0x021C,
DTStamp: 0x021D,
EndTime: 0x021E,
InstanceType: 0x021F,
BusyStatus: 0x0220,
Location: 0x0221,
MeetingRequest: 0x0222,
Organizer: 0x0223,
RecurrenceId: 0x0224,
Reminder: 0x0225,
ResponseRequested: 0x0226,
Recurrences: 0x0227,
Recurrence: 0x0228,
Recurrence_Type: 0x0229,
Recurrence_Until: 0x022A,
Recurrence_Occurrences: 0x022B,
Recurrence_Interval: 0x022C,
Recurrence_DayOfWeek: 0x022D,
Recurrence_DayOfMonth: 0x022E,
Recurrence_WeekOfMonth: 0x022F,
Recurrence_MonthOfYear: 0x0230,
StartTime: 0x0231,
Sensitivity: 0x0232,
TimeZone: 0x0233,
GlobalObjId: 0x0234,
ThreadTopic: 0x0235,
MIMEData: 0x0236,
MIMETruncated: 0x0237,
MIMESize: 0x0238,
InternetCPID: 0x0239,
Flag: 0x023A,
Status: 0x023B,
ContentClass: 0x023C,
FlagType: 0x023D,
CompleteTime: 0x023E,
DisallowNewTimeProposal: 0x023F,
},
Enums: {
Importance: {
Low: '0',
Normal: '1',
High: '2',
},
InstanceType: {
Single: '0',
RecurringMaster: '1',
RecurringInstance: '2',
RecurringException: '3',
},
BusyStatus: {
Free: '0',
Tentative: '1',
Busy: '2',
Oof: '3',
},
Recurrence_Type: {
Daily: '0',
Weekly: '1',
MonthlyNthDay: '2',
Monthly: '3',
YearlyNthDay: '5',
YearlyNthDayOfWeek: '6',
},
/* XXX: missing Recurrence_DayOfWeek */
Sensitivity: {
Normal: '0',
Personal: '1',
Private: '2',
Confidential: '3',
},
Status: {
Cleared: '0',
Complete: '1',
Active: '2',
},
},
};
}));

View file

@ -0,0 +1,89 @@
/* Copyright 2012 Mozilla Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
(function (root, factory) {
if (typeof exports === 'object')
module.exports = factory();
else if (typeof define === 'function' && define.amd)
define([], factory);
else
root.ASCPEmail2 = factory();
}(this, function() {
'use strict';
return {
Tags: {
UmCallerID: 0x1605,
UmUserNotes: 0x1606,
UmAttDuration: 0x1607,
UmAttOrder: 0x1608,
ConversationId: 0x1609,
ConversationIndex: 0x160A,
LastVerbExecuted: 0x160B,
LastVerbExecutionTime: 0x160C,
ReceivedAsBcc: 0x160D,
Sender: 0x160E,
CalendarType: 0x160F,
IsLeapMonth: 0x1610,
AccountId: 0x1611,
FirstDayOfWeek: 0x1612,
MeetingMessageType: 0x1613,
},
Enums: {
LastVerbExecuted: {
Unknown: '0',
ReplyToSender: '1',
ReplyToAll: '2',
Forward: '3',
},
CalendarType: {
Default: '0',
Gregorian: '1',
GregorianUS: '2',
Japan: '3',
Taiwan: '4',
Korea: '5',
Hijri: '6',
Thai: '7',
Hebrew: '8',
GregorianMeFrench: '9',
GregorianArabic: '10',
GregorianTranslatedEnglish: '11',
GregorianTranslatedFrench: '12',
JapaneseLunar: '14',
ChineseLunar: '15',
KoreanLunar: '20',
},
FirstDayOfWeek: {
Sunday: '0',
Monday: '1',
Tuesday: '2',
Wednesday: '3',
Thursday: '4',
Friday: '5',
Saturday: '6',
},
MeetingMessageType: {
Unspecified: '0',
InitialRequest: '1',
FullUpdate: '2',
InformationalUpdate: '3',
Outdated: '4',
DelegatorsCopy: '5',
Delegated: '6',
}
}
};
}));

View file

@ -0,0 +1,84 @@
/* Copyright 2012 Mozilla Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
(function (root, factory) {
if (typeof exports === 'object')
module.exports = factory();
else if (typeof define === 'function' && define.amd)
define([], factory);
else
root.ASCPHierarchy = factory();
}(this, function() {
'use strict';
return {
Tags: {
Folders: 0x0705,
Folder: 0x0706,
DisplayName: 0x0707,
ServerId: 0x0708,
ParentId: 0x0709,
Type: 0x070A,
Response: 0x070B,
Status: 0x070C,
ContentClass: 0x070D,
Changes: 0x070E,
Add: 0x070F,
Delete: 0x0710,
Update: 0x0711,
SyncKey: 0x0712,
FolderCreate: 0x0713,
FolderDelete: 0x0714,
FolderUpdate: 0x0715,
FolderSync: 0x0716,
Count: 0x0717,
},
Enums: {
Type: {
Generic: '1',
DefaultInbox: '2',
DefaultDrafts: '3',
DefaultDeleted: '4',
DefaultSent: '5',
DefaultOutbox: '6',
DefaultTasks: '7',
DefaultCalendar: '8',
DefaultContacts: '9',
DefaultNotes: '10',
DefaultJournal: '11',
Mail: '12',
Calendar: '13',
Contacts: '14',
Tasks: '15',
Journal: '16',
Notes: '17',
Unknown: '18',
RecipientCache: '19',
},
Status: {
Success: '1',
FolderExists: '2',
SystemFolder: '3',
FolderNotFound: '4',
ParentFolderNotFound: '5',
ServerError: '6',
InvalidSyncKey: '9',
MalformedRequest: '10',
UnknownError: '11',
CodeUnknown: '12',
}
}
};
}));

View file

@ -0,0 +1,44 @@
/* Copyright 2012 Mozilla Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
(function (root, factory) {
if (typeof exports === 'object')
module.exports = factory();
else if (typeof define === 'function' && define.amd)
define([], factory);
else
root.ASCPGAL = factory();
}(this, function() {
'use strict';
return {
Tags: {
DisplayName: 0x1005,
Phone: 0x1006,
Office: 0x1007,
Title: 0x1008,
Company: 0x1009,
Alias: 0x100A,
FirstName: 0x100B,
LastName: 0x100C,
HomePhone: 0x100D,
MobilePhone: 0x100E,
EmailAddress: 0x100F,
Picture: 0x1010,
Status: 0x1011,
Data: 0x1012,
}
};
}));

View file

@ -0,0 +1,48 @@
/* Copyright 2012 Mozilla Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
(function (root, factory) {
if (typeof exports === 'object')
module.exports = factory();
else if (typeof define === 'function' && define.amd)
define([], factory);
else
root.ASCPItemEstimate = factory();
}(this, function() {
'use strict';
return {
Tags: {
GetItemEstimate: 0x0605,
Version: 0x0606,
Collections: 0x0607,
Collection: 0x0608,
Class: 0x0609,
CollectionId: 0x060A,
DateTime: 0x060B,
Estimate: 0x060C,
Response: 0x060D,
Status: 0x060E,
},
Enums: {
Status: {
Success: '1',
InvalidCollection: '2',
NoSyncState: '3',
InvalidSyncKey: '4',
},
},
};
}));

View file

@ -0,0 +1,70 @@
/* Copyright 2012 Mozilla Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
(function (root, factory) {
if (typeof exports === 'object')
module.exports = factory();
else if (typeof define === 'function' && define.amd)
define([], factory);
else
root.ASCPItemOperations = factory();
}(this, function() {
'use strict';
return {
Tags: {
ItemOperations: 0x1405,
Fetch: 0x1406,
Store: 0x1407,
Options: 0x1408,
Range: 0x1409,
Total: 0x140A,
Properties: 0x140B,
Data: 0x140C,
Status: 0x140D,
Response: 0x140E,
Version: 0x140F,
Schema: 0x1410,
Part: 0x1411,
EmptyFolderContents: 0x1412,
DeleteSubFolders: 0x1413,
UserName: 0x1414,
Password: 0x1415,
Move: 0x1416,
DstFldId: 0x1417,
ConversationId: 0x1418,
MoveAlways: 0x1419,
},
Enums: {
Status: {
Success: '1',
ProtocolError: '2',
ServerError: '3',
BadURI: '4',
AccessDenied: '5',
ObjectNotFound: '6',
ConnectionFailure: '7',
InvalidByteRange: '8',
UnknownStore: '9',
EmptyFile: '10',
DataTooLarge: '11',
IOFailure: '12',
ConversionFailure: '14',
InvalidAttachment: '15',
ResourceAccessDenied: '16',
},
},
};
}));

View file

@ -0,0 +1,52 @@
/* Copyright 2012 Mozilla Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
(function (root, factory) {
if (typeof exports === 'object')
module.exports = factory();
else if (typeof define === 'function' && define.amd)
define([], factory);
else
root.ASCPMeetingResponse = factory();
}(this, function() {
'use strict';
return {
Tags: {
CalendarId: 0x0805,
CollectionId: 0x0806,
MeetingResponse: 0x0807,
RequestId: 0x0808,
Request: 0x0809,
Result: 0x080A,
Status: 0x080B,
UserResponse: 0x080C,
InstanceId: 0x080E,
},
Enums: {
Status: {
Success: '1',
InvalidRequest: '2',
MailboxError: '3',
ServerError: '4',
},
UserResponse: {
Accepted: '1',
Tentative: '2',
Declined: '3',
},
},
};
}));

View file

@ -0,0 +1,48 @@
/* Copyright 2012 Mozilla Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
(function (root, factory) {
if (typeof exports === 'object')
module.exports = factory();
else if (typeof define === 'function' && define.amd)
define([], factory);
else
root.ASCPMove = factory();
}(this, function() {
'use strict';
return {
Tags: {
MoveItems: 0x0505,
Move: 0x0506,
SrcMsgId: 0x0507,
SrcFldId: 0x0508,
DstFldId: 0x0509,
Response: 0x050A,
Status: 0x050B,
DstMsgId: 0x050C,
},
Enums: {
Status: {
InvalidSourceID: '1',
InvalidDestID: '2',
Success: '3',
SourceIsDest: '4',
MoveFailure: '5',
ItemLocked: '7',
},
},
};
}));

View file

@ -0,0 +1,35 @@
/* Copyright 2012 Mozilla Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
(function (root, factory) {
if (typeof exports === 'object')
module.exports = factory();
else if (typeof define === 'function' && define.amd)
define([], factory);
else
root.ASCPNotes = factory();
}(this, function() {
'use strict';
return {
Tags: {
Subject: 0x1705,
MessageClass: 0x1706,
LastModifiedDate: 0x1707,
Categories: 0x1708,
Category: 0x1709,
}
};
}));

View file

@ -0,0 +1,51 @@
/* Copyright 2012 Mozilla Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
(function (root, factory) {
if (typeof exports === 'object')
module.exports = factory();
else if (typeof define === 'function' && define.amd)
define([], factory);
else
root.ASCPPing = factory();
}(this, function() {
'use strict';
return {
Tags: {
Ping: 0x0D05,
AutdState: 0x0D06,
Status: 0x0D07,
HeartbeatInterval: 0x0D08,
Folders: 0x0D09,
Folder: 0x0D0A,
Id: 0x0D0B,
Class: 0x0D0C,
MaxFolders: 0x0D0D,
},
Enums: {
Status: {
Expired: '1',
Changed: '2',
MissingParameters: '3',
SyntaxError: '4',
InvalidInterval: '5',
TooManyFolders: '6',
SyncFolders: '7',
ServerError: '8',
},
},
};
}));

View file

@ -0,0 +1,84 @@
/* Copyright 2012 Mozilla Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
(function (root, factory) {
if (typeof exports === 'object')
module.exports = factory();
else if (typeof define === 'function' && define.amd)
define([], factory);
else
root.ASCPProvision = factory();
}(this, function() {
'use strict';
return {
Tags: {
Provision: 0x0E05,
Policies: 0x0E06,
Policy: 0x0E07,
PolicyType: 0x0E08,
PolicyKey: 0x0E09,
Data: 0x0E0A,
Status: 0x0E0B,
RemoteWipe: 0x0E0C,
EASProvisionDoc: 0x0E0D,
DevicePasswordEnabled: 0x0E0E,
AlphanumericDevicePasswordRequired: 0x0E0F,
DeviceEncryptionEnabled: 0x0E10,
RequireStorageCardEncryption: 0x0E10,
PasswordRecoveryEnabled: 0x0E11,
AttachmentsEnabled: 0x0E13,
MinDevicePasswordLength: 0x0E14,
MaxInactivityTimeDeviceLock: 0x0E15,
MaxDevicePasswordFailedAttempts: 0x0E16,
MaxAttachmentSize: 0x0E17,
AllowSimpleDevicePassword: 0x0E18,
DevicePasswordExpiration: 0x0E19,
DevicePasswordHistory: 0x0E1A,
AllowStorageCard: 0x0E1B,
AllowCamera: 0x0E1C,
RequireDeviceEncryption: 0x0E1D,
AllowUnsignedApplications: 0x0E1E,
AllowUnsignedInstallationPackages: 0x0E1F,
MinDevicePasswordComplexCharacters: 0x0E20,
AllowWiFi: 0x0E21,
AllowTextMessaging: 0x0E22,
AllowPOPIMAPEmail: 0x0E23,
AllowBluetooth: 0x0E24,
AllowIrDA: 0x0E25,
RequireManualSyncWhenRoaming: 0x0E26,
AllowDesktopSync: 0x0E27,
MaxCalendarAgeFilter: 0x0E28,
AllowHTMLEmail: 0x0E29,
MaxEmailAgeFilter: 0x0E2A,
MaxEmailBodyTruncationSize: 0x0E2B,
MaxEmailHTMLBodyTruncationSize: 0x0E2C,
RequireSignedSMIMEMessages: 0x0E2D,
RequireEncryptedSMIMEMessages: 0x0E2E,
RequireSignedSMIMEAlgorithm: 0x0E2F,
RequireEncryptionSMIMEAlgorithm: 0x0E30,
AllowSMIMEEncryptionAlgorithmNegotiation: 0x0E31,
AllowSMIMESoftCerts: 0x0E32,
AllowBrowser: 0x0E33,
AllowConsumerEmail: 0x0E34,
AllowRemoteDesktop: 0x0E35,
AllowInternetSharing: 0x0E36,
UnapprovedInROMApplicationList: 0x0E37,
ApplicationName: 0x0E38,
ApprovedApplicationList: 0x0E39,
Hash: 0x0E3A,
}
};
}));

View file

@ -0,0 +1,79 @@
/* Copyright 2012 Mozilla Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
(function (root, factory) {
if (typeof exports === 'object')
module.exports = factory();
else if (typeof define === 'function' && define.amd)
define([], factory);
else
root.ASCPResolveRecipients = factory();
}(this, function() {
'use strict';
return {
Tags: {
ResolveRecipients: 0x0A05,
Response: 0x0A06,
Status: 0x0A07,
Type: 0x0A08,
Recipient: 0x0A09,
DisplayName: 0x0A0A,
EmailAddress: 0x0A0B,
Certificates: 0x0A0C,
Certificate: 0x0A0D,
MiniCertificate: 0x0A0E,
Options: 0x0A0F,
To: 0x0A10,
CertificateRetrieval: 0x0A11,
RecipientCount: 0x0A12,
MaxCertificates: 0x0A13,
MaxAmbiguousRecipients: 0x0A14,
CertificateCount: 0x0A15,
Availability: 0x0A16,
StartTime: 0x0A17,
EndTime: 0x0A18,
MergedFreeBusy: 0x0A19,
Picture: 0x0A1A,
MaxSize: 0x0A1B,
Data: 0x0A1C,
MaxPictures: 0x0A1D,
},
Enums: {
Status: {
Success: '1',
AmbiguousRecipientFull: '2',
AmbiguousRecipientPartial: '3',
RecipientNotFound: '4',
ProtocolError: '5',
ServerError: '6',
InvalidSMIMECert: '7',
CertLimitReached: '8',
},
CertificateRetrieval: {
None: '1',
Full: '2',
Mini: '3',
},
MergedFreeBusy: {
Free: '0',
Tentative: '1',
Busy: '2',
Oof: '3',
NoData: '4',
},
},
};
}));

View file

@ -0,0 +1,50 @@
/* Copyright 2012 Mozilla Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
(function (root, factory) {
if (typeof exports === 'object')
module.exports = factory();
else if (typeof define === 'function' && define.amd)
define([], factory);
else
root.ASCPRightsManagement = factory();
}(this, function() {
'use strict';
return {
Tags: {
RightsManagementSupport: 0x1805,
RightsManagementTemplates: 0x1806,
RightsManagementTemplate: 0x1807,
RightsManagementLicense: 0x1808,
EditAllowed: 0x1809,
ReplyAllowed: 0x180A,
ReplyAllAllowed: 0x180B,
ForwardAllowed: 0x180C,
ModifyRecipientsAllowed: 0x180D,
ExtractAllowed: 0x180E,
PrintAllowed: 0x180F,
ExportAllowed: 0x1810,
ProgrammaticAccessAllowed: 0x1811,
Owner: 0x1812,
ContentExpiryDate: 0x1813,
TemplateID: 0x1814,
TemplateName: 0x1815,
TemplateDescription: 0x1816,
ContentOwner: 0x1817,
RemoveRightsManagementDistribution: 0x1818,
}
};
}));

View file

@ -0,0 +1,77 @@
/* Copyright 2012 Mozilla Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
(function (root, factory) {
if (typeof exports === 'object')
module.exports = factory();
else if (typeof define === 'function' && define.amd)
define([], factory);
else
root.ASCPSearch = factory();
}(this, function() {
'use strict';
return {
Tags: {
Search: 0x0F05,
Stores: 0x0F06,
Store: 0x0F07,
Name: 0x0F08,
Query: 0x0F09,
Options: 0x0F0A,
Range: 0x0F0B,
Status: 0x0F0C,
Response: 0x0F0D,
Result: 0x0F0E,
Properties: 0x0F0F,
Total: 0x0F10,
EqualTo: 0x0F11,
Value: 0x0F12,
And: 0x0F13,
Or: 0x0F14,
FreeText: 0x0F15,
DeepTraversal: 0x0F17,
LongId: 0x0F18,
RebuildResults: 0x0F19,
LessThan: 0x0F1A,
GreaterThan: 0x0F1B,
Schema: 0x0F1C,
Supported: 0x0F1D,
UserName: 0x0F1E,
Password: 0x0F1F,
ConversationId: 0x0F20,
Picture: 0x0F21,
MaxSize: 0x0F22,
MaxPictures: 0x0F23,
},
Enums: {
Status: {
Success: '1',
InvalidRequest: '2',
ServerError: '3',
BadLink: '4',
AccessDenied: '5',
NotFound: '6',
ConnectionFailure: '7',
TooComplex: '8',
Timeout: '10',
SyncFolders: '11',
EndOfRange: '12',
AccessBlocked: '13',
CredentialsRequired: '14',
}
}
};
}));

View file

@ -0,0 +1,85 @@
/* Copyright 2012 Mozilla Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
(function (root, factory) {
if (typeof exports === 'object')
module.exports = factory();
else if (typeof define === 'function' && define.amd)
define([], factory);
else
root.ASCPSettings = factory();
}(this, function() {
'use strict';
return {
Tags: {
Settings: 0x1205,
Status: 0x1206,
Get: 0x1207,
Set: 0x1208,
Oof: 0x1209,
OofState: 0x120A,
StartTime: 0x120B,
EndTime: 0x120C,
OofMessage: 0x120D,
AppliesToInternal: 0x120E,
AppliesToExternalKnown: 0x120F,
AppliesToExternalUnknown: 0x1210,
Enabled: 0x1211,
ReplyMessage: 0x1212,
BodyType: 0x1213,
DevicePassword: 0x1214,
Password: 0x1215,
DeviceInformation: 0x1216,
Model: 0x1217,
IMEI: 0x1218,
FriendlyName: 0x1219,
OS: 0x121A,
OSLanguage: 0x121B,
PhoneNumber: 0x121C,
UserInformation: 0x121D,
EmailAddresses: 0x121E,
SmtpAddress: 0x121F,
UserAgent: 0x1220,
EnableOutboundSMS: 0x1221,
MobileOperator: 0x1222,
PrimarySmtpAddress: 0x1223,
Accounts: 0x1224,
Account: 0x1225,
AccountId: 0x1226,
AccountName: 0x1227,
UserDisplayName: 0x1228,
SendDisabled: 0x1229,
/* Missing tag value 0x122A */
RightsManagementInformation: 0x122B,
},
Enums: {
Status: {
Success: '1',
ProtocolError: '2',
AccessDenied: '3',
ServerError: '4',
InvalidArguments: '5',
ConflictingArguments: '6',
DeniedByPolicy: '7',
},
OofState: {
Disabled: '0',
Global: '1',
TimeBased: '2',
}
}
};
}));

View file

@ -0,0 +1,64 @@
/* Copyright 2012 Mozilla Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
(function (root, factory) {
if (typeof exports === 'object')
module.exports = factory();
else if (typeof define === 'function' && define.amd)
define([], factory);
else
root.ASCPTasks = factory();
}(this, function() {
'use strict';
return {
Tags: {
Body: 0x0905,
BodySize: 0x0906,
BodyTruncated: 0x0907,
Categories: 0x0908,
Category: 0x0909,
Complete: 0x090A,
DateCompleted: 0x090B,
DueDate: 0x090C,
UtcDueDate: 0x090D,
Importance: 0x090E,
Recurrence: 0x090F,
Recurrence_Type: 0x0910,
Recurrence_Start: 0x0911,
Recurrence_Until: 0x0912,
Recurrence_Occurrences: 0x0913,
Recurrence_Interval: 0x0914,
Recurrence_DayOfMonth: 0x0915,
Recurrence_DayOfWeek: 0x0916,
Recurrence_WeekOfMonth: 0x0917,
Recurrence_MonthOfYear: 0x0918,
Recurrence_Regenerate: 0x0919,
Recurrence_DeadOccur: 0x091A,
ReminderSet: 0x091B,
ReminderTime: 0x091C,
Sensitivity: 0x091D,
StartDate: 0x091E,
UtcStartDate: 0x091F,
Subject: 0x0920,
CompressedRTF: 0x0921,
OrdinalDate: 0x0922,
SubOrdinalDate: 0x0923,
CalendarType: 0x0924,
IsLeapMonth: 0x0925,
FirstDayOfWeek: 0x0926,
}
};
}));

View file

@ -0,0 +1,57 @@
/* Copyright 2012 Mozilla Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
(function (root, factory) {
if (typeof exports === 'object')
module.exports = factory();
else if (typeof define === 'function' && define.amd)
define([], factory);
else
root.ASCPValidateCert = factory();
}(this, function() {
'use strict';
return {
Tags: {
ValidateCert: 0x0B05,
Certificates: 0x0B06,
Certificate: 0x0B07,
CertificateChain: 0x0B08,
CheckCRL: 0x0B09,
Status: 0x0B0A,
},
Enums: {
Status: {
Success: '1',
ProtocolError: '2',
InvalidSignature: '3',
UntrustedSource: '4',
InvalidChain: '5',
NotForEmail: '6',
Expired: '7',
InconsistentTimes: '8',
IdMisused: '9',
MissingInformation: '10',
CAEndMismatch: '11',
EmailAddressMismatch: '12',
Revoked: '13',
ServerOffline: '14',
ChainRevoked: '15',
RevocationUnknown: '16',
UnknownError: '17',
},
},
};
}));

View file

@ -0,0 +1,698 @@
/* Copyright 2012 Mozilla Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
(function (root, factory) {
if (typeof exports === 'object')
module.exports = factory(require('wbxml'), require('activesync/codepages'));
else if (typeof define === 'function' && define.amd)
define(['wbxml', 'activesync/codepages'], factory);
else
root.ActiveSyncProtocol = factory(WBXML, ActiveSyncCodepages);
}(this, function(WBXML, ASCP) {
'use strict';
var exports = {};
var USER_AGENT = 'JavaScript ActiveSync (jsas) Client';
function nullCallback() {}
/**
* Create a constructor for a custom error type that works like a built-in
* Error.
*
* @param name the string name of the error
* @param parent (optional) a parent class for the error, defaults to Error
* @param extraArgs an array of extra arguments that can be passed to the
* constructor of this error type
* @return the constructor for this error
*/
function makeError(name, parent, extraArgs) {
function CustomError() {
// Try to let users call this as CustomError(...) without the "new". This
// is imperfect, and if you call this function directly and give it a
// |this| that's a CustomError, things will break. Don't do it!
var self = this instanceof CustomError ?
this : Object.create(CustomError.prototype);
var tmp = Error();
var offset = 1;
self.stack = tmp.stack.substring(tmp.stack.indexOf('\n') + 1);
self.message = arguments[0] || tmp.message;
if (extraArgs) {
offset += extraArgs.length;
for (var i = 0; i < extraArgs.length; i++)
self[extraArgs[i]] = arguments[i+1];
}
var m = /@(.+):(.+)/.exec(self.stack);
self.fileName = arguments[offset] || (m && m[1]) || "";
self.lineNumber = arguments[offset + 1] || (m && m[2]) || 0;
return self;
}
CustomError.prototype = Object.create((parent || Error).prototype);
CustomError.prototype.name = name;
CustomError.prototype.constructor = CustomError;
return CustomError;
}
var AutodiscoverError = makeError('ActiveSync.AutodiscoverError');
exports.AutodiscoverError = AutodiscoverError;
var AutodiscoverDomainError = makeError('ActiveSync.AutodiscoverDomainError',
AutodiscoverError);
exports.AutodiscoverDomainError = AutodiscoverDomainError;
var HttpError = makeError('ActiveSync.HttpError', null, ['status']);
exports.HttpError = HttpError;
function nsResolver(prefix) {
var baseUrl = 'http://schemas.microsoft.com/exchange/autodiscover/';
var ns = {
rq: baseUrl + 'mobilesync/requestschema/2006',
ad: baseUrl + 'responseschema/2006',
ms: baseUrl + 'mobilesync/responseschema/2006',
};
return ns[prefix] || null;
}
function Version(str) {
var details = str.split('.').map(function(x) {
return parseInt(x);
});
this.major = details[0], this.minor = details[1];
}
exports.Version = Version;
Version.prototype = {
eq: function(other) {
if (!(other instanceof Version))
other = new Version(other);
return this.major === other.major && this.minor === other.minor;
},
ne: function(other) {
return !this.eq(other);
},
gt: function(other) {
if (!(other instanceof Version))
other = new Version(other);
return this.major > other.major ||
(this.major === other.major && this.minor > other.minor);
},
gte: function(other) {
if (!(other instanceof Version))
other = new Version(other);
return this.major >= other.major ||
(this.major === other.major && this.minor >= other.minor);
},
lt: function(other) {
return !this.gte(other);
},
lte: function(other) {
return !this.gt(other);
},
toString: function() {
return this.major + '.' + this.minor;
},
};
/**
* Set the Authorization header on an XMLHttpRequest.
*
* @param xhr the XMLHttpRequest
* @param username the username
* @param password the user's password
*/
function setAuthHeader(xhr, username, password) {
var authorization = 'Basic ' + btoa(username + ':' + password);
xhr.setRequestHeader('Authorization', authorization);
}
/**
* Perform autodiscovery for the server associated with this account.
*
* @param aEmailAddress the user's email address
* @param aPassword the user's password
* @param aTimeout a timeout (in milliseconds) for the request
* @param aCallback a callback taking an error status (if any) and the
* server's configuration
* @param aNoRedirect true if autodiscovery should *not* follow any
* specified redirects (typically used when autodiscover has already
* told us about a redirect)
*/
function autodiscover(aEmailAddress, aPassword, aTimeout, aCallback,
aNoRedirect) {
if (!aCallback) aCallback = nullCallback;
var domain = aEmailAddress.substring(aEmailAddress.indexOf('@') + 1);
// The first time we try autodiscovery, we should try to recover from
// AutodiscoverDomainErrors and HttpErrors. The second time, *all* errors
// should be reported to the callback.
do_autodiscover(domain, aEmailAddress, aPassword, aTimeout, aNoRedirect,
function(aError, aConfig) {
if (aError instanceof AutodiscoverDomainError ||
aError instanceof HttpError)
do_autodiscover('autodiscover.' + domain, aEmailAddress, aPassword,
aTimeout, aNoRedirect, aCallback);
else
aCallback(aError, aConfig);
});
}
exports.autodiscover = autodiscover;
/**
* Perform the actual autodiscovery process for a given URL.
*
* @param aHost the host name to attempt autodiscovery for
* @param aEmailAddress the user's email address
* @param aPassword the user's password
* @param aTimeout a timeout (in milliseconds) for the request
* @param aNoRedirect true if autodiscovery should *not* follow any
* specified redirects (typically used when autodiscover has already
* told us about a redirect)
* @param aCallback a callback taking an error status (if any) and the
* server's configuration
*/
function do_autodiscover(aHost, aEmailAddress, aPassword, aTimeout,
aNoRedirect, aCallback) {
var url = 'https://' + aHost + '/autodiscover/autodiscover.xml';
return raw_autodiscover(url, aEmailAddress, aPassword, aTimeout,
aNoRedirect, aCallback);
}
function raw_autodiscover(aUrl, aEmailAddress, aPassword, aTimeout,
aNoRedirect, aCallback) {
var xhr = new XMLHttpRequest({mozSystem: true, mozAnon: true});
xhr.open('POST', aUrl, true);
setAuthHeader(xhr, aEmailAddress, aPassword);
xhr.setRequestHeader('Content-Type', 'text/xml');
xhr.setRequestHeader('User-Agent', USER_AGENT);
xhr.timeout = aTimeout;
xhr.upload.onprogress = xhr.upload.onload = function() {
xhr.timeout = 0;
};
xhr.onload = function() {
if (xhr.status < 200 || xhr.status >= 300)
return aCallback(new HttpError(xhr.statusText, xhr.status));
var uid = Math.random();
self.postMessage({
uid: uid,
type: 'configparser',
cmd: 'accountactivesync',
args: [xhr.responseText, aNoRedirect]
});
self.addEventListener('message', function onworkerresponse(evt) {
var data = evt.data;
if (data.type != 'configparser' || data.cmd != 'accountactivesync' ||
data.uid != uid) {
return;
}
self.removeEventListener(evt.type, onworkerresponse);
var args = data.args;
var config = args[0], error = args[1], redirectedEmail = args[2];
if (error) {
aCallback(new AutodiscoverDomainError(error), config);
} else if (redirectedEmail) {
autodiscover(redirectedEmail, aPassword, aTimeout, aCallback, true);
} else {
aCallback(null, config);
}
});
};
xhr.ontimeout = xhr.onerror = function() {
// Something bad happened in the network layer, so treat this like an HTTP
// error.
aCallback(new HttpError('Error getting Autodiscover URL', null));
};
// TODO: use something like
// http://ejohn.org/blog/javascript-micro-templating/ here?
var postdata =
'<?xml version="1.0" encoding="utf-8"?>\n' +
'<Autodiscover xmlns="' + nsResolver('rq') + '">\n' +
' <Request>\n' +
' <EMailAddress>' + aEmailAddress + '</EMailAddress>\n' +
' <AcceptableResponseSchema>' + nsResolver('ms') +
'</AcceptableResponseSchema>\n' +
' </Request>\n' +
'</Autodiscover>';
xhr.send(postdata);
}
exports.raw_autodiscover = raw_autodiscover;
/**
* Create a new ActiveSync connection.
*
* ActiveSync connections use XMLHttpRequests to communicate with the
* server. These XHRs are created with mozSystem: true and mozAnon: true to,
* respectively, help with CORS, and to ignore the authentication cache. The
* latter is important because 1) it prevents the HTTP auth dialog from
* appearing if the user's credentials are wrong and 2) it allows us to
* connect to the same server as multiple users.
*
* @param aDeviceId (optional) a string identifying this device
* @param aDeviceType (optional) a string identifying the type of this device
*/
function Connection(aDeviceId, aDeviceType) {
this._deviceId = aDeviceId || 'v140Device';
this._deviceType = aDeviceType || 'SmartPhone';
this.timeout = 0;
this._connected = false;
this._waitingForConnection = false;
this._connectionError = null;
this._connectionCallbacks = [];
this.baseUrl = null;
this._username = null;
this._password = null;
this.versions = [];
this.supportedCommands = [];
this.currentVersion = null;
/**
* Debug support function that is called every time an XHR call completes.
* This is intended to be used for logging.
*
* The arguments to the function are:
*
* - type: 'options' if caused by a call to getOptions. 'post' if caused by
* a call to postCommand/postData.
*
* - special: 'timeout' if a timeout error occurred, 'redirect' if the
* status code was 451 and the call is being reissued, 'error' if some
* type of error occurred, or 'ok' on success. Check xhr.status for the
* specific http status code.
*
* - xhr: The XMLHttpRequest used. Use this to check the statusCode,
* statusText, or response headers.
*
* - params: The object dictionary of parameters encoded into the URL.
* Always present if type is 'post', not present for 'options'.
*
* - extraHeaders: Optional dictionary of extra request headers that were
* provided. These will not include the always-present request headers of
* MS-ASProtocolVersion and Content-Type.
*
* - sent data: If type is 'post', the ArrayBuffer provided to xhr.send().
*
* - response: In the case of a successful 'post', the WBXML Reader instance
* that will be passed to the callback for the method. If you use the
* reader, you are responsible for calling rewind() on it.
*/
this.onmessage = null;
}
exports.Connection = Connection;
Connection.prototype = {
/**
* Perform any callbacks added during the connection process.
*
* @param aError the error status (if any)
*/
_notifyConnected: function(aError) {
if (aError)
this.disconnect();
for (var iter in Iterator(this._connectionCallbacks)) {
var callback = iter[1];
callback.apply(callback, arguments);
}
this._connectionCallbacks = [];
},
/**
* Get the connection status.
*
* @return true iff we are fully connected to the server
*/
get connected() {
return this._connected;
},
/*
* Initialize the connection with a server and account credentials.
*
* @param aURL the ActiveSync URL to connect to
* @param aUsername the account's username
* @param aPassword the account's password
*/
open: function(aURL, aUsername, aPassword) {
// XXX: We add the default service path to the URL if it's not already
// there. This is a hack to work around the failings of Hotmail (and
// possibly other servers), which doesn't provide the service path in its
// URL. If it turns out this causes issues with other domains, remove it.
var servicePath = '/Microsoft-Server-ActiveSync';
this.baseUrl = aURL;
if (!this.baseUrl.endsWith(servicePath))
this.baseUrl += servicePath;
this._username = aUsername;
this._password = aPassword;
},
/**
* Connect to the server with this account by getting the OPTIONS from
* the server (and verifying the account's credentials).
*
* @param aCallback a callback taking an error status (if any) and the
* server's options.
*/
connect: function(aCallback) {
// If we're already connected, just run the callback and return.
if (this.connected) {
if (aCallback)
aCallback(null);
return;
}
// Otherwise, queue this callback up to fire when we do connect.
if (aCallback)
this._connectionCallbacks.push(aCallback);
// Don't do anything else if we're already trying to connect.
if (this._waitingForConnection)
return;
this._waitingForConnection = true;
this._connectionError = null;
this.getOptions((function(aError, aOptions) {
this._waitingForConnection = false;
this._connectionError = aError;
if (aError) {
console.error('Error connecting to ActiveSync:', aError);
return this._notifyConnected(aError, aOptions);
}
this._connected = true;
this.versions = aOptions.versions;
this.supportedCommands = aOptions.commands;
this.currentVersion = new Version(aOptions.versions.slice(-1)[0]);
return this._notifyConnected(null, aOptions);
}).bind(this));
},
/**
* Disconnect from the ActiveSync server, and reset the connection state.
* The server and credentials remain set however, so you can safely call
* connect() again immediately after.
*/
disconnect: function() {
if (this._waitingForConnection)
throw new Error("Can't disconnect while waiting for server response");
this._connected = false;
this.versions = [];
this.supportedCommands = [];
this.currentVersion = null;
},
/**
* Attempt to provision this account. XXX: Currently, this doesn't actually
* do anything, but it's useful as a test command for Gmail to ensure that
* the user entered their password correctly.
*
* @param aCallback a callback taking an error status (if any) and the
* WBXML response
*/
provision: function(aCallback) {
var pv = ASCP.Provision.Tags;
var w = new WBXML.Writer('1.3', 1, 'UTF-8');
w.stag(pv.Provision)
.etag();
this.postCommand(w, aCallback);
},
/**
* Get the options for the server associated with this account.
*
* @param aCallback a callback taking an error status (if any), and the
* resulting options.
*/
getOptions: function(aCallback) {
if (!aCallback) aCallback = nullCallback;
var conn = this;
var xhr = new XMLHttpRequest({mozSystem: true, mozAnon: true});
xhr.open('OPTIONS', this.baseUrl, true);
setAuthHeader(xhr, this._username, this._password);
xhr.setRequestHeader('User-Agent', USER_AGENT);
xhr.timeout = this.timeout;
xhr.upload.onprogress = xhr.upload.onload = function() {
xhr.timeout = 0;
};
xhr.onload = function() {
if (xhr.status < 200 || xhr.status >= 300) {
console.error('ActiveSync options request failed with response ' +
xhr.status);
if (conn.onmessage)
conn.onmessage('options', 'error', xhr, null, null, null, null);
aCallback(new HttpError(xhr.statusText, xhr.status));
return;
}
// These headers are comma-separated lists. Sometimes, people like to
// put spaces after the commas, so make sure we trim whitespace too.
var result = {
versions: xhr.getResponseHeader('MS-ASProtocolVersions')
.split(/\s*,\s*/),
commands: xhr.getResponseHeader('MS-ASProtocolCommands')
.split(/\s*,\s*/)
};
if (conn.onmessage)
conn.onmessage('options', 'ok', xhr, null, null, null, result);
aCallback(null, result);
};
xhr.ontimeout = xhr.onerror = function() {
var error = new Error('Error getting OPTIONS URL');
console.error(error);
if (conn.onmessage)
conn.onmessage('options', 'timeout', xhr, null, null, null, null);
aCallback(error);
};
// Set the response type to "text" so that we don't try to parse an empty
// body as XML.
xhr.responseType = 'text';
xhr.send();
},
/**
* Check if the server supports a particular command. Requires that we be
* connected to the server already.
*
* @param aCommand a string/tag representing the command type
* @return true iff the command is supported
*/
supportsCommand: function(aCommand) {
if (!this.connected)
throw new Error('Connection required to get command');
if (typeof aCommand === 'number')
aCommand = ASCP.__tagnames__[aCommand];
return this.supportedCommands.indexOf(aCommand) !== -1;
},
/**
* DEPRECATED. See postCommand() below.
*/
doCommand: function() {
console.warn('doCommand is deprecated. Use postCommand instead.');
this.postCommand.apply(this, arguments);
},
/**
* Send a WBXML command to the ActiveSync server and listen for the
* response.
*
* @param aCommand {WBXML.Writer|String|Number}
* The WBXML representing the command or a string/tag representing the
* command type for empty commands
* @param aCallback a callback to call when the server has responded; takes
* two arguments: an error status (if any) and the response as a
* WBXML reader. If the server returned an empty response, the
* response argument is null.
* @param aExtraParams (optional) an object containing any extra URL
* parameters that should be added to the end of the request URL
* @param aExtraHeaders (optional) an object containing any extra HTTP
* headers to send in the request
* @param aProgressCallback (optional) a callback to invoke with progress
* information, when available. Two arguments are provided: the
* number of bytes received so far, and the total number of bytes
* expected (when known, 0 if unknown).
*/
postCommand: function(aCommand, aCallback, aExtraParams, aExtraHeaders,
aProgressCallback) {
var contentType = 'application/vnd.ms-sync.wbxml';
if (typeof aCommand === 'string' || typeof aCommand === 'number') {
this.postData(aCommand, contentType, null, aCallback, aExtraParams,
aExtraHeaders);
}
// WBXML.Writer
else {
var commandName = ASCP.__tagnames__[aCommand.rootTag];
this.postData(
commandName, contentType,
aCommand.dataType === 'blob' ? aCommand.blob : aCommand.buffer,
aCallback, aExtraParams, aExtraHeaders, aProgressCallback);
}
},
/**
* Send arbitrary data to the ActiveSync server and listen for the response.
*
* @param aCommand a string (or WBXML tag) representing the command type
* @param aContentType the content type of the post data
* @param aData {ArrayBuffer|Blob} the data to be posted
* @param aCallback a callback to call when the server has responded; takes
* two arguments: an error status (if any) and the response as a
* WBXML reader. If the server returned an empty response, the
* response argument is null.
* @param aExtraParams (optional) an object containing any extra URL
* parameters that should be added to the end of the request URL
* @param aExtraHeaders (optional) an object containing any extra HTTP
* headers to send in the request
* @param aProgressCallback (optional) a callback to invoke with progress
* information, when available. Two arguments are provided: the
* number of bytes received so far, and the total number of bytes
* expected (when known, 0 if unknown).
*/
postData: function(aCommand, aContentType, aData, aCallback, aExtraParams,
aExtraHeaders, aProgressCallback) {
// Make sure our command name is a string.
if (typeof aCommand === 'number')
aCommand = ASCP.__tagnames__[aCommand];
if (!this.supportsCommand(aCommand)) {
var error = new Error("This server doesn't support the command " +
aCommand);
console.error(error);
aCallback(error);
return;
}
// Build the URL parameters.
var params = [
['Cmd', aCommand],
['User', this._username],
['DeviceId', this._deviceId],
['DeviceType', this._deviceType]
];
if (aExtraParams) {
for (var iter in Iterator(params)) {
var param = iter[1];
if (param[0] in aExtraParams)
throw new TypeError('reserved URL parameter found');
}
for (var kv in Iterator(aExtraParams))
params.push(kv);
}
var paramsStr = params.map(function(i) {
return encodeURIComponent(i[0]) + '=' + encodeURIComponent(i[1]);
}).join('&');
// Now it's time to make our request!
var xhr = new XMLHttpRequest({mozSystem: true, mozAnon: true});
xhr.open('POST', this.baseUrl + '?' + paramsStr, true);
setAuthHeader(xhr, this._username, this._password);
xhr.setRequestHeader('MS-ASProtocolVersion', this.currentVersion);
xhr.setRequestHeader('Content-Type', aContentType);
xhr.setRequestHeader('User-Agent', USER_AGENT);
// Add extra headers if we have any.
if (aExtraHeaders) {
for (var iter in Iterator(aExtraHeaders)) {
var key = iter[0], key = iter[1];
xhr.setRequestHeader(key, value);
}
}
xhr.timeout = this.timeout;
xhr.upload.onprogress = xhr.upload.onload = function() {
xhr.timeout = 0;
};
xhr.onprogress = function(event) {
if (aProgressCallback)
aProgressCallback(event.loaded, event.total);
};
var conn = this;
var parentArgs = arguments;
xhr.onload = function() {
// This status code is a proprietary Microsoft extension used to
// indicate a redirect, not to be confused with the draft-standard
// "Unavailable For Legal Reasons" status. More info available here:
// <http://msdn.microsoft.com/en-us/library/gg651019.aspx>
if (xhr.status === 451) {
conn.baseUrl = xhr.getResponseHeader('X-MS-Location');
if (conn.onmessage)
conn.onmessage(aCommand, 'redirect', xhr, params, aExtraHeaders,
aData, null);
conn.postData.apply(conn, parentArgs);
return;
}
if (xhr.status < 200 || xhr.status >= 300) {
console.error('ActiveSync command ' + aCommand + ' failed with ' +
'response ' + xhr.status);
if (conn.onmessage)
conn.onmessage(aCommand, 'error', xhr, params, aExtraHeaders,
aData, null);
aCallback(new HttpError(xhr.statusText, xhr.status));
return;
}
var response = null;
if (xhr.response.byteLength > 0)
response = new WBXML.Reader(new Uint8Array(xhr.response), ASCP);
if (conn.onmessage)
conn.onmessage(aCommand, 'ok', xhr, params, aExtraHeaders,
aData, response);
aCallback(null, response);
};
xhr.ontimeout = xhr.onerror = function(evt) {
var error = new Error('Command URL ' + evt.type + ' for command ' +
aCommand + ' at baseUrl ' + this.baseUrl);
console.error(error);
if (conn.onmessage)
conn.onmessage(aCommand, evt.type, xhr, params, aExtraHeaders,
aData, null);
aCallback(error);
}.bind(this);
xhr.responseType = 'arraybuffer';
xhr.send(aData);
},
};
return exports;
}));

View file

@ -0,0 +1,101 @@
/**
* Simple Blob and FileReader shim that does enough of an impression of the W3C
* File API that our unit tests can pass.
*
* Key differences:
* - Blob does not snapshot mutable TypedArray or ArrayBuffer instances passed
* in.
* - Blob surfaces helper logic/properties with underscore-prefixed names.
* - FileReader is not fully implemented and its events are bare bones.
**/
function Blob(parts, properties) {
this._parts = parts;
var size = 0;
parts.forEach(function(part) {
if (part instanceof Blob)
size += part.size;
else if (part instanceof ArrayBuffer)
size += part.byteLength;
else
size += part.length;
});
this.size = size;
this.type = properties ? properties.type : null;
};
exports.Blob = Blob;
Blob.prototype = {
_asArrayBuffer: function() {
var buffer = new ArrayBuffer(this.size);
var u8 = new Uint8Array(buffer);
var offset = 0;
this._parts.forEach(function(part) {
var sub8;
if (part instanceof Blob) {
var subarr = part._asArrayBuffer();
sub8 = new Uint8Array(subarr);
u8.set(sub8, offset);
offset += sub8.length;
}
else if (typeof(part) === 'string') {
var encoder = new TextEncoder('utf-8');
sub8 = encoder.encode(part);
u8.set(sub8, offset);
offset += sub8.length;
}
else if (part instanceof ArrayBuffer) {
sub8 = new Uint8Array(part);
u8.set(sub8, offset);
offset += sub8.length;
}
else {
u8.set(part, offset);
offset += part.length;
}
});
return buffer;
}
};
function FileReader() {
this.error = null;
this.readyState = this.EMPTY;
this.result = null;
this.onabort = null;
this.onerror = null;
this.onload = null;
this.onloadend = null;
this.onloadstart = null;
this.onprogress = null;
}
exports.FileReader = FileReader;
FileReader.prototype = {
EMPTY: 0,
LOADING: 1,
DONE: 2,
readAsArrayBuffer: function(blob) {
process.nextTick(function() {
var event = { target: this };
if (this.onload)
this.onload(event);
if (this.onloadend)
this.onloadend(event);
}.bind(this));
this.result = blob._asArrayBuffer();
},
readAsBinaryString: function(blob) {
throw new Error('not implemented');
},
readAsDataURL: function(blob) {
throw new Error('not implemented');
},
readAsText: function(blob, encoding) {
throw new Error('not implemented');
},
};

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,325 @@
// Copyright (c) 2013 Andris Reinman
//
// 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.
(function(root, factory) {
"use strict";
if (typeof define === 'function' && define.amd) {
define(factory);
} else if (typeof exports === 'object') {
module.exports = factory();
} else {
root.addressparser = factory();
}
}(this, function() {
"use strict";
/**
* Defines an object as a namespace for the parsing function
*/
var addressparser = {};
/**
* Parses structured e-mail addresses from an address field
*
* Example:
*
* "Name <address@domain>"
*
* will be converted to
*
* [{name: "Name", address: "address@domain"}]
*
* @param {String} str Address field
* @return {Array} An array of address objects
*/
addressparser.parse = function(str) {
var tokenizer = new addressparser.Tokenizer(str),
tokens = tokenizer.tokenize();
var addresses = [],
address = [],
parsedAddresses = [];
tokens.forEach(function(token) {
if (token.type === "operator" && (token.value === "," || token.value === ";")) {
if (address.length) {
addresses.push(address);
}
address = [];
} else {
address.push(token);
}
});
if (address.length) {
addresses.push(address);
}
addresses.forEach(function(address) {
address = addressparser._handleAddress(address);
if (address.length) {
parsedAddresses = parsedAddresses.concat(address);
}
});
return parsedAddresses;
};
/**
* Converts tokens for a single address into an address object
*
* @param {Array} tokens Tokens object
* @return {Object} Address object
*/
addressparser._handleAddress = function(tokens) {
var token,
isGroup = false,
state = "text",
address,
addresses = [],
data = {
address: [],
comment: [],
group: [],
text: []
},
i, len;
// Filter out <addresses>, (comments) and regular text
for (i = 0, len = tokens.length; i < len; i++) {
token = tokens[i];
if (token.type === "operator") {
switch (token.value) {
case "<":
state = "address";
break;
case "(":
state = "comment";
break;
case ":":
state = "group";
isGroup = true;
break;
default:
state = "text";
}
} else {
if (token.value) {
data[state].push(token.value);
}
}
}
// If there is no text but a comment, replace the two
if (!data.text.length && data.comment.length) {
data.text = data.comment;
data.comment = [];
}
if (isGroup) {
// http://tools.ietf.org/html/rfc2822#appendix-A.1.3
data.text = data.text.join(" ");
addresses.push({
name: data.text || (address && address.name),
group: data.group.length ? addressparser.parse(data.group.join(",")) : []
});
} else {
// If no address was found, try to detect one from regular text
if (!data.address.length && data.text.length) {
for (i = data.text.length - 1; i >= 0; i--) {
if (data.text[i].match(/^[^@\s]+@[^@\s]+$/)) {
data.address = data.text.splice(i, 1);
break;
}
}
var _regexHandler = function(address) {
if (!data.address.length) {
data.address = [address.trim()];
return " ";
} else {
return address;
}
};
// still no address
if (!data.address.length) {
for (i = data.text.length - 1; i >= 0; i--) {
data.text[i] = data.text[i].replace(/\s*\b[^@\s]+@[^@\s]+\b\s*/, _regexHandler).trim();
if (data.address.length) {
break;
}
}
}
}
// If there's still is no text but a comment exixts, replace the two
if (!data.text.length && data.comment.length) {
data.text = data.comment;
data.comment = [];
}
// Keep only the first address occurence, push others to regular text
if (data.address.length > 1) {
data.text = data.text.concat(data.address.splice(1));
}
// Join values with spaces
data.text = data.text.join(" ");
data.address = data.address.join(" ");
if (!data.address && isGroup) {
return [];
} else {
address = {
address: data.address || data.text || "",
name: data.text || data.address || ""
};
if (address.address === address.name) {
if ((address.address || "").match(/@/)) {
address.name = "";
} else {
address.address = "";
}
}
addresses.push(address);
}
}
return addresses;
};
/**
* Creates a Tokenizer object for tokenizing address field strings
*
* @constructor
* @param {String} str Address field string
*/
addressparser.Tokenizer = function(str) {
this.str = (str || "").toString();
this.operatorCurrent = "";
this.operatorExpecting = "";
this.node = null;
this.escaped = false;
this.list = [];
};
/**
* Operator tokens and which tokens are expected to end the sequence
*/
addressparser.Tokenizer.prototype.operators = {
"\"": "\"",
"(": ")",
"<": ">",
",": "",
// Groups are ended by semicolons
":": ";",
// Semicolons are not a legal delimiter per the RFC2822 grammar other
// than for terminating a group, but they are also not valid for any
// other use in this context. Given that some mail clients have
// historically allowed the semicolon as a delimiter equivalent to the
// comma in their UI, it makes sense to treat them the same as a comma
// when used outside of a group.
";": ""
};
/**
* Tokenizes the original input string
*
* @return {Array} An array of operator|text tokens
*/
addressparser.Tokenizer.prototype.tokenize = function() {
var chr, list = [];
for (var i = 0, len = this.str.length; i < len; i++) {
chr = this.str.charAt(i);
this.checkChar(chr);
}
this.list.forEach(function(node) {
node.value = (node.value || "").toString().trim();
if (node.value) {
list.push(node);
}
});
return list;
};
/**
* Checks if a character is an operator or text and acts accordingly
*
* @param {String} chr Character from the address field
*/
addressparser.Tokenizer.prototype.checkChar = function(chr) {
if ((chr in this.operators || chr === "\\") && this.escaped) {
this.escaped = false;
} else if (this.operatorExpecting && chr === this.operatorExpecting) {
this.node = {
type: "operator",
value: chr
};
this.list.push(this.node);
this.node = null;
this.operatorExpecting = "";
this.escaped = false;
return;
} else if (!this.operatorExpecting && chr in this.operators) {
this.node = {
type: "operator",
value: chr
};
this.list.push(this.node);
this.node = null;
this.operatorExpecting = this.operators[chr];
this.escaped = false;
return;
}
if (!this.escaped && chr === "\\") {
this.escaped = true;
return;
}
if (!this.node) {
this.node = {
type: "text",
value: ""
};
this.list.push(this.node);
}
if (this.escaped && chr !== "\\") {
this.node.value += "\\";
}
this.node.value += chr;
this.escaped = false;
};
return addressparser;
}));

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,13 @@
/**
* Shim for 'axe-logger' as required by the email.js libs.
*/
define(function() {
// For now, silence debug/log, and forward warn/error to the
// console just in case.
return {
debug: function() { /* shh */ },
log: function() { /* shh */ },
warn: console.warn.bind(console),
error: console.error.bind(console)
};
});

View file

@ -0,0 +1,8 @@
/**
* Alternate-named shim for 'axe-logger', referenced in third-party
* dependencies. This could alternately just be included within our
* RequireJS configuration.
*/
define(['axe-logger'], function(axe) {
return axe;
});

View file

@ -0,0 +1,36 @@
/**
* Customized shim for browserbox to use 'slog' with configurable logging level
* that can be cranked up.
*/
define(function(require) {
var logic = require('logic');
var scope = logic.scope('BrowserBox');
return {
/**
* Provide a .debug for things that are *only* logged when
* sensitive logging is enabled. This exists right now mainly for
* the benefit of the email.js libs. We're tying "debug" to
* logSensitiveData both because we haven't audited the use of
* debug and also because it is indeed a bit chatty.
*
* TODO: Address the logging detail level as a separate issue,
* ideally while working with whiteout.io to fancify the email.js
* logging slightly.
*/
debug: function(ignoredTag, msg) {
if (!logic.isCensored) {
logic(scope, 'debug', { msg: msg });
}
},
log: function(ignoredTag, msg) {
logic(scope, 'log', { msg: msg });
},
warn: function(ignoredTag, msg) {
logic(scope, 'warn', { msg: msg });
},
error: function(ignoredTag, msg) {
logic(scope, 'error', { msg: msg });
}
};
});

View file

@ -0,0 +1,26 @@
/**
* Customized shim for browserbox to use 'slog' with configurable logging level
* that can be cranked up.
*/
define(function(require) {
var logic = require('logic');
var scope = logic.scope('SmtpClient');
return {
// see axeshim-browserbox's comment about '.debug'
debug: function(ignoredTag, msg) {
if (!logic.isCensored) {
logic(scope, 'debug', { msg: msg });
}
},
log: function(ignoredTag, msg) {
logic(scope, 'log', { msg: msg });
},
warn: function(ignoredTag, msg) {
logic(scope, 'warn', { msg: msg });
},
error: function(ignoredTag, msg) {
logic(scope, 'error', { msg: msg });
}
};
});

View file

@ -0,0 +1,10 @@
// for node, require jsdom bindings, and pass it in as the document constructor
var jsdom = require('jsdom');
module.exports = require('./lib/bleach.js');
module.exports.documentConstructor = jsdom.jsdom;
module.exports._preCleanNodeHack = function(node, html) {
// workaround for jsdom comment bug
if (node.innerHTML === '' && html.match(/<!--/)) {
node.innerHTML = (html + '-->');
}
};

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,380 @@
(function (root, factory) {
// Universal Module Definition (UMD) to support AMD, CommonJS/Node.js,
// Rhino, and plain browser loading.
if (typeof define === 'function' && define.amd) {
define(['require', 'exports'], factory);
} else if (typeof exports !== 'undefined') {
factory(require, exports);
} else {
factory(root);
}
}(this, function (require, exports) {
var tokenizer = require('./tokenizer');
function parse(tokens, initialMode) {
var mode = initialMode || 'top-level';
var i = -1;
var token;
var stylesheet;
switch (mode) {
case 'top-level':
stylesheet = new Stylesheet;
break;
// (Used for style attributes; start out parsing declarations which
// means that our container must be a StyleRule.)
case 'declaration':
stylesheet = new StyleRule;
break;
}
stylesheet.startTok = tokens[0];
//console.log(' initial tok', JSON.stringify(tokens[0]), JSON.stringify(stylesheet));
var stack = [stylesheet];
var rule = stack[0];
var consume = function(advance) {
if(advance === undefined) advance = 1;
i += advance;
if(i < tokens.length)
token = tokens[i];
else
token = new EOFToken;
return true;
};
var reprocess = function() {
i--;
return true;
}
var next = function() {
return tokens[i+1];
};
var switchto = function(newmode) {
if(newmode === undefined) {
if(rule.fillType !== '')
mode = rule.fillType;
else if(rule.type == 'STYLESHEET')
mode = 'top-level'
// mozmod: disable console.log
else { /* console.log("Unknown rule-type while switching to current rule's content mode: ",rule); mode = ''; */ }
} else {
mode = newmode;
}
return true;
}
var push = function(newRule) {
rule = newRule;
rule.startTok = token;
//console.log(' startTok', JSON.stringify(token), JSON.stringify(rule));
stack.push(rule);
return true;
}
var parseerror = function(msg) {
// mozmod: disable console.log
//console.log("Parse error at token " + i + ": " + token + ".\n" + msg);
return true;
}
var pop = function() {
var oldrule = stack.pop();
oldrule.endTok = token;
//console.log(' endTok', JSON.stringify(token), JSON.stringify(oldrule));
rule = stack[stack.length - 1];
rule.append(oldrule);
return true;
}
var discard = function() {
stack.pop();
rule = stack[stack.length - 1];
return true;
}
var finish = function() {
while(stack.length > 1) {
pop();
}
rule.endTok = token;
//console.log(' endTok', JSON.stringify(token), JSON.stringify(rule));
}
for(;;) {
consume();
switch(mode) {
case "top-level":
switch(token.tokenType) {
case "CDO":
case "CDC":
case "WHITESPACE": break;
case "AT-KEYWORD": push(new AtRule(token.value)) && switchto('at-rule'); break;
case "{": parseerror("Attempt to open a curly-block at top-level.") && consumeAPrimitive(); break;
case "EOF": finish(); return stylesheet;
default: push(new StyleRule) && switchto('selector') && reprocess();
}
break;
case "at-rule":
switch(token.tokenType) {
case ";": pop() && switchto(); break;
case "{":
if(rule.fillType !== '') switchto(rule.fillType);
else parseerror("Attempt to open a curly-block in a statement-type at-rule.") && discard() && switchto('next-block') && reprocess();
break;
case "EOF": finish(); return stylesheet;
default: rule.appendPrelude(consumeAPrimitive());
}
break;
case "rule":
switch(token.tokenType) {
case "WHITESPACE": break;
case "}": pop() && switchto(); break;
case "AT-KEYWORD": push(new AtRule(token.value)) && switchto('at-rule'); break;
case "EOF": finish(); return stylesheet;
default: push(new StyleRule) && switchto('selector') && reprocess();
}
break;
case "selector":
switch(token.tokenType) {
case "{": switchto('declaration'); break;
case "EOF": discard() && finish(); return stylesheet;
default: rule.appendSelector(consumeAPrimitive());
}
break;
case "declaration":
switch(token.tokenType) {
case "WHITESPACE":
case ";": break;
case "}": pop() && switchto(); break;
case "AT-RULE": push(new AtRule(token.value)) && switchto('at-rule'); break;
case "IDENT": push(new Declaration(token.value)) && switchto('after-declaration-name'); break;
case "EOF": finish(); return stylesheet;
default: parseerror() && discard() && switchto('next-declaration');
}
break;
case "after-declaration-name":
switch(token.tokenType) {
case "WHITESPACE": break;
case ":": switchto('declaration-value'); break;
case ";": parseerror("Incomplete declaration - semicolon after property name.") && discard() && switchto(); break;
case "EOF": discard() && finish(); return stylesheet;
default: parseerror("Invalid declaration - additional token after property name") && discard() && switchto('next-declaration');
}
break;
case "declaration-value":
switch(token.tokenType) {
case "DELIM":
if(token.value == "!" && next().tokenType == 'IDENTIFIER' && next().value.toLowerCase() == "important") {
consume();
rule.important = true;
switchto('declaration-end');
} else {
rule.append(token);
}
break;
case ";": pop() && switchto(); break;
case "}": pop() && pop() && switchto(); break;
case "EOF": finish(); return stylesheet;
default: rule.append(consumeAPrimitive());
}
break;
case "declaration-end":
switch(token.tokenType) {
case "WHITESPACE": break;
case ";": pop() && switchto(); break;
case "}": pop() && pop() && switchto(); break;
case "EOF": finish(); return stylesheet;
default: parseerror("Invalid declaration - additional token after !important.") && discard() && switchto('next-declaration');
}
break;
case "next-block":
switch(token.tokenType) {
case "{": consumeAPrimitive() && switchto(); break;
case "EOF": finish(); return stylesheet;
default: consumeAPrimitive(); break;
}
break;
case "next-declaration":
switch(token.tokenType) {
case ";": switchto('declaration'); break;
case "}": switchto('declaration') && reprocess(); break;
case "EOF": finish(); return stylesheet;
default: consumeAPrimitive(); break;
}
break;
default:
// If you hit this, it's because one of the switchto() calls is typo'd.
// mozmod: disable console.log
//console.log('Unknown parsing mode: ' + mode);
return;
}
}
function consumeAPrimitive() {
switch(token.tokenType) {
case "(":
case "[":
case "{": return consumeASimpleBlock();
case "FUNCTION": return consumeAFunc();
default: return token;
}
}
function consumeASimpleBlock() {
var endingTokenType = {"(":")", "[":"]", "{":"}"}[token.tokenType];
var block = new SimpleBlock(token.tokenType);
for(;;) {
consume();
switch(token.tokenType) {
case "EOF":
case endingTokenType: return block;
default: block.append(consumeAPrimitive());
}
}
}
function consumeAFunc() {
var func = new Func(token.value);
var arg = new FuncArg();
for(;;) {
consume();
switch(token.tokenType) {
case "EOF":
case ")": func.append(arg); return func;
case "DELIM":
if(token.value == ",") {
func.append(arg);
arg = new FuncArg();
} else {
arg.append(token);
}
break;
default: arg.append(consumeAPrimitive());
}
}
}
}
function CSSParserRule() { return this; }
CSSParserRule.prototype.fillType = '';
CSSParserRule.prototype.toString = function(indent) {
return JSON.stringify(this.toJSON(),null,indent);
}
CSSParserRule.prototype.append = function(val) {
this.value.push(val);
return this;
}
function Stylesheet() {
this.value = [];
return this;
}
Stylesheet.prototype = new CSSParserRule;
Stylesheet.prototype.type = "STYLESHEET";
Stylesheet.prototype.toJSON = function() {
return {type:'stylesheet', value: this.value.map(function(e){return e.toJSON();})};
}
function AtRule(name) {
this.name = name;
this.prelude = [];
this.value = [];
if(name in AtRule.registry)
this.fillType = AtRule.registry[name];
return this;
}
AtRule.prototype = new CSSParserRule;
AtRule.prototype.type = "AT-RULE";
AtRule.prototype.appendPrelude = function(val) {
this.prelude.push(val);
return this;
}
AtRule.prototype.toJSON = function() {
return {type:'at', name:this.name, prelude:this.prelude.map(function(e){return e.toJSON();}), value:this.value.map(function(e){return e.toJSON();})};
}
AtRule.registry = {
'import': '',
'media': 'rule',
'font-face': 'declaration',
'page': 'declaration',
'keyframes': 'rule',
'namespace': '',
'counter-style': 'declaration',
'supports': 'rule',
'document': 'rule',
'font-feature-values': 'declaration',
'viewport': '',
'region-style': 'rule'
};
function StyleRule() {
this.selector = [];
this.value = [];
return this;
}
StyleRule.prototype = new CSSParserRule;
StyleRule.prototype.type = "STYLE-RULE";
StyleRule.prototype.fillType = 'declaration';
StyleRule.prototype.appendSelector = function(val) {
this.selector.push(val);
return this;
}
StyleRule.prototype.toJSON = function() {
return {type:'selector', selector:this.selector.map(function(e){return e.toJSON();}), value:this.value.map(function(e){return e.toJSON();})};
}
function Declaration(name) {
this.name = name;
this.value = [];
return this;
}
Declaration.prototype = new CSSParserRule;
Declaration.prototype.type = "DECLARATION";
Declaration.prototype.toJSON = function() {
return {type:'declaration', name:this.name, value:this.value.map(function(e){return e.toJSON();})};
}
function SimpleBlock(type) {
this.name = type;
this.value = [];
return this;
}
SimpleBlock.prototype = new CSSParserRule;
SimpleBlock.prototype.type = "BLOCK";
SimpleBlock.prototype.toJSON = function() {
return {type:'block', name:this.name, value:this.value.map(function(e){return e.toJSON();})};
}
function Func(name) {
this.name = name;
this.value = [];
return this;
}
Func.prototype = new CSSParserRule;
Func.prototype.type = "FUNCTION";
Func.prototype.toJSON = function() {
return {type:'func', name:this.name, value:this.value.map(function(e){return e.toJSON();})};
}
function FuncArg() {
this.value = [];
return this;
}
FuncArg.prototype = new CSSParserRule;
FuncArg.prototype.type = "FUNCTION-ARG";
FuncArg.prototype.toJSON = function() {
return this.value.map(function(e){return e.toJSON();});
}
// Exportation.
// TODO: also export the various rule objects?
exports.parse = parse;
}));

View file

@ -0,0 +1,739 @@
(function (root, factory) {
// Universal Module Definition (UMD) to support AMD, CommonJS/Node.js,
// Rhino, and plain browser loading.
if (typeof define === 'function' && define.amd) {
define(['exports'], factory);
} else if (typeof exports !== 'undefined') {
factory(exports);
} else {
factory(root);
}
}(this, function (exports) {
var between = function (num, first, last) { return num >= first && num <= last; }
function digit(code) { return between(code, 0x30,0x39); }
function hexdigit(code) { return digit(code) || between(code, 0x41,0x46) || between(code, 0x61,0x66); }
function uppercaseletter(code) { return between(code, 0x41,0x5a); }
function lowercaseletter(code) { return between(code, 0x61,0x7a); }
function letter(code) { return uppercaseletter(code) || lowercaseletter(code); }
function nonascii(code) { return code >= 0xa0; }
function namestartchar(code) { return letter(code) || nonascii(code) || code == 0x5f; }
function namechar(code) { return namestartchar(code) || digit(code) || code == 0x2d; }
function nonprintable(code) { return between(code, 0,8) || between(code, 0xe,0x1f) || between(code, 0x7f,0x9f); }
function newline(code) { return code == 0xa || code == 0xc; }
function whitespace(code) { return newline(code) || code == 9 || code == 0x20; }
function badescape(code) { return newline(code) || isNaN(code); }
// Note: I'm not yet acting smart enough to actually handle astral characters.
var maximumallowedcodepoint = 0x10ffff;
function tokenize(str, options) {
if(options == undefined) options = {transformFunctionWhitespace:false, scientificNotation:false};
var i = -1;
var tokens = [];
var state = "data";
var code;
var currtoken;
// Line number information.
var line = 0;
var column = 0;
// The only use of lastLineLength is in reconsume().
var lastLineLength = 0;
var incrLineno = function() {
line += 1;
lastLineLength = column;
column = 0;
};
var locStart = {line:line, column:column};
var next = function(num) { if(num === undefined) num = 1; return str.charCodeAt(i+num); };
var consume = function(num) {
if(num === undefined)
num = 1;
i += num;
code = str.charCodeAt(i);
if (newline(code)) incrLineno();
else column += num;
//console.log('Consume '+i+' '+String.fromCharCode(code) + ' 0x' + code.toString(16));
return true;
};
var reconsume = function() {
i -= 1;
if (newline(code)) {
line -= 1;
column = lastLineLength;
} else {
column -= 1;
}
locStart.line = line;
locStart.column = column;
return true;
};
var eof = function() { return i >= str.length; };
var donothing = function() {};
var emit = function(token) {
if(token) {
token.finish();
} else {
token = currtoken.finish();
}
if (options.loc === true) {
token.loc = {};
token.loc.start = {line:locStart.line, column:locStart.column, idx: locStart.idx};
locStart = {line: line, column: column, idx: i};
token.loc.end = locStart;
}
tokens.push(token);
//console.log('Emitting ' + token);
currtoken = undefined;
return true;
};
var create = function(token) { currtoken = token; return true; };
// mozmod: disable console.log
var parseerror = function() { /* console.log("Parse error at index " + i + ", processing codepoint 0x" + code.toString(16) + " in state " + state + "."); */ return true; };
// mozmod: disable console.log
var catchfire = function(msg) { /* console.log("MAJOR SPEC ERROR: " + msg); */ return true;}
var switchto = function(newstate) {
state = newstate;
//console.log('Switching to ' + state);
return true;
};
var consumeEscape = function() {
// Assume the the current character is the \
consume();
if(hexdigit(code)) {
// Consume 1-6 hex digits
var digits = [];
for(var total = 0; total < 6; total++) {
if(hexdigit(code)) {
digits.push(code);
consume();
} else { break; }
}
var value = parseInt(digits.map(String.fromCharCode).join(''), 16);
if( value > maximumallowedcodepoint ) value = 0xfffd;
// If the current char is whitespace, cool, we'll just eat it.
// Otherwise, put it back.
if(!whitespace(code)) reconsume();
return value;
} else {
return code;
}
};
for(;;) {
if(i > str.length*2) return "I'm infinite-looping!";
consume();
switch(state) {
case "data":
if(whitespace(code)) {
emit(new WhitespaceToken);
while(whitespace(next())) consume();
}
else if(code == 0x22) switchto("double-quote-string");
else if(code == 0x23) switchto("hash");
else if(code == 0x27) switchto("single-quote-string");
else if(code == 0x28) emit(new OpenParenToken);
else if(code == 0x29) emit(new CloseParenToken);
else if(code == 0x2b) {
if(digit(next()) || (next() == 0x2e && digit(next(2)))) switchto("number") && reconsume();
else emit(new DelimToken(code));
}
else if(code == 0x2d) {
if(next(1) == 0x2d && next(2) == 0x3e) consume(2) && emit(new CDCToken);
else if(digit(next()) || (next(1) == 0x2e && digit(next(2)))) switchto("number") && reconsume();
else switchto('ident') && reconsume();
}
else if(code == 0x2e) {
if(digit(next())) switchto("number") && reconsume();
else emit(new DelimToken(code));
}
else if(code == 0x2f) {
if(next() == 0x2a) consume() && switchto("comment");
else emit(new DelimToken(code));
}
else if(code == 0x3a) emit(new ColonToken);
else if(code == 0x3b) emit(new SemicolonToken);
else if(code == 0x3c) {
if(next(1) == 0x21 && next(2) == 0x2d && next(3) == 0x2d) consume(3) && emit(new CDOToken);
else emit(new DelimToken(code));
}
else if(code == 0x40) switchto("at-keyword");
else if(code == 0x5b) emit(new OpenSquareToken);
else if(code == 0x5c) {
if(badescape(next())) parseerror() && emit(new DelimToken(code));
else switchto('ident') && reconsume();
}
else if(code == 0x5d) emit(new CloseSquareToken);
else if(code == 0x7b) emit(new OpenCurlyToken);
else if(code == 0x7d) emit(new CloseCurlyToken);
else if(digit(code)) switchto("number") && reconsume();
else if(code == 0x55 || code == 0x75) {
if(next(1) == 0x2b && hexdigit(next(2))) consume() && switchto("unicode-range");
else switchto('ident') && reconsume();
}
else if(namestartchar(code)) switchto('ident') && reconsume();
else if(eof()) { emit(new EOFToken); return tokens; }
else emit(new DelimToken(code));
break;
case "double-quote-string":
if(currtoken == undefined) create(new StringToken);
if(code == 0x22) emit() && switchto("data");
else if(eof()) parseerror() && emit() && switchto("data") && reconsume();
else if(newline(code)) parseerror() && emit(new BadStringToken) && switchto("data") && reconsume();
else if(code == 0x5c) {
if(badescape(next())) parseerror() && emit(new BadStringToken) && switchto("data");
else if(newline(next())) consume();
else currtoken.append(consumeEscape());
}
else currtoken.append(code);
break;
case "single-quote-string":
if(currtoken == undefined) create(new StringToken);
if(code == 0x27) emit() && switchto("data");
else if(eof()) parseerror() && emit() && switchto("data");
else if(newline(code)) parseerror() && emit(new BadStringToken) && switchto("data") && reconsume();
else if(code == 0x5c) {
if(badescape(next())) parseerror() && emit(new BadStringToken) && switchto("data");
else if(newline(next())) consume();
else currtoken.append(consumeEscape());
}
else currtoken.append(code);
break;
case "hash":
if(namechar(code)) create(new HashToken(code)) && switchto("hash-rest");
else if(code == 0x5c) {
if(badescape(next())) parseerror() && emit(new DelimToken(0x23)) && switchto("data") && reconsume();
else create(new HashToken(consumeEscape())) && switchto('hash-rest');
}
else emit(new DelimToken(0x23)) && switchto('data') && reconsume();
break;
case "hash-rest":
if(namechar(code)) currtoken.append(code);
else if(code == 0x5c) {
if(badescape(next())) parseerror() && emit() && switchto("data") && reconsume();
else currtoken.append(consumeEscape());
}
else emit() && switchto('data') && reconsume();
break;
case "comment":
if(code == 0x2a) {
if(next() == 0x2f) consume() && switchto('data');
else donothing();
}
else if(eof()) parseerror() && switchto('data') && reconsume();
else donothing();
break;
case "at-keyword":
if(code == 0x2d) {
if(namestartchar(next())) create(new AtKeywordToken(0x2d)) && switchto('at-keyword-rest');
else if(next(1) == 0x5c && !badescape(next(2))) create(new AtKeywordtoken(0x2d)) && switchto('at-keyword-rest');
else parseerror() && emit(new DelimToken(0x40)) && switchto('data') && reconsume();
}
else if(namestartchar(code)) create(new AtKeywordToken(code)) && switchto('at-keyword-rest');
else if(code == 0x5c) {
if(badescape(next())) parseerror() && emit(new DelimToken(0x23)) && switchto("data") && reconsume();
else create(new AtKeywordToken(consumeEscape())) && switchto('at-keyword-rest');
}
else emit(new DelimToken(0x40)) && switchto('data') && reconsume();
break;
case "at-keyword-rest":
if(namechar(code)) currtoken.append(code);
else if(code == 0x5c) {
if(badescape(next())) parseerror() && emit() && switchto("data") && reconsume();
else currtoken.append(consumeEscape());
}
else emit() && switchto('data') && reconsume();
break;
case "ident":
if(code == 0x2d) {
if(namestartchar(next())) create(new IdentifierToken(code)) && switchto('ident-rest');
else if(next(1) == 0x5c && !badescape(next(2))) create(new IdentifierToken(code)) && switchto('ident-rest');
else emit(new DelimToken(0x2d)) && switchto('data');
}
else if(namestartchar(code)) create(new IdentifierToken(code)) && switchto('ident-rest');
else if(code == 0x5c) {
if(badescape(next())) parseerror() && switchto("data") && reconsume();
else create(new IdentifierToken(consumeEscape())) && switchto('ident-rest');
}
else catchfire("Hit the generic 'else' clause in ident state.") && switchto('data') && reconsume();
break;
case "ident-rest":
if(namechar(code)) currtoken.append(code);
else if(code == 0x5c) {
if(badescape(next())) parseerror() && emit() && switchto("data") && reconsume();
else currtoken.append(consumeEscape());
}
else if(code == 0x28) {
if(currtoken.ASCIImatch('url')) switchto('url');
else emit(new FunctionToken(currtoken)) && switchto('data');
}
else if(whitespace(code) && options.transformFunctionWhitespace) switchto('transform-function-whitespace') && reconsume();
else emit() && switchto('data') && reconsume();
break;
case "transform-function-whitespace":
if(whitespace(next())) donothing();
else if(code == 0x28) emit(new FunctionToken(currtoken)) && switchto('data');
else emit() && switchto('data') && reconsume();
break;
case "number":
create(new NumberToken());
if(code == 0x2d) {
if(digit(next())) consume() && currtoken.append([0x2d,code]) && switchto('number-rest');
else if(next(1) == 0x2e && digit(next(2))) consume(2) && currtoken.append([0x2d,0x2e,code]) && switchto('number-fraction');
else switchto('data') && reconsume();
}
else if(code == 0x2b) {
if(digit(next())) consume() && currtoken.append([0x2b,code]) && switchto('number-rest');
else if(next(1) == 0x2e && digit(next(2))) consume(2) && currtoken.append([0x2b,0x2e,code]) && switchto('number-fraction');
else switchto('data') && reconsume();
}
else if(digit(code)) currtoken.append(code) && switchto('number-rest');
else if(code == 0x2e) {
if(digit(next())) consume() && currtoken.append([0x2e,code]) && switchto('number-fraction');
else switchto('data') && reconsume();
}
else switchto('data') && reconsume();
break;
case "number-rest":
if(digit(code)) currtoken.append(code);
else if(code == 0x2e) {
if(digit(next())) consume() && currtoken.append([0x2e,code]) && switchto('number-fraction');
else emit() && switchto('data') && reconsume();
}
else if(code == 0x25) emit(new PercentageToken(currtoken)) && switchto('data');
else if(code == 0x45 || code == 0x65) {
if(digit(next())) consume() && currtoken.append([0x25,code]) && switchto('sci-notation');
else if((next(1) == 0x2b || next(1) == 0x2d) && digit(next(2))) currtoken.append([0x25,next(1),next(2)]) && consume(2) && switchto('sci-notation');
else create(new DimensionToken(currtoken,code)) && switchto('dimension');
}
else if(code == 0x2d) {
if(namestartchar(next())) consume() && create(new DimensionToken(currtoken,[0x2d,code])) && switchto('dimension');
else if(next(1) == 0x5c && badescape(next(2))) parseerror() && emit() && switchto('data') && reconsume();
else if(next(1) == 0x5c) consume() && create(new DimensionToken(currtoken, [0x2d,consumeEscape()])) && switchto('dimension');
else emit() && switchto('data') && reconsume();
}
else if(namestartchar(code)) create(new DimensionToken(currtoken, code)) && switchto('dimension');
else if(code == 0x5c) {
if(badescape(next)) parseerror() && emit() && switchto('data') && reconsume();
else create(new DimensionToken(currtoken,consumeEscape)) && switchto('dimension');
}
else emit() && switchto('data') && reconsume();
break;
case "number-fraction":
currtoken.type = "number";
if(digit(code)) currtoken.append(code);
else if(code == 0x25) emit(new PercentageToken(currtoken)) && switchto('data');
else if(code == 0x45 || code == 0x65) {
if(digit(next())) consume() && currtoken.append([0x65,code]) && switchto('sci-notation');
else if((next(1) == 0x2b || next(1) == 0x2d) && digit(next(2))) currtoken.append([0x65,next(1),next(2)]) && consume(2) && switchto('sci-notation');
else create(new DimensionToken(currtoken,code)) && switchto('dimension');
}
else if(code == 0x2d) {
if(namestartchar(next())) consume() && create(new DimensionToken(currtoken,[0x2d,code])) && switchto('dimension');
else if(next(1) == 0x5c && badescape(next(2))) parseerror() && emit() && switchto('data') && reconsume();
else if(next(1) == 0x5c) consume() && create(new DimensionToken(currtoken, [0x2d,consumeEscape()])) && switchto('dimension');
else emit() && switchto('data') && reconsume();
}
else if(namestartchar(code)) create(new DimensionToken(currtoken, code)) && switchto('dimension');
else if(code == 0x5c) {
if(badescape(next)) parseerror() && emit() && switchto('data') && reconsume();
else create(new DimensionToken(currtoken,consumeEscape())) && switchto('dimension');
}
else emit() && switchto('data') && reconsume();
break;
case "dimension":
if(namechar(code)) currtoken.append(code);
else if(code == 0x5c) {
if(badescape(next())) parseerror() && emit() && switchto('data') && reconsume();
else currtoken.append(consumeEscape());
}
else emit() && switchto('data') && reconsume();
break;
case "sci-notation":
currtoken.type = "number";
if(digit(code)) currtoken.append(code);
else emit() && switchto('data') && reconsume();
break;
case "url":
if(eof()) parseerror() && emit(new BadURLToken) && switchto('data');
else if(code == 0x22) switchto('url-double-quote');
else if(code == 0x27) switchto('url-single-quote');
else if(code == 0x29) emit(new URLToken) && switchto('data');
else if(whitespace(code)) donothing();
else switchto('url-unquoted') && reconsume();
break;
case "url-double-quote":
if(! (currtoken instanceof URLToken)) create(new URLToken);
if(eof()) parseerror() && emit(new BadURLToken) && switchto('data');
else if(code == 0x22) switchto('url-end');
else if(newline(code)) parseerror() && switchto('bad-url');
else if(code == 0x5c) {
if(newline(next())) consume();
else if(badescape(next())) parseerror() && emit(new BadURLToken) && switchto('data') && reconsume();
else currtoken.append(consumeEscape());
}
else currtoken.append(code);
break;
case "url-single-quote":
if(! (currtoken instanceof URLToken)) create(new URLToken);
if(eof()) parseerror() && emit(new BadURLToken) && switchto('data');
else if(code == 0x27) switchto('url-end');
else if(newline(code)) parseerror() && switchto('bad-url');
else if(code == 0x5c) {
if(newline(next())) consume();
else if(badescape(next())) parseerror() && emit(new BadURLToken) && switchto('data') && reconsume();
else currtoken.append(consumeEscape());
}
else currtoken.append(code);
break;
case "url-end":
if(eof()) parseerror() && emit(new BadURLToken) && switchto('data');
else if(whitespace(code)) donothing();
else if(code == 0x29) emit() && switchto('data');
else parseerror() && switchto('bad-url') && reconsume();
break;
case "url-unquoted":
if(! (currtoken instanceof URLToken)) create(new URLToken);
if(eof()) parseerror() && emit(new BadURLToken) && switchto('data');
else if(whitespace(code)) switchto('url-end');
else if(code == 0x29) emit() && switchto('data');
else if(code == 0x22 || code == 0x27 || code == 0x28 || nonprintable(code)) parseerror() && switchto('bad-url');
else if(code == 0x5c) {
if(badescape(next())) parseerror() && switchto('bad-url');
else currtoken.append(consumeEscape());
}
else currtoken.append(code);
break;
case "bad-url":
if(eof()) parseerror() && emit(new BadURLToken) && switchto('data');
else if(code == 0x29) emit(new BadURLToken) && switchto('data');
else if(code == 0x5c) {
if(badescape(next())) donothing();
else consumeEscape();
}
else donothing();
break;
case "unicode-range":
// We already know that the current code is a hexdigit.
var start = [code], end = [code];
for(var total = 1; total < 6; total++) {
if(hexdigit(next())) {
consume();
start.push(code);
end.push(code);
}
else break;
}
if(next() == 0x3f) {
for(;total < 6; total++) {
if(next() == 0x3f) {
consume();
start.push("0".charCodeAt(0));
end.push("f".charCodeAt(0));
}
else break;
}
emit(new UnicodeRangeToken(start,end)) && switchto('data');
}
else if(next(1) == 0x2d && hexdigit(next(2))) {
consume();
consume();
end = [code];
for(var total = 1; total < 6; total++) {
if(hexdigit(next())) {
consume();
end.push(code);
}
else break;
}
emit(new UnicodeRangeToken(start,end)) && switchto('data');
}
else emit(new UnicodeRangeToken(start)) && switchto('data');
break;
default:
catchfire("Unknown state '" + state + "'");
}
}
}
function stringFromCodeArray(arr) {
return String.fromCharCode.apply(null,arr.filter(function(e){return e;}));
}
function CSSParserToken(options) { return this; }
CSSParserToken.prototype.finish = function() { return this; }
CSSParserToken.prototype.toString = function() { return this.tokenType; }
CSSParserToken.prototype.toJSON = function() { return this.toString(); }
function BadStringToken() { return this; }
BadStringToken.prototype = new CSSParserToken;
BadStringToken.prototype.tokenType = "BADSTRING";
function BadURLToken() { return this; }
BadURLToken.prototype = new CSSParserToken;
BadURLToken.prototype.tokenType = "BADURL";
function WhitespaceToken() { return this; }
WhitespaceToken.prototype = new CSSParserToken;
WhitespaceToken.prototype.tokenType = "WHITESPACE";
WhitespaceToken.prototype.toString = function() { return "WS"; }
function CDOToken() { return this; }
CDOToken.prototype = new CSSParserToken;
CDOToken.prototype.tokenType = "CDO";
function CDCToken() { return this; }
CDCToken.prototype = new CSSParserToken;
CDCToken.prototype.tokenType = "CDC";
function ColonToken() { return this; }
ColonToken.prototype = new CSSParserToken;
ColonToken.prototype.tokenType = ":";
function SemicolonToken() { return this; }
SemicolonToken.prototype = new CSSParserToken;
SemicolonToken.prototype.tokenType = ";";
function OpenCurlyToken() { return this; }
OpenCurlyToken.prototype = new CSSParserToken;
OpenCurlyToken.prototype.tokenType = "{";
function CloseCurlyToken() { return this; }
CloseCurlyToken.prototype = new CSSParserToken;
CloseCurlyToken.prototype.tokenType = "}";
function OpenSquareToken() { return this; }
OpenSquareToken.prototype = new CSSParserToken;
OpenSquareToken.prototype.tokenType = "[";
function CloseSquareToken() { return this; }
CloseSquareToken.prototype = new CSSParserToken;
CloseSquareToken.prototype.tokenType = "]";
function OpenParenToken() { return this; }
OpenParenToken.prototype = new CSSParserToken;
OpenParenToken.prototype.tokenType = "(";
function CloseParenToken() { return this; }
CloseParenToken.prototype = new CSSParserToken;
CloseParenToken.prototype.tokenType = ")";
function EOFToken() { return this; }
EOFToken.prototype = new CSSParserToken;
EOFToken.prototype.tokenType = "EOF";
function DelimToken(code) {
this.value = String.fromCharCode(code);
return this;
}
DelimToken.prototype = new CSSParserToken;
DelimToken.prototype.tokenType = "DELIM";
DelimToken.prototype.toString = function() { return "DELIM("+this.value+")"; }
function StringValuedToken() { return this; }
StringValuedToken.prototype = new CSSParserToken;
StringValuedToken.prototype.append = function(val) {
if(val instanceof Array) {
for(var i = 0; i < val.length; i++) {
this.value.push(val[i]);
}
} else {
this.value.push(val);
}
return true;
}
StringValuedToken.prototype.finish = function() {
this.value = this.valueAsString();
return this;
}
StringValuedToken.prototype.ASCIImatch = function(str) {
return this.valueAsString().toLowerCase() == str.toLowerCase();
}
StringValuedToken.prototype.valueAsString = function() {
if(typeof this.value == 'string') return this.value;
return stringFromCodeArray(this.value);
}
StringValuedToken.prototype.valueAsCodes = function() {
if(typeof this.value == 'string') {
var ret = [];
for(var i = 0; i < this.value.length; i++)
ret.push(this.value.charCodeAt(i));
return ret;
}
return this.value.filter(function(e){return e;});
}
function IdentifierToken(val) {
this.value = [];
this.append(val);
}
IdentifierToken.prototype = new StringValuedToken;
IdentifierToken.prototype.tokenType = "IDENT";
IdentifierToken.prototype.toString = function() { return "IDENT("+this.value+")"; }
function FunctionToken(val) {
// These are always constructed by passing an IdentifierToken
this.value = val.finish().value;
}
FunctionToken.prototype = new StringValuedToken;
FunctionToken.prototype.tokenType = "FUNCTION";
FunctionToken.prototype.toString = function() { return "FUNCTION("+this.value+")"; }
function AtKeywordToken(val) {
this.value = [];
this.append(val);
}
AtKeywordToken.prototype = new StringValuedToken;
AtKeywordToken.prototype.tokenType = "AT-KEYWORD";
AtKeywordToken.prototype.toString = function() { return "AT("+this.value+")"; }
function HashToken(val) {
this.value = [];
this.append(val);
}
HashToken.prototype = new StringValuedToken;
HashToken.prototype.tokenType = "HASH";
HashToken.prototype.toString = function() { return "HASH("+this.value+")"; }
function StringToken(val) {
this.value = [];
this.append(val);
}
StringToken.prototype = new StringValuedToken;
StringToken.prototype.tokenType = "STRING";
StringToken.prototype.toString = function() { return "\""+this.value+"\""; }
function URLToken(val) {
this.value = [];
this.append(val);
}
URLToken.prototype = new StringValuedToken;
URLToken.prototype.tokenType = "URL";
URLToken.prototype.toString = function() { return "URL("+this.value+")"; }
function NumberToken(val) {
this.value = [];
this.append(val);
this.type = "integer";
}
NumberToken.prototype = new StringValuedToken;
NumberToken.prototype.tokenType = "NUMBER";
NumberToken.prototype.toString = function() {
if(this.type == "integer")
return "INT("+this.value+")";
return "NUMBER("+this.value+")";
}
NumberToken.prototype.finish = function() {
this.repr = this.valueAsString();
this.value = this.repr * 1;
if(Math.abs(this.value) % 1 != 0) this.type = "number";
return this;
}
function PercentageToken(val) {
// These are always created by passing a NumberToken as val
val.finish();
this.value = val.value;
this.repr = val.repr;
}
PercentageToken.prototype = new CSSParserToken;
PercentageToken.prototype.tokenType = "PERCENTAGE";
PercentageToken.prototype.toString = function() { return "PERCENTAGE("+this.value+")"; }
function DimensionToken(val,unit) {
// These are always created by passing a NumberToken as the val
val.finish();
this.num = val.value;
this.unit = [];
this.repr = val.repr;
this.append(unit);
}
DimensionToken.prototype = new CSSParserToken;
DimensionToken.prototype.tokenType = "DIMENSION";
DimensionToken.prototype.toString = function() { return "DIM("+this.num+","+this.unit+")"; }
DimensionToken.prototype.append = function(val) {
if(val instanceof Array) {
for(var i = 0; i < val.length; i++) {
this.unit.push(val[i]);
}
} else {
this.unit.push(val);
}
return true;
}
DimensionToken.prototype.finish = function() {
this.unit = stringFromCodeArray(this.unit);
this.repr += this.unit;
return this;
}
function UnicodeRangeToken(start,end) {
// start and end are array of char codes, completely finished
start = parseInt(stringFromCodeArray(start),16);
if(end === undefined) end = start + 1;
else end = parseInt(stringFromCodeArray(end),16);
if(start > maximumallowedcodepoint) end = start;
if(end < start) end = start;
if(end > maximumallowedcodepoint) end = maximumallowedcodepoint;
this.start = start;
this.end = end;
return this;
}
UnicodeRangeToken.prototype = new CSSParserToken;
UnicodeRangeToken.prototype.tokenType = "UNICODE-RANGE";
UnicodeRangeToken.prototype.toString = function() {
if(this.start+1 == this.end)
return "UNICODE-RANGE("+this.start.toString(16).toUpperCase()+")";
if(this.start < this.end)
return "UNICODE-RANGE("+this.start.toString(16).toUpperCase()+"-"+this.end.toString(16).toUpperCase()+")";
return "UNICODE-RANGE()";
}
UnicodeRangeToken.prototype.contains = function(code) {
return code >= this.start && code < this.end;
}
// Exportation.
// TODO: also export the various tokens objects?
exports.tokenize = tokenize;
exports.EOFToken = EOFToken;
}));

View file

@ -0,0 +1,818 @@
// Copyright (c) 2014 Andris Reinman
// 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.
(function(root, factory) {
'use strict';
if (typeof define === 'function' && define.amd) {
define(['tcp-socket', 'imap-handler', 'mimefuncs', 'axe'], function(TCPSocket, imapHandler, mimefuncs, axe) {
return factory(TCPSocket, imapHandler, mimefuncs, axe);
});
} else if (typeof exports === 'object') {
module.exports = factory(require('tcp-socket'), require('wo-imap-handler'), require('mimefuncs'), require('axe-logger'));
} else {
root.BrowserboxImapClient = factory(navigator.TCPSocket, root.imapHandler, root.mimefuncs, root.axe);
}
}(this, function(TCPSocket, imapHandler, mimefuncs, axe) {
'use strict';
var DEBUG_TAG = 'browserbox IMAP';
/**
* Creates a connection object to an IMAP server. Call `connect` method to inititate
* the actual connection, the constructor only defines the properties but does not actually connect.
*
* @constructor
*
* @param {String} [host='localhost'] Hostname to conenct to
* @param {Number} [port=143] Port number to connect to
* @param {Object} [options] Optional options object
* @param {Boolean} [options.useSecureTransport] Set to true, to use encrypted connection
*/
function ImapClient(host, port, options) {
this._TCPSocket = TCPSocket;
this.options = options || {};
this.port = port || (this.options.useSecureTransport ? 993 : 143);
this.host = host || 'localhost';
/**
* If set to true, start an encrypted connection instead of the plaintext one
* (recommended if applicable). If useSecureTransport is not set but the port used is 993,
* then ecryption is used by default.
*/
this.options.useSecureTransport = 'useSecureTransport' in this.options ? !!this.options.useSecureTransport : this.port === 993;
/**
* Authentication object. If not set, authentication step will be skipped.
*/
this.options.auth = this.options.auth || false;
/**
* Downstream TCP socket to the IMAP server, created with TCPSocket
*/
this.socket = false;
/**
* Indicates if the connection has been closed and can't be used anymore
*
*/
this.destroyed = false;
/**
* Keeps track if the downstream socket is currently full and
* a drain event should be waited for or not
*/
this.waitDrain = false;
// Private properties
/**
* Does the connection use SSL/TLS
*/
this.secureMode = !!this.options.useSecureTransport;
/**
* Is the conection established and greeting is received from the server
*/
this._connectionReady = false;
/**
* As the server sends data in chunks, it needs to be split into
* separate lines. These variables help with parsing the input.
*/
this._remainder = '';
this._command = '';
this._literalRemaining = 0;
/**
* Is there something being processed
*/
this._processingServerData = false;
/**
* Queue of received commands
*/
this._serverQueue = [];
/**
* Is it OK to send something to the server
*/
this._canSend = false;
/**
* Queue of outgoing commands
*/
this._clientQueue = [];
/**
* Counter to allow uniqueue imap tags
*/
this._tagCounter = 0;
/**
* Current command that is waiting for response from the server
*/
this._currentCommand = false;
/**
* Global handlers for unrelated responses (EXPUNGE, EXISTS etc.)
*/
this._globalAcceptUntagged = {};
/**
* Timer waiting to enter idle
*/
this._idleTimer = false;
/**
* Timer waiting to declare the socket dead starting from the last write
*/
this._socketTimeoutTimer = false;
}
// Constants
/**
* How much time to wait since the last response until the connection is considered idling
*/
ImapClient.prototype.TIMEOUT_ENTER_IDLE = 1000;
/**
* Lower Bound for socket timeout to wait since the last data was written to a socket
*/
ImapClient.prototype.TIMEOUT_SOCKET_LOWER_BOUND = 10000;
/**
* Multiplier for socket timeout:
*
* We assume at least a GPRS connection with 115 kb/s = 14,375 kB/s tops, so 10 KB/s to be on
* the safe side. We can timeout after a lower bound of 10s + (n KB / 10 KB/s). A 1 MB message
* upload would be 110 seconds to wait for the timeout. 10 KB/s === 0.1 s/B
*/
ImapClient.prototype.TIMEOUT_SOCKET_MULTIPLIER = 0.1;
// PUBLIC EVENTS
// Event functions should be overriden, these are just placeholders
/**
* Will be run when an error occurs. Connection to the server will be closed automatically,
* so wait for an `onclose` event as well.
*
* @event
* @param {Error} err Error object
*/
ImapClient.prototype.onerror = function() {};
/**
* More data can be buffered in the socket. See `waitDrain` property or
* check if `send` method returns false to see if you should be waiting
* for the drain event.
*
* @event
*/
ImapClient.prototype.ondrain = function() {};
/**
* The connection to the server has been closed
*
* @event
*/
ImapClient.prototype.onclose = function() {};
/**
* The connection to the server has been established and greeting is received
*
* @event
*/
ImapClient.prototype.onready = function() {};
/**
* There are no more commands to process
*
* @event
*/
ImapClient.prototype.onidle = function() {};
// PUBLIC METHODS
/**
* Initiate a connection to the server. Wait for onready event
*/
ImapClient.prototype.connect = function() {
this.socket = this._TCPSocket.open(this.host, this.port, {
binaryType: 'arraybuffer',
useSecureTransport: this.secureMode,
ca: this.options.ca,
tlsWorkerPath: this.options.tlsWorkerPath
});
// allows certificate handling for platform w/o native tls support
// oncert is non standard so setting it might throw if the socket object is immutable
try {
this.socket.oncert = this.oncert;
} catch (E) {}
this.socket.onerror = this._onError.bind(this);
this.socket.onopen = this._onOpen.bind(this);
};
/**
* Closes the connection to the server
*/
ImapClient.prototype.close = function() {
if (this.socket && this.socket.readyState === 'open') {
this.socket.close();
} else {
this._destroy();
}
};
/**
* Closes the connection to the server
*/
ImapClient.prototype.upgrade = function(callback) {
if (this.secureMode) {
return callback(null, false);
}
this.secureMode = true;
this.socket.upgradeToSecure();
callback(null, true);
};
/**
* Schedules a command to be sent to the server. This method is chainable.
* See https://github.com/Kreata/imapHandler for request structure.
* Do not provide a tag property, it will be set byt the queue manager.
*
* To catch untagged responses use acceptUntagged property. For example, if
* the value for it is 'FETCH' then the reponse includes 'payload.FETCH' property
* that is an array including all listed * FETCH responses.
*
* Callback function provides 2 arguments, parsed response object and continue callback.
*
* function(response, next){
* console.log(response);
* next();
* }
*
* @param {Object} request Structured request object
* @param {Array} acceptUntagged a list of untagged responses that will be included in 'payload' property
* @param {Object} [options] Optional data for the command payload, eg. {onplustagged: function(response, next){next();}}
* @param {Function} callback Callback function to run once the command has been processed
*/
ImapClient.prototype.exec = function(request, acceptUntagged, options, callback) {
if (typeof request === 'string') {
request = {
command: request
};
}
this._addToClientQueue(request, acceptUntagged, options, callback);
return this;
};
/**
* Send data to the TCP socket
* Arms a timeout waiting for a response from the server.
*
* @param {String} str Payload
*/
ImapClient.prototype.send = function(str) {
var buffer = mimefuncs.toTypedArray(str).buffer,
timeout = this.TIMEOUT_SOCKET_LOWER_BOUND + Math.floor(buffer.byteLength * this.TIMEOUT_SOCKET_MULTIPLIER);
clearTimeout(this._socketTimeoutTimer); // clear pending timeouts
this._socketTimeoutTimer = setTimeout(this._onTimeout.bind(this), timeout); // arm the next timeout
this.waitDrain = this.socket.send(buffer);
};
/**
* Set a global handler for an untagged response. If currently processed command
* has not listed untagged command it is forwarded to the global handler. Useful
* with EXPUNGE, EXISTS etc.
*
* @param {String} command Untagged command name
* @param {Function} callback Callback function with response object and continue callback function
*/
ImapClient.prototype.setHandler = function(command, callback) {
this._globalAcceptUntagged[(command || '').toString().toUpperCase().trim()] = callback;
};
// INTERNAL EVENTS
/**
* Error handler for the socket
*
* @event
* @param {Event} evt Event object. See evt.data for the error
*/
ImapClient.prototype._onError = function(evt) {
if (this.isError(evt)) {
this.onerror(evt);
} else if (evt && this.isError(evt.data)) {
this.onerror(evt.data);
} else {
this.onerror(new Error(evt && evt.data && evt.data.message || evt.data || evt || 'Error'));
}
this.close();
};
/**
* Ensures that the connection is closed
*/
ImapClient.prototype._destroy = function() {
this._serverQueue = [];
this._clientQueue = [];
this._currentCommand = false;
clearTimeout(this._idleTimer);
clearTimeout(this._socketTimeoutTimer);
if (!this.destroyed) {
this.destroyed = true;
this.onclose();
}
};
/**
* Indicates that the socket has been closed
*
* @event
* @param {Event} evt Event object. Not used
*/
ImapClient.prototype._onClose = function() {
this._destroy();
};
/**
* Indicates that a socket timeout has occurred
*/
ImapClient.prototype._onTimeout = function() {
// inform about the timeout, _onError takes case of the rest
var error = new Error(this.options.sessionId + ' Socket timed out!');
axe.error(DEBUG_TAG, error);
this._onError(error);
};
/**
* More data can be buffered in the socket, `waitDrain` is reset to false
*
* @event
* @param {Event} evt Event object. Not used
*/
ImapClient.prototype._onDrain = function() {
this.waitDrain = false;
this.ondrain();
};
/**
* Handler for incoming data from the server. The data is sent in arbitrary
* chunks and can't be used directly so this function makes sure the data
* is split into complete lines before the data is passed to the command
* handler
*
* @param {Event} evt
*/
ImapClient.prototype._onData = function(evt) {
if (!evt || !evt.data) {
return;
}
clearTimeout(this._socketTimeoutTimer);
var match,
str = mimefuncs.fromTypedArray(evt.data);
if (this._literalRemaining) {
if (this._literalRemaining > str.length) {
this._literalRemaining -= str.length;
this._command += str;
return;
}
this._command += str.substr(0, this._literalRemaining);
str = str.substr(this._literalRemaining);
this._literalRemaining = 0;
}
this._remainder = str = this._remainder + str;
while ((match = str.match(/(\{(\d+)(\+)?\})?\r?\n/))) {
if (!match[2]) {
// Now we have a full command line, so lets do something with it
this._addToServerQueue(this._command + str.substr(0, match.index));
this._remainder = str = str.substr(match.index + match[0].length);
this._command = '';
continue;
}
this._remainder = '';
this._command += str.substr(0, match.index + match[0].length);
this._literalRemaining = Number(match[2]);
str = str.substr(match.index + match[0].length);
if (this._literalRemaining > str.length) {
this._command += str;
this._literalRemaining -= str.length;
return;
} else {
this._command += str.substr(0, this._literalRemaining);
this._remainder = str = str.substr(this._literalRemaining);
this._literalRemaining = 0;
}
}
};
/**
* Connection listener that is run when the connection to the server is opened.
* Sets up different event handlers for the opened socket
*
* @event
*/
ImapClient.prototype._onOpen = function() {
axe.debug(DEBUG_TAG, this.options.sessionId + ' tcp socket opened');
this.socket.ondata = this._onData.bind(this);
this.socket.onclose = this._onClose.bind(this);
this.socket.ondrain = this._onDrain.bind(this);
};
// PRIVATE METHODS
/**
* Pushes command line from the server to the server processing queue. If the
* processor is idle, start processing.
*
* @param {String} cmd Command line
*/
ImapClient.prototype._addToServerQueue = function(cmd) {
this._serverQueue.push(cmd);
if (this._processingServerData) {
return;
}
this._processingServerData = true;
this._processServerQueue();
};
/**
* Process a command from the queue. The command is parsed and feeded to a handler
*/
ImapClient.prototype._processServerQueue = function() {
if (!this._serverQueue.length) {
this._processingServerData = false;
return;
} else {
this._clearIdle();
}
var data = this._serverQueue.shift(),
response;
try {
// + tagged response is a special case, do not try to parse it
if (/^\+/.test(data)) {
response = {
tag: '+',
payload: data.substr(2) || ''
};
} else {
response = imapHandler.parser(data);
axe.debug(DEBUG_TAG, this.options.sessionId + ' S: ' + imapHandler.compiler(response, false, true));
}
} catch (e) {
axe.error(DEBUG_TAG, this.options.sessionId + ' error parsing imap response: ' + e + '\n' + e.stack + '\nraw:' + data);
return this._onError(e);
}
if (response.tag === '*' &&
/^\d+$/.test(response.command) &&
response.attributes && response.attributes.length && response.attributes[0].type === 'ATOM') {
response.nr = Number(response.command);
response.command = (response.attributes.shift().value || '').toString().toUpperCase().trim();
}
// feed the next chunk to the server if a + tagged response was received
if (response.tag === '+') {
if (this._currentCommand.data.length) {
data = this._currentCommand.data.shift();
this.send(data + (!this._currentCommand.data.length ? '\r\n' : ''));
} else if (typeof this._currentCommand.onplustagged === 'function') {
this._currentCommand.onplustagged(response, this._processServerQueue.bind(this));
return;
}
setTimeout(this._processServerQueue.bind(this), 0);
return;
}
this._processServerResponse(response, function(err) {
if (err) {
return this._onError(err);
}
// first response from the server, connection is now usable
if (!this._connectionReady) {
this._connectionReady = true;
this.onready();
this._canSend = true;
this._sendRequest();
} else if (response.tag !== '*') {
// allow sending next command after full response
this._canSend = true;
this._sendRequest();
}
setTimeout(this._processServerQueue.bind(this), 0);
}.bind(this));
};
/**
* Feeds a parsed response object to an appropriate handler
*
* @param {Object} response Parsed command object
* @param {Function} callback Continue callback function
*/
ImapClient.prototype._processServerResponse = function(response, callback) {
var command = (response && response.command || '').toUpperCase().trim();
this._processResponse(response);
if (!this._currentCommand) {
if (response.tag === '*' && command in this._globalAcceptUntagged) {
return this._globalAcceptUntagged[command](response, callback);
} else {
return callback();
}
}
if (this._currentCommand.payload && response.tag === '*' && command in this._currentCommand.payload) {
this._currentCommand.payload[command].push(response);
return callback();
} else if (response.tag === '*' && command in this._globalAcceptUntagged) {
this._globalAcceptUntagged[command](response, callback);
} else if (response.tag === this._currentCommand.tag) {
if (typeof this._currentCommand.callback === 'function') {
if (this._currentCommand.payload && Object.keys(this._currentCommand.payload).length) {
response.payload = this._currentCommand.payload;
}
return this._currentCommand.callback(response, callback);
} else {
return callback();
}
} else {
// Unexpected response
return callback();
}
};
/**
* Adds a request object to outgoing queue. And if data can be sent to the server,
* the command is executed
*
* @param {Object} request Structured request object
* @param {Array} [acceptUntagged] a list of untagged responses that will be included in 'payload' property
* @param {Object} [options] Optional data for the command payload, eg. {onplustagged: function(response, next){next();}}
* @param {Function} callback Callback function to run once the command has been processed
*/
ImapClient.prototype._addToClientQueue = function(request, acceptUntagged, options, callback) {
var tag = 'W' + (++this._tagCounter),
data;
if (!callback && typeof options === 'function') {
callback = options;
options = undefined;
}
if (!callback && typeof acceptUntagged === 'function') {
callback = acceptUntagged;
acceptUntagged = undefined;
}
acceptUntagged = [].concat(acceptUntagged || []).map(function(untagged) {
return (untagged || '').toString().toUpperCase().trim();
});
request.tag = tag;
data = {
tag: tag,
request: request,
payload: acceptUntagged.length ? {} : undefined,
callback: callback
};
// apply any additional options to the command
Object.keys(options || {}).forEach(function(key) {
data[key] = options[key];
});
acceptUntagged.forEach(function(command) {
data.payload[command] = [];
});
// if we're in priority mode (i.e. we ran commands in a precheck),
// queue any commands BEFORE the command that contianed the precheck,
// otherwise just queue command as usual
var index = data.ctx ? this._clientQueue.indexOf(data.ctx) : -1;
if (index >= 0) {
data.tag += '.p';
data.request.tag += '.p';
this._clientQueue.splice(index, 0, data);
} else {
this._clientQueue.push(data);
}
if (this._canSend) {
this._sendRequest();
}
};
/**
* Sends a command from client queue to the server.
*/
ImapClient.prototype._sendRequest = function() {
if (!this._clientQueue.length) {
return this._enterIdle();
}
this._clearIdle();
// an operation was made in the precheck, no need to restart the queue manually
this._restartQueue = false;
var command = this._clientQueue[0];
if (typeof command.precheck === 'function') {
// remember the context
var context = command;
var precheck = context.precheck;
delete context.precheck;
// we need to restart the queue handling if no operation was made in the precheck
this._restartQueue = true;
// invoke the precheck command with a callback to signal that you're
// done with precheck and ready to resume normal operation
precheck(context, function(err) {
// we're done with the precheck
if (!err) {
if (this._restartQueue) {
// we need to restart the queue handling
this._sendRequest();
}
return;
}
// precheck callback failed, so we remove the initial command
// from the queue, invoke its callback and resume normal operation
var cmd, index = this._clientQueue.indexOf(context);
if (index >= 0) {
cmd = this._clientQueue.splice(index, 1)[0];
}
if (cmd && cmd.callback) {
cmd.callback(err, function() {
this._canSend = true;
this._sendRequest();
setTimeout(this._processServerQueue.bind(this), 0);
}.bind(this));
}
}.bind(this));
return;
}
this._canSend = false;
this._currentCommand = this._clientQueue.shift();
var loggedCommand = false;
try {
this._currentCommand.data = imapHandler.compiler(this._currentCommand.request, true);
loggedCommand = imapHandler.compiler(this._currentCommand.request, false, true);
} catch (e) {
axe.error(DEBUG_TAG, this.options.sessionId + ' error compiling imap command: ' + e + '\nstack trace: ' + e.stack + '\nraw:' + this._currentCommand.request);
return this._onError(e);
}
axe.debug(DEBUG_TAG, this.options.sessionId + ' C: ' + loggedCommand);
var data = this._currentCommand.data.shift();
this.send(data + (!this._currentCommand.data.length ? '\r\n' : ''));
return this.waitDrain;
};
/**
* Emits onidle, noting to do currently
*/
ImapClient.prototype._enterIdle = function() {
clearTimeout(this._idleTimer);
this._idleTimer = setTimeout(function() {
this.onidle();
}.bind(this), this.TIMEOUT_ENTER_IDLE);
};
/**
* Cancel idle timer
*/
ImapClient.prototype._clearIdle = function() {
clearTimeout(this._idleTimer);
};
// HELPER FUNCTIONS
/**
* Method checks if a response includes optional response codes
* and copies these into separate properties. For example the
* following response includes a capability listing and a human
* readable message:
*
* * OK [CAPABILITY ID NAMESPACE] All ready
*
* This method adds a 'capability' property with an array value ['ID', 'NAMESPACE']
* to the response object. Additionally 'All ready' is added as 'humanReadable' property.
*
* See possiblem IMAP Response Codes at https://tools.ietf.org/html/rfc5530
*
* @param {Object} response Parsed response object
*/
ImapClient.prototype._processResponse = function(response) {
var command = (response && response.command || '').toString().toUpperCase().trim(),
option,
key;
if (['OK', 'NO', 'BAD', 'BYE', 'PREAUTH'].indexOf(command) >= 0) {
// Check if the response includes an optional response code
if (
(option = response && response.attributes &&
response.attributes.length && response.attributes[0].type === 'ATOM' &&
response.attributes[0].section && response.attributes[0].section.map(function(key) {
if (!key) {
return;
}
if (Array.isArray(key)) {
return key.map(function(key) {
return (key.value || '').toString().trim();
});
} else {
return (key.value || '').toString().toUpperCase().trim();
}
}))) {
key = option && option.shift();
response.code = key;
if (option.length) {
option = [].concat(option || []);
response[key.toLowerCase()] = option.length === 1 ? option[0] : option;
}
}
// If last element of the response is TEXT then this is for humans
if (response && response.attributes && response.attributes.length &&
response.attributes[response.attributes.length - 1].type === 'TEXT') {
response.humanReadable = response.attributes[response.attributes.length - 1].value;
}
}
};
/**
* Checks if a value is an Error object
*
* @param {Mixed} value Value to be checked
* @return {Boolean} returns true if the value is an Error
*/
ImapClient.prototype.isError = function(value) {
return !!Object.prototype.toString.call(value).match(/Error\]$/);
};
return ImapClient;
}));

File diff suppressed because it is too large Load diff

236
gaia/apps/email/js/ext/ext/co.js Executable file
View file

@ -0,0 +1,236 @@
define(function(require) {
/**
* slice() reference.
*/
var slice = Array.prototype.slice;
/**
* Expose `co`.
*/
co['default'] = co.co = co;
/**
* Wrap the given generator `fn` into a
* function that returns a promise.
* This is a separate function so that
* every `co()` call doesn't create a new,
* unnecessary closure.
*
* @param {GeneratorFunction} fn
* @return {Function}
* @api public
*/
co.wrap = function (fn) {
createPromise.__generatorFunction__ = fn;
return createPromise;
function createPromise() {
return co.call(this, fn.apply(this, arguments));
}
};
/**
* Execute the generator function or a generator
* and return a promise.
*
* @param {Function} fn
* @return {Promise}
* @api public
*/
function co(gen) {
var ctx = this;
if (typeof gen === 'function') gen = gen.call(this);
// we wrap everything in a promise to avoid promise chaining,
// which leads to memory leak errors.
// see https://github.com/tj/co/issues/180
return new Promise(function(resolve, reject) {
onFulfilled();
/**
* @param {Mixed} res
* @return {Promise}
* @api private
*/
function onFulfilled(res) {
var ret;
try {
ret = gen.next(res);
} catch (e) {
return reject(e);
}
next(ret);
}
/**
* @param {Error} err
* @return {Promise}
* @api private
*/
function onRejected(err) {
var ret;
try {
ret = gen.throw(err);
} catch (e) {
return reject(e);
}
next(ret);
}
/**
* Get the next value in the generator,
* return a promise.
*
* @param {Object} ret
* @return {Promise}
* @api private
*/
function next(ret) {
if (ret.done) return resolve(ret.value);
var value = toPromise.call(ctx, ret.value);
if (value && isPromise(value)) return value.then(onFulfilled, onRejected);
return onRejected(new TypeError('You may only yield a function, promise, generator, array, or object, '
+ 'but the following object was passed: "' + String(ret.value) + '"'));
}
});
}
/**
* Convert a `yield`ed value into a promise.
*
* @param {Mixed} obj
* @return {Promise}
* @api private
*/
function toPromise(obj) {
if (!obj) return obj;
if (isPromise(obj)) return obj;
if (isGeneratorFunction(obj) || isGenerator(obj)) return co.call(this, obj);
if ('function' == typeof obj) return thunkToPromise.call(this, obj);
if (Array.isArray(obj)) return arrayToPromise.call(this, obj);
if (isObject(obj)) return objectToPromise.call(this, obj);
return obj;
}
/**
* Convert a thunk to a promise.
*
* @param {Function}
* @return {Promise}
* @api private
*/
function thunkToPromise(fn) {
var ctx = this;
return new Promise(function (resolve, reject) {
fn.call(ctx, function (err, res) {
if (err) return reject(err);
if (arguments.length > 2) res = slice.call(arguments, 1);
resolve(res);
});
});
}
/**
* Convert an array of "yieldables" to a promise.
* Uses `Promise.all()` internally.
*
* @param {Array} obj
* @return {Promise}
* @api private
*/
function arrayToPromise(obj) {
return Promise.all(obj.map(toPromise, this));
}
/**
* Convert an object of "yieldables" to a promise.
* Uses `Promise.all()` internally.
*
* @param {Object} obj
* @return {Promise}
* @api private
*/
function objectToPromise(obj){
var results = new obj.constructor();
var keys = Object.keys(obj);
var promises = [];
for (var i = 0; i < keys.length; i++) {
var key = keys[i];
var promise = toPromise.call(this, obj[key]);
if (promise && isPromise(promise)) defer(promise, key);
else results[key] = obj[key];
}
return Promise.all(promises).then(function () {
return results;
});
function defer(promise, key) {
// predefine the key in the result
results[key] = undefined;
promises.push(promise.then(function (res) {
results[key] = res;
}));
}
}
/**
* Check if `obj` is a promise.
*
* @param {Object} obj
* @return {Boolean}
* @api private
*/
function isPromise(obj) {
return 'function' == typeof obj.then;
}
/**
* Check if `obj` is a generator.
*
* @param {Mixed} obj
* @return {Boolean}
* @api private
*/
function isGenerator(obj) {
return 'function' == typeof obj.next && 'function' == typeof obj.throw;
}
/**
* Check if `obj` is a generator function.
*
* @param {Mixed} obj
* @return {Boolean}
* @api private
*/
function isGeneratorFunction(obj) {
var constructor = obj.constructor;
if (!constructor) return false;
if ('GeneratorFunction' === constructor.name || 'GeneratorFunction' === constructor.displayName) return true;
return isGenerator(constructor.prototype);
}
/**
* Check for plain object.
*
* @param {Mixed} val
* @return {Boolean}
* @api private
*/
function isObject(val) {
return Object == val.constructor;
}
return co;
});

View file

@ -0,0 +1,120 @@
/**
* Sane equivalence checking, originally from loggest's rdcommon/log.js.
*/
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at:
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is Mozilla Raindrop Code.
*
* The Initial Developer of the Original Code is
* The Mozilla Foundation
* Portions created by the Initial Developer are Copyright (C) 2011
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Andrew Sutherland <asutherland@asutherland.org>
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
/**
* This module exports a single comparison function:
*
* equal(a, b) -> boolean
*
*/
define(function(require) {
/**
* Maximum comparison depth for argument equivalence in expectation checking.
* This value gets bumped every time I throw something at it that fails that
* still seems reasonable to me.
*/
var COMPARE_DEPTH = 6;
function boundedCmpObjs(a, b, depthLeft) {
var aAttrCount = 0, bAttrCount = 0, key, nextDepth = depthLeft - 1;
if ('toJSON' in a)
a = a.toJSON();
if ('toJSON' in b)
b = b.toJSON();
for (key in a) {
aAttrCount++;
if (!(key in b))
return false;
if (depthLeft) {
if (!equal(a[key], b[key], nextDepth))
return false;
}
else {
if (a[key] !== b[key])
return false;
}
}
// the theory is that if every key in a is in b and its value is equal, and
// there are the same number of keys in b, then they must be equal.
for (key in b) {
bAttrCount++;
}
if (aAttrCount !== bAttrCount)
return false;
return true;
}
/**
* @return[Boolean]{
* True when equivalent, false when not equivalent.
* }
*/
function equal(a, b, depthLeft) {
if (depthLeft === undefined) {
depthLeft = COMPARE_DEPTH;
}
var ta = typeof(a), tb = typeof(b);
if (ta !== 'object' || (tb !== ta) || (a == null) || (b == null))
return a === b;
// fast-path for identical objects
if (a === b)
return true;
if (Array.isArray(a)) {
if (!Array.isArray(b))
return false;
if (a.length !== b.length)
return false;
for (var iArr = 0; iArr < a.length; iArr++) {
if (!equal(a[iArr], b[iArr], depthLeft - 1))
return false;
}
return true;
}
return boundedCmpObjs(a, b, depthLeft);
}
return equal;
}); // end define

162
gaia/apps/email/js/ext/ext/evt.js Executable file
View file

@ -0,0 +1,162 @@
// COPIED FROM gaia/apps/emailjs/evt.js
/*global define, setTimeout */
/*
* Custom events lib. Notable features:
*
* - the module itself is an event emitter. Useful for "global" pub/sub.
* - evt.mix can be used to mix in an event emitter into existing object.
* - notification of listeners is done in a try/catch, so all listeners
* are notified even if one fails. Errors are thrown async via setTimeout
* so that all the listeners can be notified without escaping from the
* code via a throw within the listener group notification.
* - new evt.Emitter() can be used to create a new instance of an
* event emitter.
* - Uses "this" insternally, so always call object with the emitter args
*
*/
define(function() {
var evt,
slice = Array.prototype.slice,
props = ['_events', '_pendingEvents', 'on', 'once', 'latest',
'latestOnce', 'removeListener', 'emitWhenListener', 'emit'];
function Emitter() {
this._events = {};
this._pendingEvents = {};
}
Emitter.prototype = {
on: function(id, fn) {
var listeners = this._events[id],
pending = this._pendingEvents[id];
if (!listeners) {
listeners = this._events[id] = [];
}
listeners.push(fn);
if (pending) {
pending.forEach(function(args) {
fn.apply(null, args);
});
delete this._pendingEvents[id];
}
return this;
},
once: function(id, fn) {
var self = this,
fired = false;
function one() {
if (fired)
return;
fired = true;
fn.apply(null, arguments);
// Remove at a further turn so that the event
// forEach in emit does not get modified during
// this turn.
setTimeout(function() {
self.removeListener(id, one);
});
}
return this.on(id, one);
},
/**
* Waits for a property on the object that has the event interface
* to be available. That property MUST EVALUATE TO A TRUTHY VALUE.
* hasOwnProperty is not used because many objects are created with
* null placeholders to give a proper JS engine shape to them, and
* this method should not trigger the listener for those cases.
* If the property is already available, call the listener right
* away. If not available right away, listens for an event name that
* matches the property name.
* @param {String} id property name.
* @param {Function} fn listener.
*/
latest: function(id, fn) {
if (this[id] && !this._pendingEvents[id]) {
fn(this[id]);
}
this.on(id, fn);
},
/**
* Same as latest, but only calls the listener once.
* @param {String} id property name.
* @param {Function} fn listener.
*/
latestOnce: function(id, fn) {
if (this[id] && !this._pendingEvents[id])
fn(this[id]);
else
this.once(id, fn);
},
removeListener: function(id, fn) {
var i,
listeners = this._events[id];
if (listeners) {
i = listeners.indexOf(fn);
if (i !== -1) {
listeners.splice(i, 1);
}
if (listeners.length === 0)
delete this._events[id];
}
},
/**
* Like emit, but if no listeners yet, holds on
* to the value until there is one. Any other
* args after first one are passed to listeners.
* @param {String} id event ID.
*/
emitWhenListener: function(id) {
var listeners = this._events[id];
if (listeners) {
this.emit.apply(this, arguments);
} else {
if (!this._pendingEvents[id])
this._pendingEvents[id] = [];
this._pendingEvents[id].push(slice.call(arguments, 1));
}
},
emit: function(id) {
var args = slice.call(arguments, 1),
listeners = this._events[id];
if (listeners) {
listeners.forEach(function(fn) {
try {
fn.apply(null, args);
} catch (e) {
// Throw at later turn so that other listeners
// can complete. While this messes with the
// stack for the error, continued operation is
// valued more in this tradeoff.
setTimeout(function() {
throw e;
});
}
});
}
}
};
evt = new Emitter();
evt.Emitter = Emitter;
evt.mix = function(obj) {
var e = new Emitter();
props.forEach(function(prop) {
if (obj.hasOwnProperty(prop)) {
throw new Error('Object already has a property "' + prop + '"');
}
obj[prop] = e[prop];
});
return obj;
};
return evt;
});

View file

@ -0,0 +1,3 @@
define(['./imap-handler/src/imap-handler'], function(imapHandler) {
return imapHandler;
});

View file

@ -0,0 +1,144 @@
// Copyright (c) 2013 Andris Reinman
//
// 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.
(function(root, factory) {
'use strict';
if (typeof define === 'function' && define.amd) {
define(['imap-formal-syntax'], factory);
} else if (typeof exports === 'object') {
module.exports = factory(require('./imap-formal-syntax'));
} else {
root.imapCompiler = factory(root.imapFormalSyntax);
}
}(this, function(imapFormalSyntax) {
'use strict';
/**
* Compiles an input object into
*/
return function(response, asArray, isLogging) {
var respParts = [],
resp = (response.tag || '') + (response.command ? ' ' + response.command : ''),
val, lastType,
walk = function(node) {
if (lastType === 'LITERAL' || (['(', '<', '['].indexOf(resp.substr(-1)) < 0 && resp.length)) {
resp += ' ';
}
if (Array.isArray(node)) {
lastType = 'LIST';
resp += '(';
node.forEach(walk);
resp += ')';
return;
}
if (!node && typeof node !== 'string' && typeof node !== 'number') {
resp += 'NIL';
return;
}
if (typeof node === 'string') {
if (isLogging && node.length > 20) {
resp += '"(* ' + node.length + 'B string *)"';
} else {
resp += JSON.stringify(node);
}
return;
}
if (typeof node === 'number') {
resp += Math.round(node) || 0; // Only integers allowed
return;
}
lastType = node.type;
if (isLogging && node.sensitive) {
resp += '"(* value hidden *)"';
return;
}
switch (node.type.toUpperCase()) {
case 'LITERAL':
if (isLogging) {
resp += '"(* ' + node.value.length + 'B literal *)"';
} else {
if (!node.value) {
resp += '{0}\r\n';
} else {
resp += '{' + node.value.length + '}\r\n';
}
respParts.push(resp);
resp = node.value || '';
}
break;
case 'STRING':
if (isLogging && node.value.length > 20) {
resp += '"(* ' + node.value.length + 'B string *)"';
} else {
resp += JSON.stringify(node.value || '');
}
break;
case 'TEXT':
case 'SEQUENCE':
resp += node.value || '';
break;
case 'NUMBER':
resp += (node.value || 0);
break;
case 'ATOM':
case 'SECTION':
val = node.value || '';
if (imapFormalSyntax.verify(val.charAt(0) === '\\' ? val.substr(1) : val, imapFormalSyntax['ATOM-CHAR']()) >= 0) {
val = JSON.stringify(val);
}
resp += val;
if (node.section) {
resp += '[';
node.section.forEach(walk);
resp += ']';
}
if (node.partial) {
resp += '<' + node.partial.join('.') + '>';
}
break;
}
};
[].concat(response.attributes || []).forEach(walk);
if (resp.length) {
respParts.push(resp);
}
return asArray ? respParts : respParts.join('');
};
}));

View file

@ -0,0 +1,180 @@
// Copyright (c) 2013 Andris Reinman
//
// 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.
(function(root, factory) {
'use strict';
if (typeof define === 'function' && define.amd) {
define(factory);
} else if (typeof exports === 'object') {
module.exports = factory();
} else {
root.imapFormalSyntax = factory();
}
}(this, function() {
'use strict';
// IMAP Formal Syntax
// http://tools.ietf.org/html/rfc3501#section-9
function expandRange(start, end) {
var chars = [];
for (var i = start; i <= end; i++) {
chars.push(i);
}
return String.fromCharCode.apply(String, chars);
}
function excludeChars(source, exclude) {
var sourceArr = Array.prototype.slice.call(source);
for (var i = sourceArr.length - 1; i >= 0; i--) {
if (exclude.indexOf(sourceArr[i]) >= 0) {
sourceArr.splice(i, 1);
}
}
return sourceArr.join('');
}
return {
CHAR: function() {
var value = expandRange(0x01, 0x7F);
this.CHAR = function() {
return value;
};
return value;
},
CHAR8: function() {
var value = expandRange(0x01, 0xFF);
this.CHAR8 = function() {
return value;
};
return value;
},
SP: function() {
return ' ';
},
CTL: function() {
var value = expandRange(0x00, 0x1F) + '\x7F';
this.CTL = function() {
return value;
};
return value;
},
DQUOTE: function() {
return '"';
},
ALPHA: function() {
var value = expandRange(0x41, 0x5A) + expandRange(0x61, 0x7A);
this.ALPHA = function() {
return value;
};
return value;
},
DIGIT: function() {
var value = expandRange(0x30, 0x39) + expandRange(0x61, 0x7A);
this.DIGIT = function() {
return value;
};
return value;
},
'ATOM-CHAR': function() {
var value = excludeChars(this.CHAR(), this['atom-specials']());
this['ATOM-CHAR'] = function() {
return value;
};
return value;
},
'ASTRING-CHAR': function() {
var value = this['ATOM-CHAR']() + this['resp-specials']();
this['ASTRING-CHAR'] = function() {
return value;
};
return value;
},
'TEXT-CHAR': function() {
var value = excludeChars(this.CHAR(), '\r\n');
this['TEXT-CHAR'] = function() {
return value;
};
return value;
},
'atom-specials': function() {
var value = '(' + ')' + '{' + this.SP() + this.CTL() + this['list-wildcards']() +
this['quoted-specials']() + this['resp-specials']();
this['atom-specials'] = function() {
return value;
};
return value;
},
'list-wildcards': function() {
return '%' + '*';
},
'quoted-specials': function() {
var value = this.DQUOTE() + '\\';
this['quoted-specials'] = function() {
return value;
};
return value;
},
'resp-specials': function() {
return ']';
},
tag: function() {
var value = excludeChars(this['ASTRING-CHAR'](), '+');
this.tag = function() {
return value;
};
return value;
},
command: function() {
var value = this.ALPHA() + this.DIGIT();
this.command = function() {
return value;
};
return value;
},
verify: function(str, allowedChars) {
for (var i = 0, len = str.length; i < len; i++) {
if (allowedChars.indexOf(str.charAt(i)) < 0) {
return i;
}
}
return -1;
}
};
}));

View file

@ -0,0 +1,40 @@
// Copyright (c) 2013 Andris Reinman
//
// 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.
// UMD shim, see: https://github.com/umdjs/umd/blob/master/returnExports.js
(function(root, factory) {
'use strict';
if (typeof define === 'function' && define.amd) {
define(['./imap-parser', './imap-compiler'], factory);
} else if (typeof exports === 'object') {
module.exports = factory(require('./imap-parser'), require('./imap-compiler'));
} else {
root.imapHandler = factory(root.imapParser, root.imapCompiler);
}
}(this, function(imapParser, imapCompiler) {
'use strict';
return {
parser: imapParser,
compiler: imapCompiler
};
}));

View file

@ -0,0 +1,656 @@
// Copyright (c) 2013 Andris Reinman
//
// 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.
(function(root, factory) {
'use strict';
if (typeof define === 'function' && define.amd) {
define(['imap-formal-syntax'], factory);
} else if (typeof exports === 'object') {
module.exports = factory(require('./imap-formal-syntax'));
} else {
root.imapParser = factory(root.imapFormalSyntax);
}
}(this, function(imapFormalSyntax) {
'use strict';
function ParserInstance(input, options) {
this.input = (input || '').toString();
this.options = options || {};
this.remainder = this.input;
this.pos = 0;
}
ParserInstance.prototype.getTag = function() {
if (!this.tag) {
this.tag = this.getElement(imapFormalSyntax.tag() + '*+', true);
}
return this.tag;
};
ParserInstance.prototype.getCommand = function() {
var responseCode;
if (!this.command) {
this.command = this.getElement(imapFormalSyntax.command());
}
switch ((this.command || '').toString().toUpperCase()) {
case 'OK':
case 'NO':
case 'BAD':
case 'PREAUTH':
case 'BYE':
responseCode = this.remainder.match(/^ \[(?:[^\]]*\])+/);
if (responseCode) {
this.humanReadable = this.remainder.substr(responseCode[0].length).trim();
this.remainder = responseCode[0];
} else {
this.humanReadable = this.remainder.trim();
this.remainder = '';
}
break;
}
return this.command;
};
ParserInstance.prototype.getElement = function(syntax) {
var match, element, errPos;
if (this.remainder.match(/^\s/)) {
throw new Error('Unexpected whitespace at position ' + this.pos);
}
if ((match = this.remainder.match(/^[^\s]+(?=\s|$)/))) {
element = match[0];
if ((errPos = imapFormalSyntax.verify(element, syntax)) >= 0) {
throw new Error('Unexpected char at position ' + (this.pos + errPos));
}
} else {
throw new Error('Unexpected end of input at position ' + this.pos);
}
this.pos += match[0].length;
this.remainder = this.remainder.substr(match[0].length);
return element;
};
ParserInstance.prototype.getSpace = function() {
if (!this.remainder.length) {
throw new Error('Unexpected end of input at position ' + this.pos);
}
if (imapFormalSyntax.verify(this.remainder.charAt(0), imapFormalSyntax.SP()) >= 0) {
throw new Error('Unexpected char at position ' + this.pos);
}
this.pos++;
this.remainder = this.remainder.substr(1);
};
ParserInstance.prototype.getAttributes = function() {
if (!this.remainder.length) {
throw new Error('Unexpected end of input at position ' + this.pos);
}
if (this.remainder.match(/^\s/)) {
throw new Error('Unexpected whitespace at position ' + this.pos);
}
return new TokenParser(this, this.pos, this.remainder, this.options).getAttributes();
};
function TokenParser(parent, startPos, str, options) {
this.str = (str || '').toString();
this.options = options || {};
this.parent = parent;
this.tree = this.currentNode = this.createNode();
this.pos = startPos || 0;
this.currentNode.type = 'TREE';
this.state = 'NORMAL';
this.processString();
}
TokenParser.prototype.getAttributes = function() {
var attributes = [],
branch = attributes;
var walk = function(node) {
var elm, curBranch = branch,
partial;
if (!node.closed && node.type === 'SEQUENCE' && node.value === '*') {
node.closed = true;
node.type = 'ATOM';
}
// If the node was never closed, throw it
if (!node.closed) {
throw new Error('Unexpected end of input at position ' + (this.pos + this.str.length - 1));
}
switch (node.type.toUpperCase()) {
case 'LITERAL':
case 'STRING':
case 'SEQUENCE':
elm = {
type: node.type.toUpperCase(),
value: node.value
};
branch.push(elm);
break;
case 'ATOM':
if (node.value.toUpperCase() === 'NIL') {
branch.push(null);
break;
}
elm = {
type: node.type.toUpperCase(),
value: node.value
};
branch.push(elm);
break;
case 'SECTION':
branch = branch[branch.length - 1].section = [];
break;
case 'LIST':
elm = [];
branch.push(elm);
branch = elm;
break;
case 'PARTIAL':
partial = node.value.split('.').map(Number);
if (partial.slice(-1)[0] < partial.slice(0, 1)[0]) {
throw new Error('Invalid partial value at position ' + node.startPos);
}
branch[branch.length - 1].partial = partial;
break;
}
node.childNodes.forEach(function(childNode) {
walk(childNode);
});
branch = curBranch;
}.bind(this);
walk(this.tree);
return attributes;
};
TokenParser.prototype.createNode = function(parentNode, startPos) {
var node = {
childNodes: [],
type: false,
value: '',
closed: true
};
if (parentNode) {
node.parentNode = parentNode;
}
if (typeof startPos === 'number') {
node.startPos = startPos;
}
if (parentNode) {
parentNode.childNodes.push(node);
}
return node;
};
TokenParser.prototype.processString = function() {
var chr, i, len,
checkSP = function() {
// jump to the next non whitespace pos
while (this.str.charAt(i + 1) === ' ') {
i++;
}
}.bind(this);
for (i = 0, len = this.str.length; i < len; i++) {
chr = this.str.charAt(i);
switch (this.state) {
case 'NORMAL':
switch (chr) {
// DQUOTE starts a new string
case '"':
this.currentNode = this.createNode(this.currentNode, this.pos + i);
this.currentNode.type = 'string';
this.state = 'STRING';
this.currentNode.closed = false;
break;
// ( starts a new list
case '(':
this.currentNode = this.createNode(this.currentNode, this.pos + i);
this.currentNode.type = 'LIST';
this.currentNode.closed = false;
break;
// ) closes a list
case ')':
if (this.currentNode.type !== 'LIST') {
throw new Error('Unexpected list terminator ) at position ' + (this.pos + i));
}
this.currentNode.closed = true;
this.currentNode.endPos = this.pos + i;
this.currentNode = this.currentNode.parentNode;
checkSP();
break;
// ] closes section group
case ']':
if (this.currentNode.type !== 'SECTION') {
throw new Error('Unexpected section terminator ] at position ' + (this.pos + i));
}
this.currentNode.closed = true;
this.currentNode.endPos = this.pos + i;
this.currentNode = this.currentNode.parentNode;
checkSP();
break;
// < starts a new partial
case '<':
if (this.str.charAt(i - 1) !== ']') {
this.currentNode = this.createNode(this.currentNode, this.pos + i);
this.currentNode.type = 'ATOM';
this.currentNode.value = chr;
this.state = 'ATOM';
} else {
this.currentNode = this.createNode(this.currentNode, this.pos + i);
this.currentNode.type = 'PARTIAL';
this.state = 'PARTIAL';
this.currentNode.closed = false;
}
break;
// { starts a new literal
case '{':
this.currentNode = this.createNode(this.currentNode, this.pos + i);
this.currentNode.type = 'LITERAL';
this.state = 'LITERAL';
this.currentNode.closed = false;
break;
// ( starts a new sequence
case '*':
this.currentNode = this.createNode(this.currentNode, this.pos + i);
this.currentNode.type = 'SEQUENCE';
this.currentNode.value = chr;
this.currentNode.closed = false;
this.state = 'SEQUENCE';
break;
// normally a space should never occur
case ' ':
// just ignore
break;
// [ starts section
case '[':
// If it is the *first* element after response command, then process as a response argument list
if (['OK', 'NO', 'BAD', 'BYE', 'PREAUTH'].indexOf(this.parent.command.toUpperCase()) >= 0 && this.currentNode === this.tree) {
this.currentNode.endPos = this.pos + i;
this.currentNode = this.createNode(this.currentNode, this.pos + i);
this.currentNode.type = 'ATOM';
this.currentNode = this.createNode(this.currentNode, this.pos + i);
this.currentNode.type = 'SECTION';
this.currentNode.closed = false;
this.state = 'NORMAL';
// RFC2221 defines a response code REFERRAL whose payload is an
// RFC2192/RFC5092 imapurl that we will try to parse as an ATOM but
// fail quite badly at parsing. Since the imapurl is such a unique
// (and crazy) term, we just specialize that case here.
if (this.str.substr(i + 1, 9).toUpperCase() === 'REFERRAL ') {
// create the REFERRAL atom
this.currentNode = this.createNode(this.currentNode, this.pos + i + 1);
this.currentNode.type = 'ATOM';
this.currentNode.endPos = this.pos + i + 8;
this.currentNode.value = 'REFERRAL';
this.currentNode = this.currentNode.parentNode;
// eat all the way through the ] to be the IMAPURL token.
this.currentNode = this.createNode(this.currentNode, this.pos + i + 10);
// just call this an ATOM, even though IMAPURL might be more correct
this.currentNode.type = 'ATOM';
// jump i to the ']'
i = this.str.indexOf(']', i + 10);
this.currentNode.endPos = this.pos + i - 1;
this.currentNode.value = this.str.substring(this.currentNode.startPos - this.pos,
this.currentNode.endPos - this.pos + 1);
this.currentNode = this.currentNode.parentNode;
// close out the SECTION
this.currentNode.closed = true;
this.currentNode = this.currentNode.parentNode;
checkSP();
}
break;
}
/* falls through */
default:
// Any ATOM supported char starts a new Atom sequence, otherwise throw an error
// Allow \ as the first char for atom to support system flags
// Allow % to support LIST '' %
if (imapFormalSyntax['ATOM-CHAR']().indexOf(chr) < 0 && chr !== '\\' && chr !== '%') {
throw new Error('Unexpected char at position ' + (this.pos + i));
}
this.currentNode = this.createNode(this.currentNode, this.pos + i);
this.currentNode.type = 'ATOM';
this.currentNode.value = chr;
this.state = 'ATOM';
break;
}
break;
case 'ATOM':
// space finishes an atom
if (chr === ' ') {
this.currentNode.endPos = this.pos + i - 1;
this.currentNode = this.currentNode.parentNode;
this.state = 'NORMAL';
break;
}
//
if (
this.currentNode.parentNode &&
(
(chr === ')' && this.currentNode.parentNode.type === 'LIST') ||
(chr === ']' && this.currentNode.parentNode.type === 'SECTION')
)
) {
this.currentNode.endPos = this.pos + i - 1;
this.currentNode = this.currentNode.parentNode;
this.currentNode.closed = true;
this.currentNode.endPos = this.pos + i;
this.currentNode = this.currentNode.parentNode;
this.state = 'NORMAL';
checkSP();
break;
}
if ((chr === ',' || chr === ':') && this.currentNode.value.match(/^\d+$/)) {
this.currentNode.type = 'SEQUENCE';
this.currentNode.closed = true;
this.state = 'SEQUENCE';
}
// [ starts a section group for this element
if (chr === '[') {
// allowed only for selected elements
if (['BODY', 'BODY.PEEK'].indexOf(this.currentNode.value.toUpperCase()) < 0) {
throw new Error('Unexpected section start char [ at position ' + this.pos);
}
this.currentNode.endPos = this.pos + i;
this.currentNode = this.createNode(this.currentNode.parentNode, this.pos + i);
this.currentNode.type = 'SECTION';
this.currentNode.closed = false;
this.state = 'NORMAL';
break;
}
if (chr === '<') {
throw new Error('Unexpected start of partial at position ' + this.pos);
}
// if the char is not ATOM compatible, throw. Allow \* as an exception
if (imapFormalSyntax['ATOM-CHAR']().indexOf(chr) < 0 && chr !== ']' && !(chr === '*' && this.currentNode.value === '\\')) {
throw new Error('Unexpected char at position ' + (this.pos + i));
} else if (this.currentNode.value === '\\*') {
throw new Error('Unexpected char at position ' + (this.pos + i));
}
this.currentNode.value += chr;
break;
case 'STRING':
// DQUOTE ends the string sequence
if (chr === '"') {
this.currentNode.endPos = this.pos + i;
this.currentNode.closed = true;
this.currentNode = this.currentNode.parentNode;
this.state = 'NORMAL';
checkSP();
break;
}
// \ Escapes the following char
if (chr === '\\') {
i++;
if (i >= len) {
throw new Error('Unexpected end of input at position ' + (this.pos + i));
}
chr = this.str.charAt(i);
}
/* // skip this check, otherwise the parser might explode on binary input
if (imapFormalSyntax['TEXT-CHAR']().indexOf(chr) < 0) {
throw new Error('Unexpected char at position ' + (this.pos + i));
}
*/
this.currentNode.value += chr;
break;
case 'PARTIAL':
if (chr === '>') {
if (this.currentNode.value.substr(-1) === '.') {
throw new Error('Unexpected end of partial at position ' + this.pos);
}
this.currentNode.endPos = this.pos + i;
this.currentNode.closed = true;
this.currentNode = this.currentNode.parentNode;
this.state = 'NORMAL';
checkSP();
break;
}
if (chr === '.' && (!this.currentNode.value.length || this.currentNode.value.match(/\./))) {
throw new Error('Unexpected partial separator . at position ' + this.pos);
}
if (imapFormalSyntax.DIGIT().indexOf(chr) < 0 && chr !== '.') {
throw new Error('Unexpected char at position ' + (this.pos + i));
}
if (this.currentNode.value.match(/^0$|\.0$/) && chr !== '.') {
throw new Error('Invalid partial at position ' + (this.pos + i));
}
this.currentNode.value += chr;
break;
case 'LITERAL':
if (this.currentNode.started) {
//if(imapFormalSyntax['CHAR8']().indexOf(chr) < 0){
if (chr === '\u0000') {
throw new Error('Unexpected \\x00 at position ' + (this.pos + i));
}
this.currentNode.value += chr;
if (this.currentNode.value.length >= this.currentNode.literalLength) {
this.currentNode.endPos = this.pos + i;
this.currentNode.closed = true;
this.currentNode = this.currentNode.parentNode;
this.state = 'NORMAL';
checkSP();
}
break;
}
if (chr === '+' && this.options.literalPlus) {
this.currentNode.literalPlus = true;
break;
}
if (chr === '}') {
if (!('literalLength' in this.currentNode)) {
throw new Error('Unexpected literal prefix end char } at position ' + (this.pos + i));
}
if (this.str.charAt(i + 1) === '\n') {
i++;
} else if (this.str.charAt(i + 1) === '\r' && this.str.charAt(i + 2) === '\n') {
i += 2;
} else {
throw new Error('Unexpected char at position ' + (this.pos + i));
}
this.currentNode.literalLength = Number(this.currentNode.literalLength);
this.currentNode.started = true;
if (!this.currentNode.literalLength) {
// special case where literal content length is 0
// close the node right away, do not wait for additional input
this.currentNode.endPos = this.pos + i;
this.currentNode.closed = true;
this.currentNode = this.currentNode.parentNode;
this.state = 'NORMAL';
checkSP();
}
break;
}
if (imapFormalSyntax.DIGIT().indexOf(chr) < 0) {
throw new Error('Unexpected char at position ' + (this.pos + i));
}
if (this.currentNode.literalLength === '0') {
throw new Error('Invalid literal at position ' + (this.pos + i));
}
this.currentNode.literalLength = (this.currentNode.literalLength || '') + chr;
break;
case 'SEQUENCE':
// space finishes the sequence set
if (chr === ' ') {
if (!this.currentNode.value.substr(-1).match(/\d/) && this.currentNode.value.substr(-1) !== '*') {
throw new Error('Unexpected whitespace at position ' + (this.pos + i));
}
if (this.currentNode.value.substr(-1) === '*' && this.currentNode.value.substr(-2, 1) !== ':') {
throw new Error('Unexpected whitespace at position ' + (this.pos + i));
}
this.currentNode.closed = true;
this.currentNode.endPos = this.pos + i - 1;
this.currentNode = this.currentNode.parentNode;
this.state = 'NORMAL';
break;
} else if (this.currentNode.parentNode &&
chr === ']' &&
this.currentNode.parentNode.type === 'SECTION') {
this.currentNode.endPos = this.pos + i - 1;
this.currentNode = this.currentNode.parentNode;
this.currentNode.closed = true;
this.currentNode.endPos = this.pos + i;
this.currentNode = this.currentNode.parentNode;
this.state = 'NORMAL';
checkSP();
break;
}
if (chr === ':') {
if (!this.currentNode.value.substr(-1).match(/\d/) && this.currentNode.value.substr(-1) !== '*') {
throw new Error('Unexpected range separator : at position ' + (this.pos + i));
}
} else if (chr === '*') {
if ([',', ':'].indexOf(this.currentNode.value.substr(-1)) < 0) {
throw new Error('Unexpected range wildcard at position ' + (this.pos + i));
}
} else if (chr === ',') {
if (!this.currentNode.value.substr(-1).match(/\d/) && this.currentNode.value.substr(-1) !== '*') {
throw new Error('Unexpected sequence separator , at position ' + (this.pos + i));
}
if (this.currentNode.value.substr(-1) === '*' && this.currentNode.value.substr(-2, 1) !== ':') {
throw new Error('Unexpected sequence separator , at position ' + (this.pos + i));
}
} else if (!chr.match(/\d/)) {
throw new Error('Unexpected char at position ' + (this.pos + i));
}
if (chr.match(/\d/) && this.currentNode.value.substr(-1) === '*') {
throw new Error('Unexpected number at position ' + (this.pos + i));
}
this.currentNode.value += chr;
break;
}
}
};
return function(command, options) {
var parser, response = {};
options = options || {};
parser = new ParserInstance(command, options);
response.tag = parser.getTag();
parser.getSpace();
response.command = parser.getCommand();
if (['UID', 'AUTHENTICATE'].indexOf((response.command || '').toUpperCase()) >= 0) {
parser.getSpace();
response.command += ' ' + parser.getElement(imapFormalSyntax.command());
}
if (parser.remainder.trim().length) {
parser.getSpace();
response.attributes = parser.getAttributes();
}
if (parser.humanReadable) {
response.attributes = (response.attributes || []).concat({
type: 'TEXT',
value: parser.humanReadable
});
}
return response;
};
}));

View file

@ -0,0 +1,730 @@
// Copyright (c) 2013 Andris Reinman
//
// 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.
(function(root, factory) {
'use strict';
if (typeof define === 'function' && define.amd) {
define(['mimefuncs', 'mimetypes', 'punycode', 'addressparser'], factory);
} else if (typeof exports === 'object') {
module.exports = factory(require('mimefuncs'), require('mimetypes'), require('punycode'), require('wo-addressparser'));
} else {
root.mailbuild = factory(mimefuncs, mimetypes, punycode, addressparser);
}
}(this, function(mimefuncs, mimetypes, punycode, addressparser) {
'use strict';
/**
* Creates a new mime tree node. Assumes 'multipart/*' as the content type
* if it is a branch, anything else counts as leaf. If rootNode is missing from
* the options, assumes this is the root.
*
* @param {String} contentType Define the content type for the node. Can be left blank for attachments (derived from filename)
* @param {Object} [options] optional options
* @param {Object} [options.rootNode] root node for this tree
* @param {Object} [options.parentNode] immediate parent for this node
* @param {Object} [options.filename] filename for an attachment node
* @param {String} [options.baseBoundary] shared part of the unique multipart boundary
*/
function MimeNode(contentType, options) {
this.nodeCounter = 0;
options = options || {};
/**
* shared part of the unique multipart boundary
*/
this.baseBoundary = options.baseBoundary || Date.now().toString() + Math.random();
/**
* If date headers is missing and current node is the root, this value is used instead
*/
this.date = new Date();
/**
* Root node for current mime tree
*/
this.rootNode = options.rootNode || this;
/**
* If filename is specified but contentType is not (probably an attachment)
* detect the content type from filename extension
*/
if (options.filename) {
/**
* Filename for this node. Useful with attachments
*/
this.filename = options.filename;
if (!contentType) {
contentType = mimetypes.detectMimeType(this.filename.split('.').pop());
}
}
/**
* Immediate parent for this node (or undefined if not set)
*/
this.parentNode = options.parentNode;
/**
* Used for generating unique boundaries (prepended to the shared base)
*/
this._nodeId = ++this.rootNode.nodeCounter;
/**
* An array for possible child nodes
*/
this._childNodes = [];
/**
* A list of header values for this node in the form of [{key:'', value:''}]
*/
this._headers = [];
/**
* If content type is set (or derived from the filename) add it to headers
*/
if (contentType) {
this.setHeader('content-type', contentType);
}
}
/////// PUBLIC METHODS
/**
* Creates and appends a child node. Arguments provided are passed to MimeNode constructor
*
* @param {String} [contentType] Optional content type
* @param {Object} [options] Optional options object
* @return {Object} Created node object
*/
MimeNode.prototype.createChild = function(contentType, options) {
if (!options && typeof contentType === 'object') {
options = contentType;
contentType = undefined;
}
var node = new MimeNode(contentType, options);
this.appendChild(node);
return node;
};
/**
* Appends an existing node to the mime tree. Removes the node from an existing
* tree if needed
*
* @param {Object} childNode node to be appended
* @return {Object} Appended node object
*/
MimeNode.prototype.appendChild = function(childNode) {
if (childNode.rootNode !== this.rootNode) {
childNode.rootNode = this.rootNode;
childNode._nodeId = ++this.rootNode.nodeCounter;
}
childNode.parentNode = this;
this._childNodes.push(childNode);
return childNode;
};
/**
* Replaces current node with another node
*
* @param {Object} node Replacement node
* @return {Object} Replacement node
*/
MimeNode.prototype.replace = function(node) {
if (node === this) {
return this;
}
this.parentNode._childNodes.forEach(function(childNode, i) {
if (childNode === this) {
node.rootNode = this.rootNode;
node.parentNode = this.parentNode;
node._nodeId = this._nodeId;
this.rootNode = this;
this.parentNode = undefined;
node.parentNode._childNodes[i] = node;
}
}.bind(this));
return node;
};
/**
* Removes current node from the mime tree
*
* @return {Object} removed node
*/
MimeNode.prototype.remove = function() {
if (!this.parentNode) {
return this;
}
for (var i = this.parentNode._childNodes.length - 1; i >= 0; i--) {
if (this.parentNode._childNodes[i] === this) {
this.parentNode._childNodes.splice(i, 1);
this.parentNode = undefined;
this.rootNode = this;
return this;
}
}
};
/**
* Sets a header value. If the value for selected key exists, it is overwritten.
* You can set multiple values as well by using [{key:'', value:''}] or
* {key: 'value'} as the first argument.
*
* @param {String|Array|Object} key Header key or a list of key value pairs
* @param {String} value Header value
* @return {Object} current node
*/
MimeNode.prototype.setHeader = function(key, value) {
var added = false,
headerValue;
// Allow setting multiple headers at once
if (!value && key && typeof key === 'object') {
// allow {key:'content-type', value: 'text/plain'}
if (key.key && key.value) {
this.setHeader(key.key, key.value);
}
// allow [{key:'content-type', value: 'text/plain'}]
else if (Array.isArray(key)) {
key.forEach(function(i) {
this.setHeader(i.key, i.value);
}.bind(this));
}
// allow {'content-type': 'text/plain'}
else {
Object.keys(key).forEach(function(i) {
this.setHeader(i, key[i]);
}.bind(this));
}
return this;
}
key = this._normalizeHeaderKey(key);
headerValue = {
key: key,
value: value
};
// Check if the value exists and overwrite
for (var i = 0, len = this._headers.length; i < len; i++) {
if (this._headers[i].key === key) {
if (!added) {
// replace the first match
this._headers[i] = headerValue;
added = true;
} else {
// remove following matches
this._headers.splice(i, 1);
i--;
len--;
}
}
}
// match not found, append the value
if (!added) {
this._headers.push(headerValue);
}
return this;
};
/**
* Adds a header value. If the value for selected key exists, the value is appended
* as a new field and old one is not touched.
* You can set multiple values as well by using [{key:'', value:''}] or
* {key: 'value'} as the first argument.
*
* @param {String|Array|Object} key Header key or a list of key value pairs
* @param {String} value Header value
* @return {Object} current node
*/
MimeNode.prototype.addHeader = function(key, value) {
// Allow setting multiple headers at once
if (!value && key && typeof key === 'object') {
// allow {key:'content-type', value: 'text/plain'}
if (key.key && key.value) {
this.addHeader(key.key, key.value);
}
// allow [{key:'content-type', value: 'text/plain'}]
else if (Array.isArray(key)) {
key.forEach(function(i) {
this.addHeader(i.key, i.value);
}.bind(this));
}
// allow {'content-type': 'text/plain'}
else {
Object.keys(key).forEach(function(i) {
this.addHeader(i, key[i]);
}.bind(this));
}
return this;
}
this._headers.push({
key: this._normalizeHeaderKey(key),
value: value
});
return this;
};
/**
* Retrieves the first mathcing value of a selected key
*
* @param {String} key Key to search for
* @retun {String} Value for the key
*/
MimeNode.prototype.getHeader = function(key) {
key = this._normalizeHeaderKey(key);
for (var i = 0, len = this._headers.length; i < len; i++) {
if (this._headers[i].key === key) {
return this._headers[i].value;
}
}
};
/**
* Sets body content for current node. If the value is a string, charset is added automatically
* to Content-Type (if it is text/*). If the value is a Typed Array, you need to specify
* the charset yourself
*
* @param (String|Uint8Array) content Body content
* @return {Object} current node
*/
MimeNode.prototype.setContent = function(content) {
this.content = content;
return this;
};
/**
* Builds the rfc2822 message from the current node. If this is a root node,
* mandatory header fields are set if missing (Date, Message-Id, MIME-Version)
*
* @return {String} Compiled message
*/
MimeNode.prototype.build = function() {
var lines = [],
contentType = (this.getHeader('Content-Type') || '').toString().toLowerCase().trim(),
transferEncoding, flowed;
if (this.content) {
transferEncoding = (this.getHeader('Content-Transfer-Encoding') || '').toString().toLowerCase().trim();
if (!transferEncoding || ['base64', 'quoted-printable'].indexOf(transferEncoding) < 0) {
if (/^text\//i.test(contentType)) {
// If there are no special symbols, no need to modify the text
if (this._isPlainText(this.content)) {
// If there are lines longer than 76 symbols/bytes, make the text 'flowed'
if (/^.{77,}/m.test(this.content)) {
flowed = true;
}
transferEncoding = '7bit';
} else {
transferEncoding = 'quoted-printable';
}
} else if (!/^multipart\//i.test(contentType)) {
transferEncoding = transferEncoding || 'base64';
}
}
if (transferEncoding) {
this.setHeader('Content-Transfer-Encoding', transferEncoding);
}
}
if (this.filename && !this.getHeader('Content-Disposition')) {
this.setHeader('Content-Disposition', 'attachment');
}
this._headers.forEach(function(header) {
var key = header.key,
value = header.value,
structured;
switch (header.key) {
case 'Content-Disposition':
structured = mimefuncs.parseHeaderValue(value);
if (this.filename) {
structured.params.filename = this.filename;
}
value = this._buildHeaderValue(structured);
break;
case 'Content-Type':
structured = mimefuncs.parseHeaderValue(value);
this._handleContentType(structured);
if (flowed) {
structured.params.format = 'flowed';
}
if (String(structured.params.format).toLowerCase().trim() === 'flowed') {
flowed = true;
}
if (structured.value.match(/^text\//) && typeof this.content === 'string' && /[\u0080-\uFFFF]/.test(this.content)) {
structured.params.charset = 'utf-8';
}
value = this._buildHeaderValue(structured);
break;
case 'Bcc':
// skip BCC values
return;
}
// skip empty lines
value = this._encodeHeaderValue(key, value);
if (!(value || '').toString().trim()) {
return;
}
lines.push(mimefuncs.foldLines(key + ': ' + value, 76));
}.bind(this));
// Ensure mandatory header fields
if (this.rootNode === this) {
if (!this.getHeader('Date')) {
lines.push('Date: ' + this.date.toUTCString().replace(/GMT/, '+0000'));
}
// You really should define your own Message-Id field
if (!this.getHeader('Message-Id')) {
lines.push('Message-Id: <' +
// crux to generate random strings like this:
// "1401391905590-58aa8c32-d32a065c-c1a2aad2"
[0, 0, 0].reduce(function(prev) {
return prev + '-' + Math.floor((1 + Math.random()) * 0x100000000).
toString(16).
substring(1);
}, Date.now()) +
'@' +
// try to use the domain of the FROM address or fallback localhost
(this.getEnvelope().from || 'localhost').split('@').pop() +
'>');
}
if (!this.getHeader('MIME-Version')) {
lines.push('MIME-Version: 1.0');
}
}
lines.push('');
if (this.content) {
switch (transferEncoding) {
case 'quoted-printable':
lines.push(mimefuncs.quotedPrintableEncode(this.content));
break;
case 'base64':
lines.push(mimefuncs.base64Encode(this.content, typeof this.content === 'object' && 'binary' || false));
break;
default:
if (flowed) {
lines.push(mimefuncs.foldLines(this.content.replace(/\r?\n/g, '\r\n').
// space stuffing http://tools.ietf.org/html/rfc3676#section-4.2
replace(/^( |From|>)/igm, ' $1'),
76, true));
} else {
lines.push(this.content.replace(/\r?\n/g, '\r\n'));
}
}
if (this.multipart) {
lines.push('');
}
}
if (this.multipart) {
this._childNodes.forEach(function(node) {
lines.push('--' + this.boundary);
lines.push(node.build());
}.bind(this));
lines.push('--' + this.boundary + '--');
lines.push('');
}
return lines.join('\r\n');
};
/**
* Generates and returns SMTP envelope with the sender address and a list of recipients addresses
*
* @return {Object} SMTP envelope in the form of {from: 'from@example.com', to: ['to@example.com']}
*/
MimeNode.prototype.getEnvelope = function() {
var envelope = {
from: false,
to: []
};
this._headers.forEach(function(header) {
var list = [];
if (header.key === 'From' || (!envelope.from && ['Reply-To', 'Sender'].indexOf(header.key) >= 0)) {
this._convertAddresses(this._parseAddresses(header.value), list);
if (list.length && list[0]) {
envelope.from = list[0];
}
} else if (['To', 'Cc', 'Bcc'].indexOf(header.key) >= 0) {
this._convertAddresses(this._parseAddresses(header.value), envelope.to);
}
}.bind(this));
return envelope;
};
/////// PRIVATE METHODS
/**
* Parses addresses. Takes in a single address or an array or an
* array of address arrays (eg. To: [[first group], [second group],...])
*
* @param {Mixed} addresses Addresses to be parsed
* @return {Array} An array of address objects
*/
MimeNode.prototype._parseAddresses = function(addresses) {
return [].concat.apply([], [].concat(addresses).map(function(address) {
if (address && address.address) {
address = this._convertAddresses(address);
}
return addressparser.parse(address);
}.bind(this)));
};
/**
* Normalizes a header key, uses Camel-Case form, except for uppercase MIME-
*
* @param {String} key Key to be normalized
* @return {String} key in Camel-Case form
*/
MimeNode.prototype._normalizeHeaderKey = function(key) {
return (key || '').toString().
// no newlines in keys
replace(/\r?\n|\r/g, ' ').
trim().toLowerCase().
// use uppercase words, except MIME
replace(/^MIME\b|^[a-z]|\-[a-z]/ig, function(c) {
return c.toUpperCase();
});
};
/**
* Joins parsed header value together as 'value; param1=value1; param2=value2'
*
* @param {Object} structured Parsed header value
* @return {String} joined header value
*/
MimeNode.prototype._buildHeaderValue = function(structured) {
var paramsArray = [];
Object.keys(structured.params || {}).forEach(function(param) {
// filename might include unicode characters so it is a special case
if (param === 'filename') {
mimefuncs.continuationEncode(param, structured.params[param], 50).forEach(function(encodedParam) {
// continuation encoded strings are always escaped, so no need to use enclosing quotes
// in fact using quotes might end up with invalid filenames in some clients
paramsArray.push(encodedParam.key + '=' + encodedParam.value);
});
} else {
paramsArray.push(param + '=' + this._escapeHeaderArgument(structured.params[param]));
}
}.bind(this));
return structured.value + (paramsArray.length ? '; ' + paramsArray.join('; ') : '');
};
/**
* Escapes a header argument value (eg. boundary value for content type),
* adds surrounding quotes if needed
*
* @param {String} value Header argument value
* @return {String} escaped and quoted (if needed) argument value
*/
MimeNode.prototype._escapeHeaderArgument = function(value) {
if (value.match(/[\s'"\\;\/=]|^\-/g)) {
return '"' + value.replace(/(["\\])/g, "\\$1") + '"';
} else {
return value;
}
};
/**
* Checks if the content type is multipart and defines boundary if needed.
* Doesn't return anything, modifies object argument instead.
*
* @param {Object} structured Parsed header value for 'Content-Type' key
*/
MimeNode.prototype._handleContentType = function(structured) {
this.contentType = structured.value.trim().toLowerCase();
this.multipart = this.contentType.split('/').reduce(function(prev, value) {
return prev === 'multipart' ? value : false;
});
if (this.multipart) {
this.boundary = structured.params.boundary = structured.params.boundary || this.boundary || this._generateBoundary();
} else {
this.boundary = false;
}
};
/**
* Generates a multipart boundary value
*
* @return {String} boundary value
*/
MimeNode.prototype._generateBoundary = function() {
return '----sinikael-?=_' + this._nodeId + '-' + this.rootNode.baseBoundary;
};
/**
* Encodes a header value for use in the generated rfc2822 email.
*
* @param {String} key Header key
* @param {String} value Header value
*/
MimeNode.prototype._encodeHeaderValue = function(key, value) {
key = this._normalizeHeaderKey(key);
switch (key) {
case 'From':
case 'Sender':
case 'To':
case 'Cc':
case 'Bcc':
case 'Reply-To':
return this._convertAddresses(this._parseAddresses(value));
case 'Message-Id':
case 'In-Reply-To':
case 'Content-Id':
value = (value || '').toString().replace(/\r?\n|\r/g, ' ');
if (value.charAt(0) !== '<') {
value = '<' + value;
}
if (value.charAt(value.length - 1) !== '>') {
value = value + '>';
}
return value;
case 'References':
value = [].concat.apply([], [].concat(value || '').map(function(elm) {
elm = (elm || '').toString().replace(/\r?\n|\r/g, ' ').trim();
return elm.replace(/<[^>]*>/g, function(str) {
return str.replace(/\s/g, '');
}).split(/\s+/);
})).map(function(elm) {
if (elm.charAt(0) !== '<') {
elm = '<' + elm;
}
if (elm.charAt(elm.length - 1) !== '>') {
elm = elm + '>';
}
return elm;
});
return value.join(' ').trim();
default:
value = (value || '').toString().replace(/\r?\n|\r/g, ' ');
// mimeWordsEncode only encodes if needed, otherwise the original string is returned
return mimefuncs.mimeWordsEncode(value, 'Q', 52);
}
};
/**
* Rebuilds address object using punycode and other adjustments
*
* @param {Array} addresses An array of address objects
* @param {Array} [uniqueList] An array to be populated with addresses
* @return {String} address string
*/
MimeNode.prototype._convertAddresses = function(addresses, uniqueList) {
var values = [];
uniqueList = uniqueList || [];
[].concat(addresses || []).forEach(function(address) {
if (address.address) {
address.address = address.address.replace(/^.*?(?=\@)/, function(user) {
return mimefuncs.mimeWordsEncode(user, 'Q', 52);
}).replace(/@.+$/, function(domain) {
return '@' + punycode.toASCII(domain.substr(1));
});
if (!address.name) {
values.push(address.address);
} else if (address.name) {
values.push(this._encodeAddressName(address.name) + ' <' + address.address + '>');
}
if (uniqueList.indexOf(address.address) < 0) {
uniqueList.push(address.address);
}
} else if (address.group) {
values.push(this._encodeAddressName(address.name) + ':' + (address.group.length ? this._convertAddresses(address.group, uniqueList) : '').trim() + ';');
}
}.bind(this));
return values.join(', ');
};
/**
* If needed, mime encodes the name part
*
* @param {String} name Name part of an address
* @returns {String} Mime word encoded string if needed
*/
MimeNode.prototype._encodeAddressName = function(name) {
if (!/^[\w ']*$/.test(name)) {
if (/^[\x20-\x7e]*$/.test(name)) {
return '"' + name.replace(/([\\"])/g, '\\$1') + '"';
} else {
return mimefuncs.mimeWordEncode(name, 'Q', 52);
}
}
return name;
};
/**
* Checks if a value is plaintext string (uses only printable 7bit chars)
*
* @param {String} value String to be tested
* @returns {Boolean} true if it is a plaintext string
*/
MimeNode.prototype._isPlainText = function(value) {
if (typeof value !== 'string' || /[\x00-\x08\x0b\x0c\x0e-\x1f\u0080-\uFFFF]/.test(value)) {
return false;
} else {
return true;
}
};
return MimeNode;
}));

387
gaia/apps/email/js/ext/ext/md5.js Executable file
View file

@ -0,0 +1,387 @@
define(function(require, exports, module) {
module.exports = function md5(data) {
return hex_md5(data);
};
/*
* A JavaScript implementation of the RSA Data Security, Inc. MD5 Message
* Digest Algorithm, as defined in RFC 1321.
* Version 2.2 Copyright (C) Paul Johnston 1999 - 2009
* Other contributors: Greg Holt, Andrew Kepert, Ydnar, Lostinet
* Distributed under the BSD License
* See http://pajhome.org.uk/crypt/md5 for more info.
*/
/*
* Configurable variables. You may need to tweak these to be compatible with
* the server-side, but the defaults work in most cases.
*/
var hexcase = 0; /* hex output format. 0 - lowercase; 1 - uppercase */
var b64pad = ""; /* base-64 pad character. "=" for strict RFC compliance */
/*
* These are the functions you'll usually want to call
* They take string arguments and return either hex or base-64 encoded strings
*/
function hex_md5(s) { return rstr2hex(rstr_md5(str2rstr_utf8(s))); }
function b64_md5(s) { return rstr2b64(rstr_md5(str2rstr_utf8(s))); }
function any_md5(s, e) { return rstr2any(rstr_md5(str2rstr_utf8(s)), e); }
function hex_hmac_md5(k, d)
{ return rstr2hex(rstr_hmac_md5(str2rstr_utf8(k), str2rstr_utf8(d))); }
function b64_hmac_md5(k, d)
{ return rstr2b64(rstr_hmac_md5(str2rstr_utf8(k), str2rstr_utf8(d))); }
function any_hmac_md5(k, d, e)
{ return rstr2any(rstr_hmac_md5(str2rstr_utf8(k), str2rstr_utf8(d)), e); }
/*
* Perform a simple self-test to see if the VM is working
*/
function md5_vm_test()
{
return hex_md5("abc").toLowerCase() == "900150983cd24fb0d6963f7d28e17f72";
}
/*
* Calculate the MD5 of a raw string
*/
function rstr_md5(s)
{
return binl2rstr(binl_md5(rstr2binl(s), s.length * 8));
}
/*
* Calculate the HMAC-MD5, of a key and some data (raw strings)
*/
function rstr_hmac_md5(key, data)
{
var bkey = rstr2binl(key);
if(bkey.length > 16) bkey = binl_md5(bkey, key.length * 8);
var ipad = Array(16), opad = Array(16);
for(var i = 0; i < 16; i++)
{
ipad[i] = bkey[i] ^ 0x36363636;
opad[i] = bkey[i] ^ 0x5C5C5C5C;
}
var hash = binl_md5(ipad.concat(rstr2binl(data)), 512 + data.length * 8);
return binl2rstr(binl_md5(opad.concat(hash), 512 + 128));
}
/*
* Convert a raw string to a hex string
*/
function rstr2hex(input)
{
try { hexcase } catch(e) { hexcase=0; }
var hex_tab = hexcase ? "0123456789ABCDEF" : "0123456789abcdef";
var output = "";
var x;
for(var i = 0; i < input.length; i++)
{
x = input.charCodeAt(i);
output += hex_tab.charAt((x >>> 4) & 0x0F)
+ hex_tab.charAt( x & 0x0F);
}
return output;
}
/*
* Convert a raw string to a base-64 string
*/
function rstr2b64(input)
{
try { b64pad } catch(e) { b64pad=''; }
var tab = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
var output = "";
var len = input.length;
for(var i = 0; i < len; i += 3)
{
var triplet = (input.charCodeAt(i) << 16)
| (i + 1 < len ? input.charCodeAt(i+1) << 8 : 0)
| (i + 2 < len ? input.charCodeAt(i+2) : 0);
for(var j = 0; j < 4; j++)
{
if(i * 8 + j * 6 > input.length * 8) output += b64pad;
else output += tab.charAt((triplet >>> 6*(3-j)) & 0x3F);
}
}
return output;
}
/*
* Convert a raw string to an arbitrary string encoding
*/
function rstr2any(input, encoding)
{
var divisor = encoding.length;
var i, j, q, x, quotient;
/* Convert to an array of 16-bit big-endian values, forming the dividend */
var dividend = Array(Math.ceil(input.length / 2));
for(i = 0; i < dividend.length; i++)
{
dividend[i] = (input.charCodeAt(i * 2) << 8) | input.charCodeAt(i * 2 + 1);
}
/*
* Repeatedly perform a long division. The binary array forms the dividend,
* the length of the encoding is the divisor. Once computed, the quotient
* forms the dividend for the next step. All remainders are stored for later
* use.
*/
var full_length = Math.ceil(input.length * 8 /
(Math.log(encoding.length) / Math.log(2)));
var remainders = Array(full_length);
for(j = 0; j < full_length; j++)
{
quotient = Array();
x = 0;
for(i = 0; i < dividend.length; i++)
{
x = (x << 16) + dividend[i];
q = Math.floor(x / divisor);
x -= q * divisor;
if(quotient.length > 0 || q > 0)
quotient[quotient.length] = q;
}
remainders[j] = x;
dividend = quotient;
}
/* Convert the remainders to the output string */
var output = "";
for(i = remainders.length - 1; i >= 0; i--)
output += encoding.charAt(remainders[i]);
return output;
}
/*
* Encode a string as utf-8.
* For efficiency, this assumes the input is valid utf-16.
*/
function str2rstr_utf8(input)
{
var output = "";
var i = -1;
var x, y;
while(++i < input.length)
{
/* Decode utf-16 surrogate pairs */
x = input.charCodeAt(i);
y = i + 1 < input.length ? input.charCodeAt(i + 1) : 0;
if(0xD800 <= x && x <= 0xDBFF && 0xDC00 <= y && y <= 0xDFFF)
{
x = 0x10000 + ((x & 0x03FF) << 10) + (y & 0x03FF);
i++;
}
/* Encode output as utf-8 */
if(x <= 0x7F)
output += String.fromCharCode(x);
else if(x <= 0x7FF)
output += String.fromCharCode(0xC0 | ((x >>> 6 ) & 0x1F),
0x80 | ( x & 0x3F));
else if(x <= 0xFFFF)
output += String.fromCharCode(0xE0 | ((x >>> 12) & 0x0F),
0x80 | ((x >>> 6 ) & 0x3F),
0x80 | ( x & 0x3F));
else if(x <= 0x1FFFFF)
output += String.fromCharCode(0xF0 | ((x >>> 18) & 0x07),
0x80 | ((x >>> 12) & 0x3F),
0x80 | ((x >>> 6 ) & 0x3F),
0x80 | ( x & 0x3F));
}
return output;
}
/*
* Encode a string as utf-16
*/
function str2rstr_utf16le(input)
{
var output = "";
for(var i = 0; i < input.length; i++)
output += String.fromCharCode( input.charCodeAt(i) & 0xFF,
(input.charCodeAt(i) >>> 8) & 0xFF);
return output;
}
function str2rstr_utf16be(input)
{
var output = "";
for(var i = 0; i < input.length; i++)
output += String.fromCharCode((input.charCodeAt(i) >>> 8) & 0xFF,
input.charCodeAt(i) & 0xFF);
return output;
}
/*
* Convert a raw string to an array of little-endian words
* Characters >255 have their high-byte silently ignored.
*/
function rstr2binl(input)
{
var output = Array(input.length >> 2);
for(var i = 0; i < output.length; i++)
output[i] = 0;
for(var i = 0; i < input.length * 8; i += 8)
output[i>>5] |= (input.charCodeAt(i / 8) & 0xFF) << (i%32);
return output;
}
/*
* Convert an array of little-endian words to a string
*/
function binl2rstr(input)
{
var output = "";
for(var i = 0; i < input.length * 32; i += 8)
output += String.fromCharCode((input[i>>5] >>> (i % 32)) & 0xFF);
return output;
}
/*
* Calculate the MD5 of an array of little-endian words, and a bit length.
*/
function binl_md5(x, len)
{
/* append padding */
x[len >> 5] |= 0x80 << ((len) % 32);
x[(((len + 64) >>> 9) << 4) + 14] = len;
var a = 1732584193;
var b = -271733879;
var c = -1732584194;
var d = 271733878;
for(var i = 0; i < x.length; i += 16)
{
var olda = a;
var oldb = b;
var oldc = c;
var oldd = d;
a = md5_ff(a, b, c, d, x[i+ 0], 7 , -680876936);
d = md5_ff(d, a, b, c, x[i+ 1], 12, -389564586);
c = md5_ff(c, d, a, b, x[i+ 2], 17, 606105819);
b = md5_ff(b, c, d, a, x[i+ 3], 22, -1044525330);
a = md5_ff(a, b, c, d, x[i+ 4], 7 , -176418897);
d = md5_ff(d, a, b, c, x[i+ 5], 12, 1200080426);
c = md5_ff(c, d, a, b, x[i+ 6], 17, -1473231341);
b = md5_ff(b, c, d, a, x[i+ 7], 22, -45705983);
a = md5_ff(a, b, c, d, x[i+ 8], 7 , 1770035416);
d = md5_ff(d, a, b, c, x[i+ 9], 12, -1958414417);
c = md5_ff(c, d, a, b, x[i+10], 17, -42063);
b = md5_ff(b, c, d, a, x[i+11], 22, -1990404162);
a = md5_ff(a, b, c, d, x[i+12], 7 , 1804603682);
d = md5_ff(d, a, b, c, x[i+13], 12, -40341101);
c = md5_ff(c, d, a, b, x[i+14], 17, -1502002290);
b = md5_ff(b, c, d, a, x[i+15], 22, 1236535329);
a = md5_gg(a, b, c, d, x[i+ 1], 5 , -165796510);
d = md5_gg(d, a, b, c, x[i+ 6], 9 , -1069501632);
c = md5_gg(c, d, a, b, x[i+11], 14, 643717713);
b = md5_gg(b, c, d, a, x[i+ 0], 20, -373897302);
a = md5_gg(a, b, c, d, x[i+ 5], 5 , -701558691);
d = md5_gg(d, a, b, c, x[i+10], 9 , 38016083);
c = md5_gg(c, d, a, b, x[i+15], 14, -660478335);
b = md5_gg(b, c, d, a, x[i+ 4], 20, -405537848);
a = md5_gg(a, b, c, d, x[i+ 9], 5 , 568446438);
d = md5_gg(d, a, b, c, x[i+14], 9 , -1019803690);
c = md5_gg(c, d, a, b, x[i+ 3], 14, -187363961);
b = md5_gg(b, c, d, a, x[i+ 8], 20, 1163531501);
a = md5_gg(a, b, c, d, x[i+13], 5 , -1444681467);
d = md5_gg(d, a, b, c, x[i+ 2], 9 , -51403784);
c = md5_gg(c, d, a, b, x[i+ 7], 14, 1735328473);
b = md5_gg(b, c, d, a, x[i+12], 20, -1926607734);
a = md5_hh(a, b, c, d, x[i+ 5], 4 , -378558);
d = md5_hh(d, a, b, c, x[i+ 8], 11, -2022574463);
c = md5_hh(c, d, a, b, x[i+11], 16, 1839030562);
b = md5_hh(b, c, d, a, x[i+14], 23, -35309556);
a = md5_hh(a, b, c, d, x[i+ 1], 4 , -1530992060);
d = md5_hh(d, a, b, c, x[i+ 4], 11, 1272893353);
c = md5_hh(c, d, a, b, x[i+ 7], 16, -155497632);
b = md5_hh(b, c, d, a, x[i+10], 23, -1094730640);
a = md5_hh(a, b, c, d, x[i+13], 4 , 681279174);
d = md5_hh(d, a, b, c, x[i+ 0], 11, -358537222);
c = md5_hh(c, d, a, b, x[i+ 3], 16, -722521979);
b = md5_hh(b, c, d, a, x[i+ 6], 23, 76029189);
a = md5_hh(a, b, c, d, x[i+ 9], 4 , -640364487);
d = md5_hh(d, a, b, c, x[i+12], 11, -421815835);
c = md5_hh(c, d, a, b, x[i+15], 16, 530742520);
b = md5_hh(b, c, d, a, x[i+ 2], 23, -995338651);
a = md5_ii(a, b, c, d, x[i+ 0], 6 , -198630844);
d = md5_ii(d, a, b, c, x[i+ 7], 10, 1126891415);
c = md5_ii(c, d, a, b, x[i+14], 15, -1416354905);
b = md5_ii(b, c, d, a, x[i+ 5], 21, -57434055);
a = md5_ii(a, b, c, d, x[i+12], 6 , 1700485571);
d = md5_ii(d, a, b, c, x[i+ 3], 10, -1894986606);
c = md5_ii(c, d, a, b, x[i+10], 15, -1051523);
b = md5_ii(b, c, d, a, x[i+ 1], 21, -2054922799);
a = md5_ii(a, b, c, d, x[i+ 8], 6 , 1873313359);
d = md5_ii(d, a, b, c, x[i+15], 10, -30611744);
c = md5_ii(c, d, a, b, x[i+ 6], 15, -1560198380);
b = md5_ii(b, c, d, a, x[i+13], 21, 1309151649);
a = md5_ii(a, b, c, d, x[i+ 4], 6 , -145523070);
d = md5_ii(d, a, b, c, x[i+11], 10, -1120210379);
c = md5_ii(c, d, a, b, x[i+ 2], 15, 718787259);
b = md5_ii(b, c, d, a, x[i+ 9], 21, -343485551);
a = safe_add(a, olda);
b = safe_add(b, oldb);
c = safe_add(c, oldc);
d = safe_add(d, oldd);
}
return Array(a, b, c, d);
}
/*
* These functions implement the four basic operations the algorithm uses.
*/
function md5_cmn(q, a, b, x, s, t)
{
return safe_add(bit_rol(safe_add(safe_add(a, q), safe_add(x, t)), s),b);
}
function md5_ff(a, b, c, d, x, s, t)
{
return md5_cmn((b & c) | ((~b) & d), a, b, x, s, t);
}
function md5_gg(a, b, c, d, x, s, t)
{
return md5_cmn((b & d) | (c & (~d)), a, b, x, s, t);
}
function md5_hh(a, b, c, d, x, s, t)
{
return md5_cmn(b ^ c ^ d, a, b, x, s, t);
}
function md5_ii(a, b, c, d, x, s, t)
{
return md5_cmn(c ^ (b | (~d)), a, b, x, s, t);
}
/*
* Add integers, wrapping at 2^32. This uses 16-bit operations internally
* to work around bugs in some JS interpreters.
*/
function safe_add(x, y)
{
var lsw = (x & 0xFFFF) + (y & 0xFFFF);
var msw = (x >> 16) + (y >> 16) + (lsw >> 16);
return (msw << 16) | (lsw & 0xFFFF);
}
/*
* Bitwise rotate a 32-bit number to the left.
*/
function bit_rol(num, cnt)
{
return (num << cnt) | (num >>> (32 - cnt));
}
});

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,193 @@
(function(root, factory) {
'use strict';
if (typeof define === 'function' && define.amd) {
define(factory);
} else if (typeof exports === 'object') {
module.exports = factory();
} else {
root.tzabbr = factory();
}
}(this, function() {
'use strict';
return {
"ACDT": "+1030",
"ACST": "+0930",
"ACT": "+0800",
"ADT": "-0300",
"AEDT": "+1100",
"AEST": "+1000",
"AFT": "+0430",
"AKDT": "-0800",
"AKST": "-0900",
"AMST": "-0300",
"AMT": "+0400",
"ART": "-0300",
"AST": "+0300",
"AWDT": "+0900",
"AWST": "+0800",
"AZOST": "-0100",
"AZT": "+0400",
"BDT": "+0800",
"BIOT": "+0600",
"BIT": "-1200",
"BOT": "-0400",
"BRT": "-0300",
"BST": "+0600",
"BTT": "+0600",
"CAT": "+0200",
"CCT": "+0630",
"CDT": "-0500",
"CEDT": "+0200",
"CEST": "+0200",
"CET": "+0100",
"CHADT": "+1345",
"CHAST": "+1245",
"CHOT": "+0800",
"CHST": "+1000",
"CHUT": "+1000",
"CIST": "-0800",
"CIT": "+0800",
"CKT": "-1000",
"CLST": "-0300",
"CLT": "-0400",
"COST": "-0400",
"COT": "-0500",
"CST": "-0600",
"CT": "+0800",
"CVT": "-0100",
"CWST": "+0845",
"CXT": "+0700",
"DAVT": "+0700",
"DDUT": "+1000",
"DFT": "+0100",
"EASST": "-0500",
"EAST": "-0600",
"EAT": "+0300",
"ECT": "-0500",
"EDT": "-0400",
"EEDT": "+0300",
"EEST": "+0300",
"EET": "+0200",
"EGST": "+0000",
"EGT": "-0100",
"EIT": "+0900",
"EST": "-0500",
"FET": "+0300",
"FJT": "+1200",
"FKST": "-0300",
"FKT": "-0400",
"FNT": "-0200",
"GALT": "-0600",
"GAMT": "-0900",
"GET": "+0400",
"GFT": "-0300",
"GILT": "+1200",
"GIT": "-0900",
"GMT": "+0000",
"GST": "+0400",
"GYT": "-0400",
"HADT": "-0900",
"HAEC": "+0200",
"HAST": "-1000",
"HKT": "+0800",
"HMT": "+0500",
"HOVT": "+0700",
"HST": "-1000",
"ICT": "+0700",
"IDT": "+0300",
"IOT": "+0300",
"IRDT": "+0430",
"IRKT": "+0900",
"IRST": "+0330",
"IST": "+0530",
"JST": "+0900",
"KGT": "+0600",
"KOST": "+1100",
"KRAT": "+0700",
"KST": "+0900",
"LHST": "+1030",
"LINT": "+1400",
"MAGT": "+1200",
"MART": "-0930",
"MAWT": "+0500",
"MDT": "-0600",
"MET": "+0100",
"MEST": "+0200",
"MHT": "+1200",
"MIST": "+1100",
"MIT": "-0930",
"MMT": "+0630",
"MSK": "+0400",
"MST": "-0700",
"MUT": "+0400",
"MVT": "+0500",
"MYT": "+0800",
"NCT": "+1100",
"NDT": "-0230",
"NFT": "+1130",
"NPT": "+0545",
"NST": "-0330",
"NT": "-0330",
"NUT": "-1100",
"NZDT": "+1300",
"NZST": "+1200",
"OMST": "+0700",
"ORAT": "+0500",
"PDT": "-0700",
"PET": "-0500",
"PETT": "+1200",
"PGT": "+1000",
"PHOT": "+1300",
"PHT": "+0800",
"PKT": "+0500",
"PMDT": "-0200",
"PMST": "-0300",
"PONT": "+1100",
"PST": "-0800",
"PYST": "-0300",
"PYT": "-0400",
"RET": "+0400",
"ROTT": "-0300",
"SAKT": "+1100",
"SAMT": "+0400",
"SAST": "+0200",
"SBT": "+1100",
"SCT": "+0400",
"SGT": "+0800",
"SLST": "+0530",
"SRT": "-0300",
"SST": "+0800",
"SYOT": "+0300",
"TAHT": "-1000",
"THA": "+0700",
"TFT": "+0500",
"TJT": "+0500",
"TKT": "+1300",
"TLT": "+0900",
"TMT": "+0500",
"TOT": "+1300",
"TVT": "+1200",
"UCT": "+0000",
"ULAT": "+0800",
"UTC": "+0000",
"UYST": "-0200",
"UYT": "-0300",
"UZT": "+0500",
"VET": "-0430",
"VLAT": "+1000",
"VOLT": "+0400",
"VOST": "+0600",
"VUT": "+1100",
"WAKT": "+1200",
"WAST": "+0200",
"WAT": "+0100",
"WEDT": "+0100",
"WEST": "+0100",
"WET": "+0000",
"WST": "+0800",
"YAKT": "+1000",
"YEKT": "+0600",
"Z": "+0000"
};
}));

View file

@ -0,0 +1,676 @@
// Copyright (c) 2013 Andris Reinman
//
// 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.
(function(root, factory) {
'use strict';
if (typeof define === 'function' && define.amd) {
define(['mimefuncs', 'addressparser', 'mimeparser-tzabbr'], factory);
} else if (typeof exports === 'object') {
module.exports = factory(require('mimefuncs'), require('wo-addressparser'), require('./mimeparser-tzabbr'));
} else {
root.MimeParser = factory(root.mimefuncs, root.addressparser, root.tzabbr);
}
}(this, function(mimefuncs, addressparser, tzabbr) {
'use strict';
/**
* Creates a parser for a mime stream
*
* @constructor
*/
function MimeParser() {
/**
* Returned to the write calls
*/
this.running = true;
/**
* Cache for parsed node objects
*/
this.nodes = {};
/**
* Root node object
*/
this.node = new MimeNode(null, this);
/**
* Data is written to nodes one line at the time. If entire line
* is not received yet, buffer it before passing on
*/
this._remainder = '';
}
/**
* Writes a chunk of data to the processing queue. Splits data to lines and feeds
* complete lines to the current node element
*
* @param {Uint8Array|String} chunk Chunk to be processed. Either an Uint8Array value or a 'binary' string
*/
MimeParser.prototype.write = function(chunk) {
if (!chunk || !chunk.length) {
return !this.running;
}
var lines = (this._remainder + (typeof chunk === 'object' ?
mimefuncs.fromTypedArray(chunk) : chunk)).split(/\r?\n/g);
this._remainder = lines.pop();
for (var i = 0, len = lines.length; i < len; i++) {
this.node.writeLine(lines[i]);
}
return !this.running;
};
/**
* Indicates that there is no more data coming
*
* @param {Uint8Array|String} [chunk] Final chunk to be processed
*/
MimeParser.prototype.end = function(chunk) {
if (chunk && chunk.length) {
this.write(chunk);
}
if (this.node._lineCount || this._remainder) {
this.node.writeLine(this._remainder);
this._remainder = '';
}
if (this.node) {
this.node.finalize();
}
this.onend();
};
/**
* Retrieves a mime part object for specified path
*
* parser.getNode('1.2.3')
*
* @param {String} path Path to the node
*/
MimeParser.prototype.getNode = function(path) {
path = path || '';
return this.nodes['node' + path] || null;
};
// PARSER EVENTS
/**
* Override this function.
* Called when the parsing is ended
* @event
*/
MimeParser.prototype.onend = function() {};
/**
* Override this function.
* Called when the parsing is ended
* @event
* @param {Object} node Current mime part. See node.header for header lines
*/
MimeParser.prototype.onheader = function() {};
/**
* Override this function.
* Called when a body chunk is emitted
* @event
* @param {Object} node Current mime part
* @param {Uint8Array} chunk Body chunk
*/
MimeParser.prototype.onbody = function() {};
// NODE PROCESSING
/**
* Creates an object that holds and manages one part of the multipart message
*
* @constructor
* @param {Object} parentNode Reference to the parent element. If not specified, then this is root node
* @param {Object} parser MimeParser object
*/
function MimeNode(parentNode, parser) {
// Public properties
/**
* An array of unfolded header lines
*/
this.header = [];
/**
* An object that holds header key=value pairs
*/
this.headers = {};
/**
* Path for this node
*/
this.path = parentNode ? parentNode.path.concat(parentNode._childNodes.length + 1) : [];
// Private properties
/**
* Reference to the 'master' parser object
*/
this._parser = parser;
/**
* Parent node for this specific node
*/
this._parentNode = parentNode;
/**
* Current state, always starts out with HEADER
*/
this._state = 'HEADER';
/**
* Body buffer
*/
this._bodyBuffer = '';
/**
* Line counter bor the body part
*/
this._lineCount = 0;
/**
* If this is a multipart or message/rfc822 mime part, the value
* will be converted to array and hold all child nodes for this node
*/
this._childNodes = false;
/**
* Active child node (if available)
*/
this._currentChild = false;
/**
* Remainder string when dealing with base64 and qp values
*/
this._lineRemainder = '';
/**
* Indicates if this is a multipart node
*/
this._isMultipart = false;
/**
* Stores boundary value for current multipart node
*/
this._multipartBoundary = false;
/**
* Indicates if this is a message/rfc822 node
*/
this._isRfc822 = false;
/**
* Stores the raw content of this node
*/
this.raw = '';
// Att this node to the path cache
this._parser.nodes['node' + this.path.join('.')] = this;
}
// Public methods
/**
* Processes an enitre input line
*
* @param {String} line Entire input line as 'binary' string
*/
MimeNode.prototype.writeLine = function(line) {
this.raw += (this.raw ? '\n' : '') + line;
if (this._state === 'HEADER') {
this._processHeaderLine(line);
} else if (this._state === 'BODY') {
this._processBodyLine(line);
}
};
/**
* Processes any remainders
*/
MimeNode.prototype.finalize = function() {
if (this._isRfc822) {
this._currentChild.finalize();
} else {
this._emitBody(true);
}
};
// Private methods
/**
* Processes a line in the HEADER state. It the line is empty, change state to BODY
*
* @param {String} line Entire input line as 'binary' string
*/
MimeNode.prototype._processHeaderLine = function(line) {
if (!line) {
this._parseHeaders();
this._parser.onheader(this);
this._state = 'BODY';
return;
}
if (line.match(/^\s/) && this.header.length) {
this.header[this.header.length - 1] += '\n' + line;
} else {
this.header.push(line);
}
};
/**
* Joins folded header lines and calls Content-Type and Transfer-Encoding processors
*/
MimeNode.prototype._parseHeaders = function() {
// Join header lines
var key, value, hasBinary;
for (var i = 0, len = this.header.length; i < len; i++) {
value = this.header[i].split(':');
key = (value.shift() || '').trim().toLowerCase();
value = (value.join(':') || '').replace(/\n/g, '').trim();
if (value.match(/[\u0080-\uFFFF]/)) {
if (!this.charset) {
hasBinary = true;
}
// use default charset at first and if the actual charset is resolved, the conversion is re-run
value = mimefuncs.charset.decode(mimefuncs.charset.convert(mimefuncs.toTypedArray(value), this.charset || 'iso-8859-1'));
}
if (!this.headers[key]) {
this.headers[key] = [this._parseHeaderValue(key, value)];
} else {
this.headers[key].push(this._parseHeaderValue(key, value));
}
if (!this.charset && key === 'content-type') {
this.charset = this.headers[key][this.headers[key].length - 1].params.charset;
}
if (hasBinary && this.charset) {
// reset values and start over once charset has been resolved and 8bit content has been found
hasBinary = false;
this.headers = {};
i = -1; // next iteration has i == 0
}
}
this._processContentType();
this._processContentTransferEncoding();
};
/**
* Parses single header value
* @param {String} key Header key
* @param {String} value Value for the key
* @return {Object} parsed header
*/
MimeNode.prototype._parseHeaderValue = function(key, value) {
var parsedValue, isAddress = false;
switch (key) {
case 'content-type':
case 'content-transfer-encoding':
case 'content-disposition':
case 'dkim-signature':
parsedValue = mimefuncs.parseHeaderValue(value);
break;
case 'from':
case 'sender':
case 'to':
case 'reply-to':
case 'cc':
case 'bcc':
case 'abuse-reports-to':
case 'errors-to':
case 'return-path':
case 'delivered-to':
isAddress = true;
parsedValue = {
value: [].concat(addressparser.parse(value) || [])
};
break;
case 'date':
parsedValue = {
value: this._parseDate(value)
};
break;
default:
parsedValue = {
value: value
};
}
parsedValue.initial = value;
this._decodeHeaderCharset(parsedValue, {
isAddress: isAddress
});
return parsedValue;
};
/**
* Checks if a date string can be parsed. Falls back replacing timezone
* abbrevations with timezone values
*
* @param {String} str Date header
* @returns {String} UTC date string if parsing succeeded, otherwise returns input value
*/
MimeNode.prototype._parseDate = function(str) {
str = (str || '').toString().trim();
var date = new Date(str);
if (this._isValidDate(date)) {
return date.toUTCString().replace(/GMT/, '+0000');
}
// Assume last alpha part is a timezone
// Ex: "Date: Thu, 15 May 2014 13:53:30 EEST"
str = str.replace(/\b[a-z]+$/i, function(tz) {
tz = tz.toUpperCase();
if (tzabbr.hasOwnProperty(tz)) {
return tzabbr[tz];
}
return tz;
});
date = new Date(str);
if (this._isValidDate(date)) {
return date.toUTCString().replace(/GMT/, '+0000');
} else {
return str;
}
};
/**
* Checks if a value is a Date object and it contains an actual date value
* @param {Date} date Date object to check
* @returns {Boolean} True if the value is a valid date
*/
MimeNode.prototype._isValidDate = function(date) {
return Object.prototype.toString.call(date) === '[object Date]' && date.toString() !== 'Invalid Date';
};
MimeNode.prototype._decodeHeaderCharset = function(parsed, options) {
options = options || {};
// decode default value
if (typeof parsed.value === 'string') {
parsed.value = mimefuncs.mimeWordsDecode(parsed.value);
}
// decode possible params
Object.keys(parsed.params || {}).forEach(function(key) {
if (typeof parsed.params[key] === 'string') {
parsed.params[key] = mimefuncs.mimeWordsDecode(parsed.params[key]);
}
});
// decode addresses
if (options.isAddress && Array.isArray(parsed.value)) {
parsed.value.forEach(function(addr) {
if (addr.name) {
addr.name = mimefuncs.mimeWordsDecode(addr.name);
if (Array.isArray(addr.group)) {
this._decodeHeaderCharset({
value: addr.group
}, {
isAddress: true
});
}
}
}.bind(this));
}
return parsed;
};
/**
* Parses Content-Type value and selects following actions.
*/
MimeNode.prototype._processContentType = function() {
var contentDisposition;
this.contentType = this.headers['content-type'] && this.headers['content-type'][0] ||
mimefuncs.parseHeaderValue('text/plain');
this.contentType.value = (this.contentType.value || '').toLowerCase().trim();
this.contentType.type = (this.contentType.value.split('/').shift() || 'text');
if (this.contentType.params && this.contentType.params.charset && !this.charset) {
this.charset = this.contentType.params.charset;
}
if (this.contentType.type === 'multipart' && this.contentType.params.boundary) {
this._childNodes = [];
this._isMultipart = (this.contentType.value.split('/').pop() || 'mixed');
this._multipartBoundary = this.contentType.params.boundary;
}
if (this.contentType.value === 'message/rfc822') {
/**
* Parse message/rfc822 only if the mime part is not marked with content-disposition: attachment,
* otherwise treat it like a regular attachment
*/
contentDisposition = this.headers['content-disposition'] && this.headers['content-disposition'][0] ||
mimefuncs.parseHeaderValue('');
if ((contentDisposition.value || '').toLowerCase().trim() !== 'attachment') {
this._childNodes = [];
this._currentChild = new MimeNode(this, this._parser);
this._childNodes.push(this._currentChild);
this._isRfc822 = true;
}
}
};
/**
* Parses Content-Trasnfer-Encoding value to see if the body needs to be converted
* before it can be emitted
*/
MimeNode.prototype._processContentTransferEncoding = function() {
this.contentTransferEncoding = this.headers['content-transfer-encoding'] && this.headers['content-transfer-encoding'][0] ||
mimefuncs.parseHeaderValue('7bit');
this.contentTransferEncoding.value = (this.contentTransferEncoding.value || '').toLowerCase().trim();
};
/**
* Processes a line in the BODY state. If this is a multipart or rfc822 node,
* passes line value to child nodes.
*
* @param {String} line Entire input line as 'binary' string
*/
MimeNode.prototype._processBodyLine = function(line) {
var curLine, match;
this._lineCount++;
if (this._isMultipart) {
if (line === '--' + this._multipartBoundary) {
if (this._currentChild) {
this._currentChild.finalize();
}
this._currentChild = new MimeNode(this, this._parser);
this._childNodes.push(this._currentChild);
} else if (line === '--' + this._multipartBoundary + '--') {
if (this._currentChild) {
this._currentChild.finalize();
}
this._currentChild = false;
} else if (this._currentChild) {
this._currentChild.writeLine(line);
} else {
// Ignore body for multipart
}
} else if (this._isRfc822) {
this._currentChild.writeLine(line);
} else {
switch (this.contentTransferEncoding.value) {
case 'base64':
curLine = this._lineRemainder + line.trim();
if (curLine.length % 4) {
this._lineRemainder = curLine.substr(-curLine.length % 4);
curLine = curLine.substr(0, curLine.length - this._lineRemainder.length);
} else {
this._lineRemainder = '';
}
if (curLine.length) {
this._bodyBuffer += mimefuncs.fromTypedArray(mimefuncs.base64.decode(curLine));
}
break;
case 'quoted-printable':
curLine = this._lineRemainder + (this._lineCount > 1 ? '\n' : '') + line;
if ((match = curLine.match(/=[a-f0-9]{0,1}$/i))) {
this._lineRemainder = match[0];
curLine = curLine.substr(0, curLine.length - this._lineRemainder.length);
} else {
this._lineRemainder = '';
}
this._bodyBuffer += curLine.replace(/\=(\r?\n|$)/g, '').replace(/=([a-f0-9]{2})/ig, function(m, code) {
return String.fromCharCode(parseInt(code, 16));
});
break;
// case '7bit':
// case '8bit':
default:
this._bodyBuffer += (this._lineCount > 1 ? '\n' : '') + line;
break;
}
}
};
/**
* Emits a chunk of the body
*
* @param {Boolean} forceEmit If set to true does not keep any remainders
*/
MimeNode.prototype._emitBody = function() {
var contentDisposition = this.headers['content-disposition'] && this.headers['content-disposition'][0] ||
mimefuncs.parseHeaderValue('');
var delSp;
if (this._isMultipart || !this._bodyBuffer) {
return;
}
// Process flowed text before emitting it
if (/^text\/(plain|html)$/i.test(this.contentType.value) &&
this.contentType.params && /^flowed$/i.test(this.contentType.params.format)) {
delSp = /^yes$/i.test(this.contentType.params.delsp);
this._bodyBuffer = this._bodyBuffer.
split('\n').
// remove soft linebreaks
// soft linebreaks are added after space symbols
reduce(function(previousValue, currentValue, index) {
var body = previousValue;
if (delSp) {
// delsp adds spaces to text to be able to fold it
// these spaces can be removed once the text is unfolded
body = body.replace(/[ ]+$/, '');
}
if (/ $/.test(previousValue) && !/(^|\n)\-\- $/.test(previousValue)) {
return body + currentValue;
} else {
return body + '\n' + currentValue;
}
}).
// remove whitespace stuffing
// http://tools.ietf.org/html/rfc3676#section-4.4
replace(/^ /gm, '');
}
this.content = mimefuncs.toTypedArray(this._bodyBuffer);
if (/^text\/(plain|html)$/i.test(this.contentType.value) && !/^attachment$/i.test(contentDisposition.value)) {
if (!this.charset && /^text\/html$/i.test(this.contentType.value)) {
this.charset = this._detectHTMLCharset(this._bodyBuffer);
}
// decode "binary" string to an unicode string
if (!/^utf[\-_]?8$/i.test(this.charset)) {
this.content = mimefuncs.charset.convert(mimefuncs.toTypedArray(this._bodyBuffer), this.charset || 'iso-8859-1');
}
// override charset for text nodes
this.charset = this.contentType.params.charset = 'utf-8';
}
this._bodyBuffer = '';
this._parser.onbody(this, this.content);
};
/**
* Detect charset from a html file
*
* @param {String} html Input HTML
* @returns {String} Charset if found or undefined
*/
MimeNode.prototype._detectHTMLCharset = function(html) {
var charset, input, meta;
if (typeof html !== 'string') {
html = html.toString('ascii');
}
html = html.replace(/\r?\n|\r/g, " ");
if ((meta = html.match(/<meta\s+http-equiv=["'\s]*content-type[^>]*?>/i))) {
input = meta[0];
}
if (input) {
charset = input.match(/charset\s?=\s?([a-zA-Z\-_:0-9]*);?/);
if (charset) {
charset = (charset[1] || '').trim().toLowerCase();
}
}
if (!charset && (meta = html.match(/<meta\s+charset=["'\s]*([^"'<>\/\s]+)/i))) {
charset = (meta[1] || '').trim().toLowerCase();
}
return charset;
};
return MimeParser;
}));

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,19 @@
/*global define*/
define(function() {
/**
* Mixes properties from source into target.
* @param {Object} target target of the mix.
* @param {Object} source source object providing properties to mix in.
* @param {Boolean} override if target already has a the property,
* override it with the one from source.
* @return {Object} the target object, now with the new properties
* mixed in.
*/
return function mix(target, source, override) {
Object.keys(source).forEach(function(key) {
if (!target.hasOwnProperty(key) || override)
target[key] = source[key];
});
return target;
};
});

View file

@ -0,0 +1,507 @@
/*! http://mths.be/punycode v1.2.4 by @mathias */
;(function(root) {
/** Detect free variables */
var freeExports = typeof exports == 'object' && exports;
var freeModule = typeof module == 'object' && module &&
module.exports == freeExports && module;
var freeGlobal = typeof global == 'object' && global;
if (freeGlobal.global === freeGlobal || freeGlobal.window === freeGlobal) {
root = freeGlobal;
}
/**
* The `punycode` object.
* @name punycode
* @type Object
*/
var punycode,
/** Highest positive signed 32-bit float value */
maxInt = 2147483647, // aka. 0x7FFFFFFF or 2^31-1
/** Bootstring parameters */
base = 36,
tMin = 1,
tMax = 26,
skew = 38,
damp = 700,
initialBias = 72,
initialN = 128, // 0x80
delimiter = '-', // '\x2D'
/** Regular expressions */
regexPunycode = /^xn--/,
regexNonASCII = /[^ -~]/, // unprintable ASCII chars + non-ASCII chars
regexSeparators = /\x2E|\u3002|\uFF0E|\uFF61/g, // RFC 3490 separators
/** Error messages */
errors = {
'overflow': 'Overflow: input needs wider integers to process',
'not-basic': 'Illegal input >= 0x80 (not a basic code point)',
'invalid-input': 'Invalid input'
},
/** Convenience shortcuts */
baseMinusTMin = base - tMin,
floor = Math.floor,
stringFromCharCode = String.fromCharCode,
/** Temporary variable */
key;
/*--------------------------------------------------------------------------*/
/**
* A generic error utility function.
* @private
* @param {String} type The error type.
* @returns {Error} Throws a `RangeError` with the applicable error message.
*/
function error(type) {
throw RangeError(errors[type]);
}
/**
* A generic `Array#map` utility function.
* @private
* @param {Array} array The array to iterate over.
* @param {Function} callback The function that gets called for every array
* item.
* @returns {Array} A new array of values returned by the callback function.
*/
function map(array, fn) {
var length = array.length;
while (length--) {
array[length] = fn(array[length]);
}
return array;
}
/**
* A simple `Array#map`-like wrapper to work with domain name strings.
* @private
* @param {String} domain The domain name.
* @param {Function} callback The function that gets called for every
* character.
* @returns {Array} A new string of characters returned by the callback
* function.
*/
function mapDomain(string, fn) {
return map(string.split(regexSeparators), fn).join('.');
}
/**
* Creates an array containing the numeric code points of each Unicode
* character in the string. While JavaScript uses UCS-2 internally,
* this function will convert a pair of surrogate halves (each of which
* UCS-2 exposes as separate characters) into a single code point,
* matching UTF-16.
* @see `punycode.ucs2.encode`
* @see <http://mathiasbynens.be/notes/javascript-encoding>
* @memberOf punycode.ucs2
* @name decode
* @param {String} string The Unicode input string (UCS-2).
* @returns {Array} The new array of code points.
*/
function ucs2decode(string) {
var output = [],
counter = 0,
length = string.length,
value,
extra;
while (counter < length) {
value = string.charCodeAt(counter++);
if (value >= 0xD800 && value <= 0xDBFF && counter < length) {
// high surrogate, and there is a next character
extra = string.charCodeAt(counter++);
if ((extra & 0xFC00) == 0xDC00) { // low surrogate
output.push(((value & 0x3FF) << 10) + (extra & 0x3FF) + 0x10000);
} else {
// unmatched surrogate; only append this code unit, in case the next
// code unit is the high surrogate of a surrogate pair
output.push(value);
counter--;
}
} else {
output.push(value);
}
}
return output;
}
/**
* Creates a string based on an array of numeric code points.
* @see `punycode.ucs2.decode`
* @memberOf punycode.ucs2
* @name encode
* @param {Array} codePoints The array of numeric code points.
* @returns {String} The new Unicode string (UCS-2).
*/
function ucs2encode(array) {
return map(array, function(value) {
var output = '';
if (value > 0xFFFF) {
value -= 0x10000;
output += stringFromCharCode(value >>> 10 & 0x3FF | 0xD800);
value = 0xDC00 | value & 0x3FF;
}
output += stringFromCharCode(value);
return output;
}).join('');
}
/**
* Converts a basic code point into a digit/integer.
* @see `digitToBasic()`
* @private
* @param {Number} codePoint The basic numeric code point value.
* @returns {Number} The numeric value of a basic code point (for use in
* representing integers) in the range `0` to `base - 1`, or `base` if
* the code point does not represent a value.
*/
function basicToDigit(codePoint) {
if (codePoint - 48 < 10) {
return codePoint - 22;
}
if (codePoint - 65 < 26) {
return codePoint - 65;
}
if (codePoint - 97 < 26) {
return codePoint - 97;
}
return base;
}
/**
* Converts a digit/integer into a basic code point.
* @see `basicToDigit()`
* @private
* @param {Number} digit The numeric value of a basic code point.
* @returns {Number} The basic code point whose value (when used for
* representing integers) is `digit`, which needs to be in the range
* `0` to `base - 1`. If `flag` is non-zero, the uppercase form is
* used; else, the lowercase form is used. The behavior is undefined
* if `flag` is non-zero and `digit` has no uppercase form.
*/
function digitToBasic(digit, flag) {
// 0..25 map to ASCII a..z or A..Z
// 26..35 map to ASCII 0..9
return digit + 22 + 75 * (digit < 26) - ((flag != 0) << 5);
}
/**
* Bias adaptation function as per section 3.4 of RFC 3492.
* http://tools.ietf.org/html/rfc3492#section-3.4
* @private
*/
function adapt(delta, numPoints, firstTime) {
var k = 0;
delta = firstTime ? floor(delta / damp) : delta >> 1;
delta += floor(delta / numPoints);
for (/* no initialization */; delta > baseMinusTMin * tMax >> 1; k += base) {
delta = floor(delta / baseMinusTMin);
}
return floor(k + (baseMinusTMin + 1) * delta / (delta + skew));
}
/**
* Converts a Punycode string of ASCII-only symbols to a string of Unicode
* symbols.
* @memberOf punycode
* @param {String} input The Punycode string of ASCII-only symbols.
* @returns {String} The resulting string of Unicode symbols.
*/
function decode(input) {
// Don't use UCS-2
var output = [],
inputLength = input.length,
out,
i = 0,
n = initialN,
bias = initialBias,
basic,
j,
index,
oldi,
w,
k,
digit,
t,
/** Cached calculation results */
baseMinusT;
// Handle the basic code points: let `basic` be the number of input code
// points before the last delimiter, or `0` if there is none, then copy
// the first basic code points to the output.
basic = input.lastIndexOf(delimiter);
if (basic < 0) {
basic = 0;
}
for (j = 0; j < basic; ++j) {
// if it's not a basic code point
if (input.charCodeAt(j) >= 0x80) {
error('not-basic');
}
output.push(input.charCodeAt(j));
}
// Main decoding loop: start just after the last delimiter if any basic code
// points were copied; start at the beginning otherwise.
for (index = basic > 0 ? basic + 1 : 0; index < inputLength; /* no final expression */) {
// `index` is the index of the next character to be consumed.
// Decode a generalized variable-length integer into `delta`,
// which gets added to `i`. The overflow checking is easier
// if we increase `i` as we go, then subtract off its starting
// value at the end to obtain `delta`.
for (oldi = i, w = 1, k = base; /* no condition */; k += base) {
if (index >= inputLength) {
error('invalid-input');
}
digit = basicToDigit(input.charCodeAt(index++));
if (digit >= base || digit > floor((maxInt - i) / w)) {
error('overflow');
}
i += digit * w;
t = k <= bias ? tMin : (k >= bias + tMax ? tMax : k - bias);
if (digit < t) {
break;
}
baseMinusT = base - t;
if (w > floor(maxInt / baseMinusT)) {
error('overflow');
}
w *= baseMinusT;
}
out = output.length + 1;
bias = adapt(i - oldi, out, oldi == 0);
// `i` was supposed to wrap around from `out` to `0`,
// incrementing `n` each time, so we'll fix that now:
if (floor(i / out) > maxInt - n) {
error('overflow');
}
n += floor(i / out);
i %= out;
// Insert `n` at position `i` of the output
output.splice(i++, 0, n);
}
return ucs2encode(output);
}
/**
* Converts a string of Unicode symbols to a Punycode string of ASCII-only
* symbols.
* @memberOf punycode
* @param {String} input The string of Unicode symbols.
* @returns {String} The resulting Punycode string of ASCII-only symbols.
*/
function encode(input) {
var n,
delta,
handledCPCount,
basicLength,
bias,
j,
m,
q,
k,
t,
currentValue,
output = [],
/** `inputLength` will hold the number of code points in `input`. */
inputLength,
/** Cached calculation results */
handledCPCountPlusOne,
baseMinusT,
qMinusT;
// Convert the input in UCS-2 to Unicode
input = ucs2decode(input);
// Cache the length
inputLength = input.length;
// Initialize the state
n = initialN;
delta = 0;
bias = initialBias;
// Handle the basic code points
for (j = 0; j < inputLength; ++j) {
currentValue = input[j];
if (currentValue < 0x80) {
output.push(stringFromCharCode(currentValue));
}
}
handledCPCount = basicLength = output.length;
// `handledCPCount` is the number of code points that have been handled;
// `basicLength` is the number of basic code points.
// Finish the basic string - if it is not empty - with a delimiter
if (basicLength) {
output.push(delimiter);
}
// Main encoding loop:
while (handledCPCount < inputLength) {
// All non-basic code points < n have been handled already. Find the next
// larger one:
for (m = maxInt, j = 0; j < inputLength; ++j) {
currentValue = input[j];
if (currentValue >= n && currentValue < m) {
m = currentValue;
}
}
// Increase `delta` enough to advance the decoder's <n,i> state to <m,0>,
// but guard against overflow
handledCPCountPlusOne = handledCPCount + 1;
if (m - n > floor((maxInt - delta) / handledCPCountPlusOne)) {
error('overflow');
}
delta += (m - n) * handledCPCountPlusOne;
n = m;
for (j = 0; j < inputLength; ++j) {
currentValue = input[j];
if (currentValue < n && ++delta > maxInt) {
error('overflow');
}
if (currentValue == n) {
// Represent delta as a generalized variable-length integer
for (q = delta, k = base; /* no condition */; k += base) {
t = k <= bias ? tMin : (k >= bias + tMax ? tMax : k - bias);
if (q < t) {
break;
}
qMinusT = q - t;
baseMinusT = base - t;
output.push(
stringFromCharCode(digitToBasic(t + qMinusT % baseMinusT, 0))
);
q = floor(qMinusT / baseMinusT);
}
output.push(stringFromCharCode(digitToBasic(q, 0)));
bias = adapt(delta, handledCPCountPlusOne, handledCPCount == basicLength);
delta = 0;
++handledCPCount;
}
}
++delta;
++n;
}
return output.join('');
}
/**
* Converts a Punycode string representing a domain name to Unicode. Only the
* Punycoded parts of the domain name will be converted, i.e. it doesn't
* matter if you call it on a string that has already been converted to
* Unicode.
* @memberOf punycode
* @param {String} domain The Punycode domain name to convert to Unicode.
* @returns {String} The Unicode representation of the given Punycode
* string.
*/
function toUnicode(domain) {
return mapDomain(domain, function(string) {
return regexPunycode.test(string)
? decode(string.slice(4).toLowerCase())
: string;
});
}
/**
* Converts a Unicode string representing a domain name to Punycode. Only the
* non-ASCII parts of the domain name will be converted, i.e. it doesn't
* matter if you call it with a domain that's already in ASCII.
* @memberOf punycode
* @param {String} domain The domain name to convert, as a Unicode string.
* @returns {String} The Punycode representation of the given domain name.
*/
function toASCII(domain) {
return mapDomain(domain, function(string) {
return regexNonASCII.test(string)
? 'xn--' + encode(string)
: string;
});
}
/*--------------------------------------------------------------------------*/
/** Define the public API */
punycode = {
/**
* A string representing the current Punycode.js version number.
* @memberOf punycode
* @type String
*/
'version': '1.2.4',
/**
* An object of methods to convert from JavaScript's internal character
* representation (UCS-2) to Unicode code points, and back.
* @see <http://mathiasbynens.be/notes/javascript-encoding>
* @memberOf punycode
* @type Object
*/
'ucs2': {
'decode': ucs2decode,
'encode': ucs2encode
},
'decode': decode,
'encode': encode,
'toASCII': toASCII,
'toUnicode': toUnicode
};
/** Expose `punycode` */
// Some AMD build optimizers, like r.js, check for specific condition patterns
// like the following:
if (
typeof define == 'function' &&
typeof define.amd == 'object' &&
define.amd
) {
define('punycode', function() {
return punycode;
});
} else if (freeExports && !freeExports.nodeType) {
if (freeModule) { // in Node.js or RingoJS v0.8.0+
freeModule.exports = punycode;
} else { // in Narwhal or RingoJS v0.7.0-
for (key in punycode) {
punycode.hasOwnProperty(key) && (freeExports[key] = punycode[key]);
}
}
} else { // in Rhino or a web browser
root.punycode = punycode;
}
}(this));

View file

@ -0,0 +1,169 @@
define(function(require, exports, module) {
/**
* Safe atob-variant that does not throw exceptions and just ignores
* characters that it does not know about. This is an attempt to
* mimic node's implementation so that we can parse base64 with
* newlines present as well as being tolerant of complete gibberish
* people throw at us. Since we are doing this by hand, we also take
* the opportunity to put the output directly in a typed array.
*
* In contrast, window.atob() throws Exceptions for all kinds of
* angry reasons.
*/
exports.decode = function(s) {
var bitsSoFar = 0, validBits = 0, iOut = 0,
arr = new Uint8Array(Math.ceil(s.length * 3 / 4));
for (var i = 0; i < s.length; i++) {
var c = s.charCodeAt(i), bits;
if (c >= 65 && c <= 90) // [A-Z]
bits = c - 65;
else if (c >= 97 && c <= 122) // [a-z]
bits = c - 97 + 26;
else if (c >= 48 && c <= 57) // [0-9]
bits = c - 48 + 52;
else if (c === 43) // +
bits = 62;
else if (c === 47) // /
bits = 63;
else if (c === 61) { // =
validBits = 0;
continue;
}
// ignore all other characters!
else
continue;
bitsSoFar = (bitsSoFar << 6) | bits;
validBits += 6;
if (validBits >= 8) {
validBits -= 8;
arr[iOut++] = bitsSoFar >> validBits;
if (validBits === 2)
bitsSoFar &= 0x3;
else if (validBits === 4)
bitsSoFar &= 0xf;
}
}
if (iOut < arr.length)
return arr.subarray(0, iOut);
return arr;
}
/**
* UInt8Array => base64 => UTF-8 String
*/
exports.encode = function(view) {
var sbits, i;
sbits = new Array(view.length);
for (i = 0; i < view.length; i++) {
sbits[i] = String.fromCharCode(view[i]);
}
// (btoa is binary JS string -> base64 ASCII string)
return window.btoa(sbits.join(''));
}
/**
* Base64 binary data from a Uint8array to a Uint8Array the way an
* RFC2822 MIME message likes it. Which is to say with a maximum of
* 76 bytes of base64 encoded data followed by a \r\n. If the last
* line has less than 76 bytes of encoded data we still put the \r\n
* on.
*
* This method came into existence because we were blowing out our
* memory limits which is how it justifies all this specialization.
* Use window.btoa if you don't need this exact logic/help.
*/
exports.mimeStyleBase64Encode = function(data) {
var wholeLines = Math.floor(data.length / 57);
var partialBytes = data.length - (wholeLines * 57);
var encodedLength = wholeLines * 78;
if (partialBytes) {
// The padding bytes mean we're always a multiple of 4 long. And then we
// still want a CRLF as part of our encoding contract.
encodedLength += Math.ceil(partialBytes / 3) * 4 + 2;
}
var encoded = new Uint8Array(encodedLength);
// A nibble is 4 bits.
function encode6Bits(nibbly) {
// [0, 25] => ['A', 'Z'], 'A'.charCodeAt(0) === 65
if (nibbly <= 25) {
encoded[iWrite++] = 65 + nibbly;
}
// [26, 51] => ['a', 'z'], 'a'.charCodeAt(0) === 97
else if (nibbly <= 51) {
encoded[iWrite++] = 97 - 26 + nibbly;
}
// [52, 61] => ['0', '9'], '0'.charCodeAt(0) === 48
else if (nibbly <= 61) {
encoded[iWrite++] = 48 - 52 + nibbly;
}
// 62 is '+', '+'.charCodeAt(0) === 43
else if (nibbly === 62) {
encoded[iWrite++] = 43;
}
// 63 is '/', '/'.charCodeAt(0) === 47
else {
encoded[iWrite++] = 47;
}
}
var iRead = 0, iWrite = 0, bytesToRead;
// Steady state
for (bytesToRead = data.length; bytesToRead >= 3; bytesToRead -= 3) {
var b1 = data[iRead++], b2 = data[iRead++], b3 = data[iRead++];
// U = Use, i = ignore
// UUUUUUii
encode6Bits(b1 >> 2);
// iiiiiiUU UUUUiiii
encode6Bits(((b1 & 0x3) << 4) | (b2 >> 4));
// iiiiUUUU UUiiiiii
encode6Bits(((b2 & 0xf) << 2) | (b3 >> 6));
// iiUUUUUU
encode6Bits(b3 & 0x3f);
// newlines; it's time to wrap every 57 bytes, or if it's our
// last full set
if ((iRead % 57) === 0 || bytesToRead === 3) {
encoded[iWrite++] = 13; // \r
encoded[iWrite++] = 10; // \n
}
}
// Leftovers (could be zero). If we ended on a full set in the
// prior loop, the newline is taken care of.
switch(bytesToRead) {
case 2:
b1 = data[iRead++];
b2 = data[iRead++];
encode6Bits(b1 >> 2);
encode6Bits(((b1 & 0x3) << 4) | (b2 >> 4));
encode6Bits(((b2 & 0xf) << 2) | 0);
encoded[iWrite++] = 61; // '='.charCodeAt(0) === 61
encoded[iWrite++] = 13; // \r
encoded[iWrite++] = 10; // \n
break;
case 1:
b1 = data[iRead++];
encode6Bits(b1 >> 2);
encode6Bits(((b1 & 0x3) << 4) | 0);
encoded[iWrite++] = 61; // '='.charCodeAt(0) === 61
encoded[iWrite++] = 61;
encoded[iWrite++] = 13; // \r
encoded[iWrite++] = 10; // \n
break;
}
// The code was used to help sanity check, but is inert. Left in for
// reviewers or those who suspect this code! :)
/*
if (iWrite !== encodedLength)
throw new Error('Badly written code! iWrite: ' + iWrite +
' encoded length: ' + encodedLength);
*/
return encoded;
}
}); // end define

View file

@ -0,0 +1,3 @@
define(['ext/smtpclient/src/smtpclient'], function(SmtpClient) {
return SmtpClient;
});

View file

@ -0,0 +1,183 @@
// Copyright (c) 2013 Andris Reinman
//
// 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.
(function(root, factory) {
'use strict';
if (typeof define === 'function' && define.amd) {
define(factory);
} else if (typeof exports === 'object') {
module.exports = factory();
} else {
root.SmtpClientResponseParser = factory();
}
}(this, function() {
'use strict';
/**
* Generates a parser object for data coming from a SMTP server
*
* @constructor
*/
var SmtpResponseParser = function() {
/**
* If the complete line is not received yet, contains the beginning of it
*/
this._remainder = '';
/**
* If the response is a list, contains previous not yet emitted lines
*/
this._block = {
data: [],
lines: [],
statusCode: null
};
/**
* If set to true, do not accept any more input
*/
this.destroyed = false;
};
// Event handlers
/**
* NB! Errors do not block, the parsing and data emitting continues despite of the errors
*/
SmtpResponseParser.prototype.onerror = function() {};
SmtpResponseParser.prototype.ondata = function() {};
SmtpResponseParser.prototype.onend = function() {};
// Public API
/**
* Queue some data from the server for parsing. Only allowed, if 'end' has not been called yet
*
* @param {String} chunk Chunk of data received from the server
*/
SmtpResponseParser.prototype.send = function(chunk) {
if (this.destroyed) {
return this.onerror(new Error('This parser has already been closed, "write" is prohibited'));
}
// Lines should always end with <CR><LF> but you never know, might be only <LF> as well
var lines = (this._remainder + (chunk || '')).split(/\r?\n/);
this._remainder = lines.pop(); // not sure if the line has completely arrived yet
for (var i = 0, len = lines.length; i < len; i++) {
this._processLine(lines[i]);
}
};
/**
* Indicate that all the data from the server has been received. Can be called only once.
*
* @param {String} [chunk] Chunk of data received from the server
*/
SmtpResponseParser.prototype.end = function(chunk) {
if (this.destroyed) {
return this.onerror(new Error('This parser has already been closed, "end" is prohibited'));
}
if (chunk) {
this.send(chunk);
}
if (this._remainder) {
this._processLine(this._remainder);
}
this.destroyed = true;
this.onend();
};
// Private API
/**
* Processes a single and complete line. If it is a continous one (slash after status code),
* queue it to this._block
*
* @param {String} line Complete line of data from the server
*/
SmtpResponseParser.prototype._processLine = function(line) {
var match, response;
// possible input strings for the regex:
// 250-MESSAGE
// 250 MESSAGE
// 250 1.2.3 MESSAGE
if (!line.trim()) {
// nothing to check, empty line
return;
}
this._block.lines.push(line);
if ((match = line.match(/^(\d{3})([\- ])(?:(\d+\.\d+\.\d+)(?: ))?(.*)/))) {
this._block.data.push(match[4]);
if (match[2] === '-') {
if (this._block.statusCode && this._block.statusCode !== Number(match[1])) {
this.onerror('Invalid status code ' + match[1] +
' for multi line response (' + this._block.statusCode + ' expected)');
} else if (!this._block.statusCode) {
this._block.statusCode = Number(match[1]);
}
return;
} else {
response = {
statusCode: Number(match[1]) || 0,
enhancedStatus: match[3] || null,
data: this._block.data.join('\n'),
line: this._block.lines.join('\n')
};
response.success = response.statusCode >= 200 && response.statusCode < 300;
this.ondata(response);
this._block = {
data: [],
lines: [],
statusCode: null
};
this._block.statusCode = null;
}
} else {
this.onerror(new Error('Invalid SMTP response "' + line + '"'));
this.ondata({
success: false,
statusCode: this._block.statusCode || null,
enhancedStatus: null,
data: [line].join('\n'),
line: this._block.lines.join('\n')
});
this._block = {
data: [],
lines: [],
statusCode: null
};
}
};
return SmtpResponseParser;
}));

View file

@ -0,0 +1,962 @@
// Copyright (c) 2013 Andris Reinman
//
// 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.
(function(root, factory) {
'use strict';
var encoding;
if (typeof define === 'function' && define.amd) {
// AMD in browser environment
define(['tcp-socket', 'stringencoding', 'axe', './smtpclient-response-parser'], function(TCPSocket, encoding, axe, SmtpClientResponseParser) {
return factory(TCPSocket, encoding.TextEncoder, encoding.TextDecoder, axe, SmtpClientResponseParser, window.btoa);
});
} else if (typeof exports === 'object' && typeof navigator !== 'undefined') {
// common.js in browser environment
encoding = require('wo-stringencoding');
module.exports = factory(require('tcp-socket'), encoding.TextEncoder, encoding.TextDecoder, require('axe-logger'), require('./smtpclient-response-parser'), btoa);
} else if (typeof exports === 'object') {
// node.js
encoding = require('wo-stringencoding');
module.exports = factory(require('tcp-socket'), encoding.TextEncoder, encoding.TextDecoder, require('axe-logger'), require('./smtpclient-response-parser'), function(str) {
var NodeBuffer = require('buffer').Buffer;
return new NodeBuffer(str, 'binary').toString("base64");
});
} else {
// browser global
navigator.TCPSocket = navigator.TCPSocket || navigator.mozTCPSocket;
root.SmtpClient = factory(navigator.TCPSocket, root.TextEncoder, root.TextDecoder, root.axe, root.SmtpClientResponseParser, window.btoa);
}
}(this, function(TCPSocket, TextEncoder, TextDecoder, axe, SmtpClientResponseParser, btoa) {
'use strict';
var DEBUG_TAG = 'SMTP Client';
/**
* Creates a connection object to a SMTP server and allows to send mail through it.
* Call `connect` method to inititate the actual connection, the constructor only
* defines the properties but does not actually connect.
*
* NB! The parameter order (host, port) differs from node.js "way" (port, host)
*
* @constructor
*
* @param {String} [host="localhost"] Hostname to conenct to
* @param {Number} [port=25] Port number to connect to
* @param {Object} [options] Optional options object
* @param {Boolean} [options.useSecureTransport] Set to true, to use encrypted connection
* @param {String} [options.name] Client hostname for introducing itself to the server
* @param {Object} [options.auth] Authentication options. Depends on the preferred authentication method. Usually {user, pass}
* @param {String} [options.authMethod] Force specific authentication method
* @param {Boolean} [options.disableEscaping] If set to true, do not escape dots on the beginning of the lines
*/
function SmtpClient(host, port, options) {
this._TCPSocket = TCPSocket;
this.options = options || {};
this.port = port || (this.options.useSecureTransport ? 465 : 25);
this.host = host || 'localhost';
/**
* If set to true, start an encrypted connection instead of the plaintext one
* (recommended if applicable). If useSecureTransport is not set but the port used is 465,
* then ecryption is used by default.
*/
this.options.useSecureTransport = 'useSecureTransport' in this.options ? !!this.options.useSecureTransport : this.port === 465;
/**
* Authentication object. If not set, authentication step will be skipped.
*/
this.options.auth = this.options.auth || false;
/**
* Hostname of the client, this will be used for introducing to the server
*/
this.options.name = this.options.name || false;
/**
* Downstream TCP socket to the SMTP server, created with mozTCPSocket
*/
this.socket = false;
/**
* Indicates if the connection has been closed and can't be used anymore
*
*/
this.destroyed = false;
/**
* Informational value that indicates the maximum size (in bytes) for
* a message sent to the current server. Detected from SIZE info.
* Not available until connection has been established.
*/
this.maxAllowedSize = 0;
/**
* Keeps track if the downstream socket is currently full and
* a drain event should be waited for or not
*/
this.waitDrain = false;
// Private properties
/**
* SMTP response parser object. All data coming from the downstream server
* is feeded to this parser
*/
this._parser = new SmtpClientResponseParser();
/**
* If authenticated successfully, stores the username
*/
this._authenticatedAs = null;
/**
* A list of authentication mechanisms detected from the EHLO response
* and which are compatible with this library
*/
this._supportedAuth = [];
/**
* If true, accepts data from the upstream to be passed
* directly to the downstream socket. Used after the DATA command
*/
this._dataMode = false;
/**
* Keep track of the last bytes to see how the terminating dot should be placed
*/
this._lastDataBytes = '';
/**
* Envelope object for tracking who is sending mail to whom
*/
this._envelope = null;
/**
* Stores the function that should be run after a response has been received
* from the server
*/
this._currentAction = null;
/**
* Indicates if the connection is secured or plaintext
*/
this._secureMode = !!this.options.useSecureTransport;
}
//
// EVENTS
//
// Event functions should be overriden, these are just placeholders
/**
* Will be run when an error occurs. Connection to the server will be closed automatically,
* so wait for an `onclose` event as well.
*
* @param {Error} err Error object
*/
SmtpClient.prototype.onerror = function() {};
/**
* More data can be buffered in the socket. See `waitDrain` property or
* check if `send` method returns false to see if you should be waiting
* for the drain event. Before sending anything else.
*/
SmtpClient.prototype.ondrain = function() {};
/**
* The connection to the server has been closed
*/
SmtpClient.prototype.onclose = function() {};
/**
* The connection is established and idle, you can send mail now
*/
SmtpClient.prototype.onidle = function() {};
/**
* The connection is waiting for the mail body
*
* @param {Array} failedRecipients List of addresses that were not accepted as recipients
*/
SmtpClient.prototype.onready = function() {};
/**
* The mail has been sent.
* Wait for `onidle` next.
*
* @param {Boolean} success Indicates if the message was queued by the server or not
*/
SmtpClient.prototype.ondone = function() {};
//
// PUBLIC METHODS
//
// Connection related methods
/**
* Initiate a connection to the server
*/
SmtpClient.prototype.connect = function() {
if (!this.options.name && 'getHostname' in this._TCPSocket && typeof this._TCPSocket.getHostname === 'function') {
this._TCPSocket.getHostname(function(err, hostname) {
this.options.name = hostname || 'localhost';
this.connect();
}.bind(this));
return;
} else if (!this.options.name) {
this.options.name = 'localhost';
}
this.socket = this._TCPSocket.open(this.host, this.port, {
binaryType: 'arraybuffer',
useSecureTransport: this._secureMode,
ca: this.options.ca,
tlsWorkerPath: this.options.tlsWorkerPath
});
// allows certificate handling for platform w/o native tls support
// oncert is non standard so setting it might throw if the socket object is immutable
try {
this.socket.oncert = this.oncert;
} catch (E) {}
this.socket.onerror = this._onError.bind(this);
this.socket.onopen = this._onOpen.bind(this);
};
/**
* Pauses `data` events from the downstream SMTP server
*/
SmtpClient.prototype.suspend = function() {
if (this.socket && this.socket.readyState === 'open') {
this.socket.suspend();
}
};
/**
* Resumes `data` events from the downstream SMTP server. Be careful of not
* resuming something that is not suspended - an error is thrown in this case
*/
SmtpClient.prototype.resume = function() {
if (this.socket && this.socket.readyState === 'open') {
this.socket.resume();
}
};
/**
* Sends QUIT
*/
SmtpClient.prototype.quit = function() {
axe.debug(DEBUG_TAG, 'Sending QUIT...');
this._sendCommand('QUIT');
this._currentAction = this.close;
};
/**
* Reset authentication
*
* @param {Object} [auth] Use this if you want to authenticate as another user
*/
SmtpClient.prototype.reset = function(auth) {
this.options.auth = auth || this.options.auth;
axe.debug(DEBUG_TAG, 'Sending RSET...');
this._sendCommand('RSET');
this._currentAction = this._actionRSET;
};
/**
* Closes the connection to the server
*/
SmtpClient.prototype.close = function() {
axe.debug(DEBUG_TAG, 'Closing connection...');
if (this.socket && this.socket.readyState === 'open') {
this.socket.close();
} else {
this._destroy();
}
};
// Mail related methods
/**
* Initiates a new message by submitting envelope data, starting with
* `MAIL FROM:` command. Use after `onidle` event
*
* @param {Object} envelope Envelope object in the form of {from:"...", to:["..."]}
*/
SmtpClient.prototype.useEnvelope = function(envelope) {
this._envelope = envelope || {};
this._envelope.from = [].concat(this._envelope.from || ('anonymous@' + this.options.name))[0];
this._envelope.to = [].concat(this._envelope.to || []);
// clone the recipients array for latter manipulation
this._envelope.rcptQueue = [].concat(this._envelope.to);
this._envelope.rcptFailed = [];
this._envelope.responseQueue = [];
this._currentAction = this._actionMAIL;
axe.debug(DEBUG_TAG, 'Sending MAIL FROM...');
this._sendCommand('MAIL FROM:<' + (this._envelope.from) + '>');
};
/**
* Send ASCII data to the server. Works only in data mode (after `onready` event), ignored
* otherwise
*
* @param {String} chunk ASCII string (quoted-printable, base64 etc.) to be sent to the server
* @return {Boolean} If true, it is safe to send more data, if false, you *should* wait for the ondrain event before sending more
*/
SmtpClient.prototype.send = function(chunk) {
// works only in data mode
if (!this._dataMode) {
// this line should never be reached but if it does,
// act like everything's normal.
return true;
}
// TODO: if the chunk is an arraybuffer, use a separate function to send the data
return this._sendString(chunk);
};
/**
* Indicates that a data stream for the socket is ended. Works only in data
* mode (after `onready` event), ignored otherwise. Use it when you are done
* with sending the mail. This method does not close the socket. Once the mail
* has been queued by the server, `ondone` and `onidle` are emitted.
*
* @param {Buffer} [chunk] Chunk of data to be sent to the server
*/
SmtpClient.prototype.end = function(chunk) {
// works only in data mode
if (!this._dataMode) {
// this line should never be reached but if it does,
// act like everything's normal.
return true;
}
if (chunk && chunk.length) {
this.send(chunk);
}
// redirect output from the server to _actionStream
this._currentAction = this._actionStream;
// indicate that the stream has ended by sending a single dot on its own line
// if the client already closed the data with \r\n no need to do it again
if (this._lastDataBytes === '\r\n') {
this.waitDrain = this.socket.send(new Uint8Array([0x2E, 0x0D, 0x0A]).buffer); // .\r\n
} else if (this._lastDataBytes.substr(-1) === '\r') {
this.waitDrain = this.socket.send(new Uint8Array([0x0A, 0x2E, 0x0D, 0x0A]).buffer); // \n.\r\n
} else {
this.waitDrain = this.socket.send(new Uint8Array([0x0D, 0x0A, 0x2E, 0x0D, 0x0A]).buffer); // \r\n.\r\n
}
// end data mode
this._dataMode = false;
return this.waitDrain;
};
// PRIVATE METHODS
// EVENT HANDLERS FOR THE SOCKET
/**
* Connection listener that is run when the connection to the server is opened.
* Sets up different event handlers for the opened socket
*
* @event
* @param {Event} evt Event object. Not used
*/
SmtpClient.prototype._onOpen = function() {
this.socket.ondata = this._onData.bind(this);
this.socket.onclose = this._onClose.bind(this);
this.socket.ondrain = this._onDrain.bind(this);
this._parser.ondata = this._onCommand.bind(this);
this._currentAction = this._actionGreeting;
};
/**
* Data listener for chunks of data emitted by the server
*
* @event
* @param {Event} evt Event object. See `evt.data` for the chunk received
*/
SmtpClient.prototype._onData = function(evt) {
var stringPayload = new TextDecoder('UTF-8').decode(new Uint8Array(evt.data));
axe.debug(DEBUG_TAG, 'SERVER: ' + stringPayload);
this._parser.send(stringPayload);
};
/**
* More data can be buffered in the socket, `waitDrain` is reset to false
*
* @event
* @param {Event} evt Event object. Not used
*/
SmtpClient.prototype._onDrain = function() {
this.waitDrain = false;
this.ondrain();
};
/**
* Error handler for the socket
*
* @event
* @param {Event} evt Event object. See evt.data for the error
*/
SmtpClient.prototype._onError = function(evt) {
if (evt instanceof Error && evt.message) {
axe.error(DEBUG_TAG, evt);
this.onerror(evt);
} else if (evt && evt.data instanceof Error) {
axe.error(DEBUG_TAG, evt.data);
this.onerror(evt.data);
} else {
axe.error(DEBUG_TAG, new Error(evt && evt.data && evt.data.message || evt.data || evt || 'Error'));
this.onerror(new Error(evt && evt.data && evt.data.message || evt.data || evt || 'Error'));
}
this.close();
};
/**
* Indicates that the socket has been closed
*
* @event
* @param {Event} evt Event object. Not used
*/
SmtpClient.prototype._onClose = function() {
axe.debug(DEBUG_TAG, 'Socket closed.');
this._destroy();
};
/**
* This is not a socket data handler but the handler for data emitted by the parser,
* so this data is safe to use as it is always complete (server might send partial chunks)
*
* @event
* @param {Object} command Parsed data
*/
SmtpClient.prototype._onCommand = function(command) {
if (typeof this._currentAction === 'function') {
this._currentAction.call(this, command);
}
};
/**
* Ensures that the connection is closed and such
*/
SmtpClient.prototype._destroy = function() {
if (!this.destroyed) {
this.destroyed = true;
this.onclose();
}
};
/**
* Sends a string to the socket.
*
* @param {String} chunk ASCII string (quoted-printable, base64 etc.) to be sent to the server
* @return {Boolean} If true, it is safe to send more data, if false, you *should* wait for the ondrain event before sending more
*/
SmtpClient.prototype._sendString = function(chunk) {
// escape dots
if (!this.options.disableEscaping) {
chunk = chunk.replace(/\n\./g, '\n..');
if ((this._lastDataBytes.substr(-1) === '\n' || !this._lastDataBytes) && chunk.charAt(0) === '.') {
chunk = '.' + chunk;
}
}
// Keeping eye on the last bytes sent, to see if there is a <CR><LF> sequence
// at the end which is needed to end the data stream
if (chunk.length > 2) {
this._lastDataBytes = chunk.substr(-2);
} else if (chunk.length === 1) {
this._lastDataBytes = this._lastDataBytes.substr(-1) + chunk;
}
axe.debug(DEBUG_TAG, 'Sending ' + chunk.length + ' bytes of payload');
// pass the chunk to the socket
this.waitDrain = this.socket.send(new TextEncoder('UTF-8').encode(chunk).buffer);
return this.waitDrain;
};
/**
* Send a string command to the server, also append \r\n if needed
*
* @param {String} str String to be sent to the server
*/
SmtpClient.prototype._sendCommand = function(str) {
this.waitDrain = this.socket.send(new TextEncoder('UTF-8').encode(str + (str.substr(-2) !== '\r\n' ? '\r\n' : '')).buffer);
};
/**
* Intitiate authentication sequence if needed
*/
SmtpClient.prototype._authenticateUser = function() {
if (!this.options.auth) {
// no need to authenticate, at least no data given
this._currentAction = this._actionIdle;
this.onidle(); // ready to take orders
return;
}
var auth;
if (!this.options.authMethod && this.options.auth.xoauth2) {
this.options.authMethod = 'XOAUTH2';
}
if (this.options.authMethod) {
auth = this.options.authMethod.toUpperCase().trim();
} else {
// use first supported
auth = (this._supportedAuth[0] || 'PLAIN').toUpperCase().trim();
}
switch (auth) {
case 'LOGIN':
// LOGIN is a 3 step authentication process
// C: AUTH LOGIN
// C: BASE64(USER)
// C: BASE64(PASS)
axe.debug(DEBUG_TAG, 'Authentication via AUTH LOGIN');
this._currentAction = this._actionAUTH_LOGIN_USER;
this._sendCommand('AUTH LOGIN');
return;
case 'PLAIN':
// AUTH PLAIN is a 1 step authentication process
// C: AUTH PLAIN BASE64(\0 USER \0 PASS)
axe.debug(DEBUG_TAG, 'Authentication via AUTH PLAIN');
this._currentAction = this._actionAUTHComplete;
this._sendCommand(
// convert to BASE64
'AUTH PLAIN ' +
btoa(unescape(encodeURIComponent(
//this.options.auth.user+'\u0000'+
'\u0000' + // skip authorization identity as it causes problems with some servers
this.options.auth.user + '\u0000' +
this.options.auth.pass))));
return;
case 'XOAUTH2':
// See https://developers.google.com/gmail/xoauth2_protocol#smtp_protocol_exchange
axe.debug(DEBUG_TAG, 'Authentication via AUTH XOAUTH2');
this._currentAction = this._actionAUTH_XOAUTH2;
this._sendCommand('AUTH XOAUTH2 ' + this._buildXOAuth2Token(this.options.auth.user, this.options.auth.xoauth2));
return;
}
this._onError(new Error('Unknown authentication method ' + auth));
};
// ACTIONS FOR RESPONSES FROM THE SMTP SERVER
/**
* Initial response from the server, must have a status 220
*
* @param {Object} command Parsed command from the server {statusCode, data, line}
*/
SmtpClient.prototype._actionGreeting = function(command) {
if (command.statusCode !== 220) {
this._onError(new Error('Invalid greeting: ' + command.data));
return;
}
if (this.options.lmtp) {
axe.debug(DEBUG_TAG, 'Sending LHLO ' + this.options.name);
this._currentAction = this._actionLHLO;
this._sendCommand('LHLO ' + this.options.name);
} else {
axe.debug(DEBUG_TAG, 'Sending EHLO ' + this.options.name);
this._currentAction = this._actionEHLO;
this._sendCommand('EHLO ' + this.options.name);
}
};
/**
* Response to LHLO
*
* @param {Object} command Parsed command from the server {statusCode, data, line}
*/
SmtpClient.prototype._actionLHLO = function(command) {
if (!command.success) {
axe.error(DEBUG_TAG, 'LHLO not successful');
this._onError(new Error(command.data));
return;
}
// Process as EHLO response
this._actionEHLO(command);
};
/**
* Response to EHLO. If the response is an error, try HELO instead
*
* @param {Object} command Parsed command from the server {statusCode, data, line}
*/
SmtpClient.prototype._actionEHLO = function(command) {
var match;
if (!command.success) {
if (!this._secureMode && this.options.requireTLS) {
var errMsg = 'STARTTLS not supported without EHLO';
axe.error(DEBUG_TAG, errMsg);
this._onError(new Error(errMsg));
return;
}
// Try HELO instead
axe.warn(DEBUG_TAG, 'EHLO not successful, trying HELO ' + this.options.name);
this._currentAction = this._actionHELO;
this._sendCommand('HELO ' + this.options.name);
return;
}
// Detect if the server supports PLAIN auth
if (command.line.match(/AUTH(?:\s+[^\n]*\s+|\s+)PLAIN/i)) {
axe.debug(DEBUG_TAG, 'Server supports AUTH PLAIN');
this._supportedAuth.push('PLAIN');
}
// Detect if the server supports LOGIN auth
if (command.line.match(/AUTH(?:\s+[^\n]*\s+|\s+)LOGIN/i)) {
axe.debug(DEBUG_TAG, 'Server supports AUTH LOGIN');
this._supportedAuth.push('LOGIN');
}
// Detect if the server supports XOAUTH2 auth
if (command.line.match(/AUTH(?:\s+[^\n]*\s+|\s+)XOAUTH2/i)) {
axe.debug(DEBUG_TAG, 'Server supports AUTH XOAUTH2');
this._supportedAuth.push('XOAUTH2');
}
// Detect maximum allowed message size
if ((match = command.line.match(/SIZE (\d+)/i)) && Number(match[1])) {
this._maxAllowedSize = Number(match[1]);
axe.debug(DEBUG_TAG, 'Maximum allowd message size: ' + this._maxAllowedSize);
}
// Detect if the server supports STARTTLS
if (!this._secureMode) {
if (command.line.match(/[ \-]STARTTLS\s?$/mi) && !this.options.ignoreTLS || !!this.options.requireTLS) {
this._currentAction = this._actionSTARTTLS;
this._sendCommand('STARTTLS');
return;
}
}
this._authenticateUser.call(this);
};
/**
* Handles server response for STARTTLS command. If there's an error
* try HELO instead, otherwise initiate TLS upgrade. If the upgrade
* succeedes restart the EHLO
*
* @param {String} str Message from the server
*/
SmtpClient.prototype._actionSTARTTLS = function(command) {
if (!command.success) {
axe.error(DEBUG_TAG, 'STARTTLS not successful');
this._onError(new Error(command.data));
return;
}
this._secureMode = true;
this.socket.upgradeToSecure();
// restart protocol flow
this._currentAction = this._actionEHLO;
this._sendCommand('EHLO ' + this.options.name);
};
/**
* Response to HELO
*
* @param {Object} command Parsed command from the server {statusCode, data, line}
*/
SmtpClient.prototype._actionHELO = function(command) {
if (!command.success) {
axe.error(DEBUG_TAG, 'HELO not successful');
this._onError(new Error(command.data));
return;
}
this._authenticateUser.call(this);
};
/**
* Response to AUTH LOGIN, if successful expects base64 encoded username
*
* @param {Object} command Parsed command from the server {statusCode, data, line}
*/
SmtpClient.prototype._actionAUTH_LOGIN_USER = function(command) {
if (command.statusCode !== 334 || command.data !== 'VXNlcm5hbWU6') {
axe.error(DEBUG_TAG, 'AUTH LOGIN USER not successful: ' + command.data);
this._onError(new Error('Invalid login sequence while waiting for "334 VXNlcm5hbWU6 ": ' + command.data));
return;
}
axe.debug(DEBUG_TAG, 'AUTH LOGIN USER successful');
this._currentAction = this._actionAUTH_LOGIN_PASS;
this._sendCommand(btoa(unescape(encodeURIComponent(this.options.auth.user))));
};
/**
* Response to AUTH LOGIN username, if successful expects base64 encoded password
*
* @param {Object} command Parsed command from the server {statusCode, data, line}
*/
SmtpClient.prototype._actionAUTH_LOGIN_PASS = function(command) {
if (command.statusCode !== 334 || command.data !== 'UGFzc3dvcmQ6') {
axe.error(DEBUG_TAG, 'AUTH LOGIN PASS not successful: ' + command.data);
this._onError(new Error('Invalid login sequence while waiting for "334 UGFzc3dvcmQ6 ": ' + command.data));
return;
}
axe.debug(DEBUG_TAG, 'AUTH LOGIN PASS successful');
this._currentAction = this._actionAUTHComplete;
this._sendCommand(btoa(unescape(encodeURIComponent(this.options.auth.pass))));
};
/**
* Response to AUTH XOAUTH2 token, if error occurs send empty response
*
* @param {Object} command Parsed command from the server {statusCode, data, line}
*/
SmtpClient.prototype._actionAUTH_XOAUTH2 = function(command) {
if (!command.success) {
axe.warn(DEBUG_TAG, 'Error during AUTH XOAUTH2, sending empty response');
this._sendCommand('');
this._currentAction = this._actionAUTHComplete;
} else {
this._actionAUTHComplete(command);
}
};
/**
* Checks if authentication succeeded or not. If successfully authenticated
* emit `idle` to indicate that an e-mail can be sent using this connection
*
* @param {Object} command Parsed command from the server {statusCode, data, line}
*/
SmtpClient.prototype._actionAUTHComplete = function(command) {
if (!command.success) {
axe.debug(DEBUG_TAG, 'Authentication failed: ' + command.data);
this._onError(new Error(command.data));
return;
}
axe.debug(DEBUG_TAG, 'Authentication successful.');
this._authenticatedAs = this.options.auth.user;
this._currentAction = this._actionIdle;
this.onidle(); // ready to take orders
};
/**
* Used when the connection is idle and the server emits timeout
*
* @param {Object} command Parsed command from the server {statusCode, data, line}
*/
SmtpClient.prototype._actionIdle = function(command) {
if (command.statusCode > 300) {
this._onError(new Error(command.line));
return;
}
this._onError(new Error(command.data));
};
/**
* Response to MAIL FROM command. Proceed to defining RCPT TO list if successful
*
* @param {Object} command Parsed command from the server {statusCode, data, line}
*/
SmtpClient.prototype._actionMAIL = function(command) {
if (!command.success) {
axe.debug(DEBUG_TAG, 'MAIL FROM unsuccessful: ' + command.data);
this._onError(new Error(command.data));
return;
}
if (!this._envelope.rcptQueue.length) {
this._onError(new Error('Can\'t send mail - no recipients defined'));
} else {
axe.debug(DEBUG_TAG, 'MAIL FROM successful, proceeding with ' + this._envelope.rcptQueue.length + ' recipients');
axe.debug(DEBUG_TAG, 'Adding recipient...');
this._envelope.curRecipient = this._envelope.rcptQueue.shift();
this._currentAction = this._actionRCPT;
this._sendCommand('RCPT TO:<' + this._envelope.curRecipient + '>');
}
};
/**
* Response to a RCPT TO command. If the command is unsuccessful, try the next one,
* as this might be related only to the current recipient, not a global error, so
* the following recipients might still be valid
*
* @param {Object} command Parsed command from the server {statusCode, data, line}
*/
SmtpClient.prototype._actionRCPT = function(command) {
if (!command.success) {
axe.warn(DEBUG_TAG, 'RCPT TO failed for: ' + this._envelope.curRecipient);
// this is a soft error
this._envelope.rcptFailed.push(this._envelope.curRecipient);
} else {
this._envelope.responseQueue.push(this._envelope.curRecipient);
}
if (!this._envelope.rcptQueue.length) {
if (this._envelope.rcptFailed.length < this._envelope.to.length) {
this._currentAction = this._actionDATA;
axe.debug(DEBUG_TAG, 'RCPT TO done, proceeding with payload');
this._sendCommand('DATA');
} else {
this._onError(new Error('Can\'t send mail - all recipients were rejected'));
this._currentAction = this._actionIdle;
return;
}
} else {
axe.debug(DEBUG_TAG, 'Adding recipient...');
this._envelope.curRecipient = this._envelope.rcptQueue.shift();
this._currentAction = this._actionRCPT;
this._sendCommand('RCPT TO:<' + this._envelope.curRecipient + '>');
}
};
/**
* Response to the RSET command. If successful, clear the current authentication
* information and reauthenticate.
*
* @param {Object} command Parsed command from the server {statusCode, data, line}
*/
SmtpClient.prototype._actionRSET = function(command) {
if (!command.success) {
axe.error(DEBUG_TAG, 'RSET unsuccessful ' + command.data);
this._onError(new Error(command.data));
return;
}
this._authenticatedAs = null;
this._authenticateUser.call(this);
};
/**
* Response to the DATA command. Server is now waiting for a message, so emit `onready`
*
* @param {Object} command Parsed command from the server {statusCode, data, line}
*/
SmtpClient.prototype._actionDATA = function(command) {
// response should be 354 but according to this issue https://github.com/eleith/emailjs/issues/24
// some servers might use 250 instead
if ([250, 354].indexOf(command.statusCode) < 0) {
axe.error(DEBUG_TAG, 'DATA unsuccessful ' + command.data);
this._onError(new Error(command.data));
return;
}
this._dataMode = true;
this._currentAction = this._actionIdle;
this.onready(this._envelope.rcptFailed);
};
/**
* Response from the server, once the message stream has ended with <CR><LF>.<CR><LF>
* Emits `ondone`.
*
* @param {Object} command Parsed command from the server {statusCode, data, line}
*/
SmtpClient.prototype._actionStream = function(command) {
var rcpt;
if (this.options.lmtp) {
// LMTP returns a response code for *every* successfully set recipient
// For every recipient the message might succeed or fail individually
rcpt = this._envelope.responseQueue.shift();
if (!command.success) {
axe.error(DEBUG_TAG, 'Local delivery to ' + rcpt + ' failed.');
this._envelope.rcptFailed.push(rcpt);
} else {
axe.error(DEBUG_TAG, 'Local delivery to ' + rcpt + ' succeeded.');
}
if (this._envelope.responseQueue.length) {
this._currentAction = this._actionStream;
return;
}
this._currentAction = this._actionIdle;
this.ondone(true);
} else {
// For SMTP the message either fails or succeeds, there is no information
// about individual recipients
if (!command.success) {
axe.error(DEBUG_TAG, 'Message sending failed.');
} else {
axe.debug(DEBUG_TAG, 'Message sent successfully.');
}
this._currentAction = this._actionIdle;
this.ondone(!!command.success);
}
// If the client wanted to do something else (eg. to quit), do not force idle
if (this._currentAction === this._actionIdle) {
// Waiting for new connections
axe.debug(DEBUG_TAG, 'Idling while waiting for new connections...');
this.onidle();
}
};
/**
* Builds a login token for XOAUTH2 authentication command
*
* @param {String} user E-mail address of the user
* @param {String} token Valid access token for the user
* @return {String} Base64 formatted login token
*/
SmtpClient.prototype._buildXOAuth2Token = function(user, token) {
var authData = [
'user=' + (user || ''),
'auth=Bearer ' + token,
'',
''
];
// base64("user={User}\x00auth=Bearer {Token}\x00\x00")
return btoa(unescape(encodeURIComponent(authData.join('\x01'))));
};
return SmtpClient;
}));

View file

@ -0,0 +1,26 @@
'use strict';
/**
* This TextEncoder and TextDecoder are used by MimeParser. In
* addition to their standard behavior, we also support decoding utf-7.
*/
define(function(require) {
var utf7 = require('utf7');
return {
TextEncoder: function(encoding) {
var encoder = new TextEncoder(encoding);
this.encode = encoder.encode.bind(encoder);
},
TextDecoder: function(encoding) {
encoding = encoding && encoding.toLowerCase();
if (encoding === 'utf-7' || encoding === 'utf7') {
this.decode = function(buf) {
var mimefuncs = require('mimefuncs');
return utf7.decode(mimefuncs.fromTypedArray(buf));
};
} else {
var decoder = new TextDecoder(encoding);
this.decode = decoder.decode.bind(decoder)
}
}
};
});

View file

@ -0,0 +1,164 @@
'use strict';
/**
* Proxies navigator.mozTCPSocket from the worker thread to the main
* thread, as TCPSocket is not available on workers yet. This API is
* largely compatible with the real mozTCPSocket implementation,
* except for bufferedAmount and other buffering semantics (because we
* can't synchronously retrieve those values).
*
* See worker-support/net-main.js for the main-thread counterpart.
*
* NOTE: There is also a whiteout-io/tcp-socket repo, which provides a
* compatibility shim for TCPSocket on non-browser runtimes. This
* module performs the same type of wrapping (i.e. exporting TCPSocket
* directly) so that we don't have to modify browserbox/smtpclient to
* expect a different socket wrapper module.
*
* ## Sending lots of data: flow control, Blobs ##
*
* mozTCPSocket provides a flow-control mechanism (the return value to
* send indicates whether we've crossed a buffering boundary and
* 'ondrain' tells us when all buffered data has been sent), but does
* not yet support enqueueing Blobs for processing (which is part of
* the proposed standard at
* http://www.w3.org/2012/sysapps/raw-sockets/). Also, the raw-sockets
* spec calls for generating the 'drain' event once our buffered
* amount goes back under the internal buffer target rather than
* waiting for it to hit zero like mozTCPSocket.
*
* Our main desire right now for flow-control is to avoid using a lot
* of memory and getting killed by the OOM-killer. As such, flow
* control is not important to us if we're just sending something that
* we're already keeping in memory. The things that will kill us are
* giant things like attachments (or message bodies we are
* quoting/repeating, potentially) that we are keeping as Blobs.
*
* As such, rather than echoing the flow-control mechanisms over to
* this worker context, we just allow ourselves to write() a Blob and
* have the net-main.js side take care of streaming the Blobs over the
* network.
*
* Note that successfully sending a lot of data may entail holding a
* wake-lock to avoid having the network device we are using turned
* off in the middle of our sending. The network-connection
* abstraction is not currently directly involved with the wake-lock
* management, but I could see it needing to beef up its error
* inference in terms of timeouts/detecting disconnections so we can
* avoid grabbing a wi-fi wake-lock, having our connection quietly
* die, and then we keep holding the wi-fi wake-lock for much longer
* than we should.
*/
define(function(require, exports, module) {
var router = require('worker-router');
var routerMaker = router.registerInstanceType('netsocket');
var DisasterRecovery = require('disaster-recovery');
function TCPSocketProxy(host, port, options) {
options = options || {};
options.binaryType = 'arraybuffer';
// Supported TCPSocket attributes:
this.host = host;
this.port = port;
this.ssl = !!options.useSecureTransport;
this.binaryType = options.binaryType;
this.bufferedAmount = 0; // This is fake.
this.readyState = 'connecting';
// Event handlers:
var routerInfo = routerMaker.register(function(data) {
var eventHandlerName = data.cmd;
var internalHandler = this['_' + eventHandlerName];
var externalHandler = this[eventHandlerName];
// Allow this class to update internal state first:
internalHandler && internalHandler.call(this, data.args);
// Then, emulate the real TCP socket events (vaguely):
DisasterRecovery.catchSocketExceptions(this, function() {
externalHandler && externalHandler.call(this, { data: data.args });
});
}.bind(this));
this._sendMessage = routerInfo.sendMessage;
this._unregisterWithRouter = routerInfo.unregister;
this._sendMessage('open', [host, port, options]);
}
TCPSocketProxy.prototype = {
_onopen: function() {
this.readyState = 'open';
},
_onclose: function() {
this._unregisterWithRouter();
this.readyState = 'closed';
},
upgradeToSecure: function() {
this._sendMessage('upgradeToSecure', []);
},
suspend: function() {
throw new Error('tcp-socket.js does not support suspend().');
},
resume: function() {
throw new Error('tcp-socket.js does not support resume().');
},
close: function() {
if (this.readyState !== 'closed') {
this._sendMessage('close');
}
},
// We do not use transferrables by default; historically we
// wrapped a NodeJS-style API whose semantics did not take
// ownership. However, there is an optimization we want to perform
// related to Uint8Array.subarray().
//
// All the subarray does is create a view on the underlying
// buffer. This is important and notable because the structured
// clone implementation for typed arrays and array buffers is
// *not* clever; it just serializes the entire underlying buffer
// and the typed array as a view on that. (This does have the
// upside that you can transfer a whole bunch of typed arrays and
// only one copy of the buffer.) The good news is that
// ArrayBuffer.slice() does create an entirely new copy of the
// buffer, so that works with our semantics and we can use that to
// transfer only what needs to be transferred.
send: function(u8array) {
if (u8array instanceof Blob) {
// We always send blobs in their entirety; you should slice the blob and
// give us that if that's what you want.
this._sendMessage('write', [u8array]);
}
else if (u8array instanceof ArrayBuffer) {
this._sendMessage('write', [u8array, 0, u8array.byteLength]);
}
// Slice the underlying buffer and transfer it if the array is a subarray
else if (u8array.byteOffset !== 0 ||
u8array.length !== u8array.buffer.byteLength) {
var buf = u8array.buffer.slice(u8array.byteOffset,
u8array.byteOffset + u8array.length);
this._sendMessage('write',
[buf, 0, buf.byteLength],
[buf]);
}
else {
this._sendMessage('write',
[u8array.buffer, u8array.byteOffset, u8array.length]);
}
return true;
}
};
return {
open: function(host, port, options) {
return new TCPSocketProxy(host, port, options);
}
};
});

View file

@ -0,0 +1,208 @@
// Copyright (c) 2010-2011 Konstantin Käfer
// 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.
(function(root, factory) {
'use strict';
if (typeof define === 'function' && define.amd) {
define(factory);
} else if (typeof exports === 'object') {
module.exports = factory();
} else {
root.utf7 = factory();
}
}(this, function() {
'use strict';
function encode(str) {
var b = new Uint8Array(str.length * 2),
octets = '',
i, bi, len, c, encoded;
for (i = 0, bi = 0, len = str.length; i < len; i++) {
// Note that we can't simply convert a UTF-8 string to Base64 because
// UTF-8 uses a different encoding. In modified UTF-7, all characters
// are represented by their two byte Unicode ID.
c = str.charCodeAt(i);
// Upper 8 bits shifted into lower 8 bits so that they fit into 1 byte.
b[bi++] = c >> 8;
// Lower 8 bits. Cut off the upper 8 bits so that they fit into 1 byte.
b[bi++] = c & 0xFF;
}
// Convert b:Uint8Array to a binary string
for (i = 0, len = b.length; i < len; i++) {
octets += String.fromCharCode(b[i]);
}
// Modified Base64 uses , instead of / and omits trailing =.
encoded = '';
if (typeof window !== 'undefined' && btoa) {
encoded = btoa(octets);
} else {
encoded = (new Buffer(octets, "binary")).toString("base64");
}
return encoded.replace(/=+$/, '');
}
/**
* Safe base64 decoding. Does not throw on unexpected input.
*
* Implementation from the MDN docs:
* https://developer.mozilla.org/en-US/docs/Web/JavaScript/Base64_encoding_and_decoding
* (MDN code samples are MIT licensed)
*
* @param {String} base64Str Base64 encoded string
* @returns {Uint8Array} Decoded binary blob
*/
function base64toTypedArray(base64Str) {
var bitsSoFar = 0;
var validBits = 0;
var iOut = 0;
var arr = new Uint8Array(Math.ceil(base64Str.length * 3 / 4));
var c;
var bits;
for (var i = 0, len = base64Str.length; i < len; i++) {
c = base64Str.charCodeAt(i);
if (c >= 0x41 && c <= 0x5a) { // [A-Z]
bits = c - 0x41;
} else if (c >= 0x61 && c <= 0x7a) { // [a-z]
bits = c - 0x61 + 0x1a;
} else if (c >= 0x30 && c <= 0x39) { // [0-9]
bits = c - 0x30 + 0x34;
} else if (c === 0x2b) { // +
bits = 0x3e;
} else if (c === 0x2f) { // /
bits = 0x3f;
} else if (c === 0x3d) { // =
validBits = 0;
continue;
} else {
// ignore all other characters!
continue;
}
bitsSoFar = (bitsSoFar << 6) | bits;
validBits += 6;
if (validBits >= 8) {
validBits -= 8;
arr[iOut++] = bitsSoFar >> validBits;
if (validBits === 2) {
bitsSoFar &= 0x03;
} else if (validBits === 4) {
bitsSoFar &= 0x0f;
}
}
}
if (iOut < arr.length) {
return arr.subarray(0, iOut);
}
return arr;
}
function decode(str) {
var octets = base64toTypedArray(str),
r = [];
for (var i = 0, len = octets.length; i < len;) {
// Calculate charcode from two adjacent bytes.
r.push(String.fromCharCode(octets[i++] << 8 | octets[i++]));
}
return r.join('');
}
// Escape RegEx from http://simonwillison.net/2006/Jan/20/escape/
function escape(chars) {
return chars.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&');
}
// Character classes defined by RFC 2152.
var setD = 'A-Za-z0-9' + escape('\'(),-./:?'),
setO = escape('!"#$%&*;<=>@[]^_\'{|}'),
setW = escape(' \r\n\t'),
// Stores compiled regexes for various replacement pattern.
regexes = {},
regexAll = new RegExp('[^' + setW + setD + setO + ']+', 'g');
return {
// RFC 2152 UTF-7 encoding.
encode: function(str, mask) {
// Generate a RegExp object from the string of mask characters.
if (!mask) {
mask = '';
}
if (!regexes[mask]) {
regexes[mask] = new RegExp('[^' + setD + escape(mask) + ']+', 'g');
}
// We replace subsequent disallowed chars with their escape sequence.
return str.replace(regexes[mask], function(chunk) {
// + is represented by an empty sequence +-, otherwise call encode().
return '+' + (chunk === '+' ? '' : encode(chunk)) + '-';
});
},
// RFC 2152 UTF-7 encoding with all optionals.
encodeAll: function(str) {
// We replace subsequent disallowed chars with their escape sequence.
return str.replace(regexAll, function(chunk) {
// + is represented by an empty sequence +-, otherwise call encode().
return '+' + (chunk === '+' ? '' : encode(chunk)) + '-';
});
},
// RFC 2152 UTF-7 decoding.
decode: function(str) {
return str.replace(/\+([A-Za-z0-9\/]*)-?/gi, function(_, chunk) {
// &- represents &.
if (chunk === '') {
return '+';
}
return decode(chunk);
});
},
imap: {
// RFC 3501, section 5.1.3 UTF-7 encoding.
encode: function(str) {
// All printable ASCII chars except for & must be represented by themselves.
// We replace subsequent non-representable chars with their escape sequence.
return str.replace(/&/g, '&-').replace(/[^\x20-\x7e]+/g, function(chunk) {
// & is represented by an empty sequence &-, otherwise call encode().
chunk = (chunk === '&' ? '' : encode(chunk)).replace(/\//g, ',');
return '&' + chunk + '-';
});
},
// RFC 3501, section 5.1.3 UTF-7 decoding.
decode: function(str) {
return str.replace(/&([^-]*)-/g, function(_, chunk) {
// &- represents &.
if (chunk === '') {
return '&';
}
return decode(chunk.replace(/,/g, '/'));
});
}
}
};
}));

View file

@ -0,0 +1,4 @@
// Alias for 'utf7', could alternately be in our RequireJS config.
define(['./utf7'], function(utf7) {
return utf7;
});