|
1 /* This Source Code Form is subject to the terms of the Mozilla Public |
|
2 * License, v. 2.0. If a copy of the MPL was not distributed with this file, |
|
3 * You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
4 |
|
5 "use strict"; |
|
6 |
|
7 const Cc = Components.classes; |
|
8 const Ci = Components.interfaces; |
|
9 const Cu = Components.utils; |
|
10 const Cr = Components.results; |
|
11 const CC = Components.Constructor; |
|
12 |
|
13 Cu.import("resource://gre/modules/XPCOMUtils.jsm"); |
|
14 Cu.import("resource://gre/modules/Services.jsm"); |
|
15 |
|
16 const InputStreamPump = CC( |
|
17 "@mozilla.org/network/input-stream-pump;1", "nsIInputStreamPump", "init"), |
|
18 AsyncStreamCopier = CC( |
|
19 "@mozilla.org/network/async-stream-copier;1", "nsIAsyncStreamCopier", "init"), |
|
20 ScriptableInputStream = CC( |
|
21 "@mozilla.org/scriptableinputstream;1", "nsIScriptableInputStream", "init"), |
|
22 BinaryInputStream = CC( |
|
23 "@mozilla.org/binaryinputstream;1", "nsIBinaryInputStream", "setInputStream"), |
|
24 StringInputStream = CC( |
|
25 '@mozilla.org/io/string-input-stream;1', 'nsIStringInputStream'), |
|
26 ArrayBufferInputStream = CC( |
|
27 '@mozilla.org/io/arraybuffer-input-stream;1', 'nsIArrayBufferInputStream'), |
|
28 MultiplexInputStream = CC( |
|
29 '@mozilla.org/io/multiplex-input-stream;1', 'nsIMultiplexInputStream'); |
|
30 const TCPServerSocket = CC( |
|
31 "@mozilla.org/tcp-server-socket;1", "nsITCPServerSocketInternal", "init"); |
|
32 |
|
33 const kCONNECTING = 'connecting'; |
|
34 const kOPEN = 'open'; |
|
35 const kCLOSING = 'closing'; |
|
36 const kCLOSED = 'closed'; |
|
37 const kRESUME_ERROR = 'Calling resume() on a connection that was not suspended.'; |
|
38 |
|
39 const BUFFER_SIZE = 65536; |
|
40 const NETWORK_STATS_THRESHOLD = 65536; |
|
41 |
|
42 // XXX we have no TCPError implementation right now because it's really hard to |
|
43 // do on b2g18. On mozilla-central we want a proper TCPError that ideally |
|
44 // sub-classes DOMError. Bug 867872 has been filed to implement this and |
|
45 // contains a documented TCPError.webidl that maps all the error codes we use in |
|
46 // this file to slightly more readable explanations. |
|
47 function createTCPError(aWindow, aErrorName, aErrorType) { |
|
48 return new (aWindow ? aWindow.DOMError : DOMError)(aErrorName); |
|
49 } |
|
50 |
|
51 |
|
52 /* |
|
53 * Debug logging function |
|
54 */ |
|
55 |
|
56 let debug = false; |
|
57 function LOG(msg) { |
|
58 if (debug) |
|
59 dump("TCPSocket: " + msg + "\n"); |
|
60 } |
|
61 |
|
62 /* |
|
63 * nsITCPSocketEvent object |
|
64 */ |
|
65 |
|
66 function TCPSocketEvent(type, sock, data) { |
|
67 this._type = type; |
|
68 this._target = sock; |
|
69 this._data = data; |
|
70 } |
|
71 |
|
72 TCPSocketEvent.prototype = { |
|
73 __exposedProps__: { |
|
74 type: 'r', |
|
75 target: 'r', |
|
76 data: 'r' |
|
77 }, |
|
78 get type() { |
|
79 return this._type; |
|
80 }, |
|
81 get target() { |
|
82 return this._target; |
|
83 }, |
|
84 get data() { |
|
85 return this._data; |
|
86 } |
|
87 } |
|
88 |
|
89 /* |
|
90 * nsIDOMTCPSocket object |
|
91 */ |
|
92 |
|
93 function TCPSocket() { |
|
94 this._readyState = kCLOSED; |
|
95 |
|
96 this._onopen = null; |
|
97 this._ondrain = null; |
|
98 this._ondata = null; |
|
99 this._onerror = null; |
|
100 this._onclose = null; |
|
101 |
|
102 this._binaryType = "string"; |
|
103 |
|
104 this._host = ""; |
|
105 this._port = 0; |
|
106 this._ssl = false; |
|
107 |
|
108 this.useWin = null; |
|
109 } |
|
110 |
|
111 TCPSocket.prototype = { |
|
112 __exposedProps__: { |
|
113 open: 'r', |
|
114 host: 'r', |
|
115 port: 'r', |
|
116 ssl: 'r', |
|
117 bufferedAmount: 'r', |
|
118 suspend: 'r', |
|
119 resume: 'r', |
|
120 close: 'r', |
|
121 send: 'r', |
|
122 readyState: 'r', |
|
123 binaryType: 'r', |
|
124 listen: 'r', |
|
125 onopen: 'rw', |
|
126 ondrain: 'rw', |
|
127 ondata: 'rw', |
|
128 onerror: 'rw', |
|
129 onclose: 'rw' |
|
130 }, |
|
131 // The binary type, "string" or "arraybuffer" |
|
132 _binaryType: null, |
|
133 |
|
134 // Internal |
|
135 _hasPrivileges: null, |
|
136 |
|
137 // Raw socket streams |
|
138 _transport: null, |
|
139 _socketInputStream: null, |
|
140 _socketOutputStream: null, |
|
141 |
|
142 // Input stream machinery |
|
143 _inputStreamPump: null, |
|
144 _inputStreamScriptable: null, |
|
145 _inputStreamBinary: null, |
|
146 |
|
147 // Output stream machinery |
|
148 _multiplexStream: null, |
|
149 _multiplexStreamCopier: null, |
|
150 |
|
151 _asyncCopierActive: false, |
|
152 _waitingForDrain: false, |
|
153 _suspendCount: 0, |
|
154 |
|
155 // Reported parent process buffer |
|
156 _bufferedAmount: 0, |
|
157 |
|
158 // IPC socket actor |
|
159 _socketBridge: null, |
|
160 |
|
161 // StartTLS |
|
162 _waitingForStartTLS: false, |
|
163 _pendingDataAfterStartTLS: [], |
|
164 |
|
165 // Used to notify when update bufferedAmount is updated. |
|
166 _onUpdateBufferedAmount: null, |
|
167 _trackingNumber: 0, |
|
168 |
|
169 #ifdef MOZ_WIDGET_GONK |
|
170 // Network statistics (Gonk-specific feature) |
|
171 _txBytes: 0, |
|
172 _rxBytes: 0, |
|
173 _appId: Ci.nsIScriptSecurityManager.NO_APP_ID, |
|
174 _activeNetwork: null, |
|
175 #endif |
|
176 |
|
177 // Public accessors. |
|
178 get readyState() { |
|
179 return this._readyState; |
|
180 }, |
|
181 get binaryType() { |
|
182 return this._binaryType; |
|
183 }, |
|
184 get host() { |
|
185 return this._host; |
|
186 }, |
|
187 get port() { |
|
188 return this._port; |
|
189 }, |
|
190 get ssl() { |
|
191 return this._ssl; |
|
192 }, |
|
193 get bufferedAmount() { |
|
194 if (this._inChild) { |
|
195 return this._bufferedAmount; |
|
196 } |
|
197 return this._multiplexStream.available(); |
|
198 }, |
|
199 get onopen() { |
|
200 return this._onopen; |
|
201 }, |
|
202 set onopen(f) { |
|
203 this._onopen = f; |
|
204 }, |
|
205 get ondrain() { |
|
206 return this._ondrain; |
|
207 }, |
|
208 set ondrain(f) { |
|
209 this._ondrain = f; |
|
210 }, |
|
211 get ondata() { |
|
212 return this._ondata; |
|
213 }, |
|
214 set ondata(f) { |
|
215 this._ondata = f; |
|
216 }, |
|
217 get onerror() { |
|
218 return this._onerror; |
|
219 }, |
|
220 set onerror(f) { |
|
221 this._onerror = f; |
|
222 }, |
|
223 get onclose() { |
|
224 return this._onclose; |
|
225 }, |
|
226 set onclose(f) { |
|
227 this._onclose = f; |
|
228 }, |
|
229 |
|
230 _activateTLS: function() { |
|
231 let securityInfo = this._transport.securityInfo |
|
232 .QueryInterface(Ci.nsISSLSocketControl); |
|
233 securityInfo.StartTLS(); |
|
234 }, |
|
235 |
|
236 // Helper methods. |
|
237 _createTransport: function ts_createTransport(host, port, sslMode) { |
|
238 let options; |
|
239 if (sslMode === 'ssl') { |
|
240 options = ['ssl']; |
|
241 } else { |
|
242 options = ['starttls']; |
|
243 } |
|
244 return Cc["@mozilla.org/network/socket-transport-service;1"] |
|
245 .getService(Ci.nsISocketTransportService) |
|
246 .createTransport(options, 1, host, port, null); |
|
247 }, |
|
248 |
|
249 _sendBufferedAmount: function ts_sendBufferedAmount() { |
|
250 if (this._onUpdateBufferedAmount) { |
|
251 this._onUpdateBufferedAmount(this.bufferedAmount, this._trackingNumber); |
|
252 } |
|
253 }, |
|
254 |
|
255 _ensureCopying: function ts_ensureCopying() { |
|
256 let self = this; |
|
257 if (this._asyncCopierActive) { |
|
258 return; |
|
259 } |
|
260 this._asyncCopierActive = true; |
|
261 this._multiplexStreamCopier.asyncCopy({ |
|
262 onStartRequest: function ts_output_onStartRequest() { |
|
263 }, |
|
264 onStopRequest: function ts_output_onStopRequest(request, context, status) { |
|
265 self._asyncCopierActive = false; |
|
266 self._multiplexStream.removeStream(0); |
|
267 self._sendBufferedAmount(); |
|
268 |
|
269 if (!Components.isSuccessCode(status)) { |
|
270 // Note that we can/will get an error here as well as in the |
|
271 // onStopRequest for inbound data. |
|
272 self._maybeReportErrorAndCloseIfOpen(status); |
|
273 return; |
|
274 } |
|
275 |
|
276 if (self._multiplexStream.count) { |
|
277 self._ensureCopying(); |
|
278 } else { |
|
279 // If we are waiting for initiating starttls, we can begin to |
|
280 // activate tls now. |
|
281 if (self._waitingForStartTLS && self._readyState == kOPEN) { |
|
282 self._activateTLS(); |
|
283 self._waitingForStartTLS = false; |
|
284 // If we have pending data, we should send them, or fire |
|
285 // a drain event if we are waiting for it. |
|
286 if (self._pendingDataAfterStartTLS.length > 0) { |
|
287 while (self._pendingDataAfterStartTLS.length) |
|
288 self._multiplexStream.appendStream(self._pendingDataAfterStartTLS.shift()); |
|
289 self._ensureCopying(); |
|
290 return; |
|
291 } |
|
292 } |
|
293 |
|
294 // If we have a callback to update bufferedAmount, we let child to |
|
295 // decide whether ondrain should be dispatched. |
|
296 if (self._waitingForDrain && !self._onUpdateBufferedAmount) { |
|
297 self._waitingForDrain = false; |
|
298 self.callListener("drain"); |
|
299 } |
|
300 if (self._readyState === kCLOSING) { |
|
301 self._socketOutputStream.close(); |
|
302 self._readyState = kCLOSED; |
|
303 self.callListener("close"); |
|
304 } |
|
305 } |
|
306 } |
|
307 }, null); |
|
308 }, |
|
309 |
|
310 _initStream: function ts_initStream(binaryType) { |
|
311 this._binaryType = binaryType; |
|
312 this._socketInputStream = this._transport.openInputStream(0, 0, 0); |
|
313 this._socketOutputStream = this._transport.openOutputStream( |
|
314 Ci.nsITransport.OPEN_UNBUFFERED, 0, 0); |
|
315 |
|
316 // If the other side is not listening, we will |
|
317 // get an onInputStreamReady callback where available |
|
318 // raises to indicate the connection was refused. |
|
319 this._socketInputStream.asyncWait( |
|
320 this, this._socketInputStream.WAIT_CLOSURE_ONLY, 0, Services.tm.currentThread); |
|
321 |
|
322 if (this._binaryType === "arraybuffer") { |
|
323 this._inputStreamBinary = new BinaryInputStream(this._socketInputStream); |
|
324 } else { |
|
325 this._inputStreamScriptable = new ScriptableInputStream(this._socketInputStream); |
|
326 } |
|
327 |
|
328 this._multiplexStream = new MultiplexInputStream(); |
|
329 |
|
330 this._multiplexStreamCopier = new AsyncStreamCopier( |
|
331 this._multiplexStream, |
|
332 this._socketOutputStream, |
|
333 // (nsSocketTransport uses gSocketTransportService) |
|
334 Cc["@mozilla.org/network/socket-transport-service;1"] |
|
335 .getService(Ci.nsIEventTarget), |
|
336 /* source buffered */ true, /* sink buffered */ false, |
|
337 BUFFER_SIZE, /* close source*/ false, /* close sink */ false); |
|
338 }, |
|
339 |
|
340 #ifdef MOZ_WIDGET_GONK |
|
341 // Helper method for collecting network statistics. |
|
342 // Note this method is Gonk-specific. |
|
343 _saveNetworkStats: function ts_saveNetworkStats(enforce) { |
|
344 if (this._txBytes <= 0 && this._rxBytes <= 0) { |
|
345 // There is no traffic at all. No need to save statistics. |
|
346 return; |
|
347 } |
|
348 |
|
349 // If "enforce" is false, the traffic amount is saved to NetworkStatsServiceProxy |
|
350 // only when the total amount exceeds the predefined threshold value. |
|
351 // The purpose is to avoid too much overhead for collecting statistics. |
|
352 let totalBytes = this._txBytes + this._rxBytes; |
|
353 if (!enforce && totalBytes < NETWORK_STATS_THRESHOLD) { |
|
354 return; |
|
355 } |
|
356 |
|
357 let nssProxy = Cc["@mozilla.org/networkstatsServiceProxy;1"] |
|
358 .getService(Ci.nsINetworkStatsServiceProxy); |
|
359 if (!nssProxy) { |
|
360 LOG("Error: Ci.nsINetworkStatsServiceProxy service is not available."); |
|
361 return; |
|
362 } |
|
363 nssProxy.saveAppStats(this._appId, this._activeNetwork, Date.now(), |
|
364 this._rxBytes, this._txBytes, false); |
|
365 |
|
366 // Reset the counters once the statistics is saved to NetworkStatsServiceProxy. |
|
367 this._txBytes = this._rxBytes = 0; |
|
368 }, |
|
369 // End of helper method for network statistics. |
|
370 #endif |
|
371 |
|
372 callListener: function ts_callListener(type, data) { |
|
373 if (!this["on" + type]) |
|
374 return; |
|
375 |
|
376 this["on" + type].call(null, new TCPSocketEvent(type, this, data || "")); |
|
377 }, |
|
378 |
|
379 /* nsITCPSocketInternal methods */ |
|
380 callListenerError: function ts_callListenerError(type, name) { |
|
381 // XXX we're not really using TCPError at this time, so there's only a name |
|
382 // attribute to pass. |
|
383 this.callListener(type, createTCPError(this.useWin, name)); |
|
384 }, |
|
385 |
|
386 callListenerData: function ts_callListenerString(type, data) { |
|
387 this.callListener(type, data); |
|
388 }, |
|
389 |
|
390 callListenerArrayBuffer: function ts_callListenerArrayBuffer(type, data) { |
|
391 this.callListener(type, data); |
|
392 }, |
|
393 |
|
394 callListenerVoid: function ts_callListenerVoid(type) { |
|
395 this.callListener(type); |
|
396 }, |
|
397 |
|
398 /** |
|
399 * This method is expected to be called by TCPSocketChild to update child's |
|
400 * readyState. |
|
401 */ |
|
402 updateReadyState: function ts_updateReadyState(readyState) { |
|
403 if (!this._inChild) { |
|
404 LOG("Calling updateReadyState in parent, which should only be called " + |
|
405 "in child"); |
|
406 return; |
|
407 } |
|
408 this._readyState = readyState; |
|
409 }, |
|
410 |
|
411 updateBufferedAmount: function ts_updateBufferedAmount(bufferedAmount, trackingNumber) { |
|
412 if (trackingNumber != this._trackingNumber) { |
|
413 LOG("updateBufferedAmount is called but trackingNumber is not matched " + |
|
414 "parent's trackingNumber: " + trackingNumber + ", child's trackingNumber: " + |
|
415 this._trackingNumber); |
|
416 return; |
|
417 } |
|
418 this._bufferedAmount = bufferedAmount; |
|
419 if (bufferedAmount == 0) { |
|
420 if (this._waitingForDrain) { |
|
421 this._waitingForDrain = false; |
|
422 this.callListener("drain"); |
|
423 } |
|
424 } else { |
|
425 LOG("bufferedAmount is updated but haven't reaches zero. bufferedAmount: " + |
|
426 bufferedAmount); |
|
427 } |
|
428 }, |
|
429 |
|
430 createAcceptedParent: function ts_createAcceptedParent(transport, binaryType) { |
|
431 let that = new TCPSocket(); |
|
432 that._transport = transport; |
|
433 that._initStream(binaryType); |
|
434 |
|
435 // ReadyState is kOpen since accepted transport stream has already been connected |
|
436 that._readyState = kOPEN; |
|
437 that._inputStreamPump = new InputStreamPump(that._socketInputStream, -1, -1, 0, 0, false); |
|
438 that._inputStreamPump.asyncRead(that, null); |
|
439 |
|
440 return that; |
|
441 }, |
|
442 |
|
443 createAcceptedChild: function ts_createAcceptedChild(socketChild, binaryType, windowObject) { |
|
444 let that = new TCPSocket(); |
|
445 |
|
446 that._binaryType = binaryType; |
|
447 that._inChild = true; |
|
448 that._readyState = kOPEN; |
|
449 socketChild.setSocketAndWindow(that, windowObject); |
|
450 that._socketBridge = socketChild; |
|
451 |
|
452 return that; |
|
453 }, |
|
454 |
|
455 setAppId: function ts_setAppId(appId) { |
|
456 #ifdef MOZ_WIDGET_GONK |
|
457 this._appId = appId; |
|
458 #else |
|
459 // Do nothing because _appId only exists on Gonk-specific platform. |
|
460 #endif |
|
461 }, |
|
462 |
|
463 setOnUpdateBufferedAmountHandler: function(aFunction) { |
|
464 if (typeof(aFunction) == 'function') { |
|
465 this._onUpdateBufferedAmount = aFunction; |
|
466 } else { |
|
467 throw new Error("only function can be passed to " + |
|
468 "setOnUpdateBufferedAmountHandler"); |
|
469 } |
|
470 }, |
|
471 |
|
472 /** |
|
473 * Handle the requst of sending data and update trackingNumber from |
|
474 * child. |
|
475 * This function is expected to be called by TCPSocketChild. |
|
476 */ |
|
477 onRecvSendFromChild: function(data, byteOffset, byteLength, trackingNumber) { |
|
478 this._trackingNumber = trackingNumber; |
|
479 this.send(data, byteOffset, byteLength); |
|
480 }, |
|
481 |
|
482 /* end nsITCPSocketInternal methods */ |
|
483 |
|
484 initWindowless: function ts_initWindowless() { |
|
485 try { |
|
486 return Services.prefs.getBoolPref("dom.mozTCPSocket.enabled"); |
|
487 } catch (e) { |
|
488 // no pref means return false |
|
489 return false; |
|
490 } |
|
491 }, |
|
492 |
|
493 init: function ts_init(aWindow) { |
|
494 if (!this.initWindowless()) |
|
495 return null; |
|
496 |
|
497 let principal = aWindow.document.nodePrincipal; |
|
498 let secMan = Cc["@mozilla.org/scriptsecuritymanager;1"] |
|
499 .getService(Ci.nsIScriptSecurityManager); |
|
500 |
|
501 let perm = principal == secMan.getSystemPrincipal() |
|
502 ? Ci.nsIPermissionManager.ALLOW_ACTION |
|
503 : Services.perms.testExactPermissionFromPrincipal(principal, "tcp-socket"); |
|
504 |
|
505 this._hasPrivileges = perm == Ci.nsIPermissionManager.ALLOW_ACTION; |
|
506 |
|
507 let util = aWindow.QueryInterface( |
|
508 Ci.nsIInterfaceRequestor |
|
509 ).getInterface(Ci.nsIDOMWindowUtils); |
|
510 |
|
511 this.useWin = XPCNativeWrapper.unwrap(aWindow); |
|
512 this.innerWindowID = util.currentInnerWindowID; |
|
513 LOG("window init: " + this.innerWindowID); |
|
514 }, |
|
515 |
|
516 observe: function(aSubject, aTopic, aData) { |
|
517 if (aTopic == "inner-window-destroyed") { |
|
518 let wId = aSubject.QueryInterface(Ci.nsISupportsPRUint64).data; |
|
519 if (wId == this.innerWindowID) { |
|
520 LOG("inner-window-destroyed: " + this.innerWindowID); |
|
521 |
|
522 // This window is now dead, so we want to clear the callbacks |
|
523 // so that we don't get a "can't access dead object" when the |
|
524 // underlying stream goes to tell us that we are closed |
|
525 this.onopen = null; |
|
526 this.ondrain = null; |
|
527 this.ondata = null; |
|
528 this.onerror = null; |
|
529 this.onclose = null; |
|
530 |
|
531 this.useWin = null; |
|
532 |
|
533 // Clean up our socket |
|
534 this.close(); |
|
535 } |
|
536 } |
|
537 }, |
|
538 |
|
539 // nsIDOMTCPSocket |
|
540 open: function ts_open(host, port, options) { |
|
541 this._inChild = Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULRuntime) |
|
542 .processType != Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT; |
|
543 LOG("content process: " + (this._inChild ? "true" : "false")); |
|
544 |
|
545 // in the testing case, init won't be called and |
|
546 // hasPrivileges will be null. We want to proceed to test. |
|
547 if (this._hasPrivileges !== true && this._hasPrivileges !== null) { |
|
548 throw new Error("TCPSocket does not have permission in this context.\n"); |
|
549 } |
|
550 let that = new TCPSocket(); |
|
551 |
|
552 that.useWin = this.useWin; |
|
553 that.innerWindowID = this.innerWindowID; |
|
554 that._inChild = this._inChild; |
|
555 |
|
556 LOG("window init: " + that.innerWindowID); |
|
557 Services.obs.addObserver(that, "inner-window-destroyed", true); |
|
558 |
|
559 LOG("startup called"); |
|
560 LOG("Host info: " + host + ":" + port); |
|
561 |
|
562 that._readyState = kCONNECTING; |
|
563 that._host = host; |
|
564 that._port = port; |
|
565 if (options !== undefined) { |
|
566 if (options.useSecureTransport) { |
|
567 that._ssl = 'ssl'; |
|
568 } else { |
|
569 that._ssl = false; |
|
570 } |
|
571 that._binaryType = options.binaryType || that._binaryType; |
|
572 } |
|
573 |
|
574 LOG("SSL: " + that.ssl); |
|
575 |
|
576 if (this._inChild) { |
|
577 that._socketBridge = Cc["@mozilla.org/tcp-socket-child;1"] |
|
578 .createInstance(Ci.nsITCPSocketChild); |
|
579 that._socketBridge.sendOpen(that, host, port, !!that._ssl, |
|
580 that._binaryType, this.useWin, this.useWin || this); |
|
581 return that; |
|
582 } |
|
583 |
|
584 let transport = that._transport = this._createTransport(host, port, that._ssl); |
|
585 transport.setEventSink(that, Services.tm.currentThread); |
|
586 that._initStream(that._binaryType); |
|
587 |
|
588 #ifdef MOZ_WIDGET_GONK |
|
589 // Set _activeNetwork, which is only required for network statistics. |
|
590 // Note that nsINetworkManager, as well as nsINetworkStatsServiceProxy, is |
|
591 // Gonk-specific. |
|
592 let networkManager = Cc["@mozilla.org/network/manager;1"].getService(Ci.nsINetworkManager); |
|
593 if (networkManager) { |
|
594 that._activeNetwork = networkManager.active; |
|
595 } |
|
596 #endif |
|
597 |
|
598 return that; |
|
599 }, |
|
600 |
|
601 upgradeToSecure: function ts_upgradeToSecure() { |
|
602 if (this._readyState !== kOPEN) { |
|
603 throw new Error("Socket not open."); |
|
604 } |
|
605 if (this._ssl == 'ssl') { |
|
606 // Already SSL |
|
607 return; |
|
608 } |
|
609 |
|
610 this._ssl = 'ssl'; |
|
611 |
|
612 if (this._inChild) { |
|
613 this._socketBridge.sendStartTLS(); |
|
614 return; |
|
615 } |
|
616 |
|
617 if (this._multiplexStream.count == 0) { |
|
618 this._activateTLS(); |
|
619 } else { |
|
620 this._waitingForStartTLS = true; |
|
621 } |
|
622 }, |
|
623 |
|
624 listen: function ts_listen(localPort, options, backlog) { |
|
625 // in the testing case, init won't be called and |
|
626 // hasPrivileges will be null. We want to proceed to test. |
|
627 if (this._hasPrivileges !== true && this._hasPrivileges !== null) { |
|
628 throw new Error("TCPSocket does not have permission in this context.\n"); |
|
629 } |
|
630 |
|
631 let that = new TCPServerSocket(this.useWin || this); |
|
632 |
|
633 options = options || { binaryType : this.binaryType }; |
|
634 backlog = backlog || -1; |
|
635 that.listen(localPort, options, backlog); |
|
636 return that; |
|
637 }, |
|
638 |
|
639 close: function ts_close() { |
|
640 if (this._readyState === kCLOSED || this._readyState === kCLOSING) |
|
641 return; |
|
642 |
|
643 LOG("close called"); |
|
644 this._readyState = kCLOSING; |
|
645 |
|
646 if (this._inChild) { |
|
647 this._socketBridge.sendClose(); |
|
648 return; |
|
649 } |
|
650 |
|
651 if (!this._multiplexStream.count) { |
|
652 this._socketOutputStream.close(); |
|
653 } |
|
654 this._socketInputStream.close(); |
|
655 }, |
|
656 |
|
657 send: function ts_send(data, byteOffset, byteLength) { |
|
658 if (this._readyState !== kOPEN) { |
|
659 throw new Error("Socket not open."); |
|
660 } |
|
661 |
|
662 if (this._binaryType === "arraybuffer") { |
|
663 byteLength = byteLength || data.byteLength; |
|
664 } |
|
665 |
|
666 if (this._inChild) { |
|
667 this._socketBridge.sendSend(data, byteOffset, byteLength, ++this._trackingNumber); |
|
668 } |
|
669 |
|
670 let length = this._binaryType === "arraybuffer" ? byteLength : data.length; |
|
671 let newBufferedAmount = this.bufferedAmount + length; |
|
672 let bufferFull = newBufferedAmount >= BUFFER_SIZE; |
|
673 |
|
674 if (bufferFull) { |
|
675 // If we buffered more than some arbitrary amount of data, |
|
676 // (65535 right now) we should tell the caller so they can |
|
677 // wait until ondrain is called if they so desire. Once all the |
|
678 // buffered data has been written to the socket, ondrain is |
|
679 // called. |
|
680 this._waitingForDrain = true; |
|
681 } |
|
682 |
|
683 if (this._inChild) { |
|
684 // In child, we just add buffer length to our bufferedAmount and let |
|
685 // parent to update our bufferedAmount when data have been sent. |
|
686 this._bufferedAmount = newBufferedAmount; |
|
687 return !bufferFull; |
|
688 } |
|
689 |
|
690 let new_stream; |
|
691 if (this._binaryType === "arraybuffer") { |
|
692 new_stream = new ArrayBufferInputStream(); |
|
693 new_stream.setData(data, byteOffset, byteLength); |
|
694 } else { |
|
695 new_stream = new StringInputStream(); |
|
696 new_stream.setData(data, length); |
|
697 } |
|
698 |
|
699 if (this._waitingForStartTLS) { |
|
700 // When we are waiting for starttls, new_stream is added to pendingData |
|
701 // and will be appended to multiplexStream after tls had been set up. |
|
702 this._pendingDataAfterStartTLS.push(new_stream); |
|
703 } else { |
|
704 this._multiplexStream.appendStream(new_stream); |
|
705 } |
|
706 |
|
707 this._ensureCopying(); |
|
708 |
|
709 #ifdef MOZ_WIDGET_GONK |
|
710 // Collect transmitted amount for network statistics. |
|
711 this._txBytes += length; |
|
712 this._saveNetworkStats(false); |
|
713 #endif |
|
714 |
|
715 return !bufferFull; |
|
716 }, |
|
717 |
|
718 suspend: function ts_suspend() { |
|
719 if (this._inChild) { |
|
720 this._socketBridge.sendSuspend(); |
|
721 return; |
|
722 } |
|
723 |
|
724 if (this._inputStreamPump) { |
|
725 this._inputStreamPump.suspend(); |
|
726 } else { |
|
727 ++this._suspendCount; |
|
728 } |
|
729 }, |
|
730 |
|
731 resume: function ts_resume() { |
|
732 if (this._inChild) { |
|
733 this._socketBridge.sendResume(); |
|
734 return; |
|
735 } |
|
736 |
|
737 if (this._inputStreamPump) { |
|
738 this._inputStreamPump.resume(); |
|
739 } else if (this._suspendCount < 1) { |
|
740 throw new Error(kRESUME_ERROR); |
|
741 } else { |
|
742 --this._suspendCount; |
|
743 } |
|
744 }, |
|
745 |
|
746 _maybeReportErrorAndCloseIfOpen: function(status) { |
|
747 #ifdef MOZ_WIDGET_GONK |
|
748 // Save network statistics once the connection is closed. |
|
749 // For now this function is Gonk-specific. |
|
750 this._saveNetworkStats(true); |
|
751 #endif |
|
752 |
|
753 // If we're closed, we've already reported the error or just don't need to |
|
754 // report the error. |
|
755 if (this._readyState === kCLOSED) |
|
756 return; |
|
757 this._readyState = kCLOSED; |
|
758 |
|
759 if (!Components.isSuccessCode(status)) { |
|
760 // Convert the status code to an appropriate error message. Raw constants |
|
761 // are used inline in all cases for consistency. Some error codes are |
|
762 // available in Components.results, some aren't. Network error codes are |
|
763 // effectively stable, NSS error codes are officially not, but we have no |
|
764 // symbolic way to dynamically resolve them anyways (other than an ability |
|
765 // to determine the error class.) |
|
766 let errName, errType; |
|
767 // security module? (and this is an error) |
|
768 if ((status & 0xff0000) === 0x5a0000) { |
|
769 const nsINSSErrorsService = Ci.nsINSSErrorsService; |
|
770 let nssErrorsService = Cc['@mozilla.org/nss_errors_service;1'] |
|
771 .getService(nsINSSErrorsService); |
|
772 let errorClass; |
|
773 // getErrorClass will throw a generic NS_ERROR_FAILURE if the error code is |
|
774 // somehow not in the set of covered errors. |
|
775 try { |
|
776 errorClass = nssErrorsService.getErrorClass(status); |
|
777 } |
|
778 catch (ex) { |
|
779 errorClass = 'SecurityProtocol'; |
|
780 } |
|
781 switch (errorClass) { |
|
782 case nsINSSErrorsService.ERROR_CLASS_SSL_PROTOCOL: |
|
783 errType = 'SecurityProtocol'; |
|
784 break; |
|
785 case nsINSSErrorsService.ERROR_CLASS_BAD_CERT: |
|
786 errType = 'SecurityCertificate'; |
|
787 break; |
|
788 // no default is required; the platform impl automatically defaults to |
|
789 // ERROR_CLASS_SSL_PROTOCOL. |
|
790 } |
|
791 |
|
792 // NSS_SEC errors (happen below the base value because of negative vals) |
|
793 if ((status & 0xffff) < |
|
794 Math.abs(nsINSSErrorsService.NSS_SEC_ERROR_BASE)) { |
|
795 // The bases are actually negative, so in our positive numeric space, we |
|
796 // need to subtract the base off our value. |
|
797 let nssErr = Math.abs(nsINSSErrorsService.NSS_SEC_ERROR_BASE) - |
|
798 (status & 0xffff); |
|
799 switch (nssErr) { |
|
800 case 11: // SEC_ERROR_EXPIRED_CERTIFICATE, sec(11) |
|
801 errName = 'SecurityExpiredCertificateError'; |
|
802 break; |
|
803 case 12: // SEC_ERROR_REVOKED_CERTIFICATE, sec(12) |
|
804 errName = 'SecurityRevokedCertificateError'; |
|
805 break; |
|
806 // per bsmith, we will be unable to tell these errors apart very soon, |
|
807 // so it makes sense to just folder them all together already. |
|
808 case 13: // SEC_ERROR_UNKNOWN_ISSUER, sec(13) |
|
809 case 20: // SEC_ERROR_UNTRUSTED_ISSUER, sec(20) |
|
810 case 21: // SEC_ERROR_UNTRUSTED_CERT, sec(21) |
|
811 case 36: // SEC_ERROR_CA_CERT_INVALID, sec(36) |
|
812 errName = 'SecurityUntrustedCertificateIssuerError'; |
|
813 break; |
|
814 case 90: // SEC_ERROR_INADEQUATE_KEY_USAGE, sec(90) |
|
815 errName = 'SecurityInadequateKeyUsageError'; |
|
816 break; |
|
817 case 176: // SEC_ERROR_CERT_SIGNATURE_ALGORITHM_DISABLED, sec(176) |
|
818 errName = 'SecurityCertificateSignatureAlgorithmDisabledError'; |
|
819 break; |
|
820 default: |
|
821 errName = 'SecurityError'; |
|
822 break; |
|
823 } |
|
824 } |
|
825 // NSS_SSL errors |
|
826 else { |
|
827 let sslErr = Math.abs(nsINSSErrorsService.NSS_SSL_ERROR_BASE) - |
|
828 (status & 0xffff); |
|
829 switch (sslErr) { |
|
830 case 3: // SSL_ERROR_NO_CERTIFICATE, ssl(3) |
|
831 errName = 'SecurityNoCertificateError'; |
|
832 break; |
|
833 case 4: // SSL_ERROR_BAD_CERTIFICATE, ssl(4) |
|
834 errName = 'SecurityBadCertificateError'; |
|
835 break; |
|
836 case 8: // SSL_ERROR_UNSUPPORTED_CERTIFICATE_TYPE, ssl(8) |
|
837 errName = 'SecurityUnsupportedCertificateTypeError'; |
|
838 break; |
|
839 case 9: // SSL_ERROR_UNSUPPORTED_VERSION, ssl(9) |
|
840 errName = 'SecurityUnsupportedTLSVersionError'; |
|
841 break; |
|
842 case 12: // SSL_ERROR_BAD_CERT_DOMAIN, ssl(12) |
|
843 errName = 'SecurityCertificateDomainMismatchError'; |
|
844 break; |
|
845 default: |
|
846 errName = 'SecurityError'; |
|
847 break; |
|
848 } |
|
849 } |
|
850 } |
|
851 // must be network |
|
852 else { |
|
853 errType = 'Network'; |
|
854 switch (status) { |
|
855 // connect to host:port failed |
|
856 case 0x804B000C: // NS_ERROR_CONNECTION_REFUSED, network(13) |
|
857 errName = 'ConnectionRefusedError'; |
|
858 break; |
|
859 // network timeout error |
|
860 case 0x804B000E: // NS_ERROR_NET_TIMEOUT, network(14) |
|
861 errName = 'NetworkTimeoutError'; |
|
862 break; |
|
863 // hostname lookup failed |
|
864 case 0x804B001E: // NS_ERROR_UNKNOWN_HOST, network(30) |
|
865 errName = 'DomainNotFoundError'; |
|
866 break; |
|
867 case 0x804B0047: // NS_ERROR_NET_INTERRUPT, network(71) |
|
868 errName = 'NetworkInterruptError'; |
|
869 break; |
|
870 default: |
|
871 errName = 'NetworkError'; |
|
872 break; |
|
873 } |
|
874 } |
|
875 let err = createTCPError(this.useWin, errName, errType); |
|
876 this.callListener("error", err); |
|
877 } |
|
878 this.callListener("close"); |
|
879 }, |
|
880 |
|
881 // nsITransportEventSink (Triggered by transport.setEventSink) |
|
882 onTransportStatus: function ts_onTransportStatus( |
|
883 transport, status, progress, max) { |
|
884 if (status === Ci.nsISocketTransport.STATUS_CONNECTED_TO) { |
|
885 this._readyState = kOPEN; |
|
886 this.callListener("open"); |
|
887 |
|
888 this._inputStreamPump = new InputStreamPump( |
|
889 this._socketInputStream, -1, -1, 0, 0, false |
|
890 ); |
|
891 |
|
892 while (this._suspendCount--) { |
|
893 this._inputStreamPump.suspend(); |
|
894 } |
|
895 |
|
896 this._inputStreamPump.asyncRead(this, null); |
|
897 } |
|
898 }, |
|
899 |
|
900 // nsIAsyncInputStream (Triggered by _socketInputStream.asyncWait) |
|
901 // Only used for detecting connection refused |
|
902 onInputStreamReady: function ts_onInputStreamReady(input) { |
|
903 try { |
|
904 input.available(); |
|
905 } catch (e) { |
|
906 // NS_ERROR_CONNECTION_REFUSED |
|
907 this._maybeReportErrorAndCloseIfOpen(0x804B000C); |
|
908 } |
|
909 }, |
|
910 |
|
911 // nsIRequestObserver (Triggered by _inputStreamPump.asyncRead) |
|
912 onStartRequest: function ts_onStartRequest(request, context) { |
|
913 }, |
|
914 |
|
915 // nsIRequestObserver (Triggered by _inputStreamPump.asyncRead) |
|
916 onStopRequest: function ts_onStopRequest(request, context, status) { |
|
917 let buffered_output = this._multiplexStream.count !== 0; |
|
918 |
|
919 this._inputStreamPump = null; |
|
920 |
|
921 let statusIsError = !Components.isSuccessCode(status); |
|
922 |
|
923 if (buffered_output && !statusIsError) { |
|
924 // If we have some buffered output still, and status is not an |
|
925 // error, the other side has done a half-close, but we don't |
|
926 // want to be in the close state until we are done sending |
|
927 // everything that was buffered. We also don't want to call onclose |
|
928 // yet. |
|
929 return; |
|
930 } |
|
931 |
|
932 // We call this even if there is no error. |
|
933 this._maybeReportErrorAndCloseIfOpen(status); |
|
934 }, |
|
935 |
|
936 // nsIStreamListener (Triggered by _inputStreamPump.asyncRead) |
|
937 onDataAvailable: function ts_onDataAvailable(request, context, inputStream, offset, count) { |
|
938 if (this._binaryType === "arraybuffer") { |
|
939 let buffer = new (this.useWin ? this.useWin.ArrayBuffer : ArrayBuffer)(count); |
|
940 this._inputStreamBinary.readArrayBuffer(count, buffer); |
|
941 this.callListener("data", buffer); |
|
942 } else { |
|
943 this.callListener("data", this._inputStreamScriptable.read(count)); |
|
944 } |
|
945 |
|
946 #ifdef MOZ_WIDGET_GONK |
|
947 // Collect received amount for network statistics. |
|
948 this._rxBytes += count; |
|
949 this._saveNetworkStats(false); |
|
950 #endif |
|
951 }, |
|
952 |
|
953 classID: Components.ID("{cda91b22-6472-11e1-aa11-834fec09cd0a}"), |
|
954 |
|
955 classInfo: XPCOMUtils.generateCI({ |
|
956 classID: Components.ID("{cda91b22-6472-11e1-aa11-834fec09cd0a}"), |
|
957 contractID: "@mozilla.org/tcp-socket;1", |
|
958 classDescription: "Client TCP Socket", |
|
959 interfaces: [ |
|
960 Ci.nsIDOMTCPSocket, |
|
961 ], |
|
962 flags: Ci.nsIClassInfo.DOM_OBJECT, |
|
963 }), |
|
964 |
|
965 QueryInterface: XPCOMUtils.generateQI([ |
|
966 Ci.nsIDOMTCPSocket, |
|
967 Ci.nsITCPSocketInternal, |
|
968 Ci.nsIDOMGlobalPropertyInitializer, |
|
969 Ci.nsIObserver, |
|
970 Ci.nsISupportsWeakReference |
|
971 ]) |
|
972 } |
|
973 |
|
974 this.NSGetFactory = XPCOMUtils.generateNSGetFactory([TCPSocket]); |