Http.hx 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497
  1. /*
  2. * Copyright (C)2005-2019 Haxe Foundation
  3. *
  4. * Permission is hereby granted, free of charge, to any person obtaining a
  5. * copy of this software and associated documentation files (the "Software"),
  6. * to deal in the Software without restriction, including without limitation
  7. * the rights to use, copy, modify, merge, publish, distribute, sublicense,
  8. * and/or sell copies of the Software, and to permit persons to whom the
  9. * Software is furnished to do so, subject to the following conditions:
  10. *
  11. * The above copyright notice and this permission notice shall be included in
  12. * all copies or substantial portions of the Software.
  13. *
  14. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  15. * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  16. * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  17. * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  18. * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
  19. * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
  20. * DEALINGS IN THE SOFTWARE.
  21. */
  22. package sys;
  23. import sys.net.Host;
  24. import sys.net.Socket;
  25. class Http extends haxe.http.HttpBase {
  26. public var noShutdown:Bool;
  27. public var cnxTimeout:Float;
  28. public var responseHeaders:Map<String, String>;
  29. var chunk_size:Null<Int>;
  30. var chunk_buf:haxe.io.Bytes;
  31. var file:{
  32. param:String,
  33. filename:String,
  34. io:haxe.io.Input,
  35. size:Int,
  36. mimeType:String
  37. };
  38. public static var PROXY:{host:String, port:Int, auth:{user:String, pass:String}} = null;
  39. public function new(url:String) {
  40. cnxTimeout = 10;
  41. #if php
  42. noShutdown = !php.Global.function_exists('stream_socket_shutdown');
  43. #end
  44. super(url);
  45. }
  46. public override function request(?post:Bool) {
  47. var output = new haxe.io.BytesOutput();
  48. var old = onError;
  49. var err = false;
  50. onError = function(e) {
  51. #if neko
  52. responseData = neko.Lib.stringReference(output.getBytes());
  53. #else
  54. responseData = output.getBytes().toString();
  55. #end
  56. err = true;
  57. // Resetting back onError before calling it allows for a second "retry" request to be sent without onError being wrapped twice
  58. onError = old;
  59. onError(e);
  60. }
  61. customRequest(post, output);
  62. if (!err)
  63. #if neko
  64. onData(responseData = neko.Lib.stringReference(output.getBytes()));
  65. #else
  66. onData(responseData = output.getBytes().toString());
  67. #end
  68. }
  69. @:noCompletion
  70. @:deprecated("Use fileTransfer instead")
  71. inline public function fileTransfert(argname:String, filename:String, file:haxe.io.Input, size:Int, mimeType = "application/octet-stream") {
  72. fileTransfer(argname, filename, file, size, mimeType);
  73. }
  74. public function fileTransfer(argname:String, filename:String, file:haxe.io.Input, size:Int, mimeType = "application/octet-stream") {
  75. this.file = {
  76. param: argname,
  77. filename: filename,
  78. io: file,
  79. size: size,
  80. mimeType: mimeType
  81. };
  82. }
  83. public function customRequest(post:Bool, api:haxe.io.Output, ?sock:sys.net.Socket, ?method:String) {
  84. this.responseData = null;
  85. var url_regexp = ~/^(https?:\/\/)?([a-zA-Z\.0-9_-]+)(:[0-9]+)?(.*)$/;
  86. if (!url_regexp.match(url)) {
  87. onError("Invalid URL");
  88. return;
  89. }
  90. var secure = (url_regexp.matched(1) == "https://");
  91. if (sock == null) {
  92. if (secure) {
  93. #if php
  94. sock = new php.net.SslSocket();
  95. #elseif java
  96. sock = new java.net.SslSocket();
  97. #elseif python
  98. sock = new python.net.SslSocket();
  99. #elseif (!no_ssl && (hxssl || hl || cpp || (neko && !(macro || interp))))
  100. sock = new sys.ssl.Socket();
  101. #else
  102. throw "Https is only supported with -lib hxssl";
  103. #end
  104. } else {
  105. #if php
  106. sock = new php.net.Socket();
  107. #else
  108. sock = new Socket();
  109. #end
  110. }
  111. }
  112. var host = url_regexp.matched(2);
  113. var portString = url_regexp.matched(3);
  114. var request = url_regexp.matched(4);
  115. // ensure path begins with a forward slash
  116. // this is required by original URL specifications and many servers have issues if it's not supplied
  117. // see https://stackoverflow.com/questions/1617058/ok-to-skip-slash-before-query-string
  118. if (request.charAt(0) != "/") {
  119. request = "/" + request;
  120. }
  121. var port = if (portString == null || portString == "") secure ? 443 : 80 else Std.parseInt(portString.substr(1, portString.length - 1));
  122. var multipart = (file != null);
  123. var boundary = null;
  124. var uri = null;
  125. if (multipart) {
  126. post = true;
  127. boundary = Std.string(Std.random(1000))
  128. + Std.string(Std.random(1000))
  129. + Std.string(Std.random(1000))
  130. + Std.string(Std.random(1000));
  131. while (boundary.length < 38)
  132. boundary = "-" + boundary;
  133. var b = new StringBuf();
  134. for (p in params) {
  135. b.add("--");
  136. b.add(boundary);
  137. b.add("\r\n");
  138. b.add('Content-Disposition: form-data; name="');
  139. b.add(p.name);
  140. b.add('"');
  141. b.add("\r\n");
  142. b.add("\r\n");
  143. b.add(p.value);
  144. b.add("\r\n");
  145. }
  146. b.add("--");
  147. b.add(boundary);
  148. b.add("\r\n");
  149. b.add('Content-Disposition: form-data; name="');
  150. b.add(file.param);
  151. b.add('"; filename="');
  152. b.add(file.filename);
  153. b.add('"');
  154. b.add("\r\n");
  155. b.add("Content-Type: " + file.mimeType + "\r\n" + "\r\n");
  156. uri = b.toString();
  157. } else {
  158. for (p in params) {
  159. if (uri == null)
  160. uri = "";
  161. else
  162. uri += "&";
  163. uri += StringTools.urlEncode(p.name) + "=" + StringTools.urlEncode('${p.value}');
  164. }
  165. }
  166. var b = new StringBuf();
  167. if (method != null) {
  168. b.add(method);
  169. b.add(" ");
  170. } else if (post)
  171. b.add("POST ");
  172. else
  173. b.add("GET ");
  174. if (Http.PROXY != null) {
  175. b.add("http://");
  176. b.add(host);
  177. if (port != 80) {
  178. b.add(":");
  179. b.add(port);
  180. }
  181. }
  182. b.add(request);
  183. if (!post && uri != null) {
  184. if (request.indexOf("?", 0) >= 0)
  185. b.add("&");
  186. else
  187. b.add("?");
  188. b.add(uri);
  189. }
  190. b.add(" HTTP/1.1\r\nHost: " + host + "\r\n");
  191. if (postData != null)
  192. b.add("Content-Length: " + postData.length + "\r\n");
  193. else if (post && uri != null) {
  194. if (multipart || !Lambda.exists(headers, function(h) return h.name == "Content-Type")) {
  195. b.add("Content-Type: ");
  196. if (multipart) {
  197. b.add("multipart/form-data");
  198. b.add("; boundary=");
  199. b.add(boundary);
  200. } else
  201. b.add("application/x-www-form-urlencoded");
  202. b.add("\r\n");
  203. }
  204. if (multipart)
  205. b.add("Content-Length: " + (uri.length + file.size + boundary.length + 6) + "\r\n");
  206. else
  207. b.add("Content-Length: " + uri.length + "\r\n");
  208. }
  209. b.add("Connection: close\r\n");
  210. for (h in headers) {
  211. b.add(h.name);
  212. b.add(": ");
  213. b.add(h.value);
  214. b.add("\r\n");
  215. }
  216. b.add("\r\n");
  217. if (postData != null)
  218. b.add(postData);
  219. else if (post && uri != null)
  220. b.add(uri);
  221. try {
  222. if (Http.PROXY != null)
  223. sock.connect(new Host(Http.PROXY.host), Http.PROXY.port);
  224. else
  225. sock.connect(new Host(host), port);
  226. if (multipart)
  227. writeBody(b.toString(),file.io,file.size,boundary,sock);
  228. else
  229. writeBody(b.toString(),null,0,null,sock);
  230. readHttpResponse(api, sock);
  231. sock.close();
  232. } catch (e:Dynamic) {
  233. try
  234. sock.close()
  235. catch (e:Dynamic) {};
  236. onError(Std.string(e));
  237. }
  238. }
  239. function writeBody(body:String, fileInput:haxe.io.Input, fileSize:Int, boundary:String, sock:sys.net.Socket) {
  240. if (body.length>0 && body!=null)
  241. sock.write(body);
  242. if (boundary!=null) {
  243. var bufsize = 4096;
  244. var buf = haxe.io.Bytes.alloc(bufsize);
  245. while (fileSize > 0) {
  246. var size = if (fileSize > bufsize) bufsize else fileSize;
  247. var len = 0;
  248. try {
  249. len = fileInput.readBytes(buf, 0, size);
  250. } catch (e:haxe.io.Eof)
  251. break;
  252. sock.output.writeFullBytes(buf, 0, len);
  253. fileSize -= len;
  254. }
  255. sock.write("\r\n");
  256. sock.write("--");
  257. sock.write(boundary);
  258. sock.write("--");
  259. }
  260. }
  261. function readHttpResponse(api:haxe.io.Output, sock:sys.net.Socket) {
  262. // READ the HTTP header (until \r\n\r\n)
  263. var b = new haxe.io.BytesBuffer();
  264. var k = 4;
  265. var s = haxe.io.Bytes.alloc(4);
  266. sock.setTimeout(cnxTimeout);
  267. while (true) {
  268. var p = sock.input.readBytes(s, 0, k);
  269. while (p != k)
  270. p += sock.input.readBytes(s, p, k - p);
  271. b.addBytes(s, 0, k);
  272. switch (k) {
  273. case 1:
  274. var c = s.get(0);
  275. if (c == 10)
  276. break;
  277. if (c == 13)
  278. k = 3;
  279. else
  280. k = 4;
  281. case 2:
  282. var c = s.get(1);
  283. if (c == 10) {
  284. if (s.get(0) == 13)
  285. break;
  286. k = 4;
  287. } else if (c == 13)
  288. k = 3;
  289. else
  290. k = 4;
  291. case 3:
  292. var c = s.get(2);
  293. if (c == 10) {
  294. if (s.get(1) != 13)
  295. k = 4;
  296. else if (s.get(0) != 10)
  297. k = 2;
  298. else
  299. break;
  300. } else if (c == 13) {
  301. if (s.get(1) != 10 || s.get(0) != 13)
  302. k = 1;
  303. else
  304. k = 3;
  305. } else
  306. k = 4;
  307. case 4:
  308. var c = s.get(3);
  309. if (c == 10) {
  310. if (s.get(2) != 13)
  311. continue;
  312. else if (s.get(1) != 10 || s.get(0) != 13)
  313. k = 2;
  314. else
  315. break;
  316. } else if (c == 13) {
  317. if (s.get(2) != 10 || s.get(1) != 13)
  318. k = 3;
  319. else
  320. k = 1;
  321. }
  322. }
  323. }
  324. #if neko
  325. var headers = neko.Lib.stringReference(b.getBytes()).split("\r\n");
  326. #else
  327. var headers = b.getBytes().toString().split("\r\n");
  328. #end
  329. var response = headers.shift();
  330. var rp = response.split(" ");
  331. var status = Std.parseInt(rp[1]);
  332. if (status == 0 || status == null)
  333. throw "Response status error";
  334. // remove the two lasts \r\n\r\n
  335. headers.pop();
  336. headers.pop();
  337. responseHeaders = new haxe.ds.StringMap();
  338. var size = null;
  339. var chunked = false;
  340. for (hline in headers) {
  341. var a = hline.split(": ");
  342. var hname = a.shift();
  343. var hval = if (a.length == 1) a[0] else a.join(": ");
  344. hval = StringTools.ltrim(StringTools.rtrim(hval));
  345. responseHeaders.set(hname, hval);
  346. switch (hname.toLowerCase()) {
  347. case "content-length":
  348. size = Std.parseInt(hval);
  349. case "transfer-encoding":
  350. chunked = (hval.toLowerCase() == "chunked");
  351. }
  352. }
  353. onStatus(status);
  354. var chunk_re = ~/^([0-9A-Fa-f]+)[ ]*\r\n/m;
  355. chunk_size = null;
  356. chunk_buf = null;
  357. var bufsize = 1024;
  358. var buf = haxe.io.Bytes.alloc(bufsize);
  359. if (chunked) {
  360. try {
  361. while (true) {
  362. var len = sock.input.readBytes(buf, 0, bufsize);
  363. if (!readChunk(chunk_re, api, buf, len))
  364. break;
  365. }
  366. } catch (e:haxe.io.Eof) {
  367. throw "Transfer aborted";
  368. }
  369. } else if (size == null) {
  370. if (!noShutdown)
  371. sock.shutdown(false, true);
  372. try {
  373. while (true) {
  374. var len = sock.input.readBytes(buf, 0, bufsize);
  375. if (len == 0)
  376. break;
  377. api.writeBytes(buf, 0, len);
  378. }
  379. } catch (e:haxe.io.Eof) {}
  380. } else {
  381. api.prepare(size);
  382. try {
  383. while (size > 0) {
  384. var len = sock.input.readBytes(buf, 0, if (size > bufsize) bufsize else size);
  385. api.writeBytes(buf, 0, len);
  386. size -= len;
  387. }
  388. } catch (e:haxe.io.Eof) {
  389. throw "Transfer aborted";
  390. }
  391. }
  392. if (chunked && (chunk_size != null || chunk_buf != null))
  393. throw "Invalid chunk";
  394. if (status < 200 || status >= 400)
  395. throw "Http Error #" + status;
  396. api.close();
  397. }
  398. function readChunk(chunk_re:EReg, api:haxe.io.Output, buf:haxe.io.Bytes, len) {
  399. if (chunk_size == null) {
  400. if (chunk_buf != null) {
  401. var b = new haxe.io.BytesBuffer();
  402. b.add(chunk_buf);
  403. b.addBytes(buf, 0, len);
  404. buf = b.getBytes();
  405. len += chunk_buf.length;
  406. chunk_buf = null;
  407. }
  408. #if neko
  409. if (chunk_re.match(neko.Lib.stringReference(buf))) {
  410. #else
  411. if (chunk_re.match(buf.toString())) {
  412. #end
  413. var p = chunk_re.matchedPos();
  414. if (p.len <= len) {
  415. var cstr = chunk_re.matched(1);
  416. chunk_size = Std.parseInt("0x" + cstr);
  417. if (chunk_size == 0) {
  418. chunk_size = null;
  419. chunk_buf = null;
  420. return false;
  421. }
  422. len -= p.len;
  423. return readChunk(chunk_re, api, buf.sub(p.len, len), len);
  424. }
  425. }
  426. // prevent buffer accumulation
  427. if (len > 10) {
  428. onError("Invalid chunk");
  429. return false;
  430. }
  431. chunk_buf = buf.sub(0, len);
  432. return true;
  433. }
  434. if (chunk_size > len) {
  435. chunk_size -= len;
  436. api.writeBytes(buf, 0, len);
  437. return true;
  438. }
  439. var end = chunk_size + 2;
  440. if (len >= end) {
  441. if (chunk_size > 0)
  442. api.writeBytes(buf, 0, chunk_size);
  443. len -= end;
  444. chunk_size = null;
  445. if (len == 0)
  446. return true;
  447. return readChunk(chunk_re, api, buf.sub(end, len), len);
  448. }
  449. if (chunk_size > 0)
  450. api.writeBytes(buf, 0, chunk_size);
  451. chunk_size -= len;
  452. return true;
  453. }
  454. /**
  455. Makes a synchronous request to `url`.
  456. This creates a new Http instance and makes a GET request by calling its
  457. `request(false)` method.
  458. If `url` is null, the result is unspecified.
  459. **/
  460. public static function requestUrl(url:String):String {
  461. var h = new Http(url);
  462. var r = null;
  463. h.onData = function(d) {
  464. r = d;
  465. }
  466. h.onError = function(e) {
  467. throw e;
  468. }
  469. h.request(false);
  470. return r;
  471. }
  472. }