NetUtil.jsm 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497
  1. /* -*- indent-tabs-mode: nil; js-indent-level: 4 -*-
  2. * vim: sw=4 ts=4 sts=4 et filetype=javascript
  3. * This Source Code Form is subject to the terms of the Mozilla Public
  4. * License, v. 2.0. If a copy of the MPL was not distributed with this
  5. * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
  6. this.EXPORTED_SYMBOLS = [
  7. "NetUtil",
  8. ];
  9. /**
  10. * Necko utilities
  11. */
  12. ////////////////////////////////////////////////////////////////////////////////
  13. //// Constants
  14. const Ci = Components.interfaces;
  15. const Cc = Components.classes;
  16. const Cr = Components.results;
  17. const Cu = Components.utils;
  18. const PR_UINT32_MAX = 0xffffffff;
  19. Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
  20. Components.utils.import("resource://gre/modules/Services.jsm");
  21. ////////////////////////////////////////////////////////////////////////////////
  22. //// NetUtil Object
  23. this.NetUtil = {
  24. /**
  25. * Function to perform simple async copying from aSource (an input stream)
  26. * to aSink (an output stream). The copy will happen on some background
  27. * thread. Both streams will be closed when the copy completes.
  28. *
  29. * @param aSource
  30. * The input stream to read from
  31. * @param aSink
  32. * The output stream to write to
  33. * @param aCallback [optional]
  34. * A function that will be called at copy completion with a single
  35. * argument: the nsresult status code for the copy operation.
  36. *
  37. * @return An nsIRequest representing the copy operation (for example, this
  38. * can be used to cancel the copying). The consumer can ignore the
  39. * return value if desired.
  40. */
  41. asyncCopy: function NetUtil_asyncCopy(aSource, aSink,
  42. aCallback = null)
  43. {
  44. if (!aSource || !aSink) {
  45. let exception = new Components.Exception(
  46. "Must have a source and a sink",
  47. Cr.NS_ERROR_INVALID_ARG,
  48. Components.stack.caller
  49. );
  50. throw exception;
  51. }
  52. // make a stream copier
  53. var copier = Cc["@mozilla.org/network/async-stream-copier;1"].
  54. createInstance(Ci.nsIAsyncStreamCopier2);
  55. copier.init(aSource, aSink,
  56. null /* Default event target */,
  57. 0 /* Default length */,
  58. true, true /* Auto-close */);
  59. var observer;
  60. if (aCallback) {
  61. observer = {
  62. onStartRequest: function(aRequest, aContext) {},
  63. onStopRequest: function(aRequest, aContext, aStatusCode) {
  64. aCallback(aStatusCode);
  65. }
  66. }
  67. } else {
  68. observer = null;
  69. }
  70. // start the copying
  71. copier.QueryInterface(Ci.nsIAsyncStreamCopier).asyncCopy(observer, null);
  72. return copier;
  73. },
  74. /**
  75. * Asynchronously opens a source and fetches the response. While the fetch
  76. * is asynchronous, I/O may happen on the main thread. When reading from
  77. * a local file, prefer using "OS.File" methods instead.
  78. *
  79. * @param aSource
  80. * This argument can be one of the following:
  81. * - An options object that will be passed to NetUtil.newChannel.
  82. * - An existing nsIChannel.
  83. * - An existing nsIInputStream.
  84. * Using an nsIURI, nsIFile, or string spec directly is deprecated.
  85. * @param aCallback
  86. * The callback function that will be notified upon completion. It
  87. * will get these arguments:
  88. * 1) An nsIInputStream containing the data from aSource, if any.
  89. * 2) The status code from opening the source.
  90. * 3) Reference to the nsIRequest.
  91. */
  92. asyncFetch: function NetUtil_asyncFetch(aSource, aCallback)
  93. {
  94. if (!aSource || !aCallback) {
  95. let exception = new Components.Exception(
  96. "Must have a source and a callback",
  97. Cr.NS_ERROR_INVALID_ARG,
  98. Components.stack.caller
  99. );
  100. throw exception;
  101. }
  102. // Create a pipe that will create our output stream that we can use once
  103. // we have gotten all the data.
  104. let pipe = Cc["@mozilla.org/pipe;1"].
  105. createInstance(Ci.nsIPipe);
  106. pipe.init(true, true, 0, PR_UINT32_MAX, null);
  107. // Create a listener that will give data to the pipe's output stream.
  108. let listener = Cc["@mozilla.org/network/simple-stream-listener;1"].
  109. createInstance(Ci.nsISimpleStreamListener);
  110. listener.init(pipe.outputStream, {
  111. onStartRequest: function(aRequest, aContext) {},
  112. onStopRequest: function(aRequest, aContext, aStatusCode) {
  113. pipe.outputStream.close();
  114. aCallback(pipe.inputStream, aStatusCode, aRequest);
  115. }
  116. });
  117. // Input streams are handled slightly differently from everything else.
  118. if (aSource instanceof Ci.nsIInputStream) {
  119. let pump = Cc["@mozilla.org/network/input-stream-pump;1"].
  120. createInstance(Ci.nsIInputStreamPump);
  121. pump.init(aSource, -1, -1, 0, 0, true);
  122. pump.asyncRead(listener, null);
  123. return;
  124. }
  125. let channel = aSource;
  126. if (!(channel instanceof Ci.nsIChannel)) {
  127. channel = this.newChannel(aSource);
  128. }
  129. try {
  130. // Open the channel using asyncOpen2() if the loadinfo contains one
  131. // of the security mode flags, otherwise fall back to use asyncOpen().
  132. if (channel.loadInfo &&
  133. channel.loadInfo.securityMode != 0) {
  134. channel.asyncOpen2(listener);
  135. }
  136. else {
  137. // Log deprecation warning to console to make sure all channels
  138. // are created providing the correct security flags in the loadinfo.
  139. // See nsILoadInfo for all available security flags and also the API
  140. // of NetUtil.newChannel() for details above.
  141. Cu.reportError("NetUtil.jsm: asyncFetch() requires the channel to have " +
  142. "one of the security flags set in the loadinfo (see nsILoadInfo). " +
  143. "Please create channel using NetUtil.newChannel()");
  144. channel.asyncOpen(listener, null);
  145. }
  146. }
  147. catch (e) {
  148. let exception = new Components.Exception(
  149. "Failed to open input source '" + channel.originalURI.spec + "'",
  150. e.result,
  151. Components.stack.caller,
  152. aSource,
  153. e
  154. );
  155. throw exception;
  156. }
  157. },
  158. /**
  159. * Constructs a new URI for the given spec, character set, and base URI, or
  160. * an nsIFile.
  161. *
  162. * @param aTarget
  163. * The string spec for the desired URI or an nsIFile.
  164. * @param aOriginCharset [optional]
  165. * The character set for the URI. Only used if aTarget is not an
  166. * nsIFile.
  167. * @param aBaseURI [optional]
  168. * The base URI for the spec. Only used if aTarget is not an
  169. * nsIFile.
  170. *
  171. * @return an nsIURI object.
  172. */
  173. newURI: function NetUtil_newURI(aTarget, aOriginCharset, aBaseURI)
  174. {
  175. if (!aTarget) {
  176. let exception = new Components.Exception(
  177. "Must have a non-null string spec or nsIFile object",
  178. Cr.NS_ERROR_INVALID_ARG,
  179. Components.stack.caller
  180. );
  181. throw exception;
  182. }
  183. if (aTarget instanceof Ci.nsIFile) {
  184. return this.ioService.newFileURI(aTarget);
  185. }
  186. return this.ioService.newURI(aTarget, aOriginCharset, aBaseURI);
  187. },
  188. /**
  189. * Constructs a new channel for the given source.
  190. *
  191. * Keep in mind that URIs coming from a webpage should *never* use the
  192. * systemPrincipal as the loadingPrincipal.
  193. *
  194. * @param aWhatToLoad
  195. * This argument used to be a string spec for the desired URI, an
  196. * nsIURI, or an nsIFile. Now it should be an options object with
  197. * the following properties:
  198. * {
  199. * uri:
  200. * The full URI spec string, nsIURI or nsIFile to create the
  201. * channel for.
  202. * Note that this cannot be an nsIFile if you have to specify a
  203. * non-default charset or base URI. Call NetUtil.newURI first if
  204. * you need to construct an URI using those options.
  205. * loadingNode:
  206. * loadingPrincipal:
  207. * triggeringPrincipal:
  208. * securityFlags:
  209. * contentPolicyType:
  210. * These will be used as values for the nsILoadInfo object on the
  211. * created channel. For details, see nsILoadInfo in nsILoadInfo.idl
  212. * loadUsingSystemPrincipal:
  213. * Set this to true to use the system principal as
  214. * loadingPrincipal. This must be omitted if loadingPrincipal or
  215. * loadingNode are present.
  216. * This should be used with care as it skips security checks.
  217. * }
  218. * @param aOriginCharset [deprecated]
  219. * The character set for the URI. Only used if aWhatToLoad is a
  220. * string, which is a deprecated API. Must be undefined otherwise.
  221. * Use NetUtil.newURI if you need to use this option.
  222. * @param aBaseURI [deprecated]
  223. * The base URI for the spec. Only used if aWhatToLoad is a string,
  224. * which is a deprecated API. Must be undefined otherwise. Use
  225. * NetUtil.newURI if you need to use this option.
  226. * @return an nsIChannel object.
  227. */
  228. newChannel: function NetUtil_newChannel(aWhatToLoad, aOriginCharset, aBaseURI)
  229. {
  230. // Check for the deprecated API first.
  231. if (typeof aWhatToLoad == "string" ||
  232. (aWhatToLoad instanceof Ci.nsIFile) ||
  233. (aWhatToLoad instanceof Ci.nsIURI)) {
  234. let uri = (aWhatToLoad instanceof Ci.nsIURI)
  235. ? aWhatToLoad
  236. : this.newURI(aWhatToLoad, aOriginCharset, aBaseURI);
  237. // log deprecation warning for developers.
  238. Services.console.logStringMessage(
  239. "Warning: NetUtil.newChannel(uri) deprecated, please provide argument 'aWhatToLoad'");
  240. // Provide default loadinfo arguments and call the new API.
  241. let systemPrincipal =
  242. Services.scriptSecurityManager.getSystemPrincipal();
  243. return this.ioService.newChannelFromURI2(
  244. uri,
  245. null, // loadingNode
  246. systemPrincipal, // loadingPrincipal
  247. null, // triggeringPrincipal
  248. Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL,
  249. Ci.nsIContentPolicy.TYPE_OTHER);
  250. }
  251. // We are using the updated API, that requires only the options object.
  252. if (typeof aWhatToLoad != "object" ||
  253. aOriginCharset !== undefined ||
  254. aBaseURI !== undefined) {
  255. throw new Components.Exception(
  256. "newChannel requires a single object argument",
  257. Cr.NS_ERROR_INVALID_ARG,
  258. Components.stack.caller
  259. );
  260. }
  261. let { uri,
  262. loadingNode,
  263. loadingPrincipal,
  264. loadUsingSystemPrincipal,
  265. triggeringPrincipal,
  266. securityFlags,
  267. contentPolicyType } = aWhatToLoad;
  268. if (!uri) {
  269. throw new Components.Exception(
  270. "newChannel requires the 'uri' property on the options object.",
  271. Cr.NS_ERROR_INVALID_ARG,
  272. Components.stack.caller
  273. );
  274. }
  275. if (typeof uri == "string" || uri instanceof Ci.nsIFile) {
  276. uri = this.newURI(uri);
  277. }
  278. if (!loadingNode && !loadingPrincipal && !loadUsingSystemPrincipal) {
  279. throw new Components.Exception(
  280. "newChannel requires at least one of the 'loadingNode'," +
  281. " 'loadingPrincipal', or 'loadUsingSystemPrincipal'" +
  282. " properties on the options object.",
  283. Cr.NS_ERROR_INVALID_ARG,
  284. Components.stack.caller
  285. );
  286. }
  287. if (loadUsingSystemPrincipal === true) {
  288. if (loadingNode || loadingPrincipal) {
  289. throw new Components.Exception(
  290. "newChannel does not accept 'loadUsingSystemPrincipal'" +
  291. " if the 'loadingNode' or 'loadingPrincipal' properties" +
  292. " are present on the options object.",
  293. Cr.NS_ERROR_INVALID_ARG,
  294. Components.stack.caller
  295. );
  296. }
  297. loadingPrincipal = Services.scriptSecurityManager
  298. .getSystemPrincipal();
  299. } else if (loadUsingSystemPrincipal !== undefined) {
  300. throw new Components.Exception(
  301. "newChannel requires the 'loadUsingSystemPrincipal'" +
  302. " property on the options object to be 'true' or 'undefined'.",
  303. Cr.NS_ERROR_INVALID_ARG,
  304. Components.stack.caller
  305. );
  306. }
  307. if (securityFlags === undefined) {
  308. securityFlags = loadUsingSystemPrincipal
  309. ? Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL
  310. : Ci.nsILoadInfo.SEC_NORMAL;
  311. }
  312. if (contentPolicyType === undefined) {
  313. if (!loadUsingSystemPrincipal) {
  314. throw new Components.Exception(
  315. "newChannel requires the 'contentPolicyType' property on" +
  316. " the options object unless loading from system principal.",
  317. Cr.NS_ERROR_INVALID_ARG,
  318. Components.stack.caller
  319. );
  320. }
  321. contentPolicyType = Ci.nsIContentPolicy.TYPE_OTHER;
  322. }
  323. return this.ioService.newChannelFromURI2(uri,
  324. loadingNode || null,
  325. loadingPrincipal || null,
  326. triggeringPrincipal || null,
  327. securityFlags,
  328. contentPolicyType);
  329. },
  330. /**
  331. * @deprecated Use newChannel({ ...options... }) instead.
  332. */
  333. newChannel2: function NetUtil_newChannel2(aWhatToLoad,
  334. aOriginCharset,
  335. aBaseURI,
  336. aLoadingNode,
  337. aLoadingPrincipal,
  338. aTriggeringPrincipal,
  339. aSecurityFlags,
  340. aContentPolicyType)
  341. {
  342. if (!aWhatToLoad) {
  343. let exception = new Components.Exception(
  344. "Must have a non-null string spec, nsIURI, or nsIFile object",
  345. Cr.NS_ERROR_INVALID_ARG,
  346. Components.stack.caller
  347. );
  348. throw exception;
  349. }
  350. let uri = aWhatToLoad;
  351. if (!(aWhatToLoad instanceof Ci.nsIURI)) {
  352. // We either have a string or an nsIFile that we'll need a URI for.
  353. uri = this.newURI(aWhatToLoad, aOriginCharset, aBaseURI);
  354. }
  355. return this.ioService.newChannelFromURI2(uri,
  356. aLoadingNode,
  357. aLoadingPrincipal,
  358. aTriggeringPrincipal,
  359. aSecurityFlags,
  360. aContentPolicyType);
  361. },
  362. /**
  363. * Reads aCount bytes from aInputStream into a string.
  364. *
  365. * @param aInputStream
  366. * The input stream to read from.
  367. * @param aCount
  368. * The number of bytes to read from the stream.
  369. * @param aOptions [optional]
  370. * charset
  371. * The character encoding of stream data.
  372. * replacement
  373. * The character to replace unknown byte sequences.
  374. * If unset, it causes an exceptions to be thrown.
  375. *
  376. * @return the bytes from the input stream in string form.
  377. *
  378. * @throws NS_ERROR_INVALID_ARG if aInputStream is not an nsIInputStream.
  379. * @throws NS_BASE_STREAM_WOULD_BLOCK if reading from aInputStream would
  380. * block the calling thread (non-blocking mode only).
  381. * @throws NS_ERROR_FAILURE if there are not enough bytes available to read
  382. * aCount amount of data.
  383. * @throws NS_ERROR_ILLEGAL_INPUT if aInputStream has invalid sequences
  384. */
  385. readInputStreamToString: function NetUtil_readInputStreamToString(aInputStream,
  386. aCount,
  387. aOptions)
  388. {
  389. if (!(aInputStream instanceof Ci.nsIInputStream)) {
  390. let exception = new Components.Exception(
  391. "First argument should be an nsIInputStream",
  392. Cr.NS_ERROR_INVALID_ARG,
  393. Components.stack.caller
  394. );
  395. throw exception;
  396. }
  397. if (!aCount) {
  398. let exception = new Components.Exception(
  399. "Non-zero amount of bytes must be specified",
  400. Cr.NS_ERROR_INVALID_ARG,
  401. Components.stack.caller
  402. );
  403. throw exception;
  404. }
  405. if (aOptions && "charset" in aOptions) {
  406. let cis = Cc["@mozilla.org/intl/converter-input-stream;1"].
  407. createInstance(Ci.nsIConverterInputStream);
  408. try {
  409. // When replacement is set, the character that is unknown sequence
  410. // replaces with aOptions.replacement character.
  411. if (!("replacement" in aOptions)) {
  412. // aOptions.replacement isn't set.
  413. // If input stream has unknown sequences for aOptions.charset,
  414. // throw NS_ERROR_ILLEGAL_INPUT.
  415. aOptions.replacement = 0;
  416. }
  417. cis.init(aInputStream, aOptions.charset, aCount,
  418. aOptions.replacement);
  419. let str = {};
  420. cis.readString(-1, str);
  421. cis.close();
  422. return str.value;
  423. }
  424. catch (e) {
  425. // Adjust the stack so it throws at the caller's location.
  426. throw new Components.Exception(e.message, e.result,
  427. Components.stack.caller, e.data);
  428. }
  429. }
  430. let sis = Cc["@mozilla.org/scriptableinputstream;1"].
  431. createInstance(Ci.nsIScriptableInputStream);
  432. sis.init(aInputStream);
  433. try {
  434. return sis.readBytes(aCount);
  435. }
  436. catch (e) {
  437. // Adjust the stack so it throws at the caller's location.
  438. throw new Components.Exception(e.message, e.result,
  439. Components.stack.caller, e.data);
  440. }
  441. },
  442. /**
  443. * Returns a reference to nsIIOService.
  444. *
  445. * @return a reference to nsIIOService.
  446. */
  447. get ioService()
  448. {
  449. delete this.ioService;
  450. return this.ioService = Cc["@mozilla.org/network/io-service;1"].
  451. getService(Ci.nsIIOService);
  452. },
  453. };