-rwxr-xr-x | src/components/xriProtocolHandler.js | 1060 |
1 files changed, 1060 insertions, 0 deletions
diff --git a/src/components/xriProtocolHandler.js b/src/components/xriProtocolHandler.js new file mode 100755 index 0000000..7046cde --- a/dev/null +++ b/src/components/xriProtocolHandler.js | |||
@@ -0,0 +1,1060 @@ | |||
1 | /*********************************************************** | ||
2 | constants | ||
3 | ***********************************************************/ | ||
4 | |||
5 | // The interface we implement - nsIProtocolHandler | ||
6 | const nsIProtocolHandler = Components.interfaces.nsIProtocolHandler; | ||
7 | |||
8 | // Interfaces that we require | ||
9 | const nsISupports = Components.interfaces.nsISupports; | ||
10 | const nsIIOService = Components.interfaces.nsIIOService; | ||
11 | const nsIURI = Components.interfaces.nsIURI; | ||
12 | const nsIURL = Components.interfaces.nsIURL; | ||
13 | const nsIRequest = Components.interfaces.nsIRequest; | ||
14 | const nsIRequestObserver = Components.interfaces.nsIRequestObserver; | ||
15 | const nsIChannel = Components.interfaces.nsIChannel; | ||
16 | const nsIHttpChannel = Components.interfaces.nsIHttpChannel; | ||
17 | const nsIStreamListener = Components.interfaces.nsIStreamListener; | ||
18 | |||
19 | |||
20 | // UUID uniquely identifying our component | ||
21 | // You can get from: http://kruithof.xs4all.nl/uuid/uuidgen here | ||
22 | const CLASS_ID = Components.ID("{ea00b610-215a-11db-a98b-0800200c9a66}"); | ||
23 | |||
24 | // textual unique identifier | ||
25 | const CONTRACT_ID = "@mozilla.org/network/protocol;1?name=xri"; | ||
26 | |||
27 | // Components that we require | ||
28 | const CID_URI = "@mozilla.org/network/simple-uri;1"; | ||
29 | const kIOSERVICE_CID_STR = "{9ac9e770-18bc-11d3-9337-00104ba0fd40}"; | ||
30 | const CID_URL = "@mozilla.org/network/standard-url;1"; | ||
31 | |||
32 | // description | ||
33 | const CLASS_NAME = "XRI Protocol Handler"; | ||
34 | |||
35 | const PROXY_URI = "http://xri.net/"; | ||
36 | |||
37 | const XP_ANY_TYPE = 0; | ||
38 | const XP_NUMBER_TYPE = 1; | ||
39 | const XP_STRING_TYPE = 2; | ||
40 | const XP_BOOLEAN_TYPE = 3; | ||
41 | const XP_UNORDERED_NODE_ITERATOR_TYPE = 4; | ||
42 | const XP_ORDERED_NODE_ITERATOR_TYPE = 5; | ||
43 | const XP_UNORDERED_NODE_SNAPSHOT_TYPE = 6; | ||
44 | const XP_ORDERED_NODE_SNAPSHOT_TYPE = 7; | ||
45 | const XP_ANY_UNORDERED_NODE_TYPE = 8; | ||
46 | const XP_FIRST_ORDERED_NODE_TYPE = 9; | ||
47 | |||
48 | |||
49 | var SERVICE_CLASSES = { | ||
50 | 'xri://+i-service*(+contact)*($v*1.0)': 'i-contact', | ||
51 | 'http://openid.net/signon/1.0': 'openid', | ||
52 | 'xri://$res*auth*($v*2.0)': 'res-auth', | ||
53 | 'xri://+i-service*(+authn)*(+saml)*($v*1.0)': 'authn-saml', | ||
54 | 'xri://+i-service*(+metadata)*(+saml)*($v*1.0)' : 'metadata-saml', | ||
55 | 'xri://+i-service*(+forwarding)*($v*1.0)': 'i-forwarding' | ||
56 | }; | ||
57 | |||
58 | |||
59 | const HTML_HEAD = "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n\ | ||
60 | <html xmlns=\"http://www.w3.org/1999/xhtml\">\n\ | ||
61 | <head>\n\ | ||
62 | <title>FoXRI Explorer - #QXRI#</title>\n\ | ||
63 | <link href=\"chrome://foxri/content/foxri_explorer.css\" rel=\"stylesheet\" type=\"text/css\" />\n\ | ||
64 | <body>\n\ | ||
65 | <h1>FoXRI Explorer</h1>\n\ | ||
66 | <div id=\"explorer_body\">\n"; | ||
67 | |||
68 | const HTML_FOOT = "</div>\n\ | ||
69 | </body>\n\ | ||
70 | </html>"; | ||
71 | |||
72 | |||
73 | |||
74 | /// Generic object method wrapper | ||
75 | function methodWrapper(obj, method) | ||
76 | { | ||
77 | return ( | ||
78 | function() { | ||
79 | /* pass it this inner closure's arguments */ | ||
80 | obj[method](arguments); | ||
81 | } | ||
82 | ); | ||
83 | } | ||
84 | |||
85 | |||
86 | |||
87 | /// XRDS utility functions | ||
88 | |||
89 | |||
90 | var nsResolver = { | ||
91 | lookupNamespaceURI: function(prefix) | ||
92 | { | ||
93 | if (prefix == "xrds") | ||
94 | return "xri://$xrds"; | ||
95 | else if (prefix == "xrd") | ||
96 | return "xri://$xrd*($v*2.0)"; | ||
97 | return ""; | ||
98 | } | ||
99 | }; | ||
100 | |||
101 | |||
102 | |||
103 | function runExpr(doc, context, expr, returnType) | ||
104 | { | ||
105 | if (!returnType) | ||
106 | returnType = XP_ANY_TYPE; | ||
107 | var res = doc.evaluate(expr, context, nsResolver, returnType, null); | ||
108 | return res; | ||
109 | } | ||
110 | |||
111 | |||
112 | function getNumeric(doc, context, expr) | ||
113 | { | ||
114 | var res = runExpr(doc, context, expr, XP_NUMBER_TYPE); | ||
115 | if (res) | ||
116 | return res.numberValue; | ||
117 | return null; | ||
118 | } | ||
119 | |||
120 | |||
121 | function getString(doc, context, expr) | ||
122 | { | ||
123 | // var res = runExpr(doc, context, expr, XPathResult.STRING_TYPE); | ||
124 | var res = runExpr(doc, context, expr, XP_STRING_TYPE); | ||
125 | if (res) | ||
126 | return res.stringValue; | ||
127 | return null; | ||
128 | } | ||
129 | |||
130 | function getNode(doc, context, expr) | ||
131 | { | ||
132 | var res = runExpr(doc, context, expr, XP_FIRST_ORDERED_NODE_TYPE); | ||
133 | if (res) | ||
134 | return res.singleNodeValue; | ||
135 | return null; | ||
136 | } | ||
137 | |||
138 | |||
139 | function getFinalXRD(doc) | ||
140 | { | ||
141 | var lastNode = doc.firstChild; | ||
142 | while (true) { | ||
143 | var node = getNode(doc, lastNode, "xrds:XRDS[position()=last()]"); | ||
144 | if (!node) | ||
145 | break; | ||
146 | lastNode = node; | ||
147 | } | ||
148 | |||
149 | return getNode(doc, lastNode, "xrd:XRD[position()=last()]"); | ||
150 | } | ||
151 | |||
152 | |||
153 | function isIName(xri) | ||
154 | { | ||
155 | if (xri.match('^xri://.!', 'i')) { | ||
156 | return false; | ||
157 | } | ||
158 | if (xri.match('^.!', 'i')) { | ||
159 | return false; | ||
160 | } | ||
161 | return true; | ||
162 | } | ||
163 | |||
164 | |||
165 | function arraySearch(a, re) | ||
166 | { | ||
167 | var returnArr = new Array(); | ||
168 | var i; | ||
169 | for (i = 0; i < a.length; i++) { | ||
170 | if (a[i].match(re)) { | ||
171 | returnArr.push(a[i]); | ||
172 | } | ||
173 | } | ||
174 | |||
175 | return returnArr; | ||
176 | } | ||
177 | |||
178 | |||
179 | function renderService(srv, doc, qxri) | ||
180 | { | ||
181 | var html_types = ''; | ||
182 | var html_paths = ''; | ||
183 | var html_mediatypes = ''; | ||
184 | var html_uris = ''; | ||
185 | var html_actions = ''; | ||
186 | |||
187 | var serviceName = friendlyServiceName(null); | ||
188 | var serviceType; // the last non-null Type | ||
189 | var knownServiceType; // first recognized service type | ||
190 | |||
191 | // get the types | ||
192 | var res = runExpr(doc, srv, "xrd:Type/text()"); | ||
193 | var t; | ||
194 | while (t = res.iterateNext()) { | ||
195 | if (t.nodeValue) { | ||
196 | if (!knownServiceType && isKnownServiceType(t.nodeValue)) { | ||
197 | knownServiceType = t.nodeValue; | ||
198 | } | ||
199 | |||
200 | serviceType = t.nodeValue; | ||
201 | html_types += "<strong>Type:</strong> " + t.nodeValue + "<br/>"; | ||
202 | } | ||
203 | } | ||
204 | |||
205 | // get the paths | ||
206 | res = runExpr(doc, srv, "xrd:Path/text()"); | ||
207 | var p; | ||
208 | var qxri_prefix = qxri; | ||
209 | if (qxri_prefix.charAt(qxri_prefix.length - 1) != '/') { | ||
210 | qxri_prefix += '/'; | ||
211 | } | ||
212 | |||
213 | while (p = res.iterateNext()) { | ||
214 | if (p.nodeValue) { | ||
215 | html_paths += "<strong>Path:</strong> " + p.nodeValue | ||
216 | + " [ <tt><a href=\"" + qxri_prefix + p.nodeValue + "\">" | ||
217 | + qxri_prefix + p.nodeValue + "</a></tt> ]" | ||
218 | + "<br/>\n"; | ||
219 | } | ||
220 | } | ||
221 | |||
222 | |||
223 | // get the mediatypes | ||
224 | mediaTypes = new Array(); | ||
225 | res = runExpr(doc, srv, "xrd:MediaType/text()"); | ||
226 | var m; | ||
227 | while (m = res.iterateNext()) { | ||
228 | if (!knownServiceType) { | ||
229 | var srvType = guessServiceTypeByMime(m.nodeValue); | ||
230 | knownServiceType = srvType? srvType : null; | ||
231 | } | ||
232 | |||
233 | mediaTypes.push(m.nodeValue); | ||
234 | if (m.nodeValue) { | ||
235 | html_mediatypes += "<strong>Media Type:</strong> " + m.nodeValue + "<br/>"; | ||
236 | } | ||
237 | } | ||
238 | |||
239 | |||
240 | res = runExpr(doc, srv, "xrd:URI/text()"); | ||
241 | var u; | ||
242 | while (u = res.iterateNext()) { | ||
243 | if (!u.nodeValue) | ||
244 | continue; | ||
245 | |||
246 | var srvType = guessServiceTypeByURI(u.nodeValue); | ||
247 | if (!knownServiceType) { | ||
248 | knownServiceType = srvType; | ||
249 | } | ||
250 | |||
251 | html_uris += "<div class=\"" + getServiceClass(srvType) + "\">"; | ||
252 | |||
253 | var linkContent = u.nodeValue; | ||
254 | var uriParts = u.nodeValue.match('^(.*):(.*)$'); | ||
255 | if (!uriParts) | ||
256 | continue; | ||
257 | |||
258 | if (uriParts[1] == 'data') { | ||
259 | uriParts = uriParts[2].match('^(.*/.*),(.*)'); | ||
260 | if (uriParts && uriParts[1].match('^image/', 'i')) { | ||
261 | linkContent = "<img src=\"" + u.nodeValue + "\"/>"; | ||
262 | } | ||
263 | else if (uriParts) { | ||
264 | linkContent = uriParts[1] + " data"; | ||
265 | } | ||
266 | } | ||
267 | else if (uriParts[1] == 'skype') { | ||
268 | uriParts = uriParts[2].match('^(.*)\\?(.*)'); | ||
269 | if (uriParts) { | ||
270 | if (uriParts[2] == "call") { | ||
271 | linkContent = "<img src=\"chrome://foxri/content/skype_call_large.png\" alt=\"Call " + uriParts[1] + "\"/>"; | ||
272 | } | ||
273 | else if (uriParts[2] == "chat") { | ||
274 | linkContent = "<img src=\"chrome://foxri/content/skype_chat_large.png\" alt=\"Chat with " + uriParts[1] + "\"/>"; | ||
275 | } | ||
276 | else if (uriParts[2] == "add") { | ||
277 | linkContent = "<img src=\"chrome://foxri/content/skype_add_large.png\" alt=\"Add " + uriParts[1] + " to Skype\"/>"; | ||
278 | } | ||
279 | } | ||
280 | } | ||
281 | else if (uriParts[1] == 'aim') { | ||
282 | uriParts = uriParts[2].match('^(.*)\\?.*screenname=([^&]*)', 'i'); | ||
283 | if (uriParts) { | ||
284 | linkContent = "<img src=\"chrome://foxri/content/aim_logo.gif\" alt=\"Chat with " + uriParts[2] + "\"/> Chat with " + uriParts[2]; | ||
285 | } | ||
286 | } | ||
287 | |||
288 | html_uris += "<a href=\""+u.nodeValue+"\">" | ||
289 | + linkContent + "</a>"; | ||
290 | html_uris += "</div>"; | ||
291 | } | ||
292 | |||
293 | var html = "<div class=\"service srv_" + getServiceClass(knownServiceType) + "\">\n"; | ||
294 | html += html_types; | ||
295 | html += html_paths; | ||
296 | html += html_mediatypes; | ||
297 | if (html_uris) { | ||
298 | html += "<strong>URI(s):</strong><br/>\n"; | ||
299 | html += html_uris; | ||
300 | } | ||
301 | html += "</div>"; | ||
302 | |||
303 | return html; | ||
304 | } | ||
305 | |||
306 | |||
307 | |||
308 | function isKnownServiceType(type) | ||
309 | { | ||
310 | if (type.toLowerCase() in SERVICE_CLASSES) { | ||
311 | return true; | ||
312 | } | ||
313 | return false; | ||
314 | } | ||
315 | |||
316 | function getServiceClass(type) | ||
317 | { | ||
318 | if (isKnownServiceType(type)) { | ||
319 | return SERVICE_CLASSES[type.toLowerCase()]; | ||
320 | } | ||
321 | return type; | ||
322 | } | ||
323 | |||
324 | |||
325 | function guessServiceTypeByURI(uri) | ||
326 | { | ||
327 | if (uri == null || uri == "") { | ||
328 | return "unknown"; | ||
329 | } | ||
330 | if (uri.match(/^https?:/i)) { | ||
331 | return "www"; | ||
332 | } | ||
333 | else if (uri.match(/^skype:/i)) { | ||
334 | return "skype"; | ||
335 | } | ||
336 | else if (uri.match(/^aim:/i)) { | ||
337 | return "aim"; | ||
338 | } | ||
339 | else if (uri.match(/^xmpp:/i)) { | ||
340 | return "jabber"; | ||
341 | } | ||
342 | else if (uri.match(/^tel:/i)) { | ||
343 | return "tel"; | ||
344 | } | ||
345 | else if (uri.match(/^callto:/i)) { | ||
346 | return "callto"; | ||
347 | } | ||
348 | else if (uri.match(/^telnet:/i)) { | ||
349 | return "telnet"; | ||
350 | } | ||
351 | else if (uri.match(/^news:/i)) { | ||
352 | return "news"; | ||
353 | } | ||
354 | else if (uri.match(/^nntp:/i)) { | ||
355 | return "nntp"; | ||
356 | } | ||
357 | else if (uri.match(/^ftp:/i)) { | ||
358 | return "ftp"; | ||
359 | } | ||
360 | else if (uri.match(/^mailto:/i)) { | ||
361 | return "email"; | ||
362 | } | ||
363 | else if (uri.match(/^urn:/i)) { | ||
364 | return "urn"; | ||
365 | } | ||
366 | else if (uri.match(/^data:/i)) { | ||
367 | return "data"; | ||
368 | } | ||
369 | else if (uri.match(/^feed:/i)) { | ||
370 | return "feed"; | ||
371 | } | ||
372 | return "unknown"; | ||
373 | } | ||
374 | |||
375 | |||
376 | function guessServiceTypeByMime(mimeType) | ||
377 | { | ||
378 | if (mimeType.match(/^application\/(rss|atom)\+xml/i)) { | ||
379 | dump("feed detected!\n"); | ||
380 | return "feed"; | ||
381 | } | ||
382 | else if (mimeType.match(/^image\//i)) { | ||
383 | return "image"; | ||
384 | } | ||
385 | return null; | ||
386 | } | ||
387 | |||
388 | |||
389 | |||
390 | function friendlyServiceName(srvType, uri) | ||
391 | { | ||
392 | if (srvType && srvType == "xri://+i-service*(+contact)*($v*1.0)") { | ||
393 | return "Contact Service"; | ||
394 | } | ||
395 | else if (srvType && srvType == "http://openid.net/signon/1.0") { | ||
396 | return "OpenID Authentication Service"; | ||
397 | } | ||
398 | else if (srvType && srvType == "xri://$res*auth*($v*2.0)") { | ||
399 | return "Authority Resolution Service"; | ||
400 | } | ||
401 | else { | ||
402 | if (uri == null) { | ||
403 | return "Generic Service"; | ||
404 | } | ||
405 | if (uri.match(/^https?:/i)) { | ||
406 | return "Web Link"; | ||
407 | } | ||
408 | else if (uri.match(/^skype:/i)) { | ||
409 | var user = uri.substring("skype:".length, uri.indexOf('?')); | ||
410 | return "Skype <a href=\"" + uri + "\"><img src=\"chrome://foxri/content/skype_call.png\"></a>"; | ||
411 | } | ||
412 | else if (uri.match(/^mailto:/i)) { | ||
413 | var qmark = uri.indexOf('?'); | ||
414 | var email = (qmark == -1)? | ||
415 | uri.substr("mailto:".length) : | ||
416 | uri.substring("mailto:".length, qmark); | ||
417 | return "Email (address: " + email + ")"; | ||
418 | } | ||
419 | else if (srvType != null) { | ||
420 | return srvType; // return verbatim | ||
421 | } | ||
422 | return "Generic Service"; | ||
423 | } | ||
424 | } | ||
425 | |||
426 | |||
427 | |||
428 | |||
429 | function subHTML(template, vars) | ||
430 | { | ||
431 | for (key in vars) { | ||
432 | template = template.replace(key, vars[key], 'g'); | ||
433 | } | ||
434 | return template; | ||
435 | } | ||
436 | |||
437 | |||
438 | /// Given the completed XMLHttpRequest object, renders the XRDS | ||
439 | function renderXRDS(xmlDoc) | ||
440 | { | ||
441 | var x = xmlDoc; | ||
442 | var qxri = getString(x, x, "/xrds:XRDS/@ref"); | ||
443 | |||
444 | var html = subHTML(HTML_HEAD, { '#QXRI#': qxri }); | ||
445 | |||
446 | // TODO: render parents as well | ||
447 | |||
448 | var lastNode = getFinalXRD(x); | ||
449 | if (lastNode) { | ||
450 | var stat = getString(x, lastNode, "xrd:Status/@code"); | ||
451 | if (stat == "100") { | ||
452 | html += "<h3>Exploring <strong>" + qxri + "</strong></h3>"; | ||
453 | } | ||
454 | else { | ||
455 | var msg = getString(x, lastNode, "xrd:Status/text()"); | ||
456 | html += "<h3 class=\"error\"><strong>" + qxri + "</strong> failed to resolve (reason: " + stat + " - " + msg + ")</h3>"; | ||
457 | } | ||
458 | |||
459 | html += "<br/>"; | ||
460 | |||
461 | var services = runExpr(x, lastNode, "xrd:Service"); | ||
462 | var s; | ||
463 | var count = getNumeric(x, lastNode, "count(xrd:Service)"); | ||
464 | if (count > 0) { | ||
465 | while (s = services.iterateNext()) { | ||
466 | count++; | ||
467 | html += renderService(s, x, qxri); | ||
468 | } | ||
469 | } | ||
470 | else if (stat == '222') { | ||
471 | var xriType = isIName(qxri)? 'I-name' : 'I-number'; | ||
472 | html += "<p class='error'>" + xriType + " does not exist.</p>\n"; | ||
473 | } | ||
474 | else { | ||
475 | html += "<p>No service has been configured for this XRI</p>"; | ||
476 | } | ||
477 | |||
478 | } | ||
479 | |||
480 | html += "</html>"; | ||
481 | |||
482 | return html; | ||
483 | } | ||
484 | |||
485 | |||
486 | |||
487 | |||
488 | |||
489 | |||
490 | /*********************************************************** | ||
491 | XriServiceExplorer class definition | ||
492 | ***********************************************************/ | ||
493 | |||
494 | |||
495 | function XRIChannel(uri) { | ||
496 | this.URI = uri; | ||
497 | var r = uri.spec.indexOf('#'); | ||
498 | if (r >= 0) { | ||
499 | this.qxri = uri.spec.substring(0, r); | ||
500 | this.fragment = uri.spec.substring(r); | ||
501 | } | ||
502 | else { | ||
503 | this.qxri = uri.spec; | ||
504 | } | ||
505 | }; | ||
506 | |||
507 | |||
508 | XRIChannel.prototype = { | ||
509 | |||
510 | fragment: null, | ||
511 | |||
512 | /* private fields used internally */ | ||
513 | qxri: null, | ||
514 | |||
515 | xmlRequest: null, | ||
516 | |||
517 | renderedHTML: null, | ||
518 | |||
519 | scriptableInStream: null, | ||
520 | |||
521 | buf: null, | ||
522 | |||
523 | mChannel: null, | ||
524 | |||
525 | |||
526 | copyFields: function(request) | ||
527 | { | ||
528 | dump("copyFields(loadFlags=" + request.loadFlags + ")\n"); | ||
529 | dump("loadGroup = " + request.loadGroup + "\n"); | ||
530 | dump("notificationCallbacks = " + request.notificationCallbacks + "\n"); | ||
531 | |||
532 | // copy request fields | ||
533 | this.loadFlags = request.loadFlags; | ||
534 | this.loadGroup = request.loadGroup; | ||
535 | this.name = request.name; | ||
536 | this.status = request.status; | ||
537 | |||
538 | var channel = request.QueryInterface(nsIChannel); | ||
539 | if (channel) { | ||
540 | this.contentCharset = channel.contentCharset; | ||
541 | this.contentLength = channel.contentLength; | ||
542 | this.contentType = channel.contentType; // XXX | ||
543 | this.contentType = "text/html"; | ||
544 | this.notificationCallbacks = channel.notificationCallbacks; | ||
545 | this.originalURI = this.originalURI; | ||
546 | this.URI = this.URI; | ||
547 | this.owner = channel.owner; | ||
548 | this.securityInfo = channel.securityInfo; | ||
549 | |||
550 | channel = channel.QueryInterface(nsIHttpChannel); | ||
551 | if (channel) { | ||
552 | this.allowPipelining = channel.allowPipelining; | ||
553 | this.redirectionLimit = channel.redirectionLimit; | ||
554 | this.referrer = channel.referrer; | ||
555 | this.requestMethod = channel.requestMethod; | ||
556 | this.requestSucceeded = channel.requestSucceeded; | ||
557 | this.responseStatus = channel.responseStatus; | ||
558 | this.responseStatusText = channel.responseStatusText; | ||
559 | } | ||
560 | } | ||
561 | |||
562 | }, | ||
563 | |||
564 | /* nsIStreamListener */ | ||
565 | asyncOpenListener: null, | ||
566 | |||
567 | /* nsISupports (but we really don't care) */ | ||
568 | asyncOpenContext: null, | ||
569 | |||
570 | |||
571 | /* has the XML finished loading? */ | ||
572 | loadDone: false, | ||
573 | |||
574 | |||
575 | |||
576 | /* public fields (nsIStreamListener implementation) */ | ||
577 | onDataAvailable : function(request, ctx, inputStream, offset, count) | ||
578 | { | ||
579 | dump("\nonDataAvailable, offset=" + offset + ", count=" + count + "\n"); | ||
580 | |||
581 | // XXX | ||
582 | /* | ||
583 | this.copyFields(request); | ||
584 | this.asyncOpenListener.onDataAvailable(this, this.asyncOpenContext, inputStream, offset, count); | ||
585 | return; | ||
586 | */ | ||
587 | |||
588 | |||
589 | if (offset == 0) { | ||
590 | this.scriptableInStream.init(inputStream); | ||
591 | } | ||
592 | |||
593 | this.buf += this.scriptableInStream.read(count); | ||
594 | |||
595 | if (!request.isPending()) { | ||
596 | dump("request finished, buf = " + this.buf + "\n"); | ||
597 | |||
598 | this.scriptableInStream = null; | ||
599 | } | ||
600 | else { | ||
601 | dump("request pending...\n"); | ||
602 | dump("buf so far = " + this.buf + "\n"); | ||
603 | } | ||
604 | }, | ||
605 | |||
606 | |||
607 | /* public fields (nsIRequestObserver implementation) */ | ||
608 | onStartRequest : function(request, ctx) | ||
609 | { | ||
610 | dump("\nonStartRequest called\n"); | ||
611 | // XXX | ||
612 | |||
613 | this.copyFields(request); | ||
614 | this.asyncOpenListener.onStartRequest(this, this.asyncOpenContext); | ||
615 | }, | ||
616 | |||
617 | |||
618 | onStopRequest : function(request, ctx, status) | ||
619 | { | ||
620 | dump("\nonStopRequest called - status " + status + "\n"); | ||
621 | |||
622 | // XXX | ||
623 | /* | ||
624 | this.asyncOpenListener.onStopRequest(this, this.asyncOpenContext, status); | ||
625 | return; | ||
626 | */ | ||
627 | |||
628 | this.copyFields(request); | ||
629 | this.loadDone = true; | ||
630 | |||
631 | if (status == 0) { | ||
632 | |||
633 | var domParser = Components.classes["@mozilla.org/xmlextras/domparser;1"].createInstance(Components.interfaces.nsIDOMParser); | ||
634 | var xmlDoc = domParser.parseFromString(this.buf, "text/xml"); | ||
635 | |||
636 | // make fake inputstream | ||
637 | var renderedHTML = renderXRDS(xmlDoc); | ||
638 | |||
639 | this.contentCharset = "UTF-8"; | ||
640 | this.contentLength = renderedHTML.length; | ||
641 | this.contentType = "text/html"; | ||
642 | |||
643 | dump("rendered HTML = \n" + renderedHTML + "\n"); | ||
644 | |||
645 | dump("\nCalling asyncOpenListener.onStartRequest\n\n"); | ||
646 | |||
647 | |||
648 | var strIStream = Components.classes["@mozilla.org/io/string-input-stream;1"].createInstance(Components.interfaces.nsIStringInputStream); | ||
649 | if (strIStream) { | ||
650 | strIStream.setData(renderedHTML, renderedHTML.length); | ||
651 | /* | ||
652 | strIStream.setData(this.buf, this.buf.length); | ||
653 | */ | ||
654 | dump("\nleftovers in string-input-stream = " + strIStream.available() + "\n"); | ||
655 | dump("\nCalling asyncOpenListener.onDataAvailable\n\n"); | ||
656 | this.asyncOpenListener.onDataAvailable(this, this.asyncOpenContext, strIStream, 0, renderedHTML.length); | ||
657 | /* | ||
658 | this.asyncOpenListener.onDataAvailable(this, this.asyncOpenContext, strIStream, 0, this.buf.length); | ||
659 | */ | ||
660 | |||
661 | dump("\nleftovers in string-input-stream = " + strIStream.available() + "\n"); | ||
662 | } | ||
663 | } | ||
664 | else { | ||
665 | dump("\nStatus = " + status + "\n"); | ||
666 | dump("Calling asyncOpenListener.onStartRequest\n\n"); | ||
667 | this.asyncOpenListener.onStartRequest(this, this.asyncOpenContext); | ||
668 | } | ||
669 | |||
670 | dump("stopping request for underlying asyncOpenListener\n"); | ||
671 | |||
672 | this.asyncOpenListener.onStopRequest(this, this.asyncOpenContext, status); | ||
673 | |||
674 | // copied from nsIWyciwygChannel | ||
675 | this.asyncOpenListener = null; | ||
676 | this.asyncOpenContext = null; | ||
677 | |||
678 | /* | ||
679 | if (this.loadGroup) { | ||
680 | this.loadGroup.removeRequest(request, null, status); | ||
681 | } | ||
682 | */ | ||
683 | |||
684 | this.notificationCallbacks = null; | ||
685 | this.mChannel = null; | ||
686 | |||
687 | dump("stopped request\n"); | ||
688 | }, | ||
689 | |||
690 | |||
691 | /* public fields (nsIInputStream implementation) */ | ||
692 | available: function() | ||
693 | { | ||
694 | dump("nsIInputStream::available called\n"); | ||
695 | return renderedHTML.length; | ||
696 | }, | ||
697 | |||
698 | close: function() | ||
699 | { | ||
700 | dump("nsIInputStream::close called\n"); | ||
701 | }, | ||
702 | |||
703 | isNonBlocking: function() { | ||
704 | dump("nsIInputStream::isNonBlocking called\n"); | ||
705 | return true; | ||
706 | }, | ||
707 | |||
708 | read: function() { dump("nsIInputStream::read() called!!!\n"); }, | ||
709 | |||
710 | |||
711 | |||
712 | |||
713 | |||
714 | /* public fields (nsIRequest implmentation) */ | ||
715 | |||
716 | loadFlags: 0, | ||
717 | |||
718 | loadGroup: null, | ||
719 | |||
720 | name: "xri://request", | ||
721 | |||
722 | status: 0, | ||
723 | |||
724 | cancel: function(status) { dump("\ncancel called...\n"); }, | ||
725 | |||
726 | isPending: function() { | ||
727 | dump("isPending called\n\n"); | ||
728 | return !this.loadDone; | ||
729 | }, | ||
730 | |||
731 | resume: function() { dump("resume called\n"); }, | ||
732 | |||
733 | suspend: function() { dump("suspend called\n"); }, | ||
734 | |||
735 | |||
736 | |||
737 | /* public fields (nsIChannel implmentation) */ | ||
738 | |||
739 | contentCharset: null, | ||
740 | |||
741 | contentLength: -1, | ||
742 | |||
743 | contentType: null, | ||
744 | |||
745 | notificationCallbacks: null, | ||
746 | |||
747 | originalURI: null, | ||
748 | |||
749 | owner: null, | ||
750 | |||
751 | securityInfo: null, | ||
752 | |||
753 | URI: null, | ||
754 | |||
755 | open: function() | ||
756 | { | ||
757 | dump("open not supporteD!!!!!!\n"); | ||
758 | }, | ||
759 | |||
760 | asyncOpen: function(listener, context) | ||
761 | { | ||
762 | dump("asyncOpen called!!!!!!\n"); | ||
763 | this.asyncOpenListener = listener; | ||
764 | this.asyncOpenContext = context; | ||
765 | |||
766 | var hxri = PROXY_URI + this.qxri | ||
767 | + "?_xrd_r=application/xrds%2Bxml;sep=false"; | ||
768 | var ioService = Components.classesByID[kIOSERVICE_CID_STR].getService(); | ||
769 | ioService = ioService.QueryInterface(nsIIOService); | ||
770 | var channel = ioService.newChannel(hxri, null, null); | ||
771 | |||
772 | if (this.scriptableInStream) { | ||
773 | dump("Hey! You can't possibly be reusing this handler?!\n"); | ||
774 | return; | ||
775 | } | ||
776 | |||
777 | dump("making scriptableInStream\n"); | ||
778 | this.scriptableInStream = Components.classes["@mozilla.org/scriptableinputstream;1"] | ||
779 | .createInstance(Components.interfaces.nsIScriptableInputStream); | ||
780 | |||
781 | this.buf = ''; | ||
782 | |||
783 | dump("notificationCallbacks = " + this.notificationCallbacks + "\n"); | ||
784 | dump("loadFlags = " + this.loadFlags + "\n"); | ||
785 | dump("loadGroup = " + this.loadGroup + "\n"); | ||
786 | dump("owner = " + this.owner + "\n"); | ||
787 | dump("securityInfo = " + this.securityInfo + "\n"); | ||
788 | |||
789 | // these nsIRequest attributes must be copied to the stub | ||
790 | // channel that we created | ||
791 | channel.notificationCallbacks = this.notificationCallbacks; | ||
792 | channel.loadGroup = this.loadGroup; | ||
793 | channel.loadFlags = this.loadFlags; | ||
794 | |||
795 | this.mChannel = channel; | ||
796 | channel.asyncOpen(this, null); | ||
797 | }, | ||
798 | |||
799 | |||
800 | /* public fields (nsIChannel implmentation) */ | ||
801 | allowPipelining: false, | ||
802 | redirectionLimit: 5, | ||
803 | referrer: "", | ||
804 | requestMethod: "GET", | ||
805 | requestSucceeded: true, | ||
806 | responseStatus: 200, | ||
807 | responseStatusText: "OK", | ||
808 | getRequestHeader: function(header) { | ||
809 | dump("getRequestHeader(" + header + ")\n"); | ||
810 | var httpChannel = this.mChannel.QueryInterface(nsIHttpChannel); | ||
811 | |||
812 | try { | ||
813 | var val = httpChannel.getRequestHeader(header); | ||
814 | dump("getRequestHeader(" + header + ") = " + val + "\n"); | ||
815 | return val; | ||
816 | } | ||
817 | catch (e) { | ||
818 | throw e; | ||
819 | } | ||
820 | }, | ||
821 | getResponseHeader: function(header) { | ||
822 | dump("getResponseHeader(" + header + ")\n"); | ||
823 | var httpChannel = this.mChannel.QueryInterface(nsIHttpChannel); | ||
824 | |||
825 | try { | ||
826 | var val = httpChannel.getResponseHeader(header); | ||
827 | dump("getResponseHeader(" + header + ") = " + val + "\n"); | ||
828 | return val; | ||
829 | } | ||
830 | catch (e) { | ||
831 | throw e; | ||
832 | } | ||
833 | /* XXX | ||
834 | if (header == "Content-Type") | ||
835 | return "text/html"; | ||
836 | */ | ||
837 | return null; | ||
838 | }, | ||
839 | isNoCacheResponse: function() { | ||
840 | dump("isNoCacheResponse()\n"); | ||
841 | var httpChannel = this.mChannel.QueryInterface(nsIHttpChannel); | ||
842 | return httpChannel.isNoCacheResponse(); | ||
843 | }, | ||
844 | isNoStoreResponse: function() { | ||
845 | dump("isNoStoreResponse()\n"); | ||
846 | var httpChannel = this.mChannel.QueryInterface(nsIHttpChannel); | ||
847 | return httpChannel.isNoStoreResponse(); | ||
848 | return true; | ||
849 | }, | ||
850 | setRequestHeader: function(header, value, merge) { | ||
851 | dump("setRequestHeader(" + header + ", " + value + ")\n"); | ||
852 | var httpChannel = this.mChannel.QueryInterface(nsIHttpChannel); | ||
853 | return httpChannel.setRequestHeader(header, value, merge); | ||
854 | }, | ||
855 | setResponseHeader: function(header, value, merge) { | ||
856 | dump("setResponseHeader(" + header + ", " + value + ")\n"); | ||
857 | var httpChannel = this.mChannel.QueryInterface(nsIHttpChannel); | ||
858 | return httpChannel.setResponseHeader(header, value, merge); | ||
859 | }, | ||
860 | visitRequestHeaders: function(visitor) { | ||
861 | dump("visitRequestHeaders()\n"); | ||
862 | }, | ||
863 | visitResponseHeaders: function(visitor) { | ||
864 | dump("visitResponseHeaders()\n"); | ||
865 | }, | ||
866 | |||
867 | QueryInterface: function(iid) | ||
868 | { | ||
869 | dump("QI.. \n"); | ||
870 | if (iid.equals(nsIChannel)) | ||
871 | dump("QI(nsIChannel)\n"); | ||
872 | else if (iid.equals(nsIHttpChannel)) | ||
873 | dump("QI(nsIHttpChannel)\n"); | ||
874 | else if (iid.equals(Components.interfaces.nsIUploadChannel)) | ||
875 | dump("QI(nsIUploadChannel) - not supported\n"); | ||
876 | else if (iid.equals(Components.interfaces.nsICachingChannel)) | ||
877 | dump("QI(nsICachingChannel) - not supported\n"); | ||
878 | else if (iid.equals(Components.interfaces.nsIClassInfo)) | ||
879 | dump("QI(nsIClassInfo) - not supported\n"); | ||
880 | else if (iid.equals(Components.interfaces.nsISecurityCheckedComponent)) | ||
881 | dump("QI(nsISecurityCheckedComponent) - not supported\n"); | ||
882 | else if (iid.equals(Components.interfaces.nsIWyciwygChannel)) | ||
883 | dump("QI(nsIWyciwygChannel) - not supported\n"); | ||
884 | else if (iid.equals(Components.interfaces.nsIMultiPartChannel)) | ||
885 | dump("QI(nsIMultiPartChannel) - not supported\n"); | ||
886 | else if (iid.equals(Components.interfaces.nsIHttpChannelInternal)) | ||
887 | dump("QI(nsIHttpChannelInternal) - not supported\n"); | ||
888 | else if (iid.equals(Components.interfaces.nsIWritablePropertyBag2)) | ||
889 | dump("QI(nsIWritablePropertyBag2) - not supported\n"); | ||
890 | else if (iid.equals(nsIRequest)) | ||
891 | dump("QI(nsIRequest)\n"); | ||
892 | else if (iid.equals(nsIRequestObserver)) | ||
893 | dump("QI(nsIRequestObserver)\n"); | ||
894 | else if (iid.equals(nsISupports)) | ||
895 | dump("QI(nsISupports)\n"); | ||
896 | else if (iid.equals(nsIStreamListener)) | ||
897 | dump("QI(nsIStreamListener)\n"); | ||
898 | else | ||
899 | dump("unknown " + iid + "\n"); | ||
900 | |||
901 | if (iid.equals(nsISupports) || | ||
902 | iid.equals(nsIRequest) || | ||
903 | iid.equals(nsIRequestObserver) || | ||
904 | iid.equals(nsIChannel) || | ||
905 | iid.equals(nsIHttpChannel) || | ||
906 | iid.equals(nsIStreamListener) | ||
907 | ) { | ||
908 | return this; | ||
909 | } | ||
910 | |||
911 | throw Components.results.NS_ERROR_NO_INTERFACE; | ||
912 | } | ||
913 | }; | ||
914 | |||
915 | |||
916 | |||
917 | /*********************************************************** | ||
918 | XriProtocolHandler class definition | ||
919 | ***********************************************************/ | ||
920 | |||
921 | //class constructor | ||
922 | function XriProtocolHandler() { | ||
923 | }; | ||
924 | |||
925 | // class definition | ||
926 | XriProtocolHandler.prototype = { | ||
927 | defaultPort: 80, // HTTP | ||
928 | |||
929 | protocolFlags : nsIProtocolHandler.ALLOWS_PROXY | nsIProtocolHandler.ALLOWS_PROXY_HTTP, | ||
930 | |||
931 | scheme: "xri", | ||
932 | |||
933 | allowPort: function() { | ||
934 | return false; // only called for blacklisted ports, should respect | ||
935 | }, | ||
936 | |||
937 | _newHttpChannel: function(aURI) | ||
938 | { | ||
939 | var HXRI = PROXY_URI + aURI.spec; | ||
940 | var ioService = Components.classesByID[kIOSERVICE_CID_STR].getService(); | ||
941 | ioService = ioService.QueryInterface(nsIIOService); | ||
942 | var channel = ioService.newChannel(HXRI, null, null); | ||
943 | return channel; | ||
944 | }, | ||
945 | |||
946 | newChannel: function(aURI) | ||
947 | { | ||
948 | // leave alone if path is not empty or just a single slash or query exists | ||
949 | |||
950 | dump("path='" + aURI.path + "'\n"); | ||
951 | dump("query='" + aURI.query + "'\n"); | ||
952 | dump("spec='" + aURI.spec + "'\n"); | ||
953 | |||
954 | var slashPos = aURI.spec.indexOf('/', 'xri://'.length); | ||
955 | var qmarkPos = aURI.spec.indexOf('?'); | ||
956 | dump("slashPos='" + slashPos + "'\n"); | ||
957 | dump("qmarkPos='" + qmarkPos + "'\n"); | ||
958 | if ((slashPos > 0 && slashPos < aURI.spec.length - 1) || qmarkPos > -1) { | ||
959 | return this._newHttpChannel(aURI); | ||
960 | } | ||
961 | |||
962 | var explorer = new XRIChannel(aURI); | ||
963 | return explorer; | ||
964 | }, | ||
965 | |||
966 | |||
967 | newURI: function(spec, originCharset, baseURI) | ||
968 | { | ||
969 | var newSpec = spec; | ||
970 | if (baseURI != null) { | ||
971 | // standard-url (nsIURL) does not work with @-GCS | ||
972 | var baseURL = Components.classes[CID_URL].createInstance(nsIURL); | ||
973 | baseURL.spec = baseURI.spec; | ||
974 | newSpec = baseURL.resolve(spec); | ||
975 | } | ||
976 | |||
977 | var uri = Components.classes[CID_URI].createInstance(nsIURI); | ||
978 | uri.spec = newSpec; | ||
979 | return uri; | ||
980 | }, | ||
981 | |||
982 | QueryInterface: function(aIID) | ||
983 | { | ||
984 | if (!aIID.equals(nsIProtocolHandler) && | ||
985 | !aIID.equals(nsISupports)) | ||
986 | throw Components.results.NS_ERROR_NO_INTERFACE; | ||
987 | return this; | ||
988 | } | ||
989 | }; | ||
990 | |||
991 | |||
992 | /*********************************************************** | ||
993 | class factory | ||
994 | |||
995 | This object is a member of the global-scope Components.classes. | ||
996 | It is keyed off of the contract ID. Eg: | ||
997 | |||
998 | myXriProtocolHandler = Components.classes["@dietrich.ganx4.com/helloworld;1"]. | ||
999 | createInstance(Components.interfaces.nsIXriProtocolHandler); | ||
1000 | |||
1001 | ***********************************************************/ | ||
1002 | var XriProtocolHandlerFactory = { | ||
1003 | createInstance: function (aOuter, aIID) | ||
1004 | { | ||
1005 | if (aOuter != null) | ||
1006 | throw Components.results.NS_ERROR_NO_AGGREGATION; | ||
1007 | return (new XriProtocolHandler()).QueryInterface(aIID); | ||
1008 | } | ||
1009 | }; | ||
1010 | |||
1011 | |||
1012 | /*********************************************************** | ||
1013 | module definition (xpcom registration) | ||
1014 | ***********************************************************/ | ||
1015 | var XriProtocolHandlerModule = { | ||
1016 | |||
1017 | _firstTime: true, | ||
1018 | |||
1019 | registerSelf: function(aCompMgr, aFileSpec, aLocation, aType) | ||
1020 | { | ||
1021 | if (this._firstTime) { | ||
1022 | this._firstTime = false; | ||
1023 | throw Components.results.NS_ERROR_FACTORY_REGISTER_AGAIN; | ||
1024 | } | ||
1025 | aCompMgr = aCompMgr. | ||
1026 | QueryInterface(Components.interfaces.nsIComponentRegistrar); | ||
1027 | aCompMgr.registerFactoryLocation(CLASS_ID, CLASS_NAME, | ||
1028 | CONTRACT_ID, aFileSpec, aLocation, aType); | ||
1029 | }, | ||
1030 | |||
1031 | unregisterSelf: function(aCompMgr, aLocation, aType) | ||
1032 | { | ||
1033 | aCompMgr = aCompMgr. | ||
1034 | QueryInterface(Components.interfaces.nsIComponentRegistrar); | ||
1035 | aCompMgr.unregisterFactoryLocation(CLASS_ID, aLocation); | ||
1036 | }, | ||
1037 | |||
1038 | getClassObject: function(aCompMgr, aCID, aIID) | ||
1039 | { | ||
1040 | if (!aIID.equals(Components.interfaces.nsIFactory)) | ||
1041 | throw Components.results.NS_ERROR_NOT_IMPLEMENTED; | ||
1042 | |||
1043 | if (aCID.equals(CLASS_ID)) | ||
1044 | return XriProtocolHandlerFactory; | ||
1045 | |||
1046 | throw Components.results.NS_ERROR_NO_INTERFACE; | ||
1047 | }, | ||
1048 | |||
1049 | canUnload: function(aCompMgr) { return true; } | ||
1050 | }; | ||
1051 | |||
1052 | |||
1053 | /*********************************************************** | ||
1054 | module initialization | ||
1055 | |||
1056 | When the application registers the component, this function | ||
1057 | is called. | ||
1058 | ***********************************************************/ | ||
1059 | function NSGetModule(aCompMgr, aFileSpec) { return XriProtocolHandlerModule; } | ||
1060 | |||