1
Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
2
Components.utils.import("resource://gre/modules/ctypes.jsm");
5
var EXPORTED_SYMBOLS = [ "Chmfox"];
7
if ("undefined" == typeof(Chmfox)) {
9
const Chmfox = (function() {
11
const Ci = Components.interfaces;
12
const Cc = Components.classes;
13
const Cr = Components.results;
15
var Application = Cc["@mozilla.org/fuel/application;1"].getService(Ci.fuelIApplication);
16
var xulRuntime = Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULRuntime);
18
const prefs = Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefService).getBranch("extensions.chmfox.");
20
const kScheme = 'chm';
22
const ioService = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService);
24
function utf8Encode(string) {
26
for (var n = 0; n < string.length; n++) {
27
var c = string.charCodeAt(n);
29
utftext += String.fromCharCode(c);
31
else if((c > 127) && (c < 2048)) {
32
utftext += String.fromCharCode((c >> 6) | 192);
33
utftext += String.fromCharCode((c & 63) | 128);
36
utftext += String.fromCharCode((c >> 12) | 224);
37
utftext += String.fromCharCode(((c >> 6) & 63) | 128);
38
utftext += String.fromCharCode((c & 63) | 128);
44
function log(message) {
45
if (false) // Disable log for release
47
var console = Cc["@mozilla.org/consoleservice;1"].getService(Ci.nsIConsoleService);
48
var msg = "[chmfox] " + message + "\n";
49
console.logStringMessage(msg);
54
function getCharsetFromLcid(lcid) {
56
case 0x0436: return "ISO-8859-1"; // "Afrikaans", "Western Europe & US"
57
case 0x041c: return "ISO-8859-2"; // "Albanian", "Central Europe"
58
case 0x0401: return "ISO-8859-6"; // "Arabic_Saudi_Arabia", "Arabic"
59
case 0x0801: return "ISO-8859-6"; // "Arabic_Iraq", "Arabic"
60
case 0x0c01: return "ISO-8859-6"; // "Arabic_Egypt", "Arabic"
61
case 0x1001: return "ISO-8859-6"; // "Arabic_Libya", "Arabic"
62
case 0x1401: return "ISO-8859-6"; // "Arabic_Algeria", "Arabic"
63
case 0x1801: return "ISO-8859-6"; // "Arabic_Morocco", "Arabic"
64
case 0x1c01: return "ISO-8859-6"; // "Arabic_Tunisia", "Arabic"
65
case 0x2001: return "ISO-8859-6"; // "Arabic_Oman", "Arabic"
66
case 0x2401: return "ISO-8859-6"; // "Arabic_Yemen", "Arabic"
67
case 0x2801: return "ISO-8859-6"; // "Arabic_Syria", "Arabic"
68
case 0x2c01: return "ISO-8859-6"; // "Arabic_Jordan", "Arabic"
69
case 0x3001: return "ISO-8859-6"; // "Arabic_Lebanon", "Arabic"
70
case 0x3401: return "ISO-8859-6"; // "Arabic_Kuwait", "Arabic"
71
case 0x3801: return "ISO-8859-6"; // "Arabic_UAE", "Arabic"
72
case 0x3c01: return "ISO-8859-6"; // "Arabic_Bahrain", "Arabic"
73
case 0x4001: return "ISO-8859-6"; // "Arabic_Qatar", "Arabic"
74
case 0x042b: return null; // "Armenian","Armenian"
75
case 0x042c: return "ISO-8859-9"; // "Azeri_Latin", "Turkish"
76
case 0x082c: return "cp1251"; // "Azeri_Cyrillic", "Cyrillic"
77
case 0x042d: return "ISO-8859-1"; // "Basque", "Western Europe & US"
78
case 0x0423: return "cp1251"; // "Belarusian", "Cyrillic"
79
case 0x0402: return "cp1251"; // "Bulgarian", "Cyrillic"
80
case 0x0403: return "ISO-8859-1"; // "Catalan", "Western Europe & US"
81
case 0x0404: return "BIG5"; // "Chinese_Taiwan", "Traditional Chinese"
82
case 0x0804: return "GBK"; // "Chinese_PRC", "Simplified Chinese"
83
case 0x0c04: return "BIG5"; // "Chinese_Hong_Kong", "Traditional Chinese"
84
case 0x1004: return "GBK"; // "Chinese_Singapore", "Simplified Chinese"
85
case 0x1404: return "BIG5"; // "Chinese_Macau", "Traditional Chinese"
86
case 0x041a: return "ISO-8859-2"; // "Croatian", "Central Europe"
87
case 0x0405: return "ISO-8859-2"; // "Czech", "Central Europe"
88
case 0x0406: return "ISO-8859-1"; // "Danish", "Western Europe & US"
89
case 0x0413: return "ISO-8859-1"; // "Dutch_Standard", "Western Europe & US"
90
case 0x0813: return "ISO-8859-1"; // "Dutch_Belgian", "Western Europe & US"
91
case 0x0409: return "ISO-8859-1"; // "English_United_States", "Western Europe & US"
92
case 0x0809: return "ISO-8859-1"; // "English_United_Kingdom", "Western Europe & US"
93
case 0x0c09: return "ISO-8859-1"; // "English_Australian", "Western Europe & US"
94
case 0x1009: return "ISO-8859-1"; // "English_Canadian", "Western Europe & US"
95
case 0x1409: return "ISO-8859-1"; // "English_New_Zealand", "Western Europe & US"
96
case 0x1809: return "ISO-8859-1"; // "English_Irish", "Western Europe & US"
97
case 0x1c09: return "ISO-8859-1"; // "English_South_Africa", "Western Europe & US"
98
case 0x2009: return "ISO-8859-1"; // "English_Jamaica", "Western Europe & US"
99
case 0x2409: return "ISO-8859-1"; // "English_Caribbean", "Western Europe & US"
100
case 0x2809: return "ISO-8859-1"; // "English_Belize", "Western Europe & US"
101
case 0x2c09: return "ISO-8859-1"; // "English_Trinidad", "Western Europe & US"
102
case 0x3009: return "ISO-8859-1"; // "English_Zimbabwe", "Western Europe & US"
103
case 0x3409: return "ISO-8859-1"; // "English_Philippines", "Western Europe & US"
104
case 0x0425: return "ISO-8859-13"; //"Estonian", "Baltic"
105
case 0x0438: return "ISO-8859-1"; // "Faeroese", "Western Europe & US"
106
case 0x0429: return "ISO-8859-6"; // "Farsi", "Arabic"
107
case 0x040b: return "ISO-8859-1"; // "Finnish", "Western Europe & US"
108
case 0x040c: return "ISO-8859-1"; // "French_Standard", "Western Europe & US"
109
case 0x080c: return "ISO-8859-1"; // "French_Belgian", "Western Europe & US"
110
case 0x0c0c: return "ISO-8859-1"; // "French_Canadian", "Western Europe & US"
111
case 0x100c: return "ISO-8859-1"; // "French_Swiss", "Western Europe & US"
112
case 0x140c: return "ISO-8859-1"; // "French_Luxembourg", "Western Europe & US"
113
case 0x180c: return "ISO-8859-1"; // "French_Monaco", "Western Europe & US"
114
case 0x0437: return null; // "Georgian", "Georgian"
115
case 0x0407: return "ISO-8859-1"; // "German_Standard", "Western Europe & US"
116
case 0x0807: return "ISO-8859-1"; // "German_Swiss", "Western Europe & US"
117
case 0x0c07: return "ISO-8859-1"; // "German_Austrian", "Western Europe & US"
118
case 0x1007: return "ISO-8859-1"; // "German_Luxembourg", "Western Europe & US"
119
case 0x1407: return "ISO-8859-1"; // "German_Liechtenstein", "Western Europe & US"
120
case 0x0408: return "ISO-8859-7"; // "Greek", "Greek"
121
case 0x040d: return "ISO-8859-8"; // "Hebrew", "Hebrew"
122
case 0x0439: return null; // "Hindi", "Indic"
123
case 0x040e: return "ISO-8859-2"; // "Hungarian", "Central Europe"
124
case 0x040f: return "ISO-8859-1"; // "Icelandic", "Western Europe & US"
125
case 0x0421: return "ISO-8859-1"; // "Indonesian", "Western Europe & US"
126
case 0x0410: return "ISO-8859-1"; // "Italian_Standard", "Western Europe & US"
127
case 0x0810: return "ISO-8859-1"; // "Italian_Swiss", "Western Europe & US"
128
case 0x0411: return "cp932"; // "Japanese", "Japanese"
129
case 0x043f: return "cp1251"; // "Kazakh", "Cyrillic"
130
case 0x0457: return null; // "Konkani", "Indic"
131
case 0x0412: return "cp949"; // "Korean", "Korean"
132
case 0x0426: return "ISO-8859-13"; //"Latvian", "Baltic"
133
case 0x0427: return "ISO-8859-13"; //"Lithuanian", "Baltic"
134
case 0x042f: return "cp1251"; // "Macedonian", "Cyrillic"
135
case 0x043e: return "ISO-8859-1"; // "Malay_Malaysia", "Western Europe & US"
136
case 0x083e: return "ISO-8859-1"; // "Malay_Brunei_Darussalam", "Western Europe & US"
137
case 0x044e: return null; // "Marathi", "Indic"
138
case 0x0414: return "ISO-8859-1"; // "Norwegian_Bokmal", "Western Europe & US"
139
case 0x0814: return "ISO-8859-1"; // "Norwegian_Nynorsk", "Western Europe & US"
140
case 0x0415: return "ISO-8859-2"; // "Polish", "Central Europe"
141
case 0x0416: return "ISO-8859-1"; // "Portuguese_Brazilian", "Western Europe & US"
142
case 0x0816: return "ISO-8859-1"; // "Portuguese_Standard", "Western Europe & US"
143
case 0x0418: return "ISO-8859-2"; // "Romanian", "Central Europe"
144
case 0x0419: return "cp1251"; // "Russian", "Cyrillic"
145
case 0x044f: return null; // "Sanskrit", "Indic"
146
case 0x081a: return "ISO-8859-2"; // "Serbian_Latin", "Central Europe"
147
case 0x0c1a: return "cp1251"; // "Serbian_Cyrillic", "Cyrillic"
148
case 0x041b: return "ISO-8859-2"; // "Slovak", "Central Europe"
149
case 0x0424: return "ISO-8859-2"; // "Slovenian", "Central Europe"
150
case 0x040a: return "ISO-8859-1"; // "Spanish_Trad_Sort", "Western Europe & US"
151
case 0x080a: return "ISO-8859-1"; // "Spanish_Mexican", "Western Europe & US"
152
case 0x0c0a: return "ISO-8859-1"; // "Spanish_Modern_Sort", "Western Europe & US"
153
case 0x100a: return "ISO-8859-1"; // "Spanish_Guatemala", "Western Europe & US"
154
case 0x140a: return "ISO-8859-1"; // "Spanish_Costa_Rica", "Western Europe & US"
155
case 0x180a: return "ISO-8859-1"; // "Spanish_Panama", "Western Europe & US"
156
case 0x1c0a: return "ISO-8859-1"; // "Spanish_Dominican_Repub", "Western Europe & US"
157
case 0x200a: return "ISO-8859-1"; // "Spanish_Venezuela", "Western Europe & US"
158
case 0x240a: return "ISO-8859-1"; // "Spanish_Colombia", "Western Europe & US"
159
case 0x280a: return "ISO-8859-1"; // "Spanish_Peru", "Western Europe & US"
160
case 0x2c0a: return "ISO-8859-1"; // "Spanish_Argentina", "Western Europe & US"
161
case 0x300a: return "ISO-8859-1"; // "Spanish_Ecuador", "Western Europe & US"
162
case 0x340a: return "ISO-8859-1"; // "Spanish_Chile", "Western Europe & US"
163
case 0x380a: return "ISO-8859-1"; // "Spanish_Uruguay", "Western Europe & US"
164
case 0x3c0a: return "ISO-8859-1"; // "Spanish_Paraguay", "Western Europe & US"
165
case 0x400a: return "ISO-8859-1"; // "Spanish_Bolivia", "Western Europe & US"
166
case 0x440a: return "ISO-8859-1"; // "Spanish_El_Salvador", "Western Europe & US"
167
case 0x480a: return "ISO-8859-1"; // "Spanish_Honduras", "Western Europe & US"
168
case 0x4c0a: return "ISO-8859-1"; // "Spanish_Nicaragua", "Western Europe & US"
169
case 0x500a: return "ISO-8859-1"; // "Spanish_Puerto_Rico", "Western Europe & US"
170
case 0x0441: return "ISO-8859-1"; // "Swahili", "Western Europe & US"
171
case 0x041d: return "ISO-8859-1"; // "Swedish", "Western Europe & US"
172
case 0x081d: return "ISO-8859-1"; // "Swedish_Finland", "Western Europe & US"
173
case 0x0449: return null; // "Tamil", "Indic"
174
case 0x0444: return "cp1251"; // "Tatar", "Cyrillic"
175
case 0x041e: return "ISO-8859-11"; //"Thai", "Thai"
176
case 0x041f: return "ISO-8859-9"; // "Turkish", "Turkish"
177
case 0x0422: return "cp1251"; // "Ukrainian", "Cyrillic"
178
case 0x0420: return "ISO-8859-6"; // "Urdu", "Arabic"
179
case 0x0443: return "ISO-8859-9"; // "Uzbek_Latin", "Turkish"
180
case 0x0843: return "cp1251"; // "Uzbek_Cyrillic", "Cyrillic"
181
case 0x042a: return null; // "Vietnamese", "Vietnamese"
186
var utf8Converter = Cc["@mozilla.org/intl/utf8converterservice;1"].getService(Ci.nsIUTF8ConverterService);
188
function nativeToUtf8(nativeString, lcid) {
189
var charset = getCharsetFromLcid(lcid);
193
return utf8Converter.convertStringToUTF8(nativeString, charset, 0);
196
var Lib = function(libPath) {
198
libPath = ioService.newURI('resource://chmfox-lib', null, null)
199
.QueryInterface(Ci.nsIFileURL).file.path;
202
this._libraryPath = libPath;
203
this._library = ctypes.open(this._libraryPath);
205
this.CHM_RESOLVE_SUCCESS = 0;
206
this.CHM_RESOLVE_FAILURE = 1;
208
this.CHM_UNCOMPRESSED = 0;
209
this.CHM_COMPRESSED = 1;
211
this.CHM_ENUMERATE_NORMAL = 1;
212
this.CHM_ENUMERATE_META = 2;
213
this.CHM_ENUMERATE_SPECIAL = 4;
214
this.CHM_ENUMERATE_FILES = 8;
215
this.CHM_ENUMERATE_DIRS = 16;
216
this.CHM_ENUMERATE_ALL = 31;
217
this.CHM_ENUMERATOR_FAILURE = 0;
219
this.CHM_ENUMERATOR_CONTINUE = 1;
220
this.CHM_ENUMERATOR_SUCCESS = 2;
222
this.CHM_MAX_PATH_LENGTH = 512;
224
this.chmFilePtr = ctypes.StructType("chmFile").ptr;
225
this.chmUnitInfo = ctypes.StructType(
227
{'start': ctypes.uint64_t},
228
{'length': ctypes.uint64_t},
229
{'space': ctypes.int},
230
{'flags': ctypes.int},
231
{'path': ctypes.char.array(this.CHM_MAX_PATH_LENGTH+1)}
234
this.enumerator = ctypes.FunctionType(
238
this.chmUnitInfo.ptr,
239
ctypes.voidptr_t]).ptr;
241
this.search_enumerator = ctypes.FunctionType(
246
ctypes.char.ptr]).ptr;
248
var openCharType = ctypes.char.ptr;
249
if ('WINNT' == xulRuntime.OS) {
250
openCharType = ctypes.jschar.ptr;
253
this.open = this._library.declare(
254
'chmfox_open', ctypes.default_abi,
258
this.close = this._library.declare(
259
'chmfox_close', ctypes.default_abi,
263
this.set_param = this._library.declare(
264
'chmfox_set_param', ctypes.default_abi,
266
this.chmFilePtr, ctypes.int, ctypes.int);
268
this.resolve_object = this._library.declare(
269
'chmfox_resolve_object', ctypes.default_abi,
271
this.chmFilePtr, ctypes.char.ptr, this.chmUnitInfo.ptr);
273
this.retrieve_object = this._library.declare(
274
'chmfox_retrieve_object', ctypes.default_abi,
276
this.chmFilePtr, this.chmUnitInfo.ptr, ctypes.unsigned_char.ptr,
277
ctypes.uint64_t, ctypes.int64_t);
279
this.enumerate = this._library.declare(
280
'chmfox_enumerate', ctypes.default_abi,
282
this.chmFilePtr, ctypes.int, this.enumerator, ctypes.voidptr_t);
284
this.enumerate_dir = this._library.declare(
285
'chmfox_enumerate_dir', ctypes.default_abi,
287
this.chmFilePtr, ctypes.char.ptr, ctypes.int, this.enumerator, ctypes.voidptr_t);
289
this.search = this._library.declare(
290
'chmfox_search', ctypes.default_abi,
292
this.chmFilePtr, ctypes.char.ptr, ctypes.int, ctypes.int, this.search_enumerator);
295
this.find_ext = function(handle, ext, where) {
301
let compare = function(handle, ui, context) {
302
var path = ui.contents.path.readString();
303
if (path.substr(path.length - ext.length) == ext) {
305
return this.CHM_ENUMERATOR_SUCCESS;
307
return this.CHM_ENUMERATOR_CONTINUE;
310
let callback = this.enumerator(compare);
311
var n = ctypes.voidptr_t();
312
this.enumerate_dir(handle, where, this.CHM_ENUMERATE_NORMAL,
317
this.search_all = function(handle, keywords, whole_words, titles_only) {
319
let store = function(handle, topic, url) {
320
result.push([topic.readString(), url.readString()]);
321
return this.CHM_ENUMERATOR_CONTINUE;
324
let callback = this.search_enumerator(store);
325
this.search(handle, keywords, whole_words, titles_only, callback);
334
function getString(array, index) {
337
var i = array[index++];
341
out += String.fromCharCode(i);
344
// return ctypes.cast(array.addressOfElement(index), ctypes.char.ptr).readString();
347
function getBuffer(array, index, len) {
349
var end = index + len;
350
for (var i = index; i < end; ++ i) {
351
out += String.fromCharCode(array[i]);
356
function getUInt32(array, index) {
357
return ctypes.cast(array.addressOfElement(index), ctypes.uint32_t.ptr).contents;
360
function getUInt64(array, index) {
361
return ctypes.cast(array.addressOfElement(index), ctypes.uint64_t.ptr).contents;
364
function prependSlash(str) {
365
if (str && str[0] != '/') {
372
function HtmlizeObject(str) {
373
var r = str.replace(/<OBJECT/ig, '<div')
374
.replace(/<\/OBJECT/ig, '</div')
375
.replace(/<PARAM/ig, '<span')
376
.replace(/<\/PARAM/ig, '</span');
378
// var i = r.indexOf('<UL>');
379
// var j = r.lastIndexOf('</BODY>');
381
// r = r.substring(i, j-1);
385
var ChmFile = function(path, uri) {
388
log('open chm file:'+ this.path);
389
this.handle = lib.open(this.path);
391
this.close = function() {
392
lib.close(this.handle);
393
log('closed chm file:' + this.path);
396
this.isValid = function() {
397
return !this.handle.isNull();
400
if (! this.isValid()) {
401
log('open chm file failed: ' + this.path);
405
this.getSystemInfo = function () {
406
var ui = lib.chmUnitInfo();
407
if (lib.CHM_RESOLVE_FAILURE == lib.resolve_object(this.handle, '/#SYSTEM', ui.address())) {
411
var buffer = ctypes.unsigned_char.array(ui.length)();
412
var length = lib.retrieve_object(
413
this.handle, ui.address(),
414
buffer.addressOfElement(0),
427
while (index < length) {
428
var type = buffer[index] + (buffer[index+1] * 256);
430
var len = buffer[index] + (buffer[index+1] * 256);
434
this.topics = prependSlash(getString(buffer, index, len));
437
this.index = prependSlash(getString(buffer, index, len));
440
this.home = prependSlash(getString(buffer, index, len));
443
this.title = prependSlash(getString(buffer, index, len));
446
this.lcid = getUInt32(buffer, index);
447
this.use_dbcs = getUInt32(buffer, index+0x4);
448
this.searchable = getUInt32(buffer, index+0x8);
449
this.has_klinks = getUInt32(buffer, index+0xc);
450
this.has_alinks = getUInt32(buffer, index+0x10);
451
this.timestamp = getUInt64(buffer, index+0x14);
453
case 5: // Always "main"?
454
this.default_window = getString(buffer, index, len);
455
case 6: // Project name
456
this.project = getString(buffer, index, len);
458
this.has_binary_index = getUInt32(buffer, index);
461
this.compiled_by = getString(buffer, index, len);
466
this.has_binary_toc = getUInt32(buffer, index);
473
this.encoding = getBuffer(buffer, index, len);
479
this.topics = nativeToUtf8(this.topics, this.lcid);
480
this.index = nativeToUtf8(this.index, this.lcid);
481
this.home = nativeToUtf8(this.home, this.lcid);
482
this.title = nativeToUtf8(this.title, this.lcid);
483
this.project = nativeToUtf8(this.project, this.lcid);
484
this.compiled_by = nativeToUtf8(this.compiled_by, this.lcid);
486
// Gets information from the #WINDOWS file.
487
// Checks the #WINDOWS file to see if it has any info that was
488
// not found in #SYSTEM (topics, index or default page.
489
if (lib.CHM_RESOLVE_FAILURE == lib.resolve_object(this.handle, '/#WINDOWS', ui.address())) {
493
const WINDOWS_HEADER_LENGTH = 8;
494
buffer = ctypes.unsigned_char.array(WINDOWS_HEADER_LENGTH)();
495
length = lib.retrieve_object(
496
this.handle, ui.address(),
497
buffer.addressOfElement(0), 0, buffer.length);
498
if (length < buffer.length) {
501
var entries = getUInt32(buffer, 0);
502
var entry_size = getUInt32(buffer, 4);
503
buffer = ctypes.unsigned_char.array(entries*entry_size)();
504
length = lib.retrieve_object(
505
this.handle, ui.address(),
506
buffer.addressOfElement(0), WINDOWS_HEADER_LENGTH, buffer.length);
512
if (lib.resolve_object(this.handle, "/#STRINGS", ui.address()) != lib.CHM_RESOLVE_SUCCESS) {
517
var factor_buffer = ctypes.unsigned_char.array(4096)();
519
for (var i = 0; i < entries; ++i) {
520
var offset = i * entry_size;
521
var off_title = getUInt32(buffer, offset + 0x14);
522
var off_home = getUInt32(buffer, offset + 0x68);
523
var off_hhc = getUInt32(buffer, offset + 0x60);
524
var off_hhk = getUInt32(buffer, offset + 0x64);
525
var factor = Math.floor(off_title / 4096);
528
size = lib.retrieve_object(
529
this.handle, ui.address(),
530
factor_buffer.addressOfElement(0),
531
factor*4096, factor_buffer.length);
534
if (size > 0 && off_title && this.title) {
535
this.title = getString(factor_buffer, off_title % 4096, ui.length);
538
if (factor != Math.floor(off_home / 4096)) {
539
factor = Math.floor(off_home / 4096);
540
size = lib.retrieve_object(
541
this.handle, ui.address(),
542
factor_buffer.addressOfElement(0),
543
factor*4096, factor_buffer.length);
546
if (size > 0 && off_home && !this.home) {
547
this.home = prependSlash(getString(factor_buffer, off_home % 4096, ui.length));
550
if (factor != Math.floor(off_hhc/4096)) {
551
factor = Math.floor(off_hhc / 4096);
552
size = lib.retrieve_object(
553
this.handle, ui.address(),
554
factor_buffer.addressOfElement(0),
556
factor_buffer.length);
559
if (size && off_hhc && !this.topics) {
560
this.topics = prependSlash(getString(factor_buffer, off_hhc % 4096, ui.length));
563
if (factor != Math.floor(off_hhk / 4096)) {
564
factor = Math.floor(off_hhk / 4096);
565
size = lib.retrieve_object(
566
this.handle, ui.address(),
567
factor_buffer.addressOfElement(0),
569
factor_buffer.length);
572
if(size && off_hhk && !this.index) {
573
this.index = prependSlash(getString(factor_buffer, off_hhk % 4096, ui.length));
576
log("lcid: " + this.lcid);
577
log("use dbcs: " + this.use_dbcs);
582
this.getSystemInfo();
584
this.getTopics = function() {
585
if (this.html_topics) {
586
return this.html_topics;
588
if (! this.isValid()) {
591
var ui = lib.chmUnitInfo();
594
var try_topics_page = '/' + this.project + '.hhc';
595
if (lib.CHM_RESOLVE_SUCCESS == lib.resolve_object(
596
this.handle, try_topics_page, ui.address())) {
597
this.topics = try_topics_page;
601
this.topics = prependSlash(lib.find_ext(this.handle, '.hhc'));
608
if (lib.CHM_RESOLVE_SUCCESS == lib.resolve_object(
609
this.handle, this.topics, ui.address())) {
610
var buf = ctypes.unsigned_char.array(Math.floor(ui.length+1))();
611
var r = lib.retrieve_object(
612
this.handle, ui.address(),
613
buf.addressOfElement(0), 0, ui.length);
615
this.topics_content = utf8Encode(nativeToUtf8(getString(buf, 0), this.lcid));
616
this.html_topics = HtmlizeObject(this.topics_content);
619
return this.html_topics;
622
this.getIndex = function() {
623
if (this.html_index) {
624
return this.html_index;
626
var ui = lib.chmUnitInfo();
629
var try_index_page = '/' + this.project + '.hhk';
630
if (lib.CHM_RESOLVE_SUCCESS == lib.resolve_object(
631
this.handle, try_index_page, ui.address())) {
632
this.topics = try_index_page;
636
this.index = prependSlash(lib.find_ext(this.handle, '.hhk'));
642
if (lib.CHM_RESOLVE_SUCCESS == lib.resolve_object(
643
this.handle, this.index, ui.address())) {
644
var buf = ctypes.unsigned_char.array(Math.floor(ui.length+1))();
645
var r = lib.retrieve_object(
646
this.handle, ui.address(),
647
buf.addressOfElement(0), 0, ui.length);
649
this.index_content = utf8Encode(nativeToUtf8(getBuffer(buf, 0, r), this.lcid));
650
this.html_index = HtmlizeObject(this.index_content);
653
return this.html_index;
656
this.getContent = function(page) {
657
var ui = lib.chmUnitInfo();
658
if (lib.CHM_RESOLVE_SUCCESS != lib.resolve_object(this.handle, page, ui.address())) {
659
log('page not found: ' + this.path + ' ' + page);
662
var buffer = ctypes.unsigned_char.array(Math.floor(ui.length+1))();
663
var length = lib.retrieve_object(
664
this.handle, ui.address(),
665
buffer.addressOfElement(0), 0, ui.length);
667
return getBuffer(buffer, 0, length);
670
log('page retrieve failed: ' + this.path + ' ' + page);
678
function normlizePath(path) {
679
var parts = path.split('/');
681
for (var i = 0; i < parts.length; ++i) {
687
if (norm.length != 0) norm.pop();
693
return '/' + norm.join('/');
696
function redirect(to, orig) {
697
var html = '<html><head><meta http-equiv="refresh" content="0; url=' +
698
utf8Encode(to) + '" /></head></html>';
699
var sis = Cc["@mozilla.org/io/string-input-stream;1"].createInstance(Ci.nsIStringInputStream);
700
sis.setData(html, html.length);
701
var isc = Cc["@mozilla.org/network/input-stream-channel;1"].createInstance(Ci.nsIInputStreamChannel);
702
isc.contentStream = sis;
705
var bc = isc.QueryInterface(Ci.nsIChannel);
706
bc.contentCharset = 'utf-8';
707
bc.contentType = "text/html";
708
bc.contentLength = html.length;
709
bc.originalURI = orig;
714
function getChmFileAndModifyUri(uri) {
715
var urlParts = decodeURI(uri.spec).split('!');
716
var chm_uri = urlParts[0];
717
var url = chm_uri.substring(4); //Remove "chm:"
720
url = url.replace('\\', '/');
721
url = ioService.newURI(url, null, null);
722
var chm = Application.storage.get(chm_uri, null);
724
var local_file = url.QueryInterface(Ci.nsIFileURL).file.path;
725
chm = new ChmFile(local_file, chm_uri);
726
if (! chm.isValid()) {
727
// @todo should use firefox default handle for file not found
728
uri = ioService.newURI("about:blank", null, null);
729
return ioService.newChannelFromURI(uri);
731
Application.storage.set(chm_uri, chm);
736
if (urlParts.length == 1) {
737
urlParts.push(chm.home);
738
uri = ioService.newURI(urlParts.join('!'), null, null);
740
else if (urlParts[1] == '/' || urlParts[1] == '') {
741
urlParts[1] = chm.home;
742
uri = ioService.newURI(urlParts.join('!'), null, null);
745
pagepath = urlParts[1];
755
function Protocol() {
758
Protocol.prototype = {
760
classDescription: "CHM Protocol",
761
classID: Components.ID("c152fc51-a5bf-4cc7-99f1-66ca8459d806"),
762
contractID: "@mozilla.org/network/protocol;1?name=" + kScheme,
763
QueryInterface: XPCOMUtils.generateQI([Ci.nsIProtocolHandler, Ci.nsISupports]),
766
protocolFlags: Ci.nsIProtocolHandler.URI_NORELATIVE |
767
Ci.nsIProtocolHandler.URI_DANGEROUS_TO_LOAD |
768
Ci.nsIProtocolHandler.URI_IS_LOCAL_FILE |
769
Ci.nsIProtocolHandler.URI_NOAUTH,
771
allowPort: function(port, scheme) {
775
newURI: function(spec, charset, baseURI) {
776
// FIXME: why there is 'chm:' uri
777
if (spec == 'chm:') {
781
// Remote websites are not allowed to load or link to local files
783
var baseUriProtocol = baseURI.spec.substr(0, baseURI.spec.indexOf(':')).toLowerCase();
784
if (baseUriProtocol != 'chm' && baseUriProtocol != 'chrome') {
789
var uri = Cc["@mozilla.org/network/simple-uri;1"].createInstance(Ci.nsIURI);
790
if (spec.substring(0, 1) == "#") {
791
var basespec = baseURI.spec;
792
var pos = basespec.indexOf("#");
794
basespec = basespec.substring(0, pos);
796
uri.spec = basespec + spec;
799
else if (spec.indexOf(":") > 0) {
803
else if (spec.substring(0, 1) != '/') {
804
var basespec = baseURI.spec;
805
var pos = basespec.lastIndexOf("!/");
807
var pagepath = basespec.substring(pos + 1, basespec.lastIndexOf('/') + 1) + spec;
808
if (pagepath.lastIndexOf('/') >= 1)
809
pagepath = normlizePath(pagepath);
810
uri.spec = basespec.substring(0, pos + 1) + pagepath;
812
uri.spec = basespec + "!/" + spec;
815
getChmFileAndModifyUri(uri);
819
newChannel: function(aURI) {
820
var chm = getChmFileAndModifyUri(aURI);
823
return redirect(chm.uri.spec, aURI);
826
if (chm.page == "/#HHC") {
827
return this.newRawTopicsChannel(aURI, chm.file);
830
if (chm.page == "/#HHK") {
831
return this.newRawIndexChannel(aURI, chm.file);
834
var pos = chm.page.indexOf("#");
836
chm.page = chm.page.substring(0, pos);
840
// Create the channel
841
var mime = "text/html";
843
var pos = chm.page.lastIndexOf(".");
845
var ext = chm.page.substring(pos + 1);
846
switch (ext.toLowerCase()) {
848
mime = "image/svg+xml";
865
mime = "message/rfc822";
880
mime = 'image/bitmap';
883
mime = "application/octet-stream";
888
var content = undefined;
890
content = chm.file.getContent(chm.page);
892
content = chm.file.path + '! ' + chm.path + ' Not Found!';
896
var filePath = decodeURI(aURI.spec).substr(6);
897
filePath = filePath.split('!')[0];
898
content = "CHM file [" + filePath + "] open failed!";
901
var is = Cc["@mozilla.org/io/string-input-stream;1"].createInstance(Ci.nsIStringInputStream);
902
is.setData(content, content.length);
903
var isc = Cc["@mozilla.org/network/input-stream-channel;1"].createInstance(Ci.nsIInputStreamChannel);
904
isc.contentStream = is;
907
var bc = isc.QueryInterface(Ci.nsIChannel);
908
bc.contentType = mime;
909
// The encoding is in the HTML header
910
// bc.contentCharset = 'utf-8';
911
bc.contentLength = content.length;
912
bc.originalURI = aURI;
918
newRawIndexChannel: function(aURI, chm) {
919
var content = chm.getIndex();
923
var sis = Cc["@mozilla.org/io/string-input-stream;1"].createInstance(Ci.nsIStringInputStream);
924
sis.setData(content, content.length);
926
var isc = Cc["@mozilla.org/network/input-stream-channel;1"].createInstance(Ci.nsIInputStreamChannel);
927
isc.contentStream = sis;
930
var bc = isc.QueryInterface(Ci.nsIChannel);
931
bc.contentCharset = 'utf-8';
932
bc.contentType = "text/html";
933
bc.contentLength = content.length;
934
bc.originalURI = aURI;
940
newRawTopicsChannel: function(aURI, chm) {
941
var content = chm.getTopics();
946
var sis = Cc["@mozilla.org/io/string-input-stream;1"].createInstance(Ci.nsIStringInputStream);
947
sis.setData(content, content.length);
949
var isc = Cc["@mozilla.org/network/input-stream-channel;1"].createInstance(Ci.nsIInputStreamChannel);
950
isc.contentStream = sis;
953
var bc = isc.QueryInterface(Ci.nsIChannel);
954
bc.contentCharset = 'utf-8';
955
bc.contentType = "text/html";
956
bc.contentLength = content.length;
957
bc.originalURI = aURI;
965
function chmXURIContentListener() {
966
this.loadCookie = null;
967
this.parentContentListener = null;
968
this.wrappedJSObject = this;
971
chmXURIContentListener.prototype = {
972
classDescription: "chmXURIContentListener",
973
classID: Components.ID("{1c811fec-ec47-45ea-a395-c70fb1fc8f9d}"),
974
contractID: "@zhuoqiang.me/chmXURIContentListener;1",
975
_xpcom_categories: [{ category: "app-startup" }],
976
QueryInterface: XPCOMUtils.generateQI([Components.interfaces.nsISupports,
977
Components.interfaces.nsIURIContentListener,
978
Components.interfaces.nsIClassInfo,
979
Components.interfaces.nsISupportsWeakReference,
980
Components.interfaces.nsIObserver]),
982
canHandleContent: function(aContentType, aIsContentPreferred) {
983
// it is chemical/x-chemdraw on Ubuntu
984
// if (aContentType == "application/octet-stream") {
990
doContent: function(aContentType, aIsContentPreferred, aRequest, aContentHandler) {
992
// it is chemical/x-chemdraw on Ubuntu
993
if (true || aContentType == "application/octet-stream") {
994
if (aRequest.name.substr(0,5).toLowerCase() == 'file:' && aRequest.name.substr(-4).toLowerCase() == '.chm') {
995
var urispec = "chm" + aRequest.name.substr(4);
996
urispec = decodeURI(urispec);
998
var lastPosition = prefs.getCharPref("lastPosition."+urispec);
1000
urispec = urispec + '!/' + lastPosition;
1005
let window = aRequest.loadGroup.notificationCallbacks.
1006
getInterface(Components.interfaces.nsIDOMWindow);
1007
let webnav = window.QueryInterface(Components.interfaces.nsIInterfaceRequestor).getInterface(Components.interfaces.nsIWebNavigation);
1008
webnav.loadURI(urispec, 0, null, null, null);
1014
throw Components.results.NS_ERROR_NOT_IMPLEMENTED; // FIXME
1017
isPreferred: function(aContentType) {
1018
if (aContentType == "application/octet-stream") {
1024
onStartURIOpen: function(aURI) {
1028
observe: function(subject, topic, data) {
1029
if (topic == "profile-after-change" || topic == "app-startup") {
1030
let uriLoader = Components.classes["@mozilla.org/uriloader;1"].
1031
getService(Components.interfaces.nsIURILoader);
1032
uriLoader.registerContentListener(contentListener);
1037
var contentListener = new chmXURIContentListener();
1039
var components = [chmXURIContentListener];
1041
return {log:log, components: [Protocol, chmXURIContentListener], prefs:prefs};
1047
const NSGetFactory = XPCOMUtils.generateNSGetFactory(Chmfox.components);