Http.hx 12 KB

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