123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500 |
- /*
- * Copyright (C)2005-2019 Haxe Foundation
- *
- * Permission is hereby granted, free of charge, to any person obtaining a
- * copy of this software and associated documentation files (the "Software"),
- * to deal in the Software without restriction, including without limitation
- * the rights to use, copy, modify, merge, publish, distribute, sublicense,
- * and/or sell copies of the Software, and to permit persons to whom the
- * Software is furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in
- * all copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
- * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
- * DEALINGS IN THE SOFTWARE.
- */
- package sys;
- import haxe.io.BytesOutput;
- import haxe.io.Bytes;
- import haxe.io.Input;
- import sys.net.Host;
- import sys.net.Socket;
- class Http extends haxe.http.HttpBase {
- public var noShutdown:Bool;
- public var cnxTimeout:Float;
- public var responseHeaders:Map<String, String>;
- var chunk_size:Null<Int>;
- var chunk_buf:haxe.io.Bytes;
- var file:{
- param:String,
- filename:String,
- io:haxe.io.Input,
- size:Int,
- mimeType:String
- };
- public static var PROXY:{host:String, port:Int, auth:{user:String, pass:String}} = null;
- public function new(url:String) {
- cnxTimeout = 10;
- #if php
- noShutdown = !php.Global.function_exists('stream_socket_shutdown');
- #end
- super(url);
- }
- public override function request(?post:Bool) {
- var output = new haxe.io.BytesOutput();
- var old = onError;
- var err = false;
- onError = function(e) {
- responseBytes = output.getBytes();
- err = true;
- // Resetting back onError before calling it allows for a second "retry" request to be sent without onError being wrapped twice
- onError = old;
- onError(e);
- }
- post = post || postBytes != null || postData != null;
- customRequest(post, output);
- if (!err) {
- success(output.getBytes());
- }
- }
- @:noCompletion
- @:deprecated("Use fileTransfer instead")
- inline public function fileTransfert(argname:String, filename:String, file:haxe.io.Input, size:Int, mimeType = "application/octet-stream") {
- fileTransfer(argname, filename, file, size, mimeType);
- }
- public function fileTransfer(argname:String, filename:String, file:haxe.io.Input, size:Int, mimeType = "application/octet-stream") {
- this.file = {
- param: argname,
- filename: filename,
- io: file,
- size: size,
- mimeType: mimeType
- };
- }
- public function customRequest(post:Bool, api:haxe.io.Output, ?sock:sys.net.Socket, ?method:String) {
- this.responseAsString = null;
- this.responseBytes = null;
- var url_regexp = ~/^(https?:\/\/)?([a-zA-Z\.0-9_-]+)(:[0-9]+)?(.*)$/;
- if (!url_regexp.match(url)) {
- onError("Invalid URL");
- return;
- }
- var secure = (url_regexp.matched(1) == "https://");
- if (sock == null) {
- if (secure) {
- #if php
- sock = new php.net.SslSocket();
- #elseif java
- sock = new java.net.SslSocket();
- #elseif python
- sock = new python.net.SslSocket();
- #elseif (!no_ssl && (hxssl || hl || cpp || (neko && !(macro || interp) || eval)))
- sock = new sys.ssl.Socket();
- #elseif (neko || cpp)
- throw "Https is only supported with -lib hxssl";
- #else
- throw "Https support in haxe.Http is not implemented for this target";
- #end
- } else {
- sock = new Socket();
- }
- sock.setTimeout(cnxTimeout);
- }
- var host = url_regexp.matched(2);
- var portString = url_regexp.matched(3);
- var request = url_regexp.matched(4);
- // ensure path begins with a forward slash
- // this is required by original URL specifications and many servers have issues if it's not supplied
- // see https://stackoverflow.com/questions/1617058/ok-to-skip-slash-before-query-string
- if (request.charAt(0) != "/") {
- request = "/" + request;
- }
- var port = if (portString == null || portString == "") secure ? 443 : 80 else Std.parseInt(portString.substr(1, portString.length - 1));
- var multipart = (file != null);
- var boundary = null;
- var uri = null;
- if (multipart) {
- post = true;
- boundary = Std.string(Std.random(1000))
- + Std.string(Std.random(1000))
- + Std.string(Std.random(1000))
- + Std.string(Std.random(1000));
- while (boundary.length < 38)
- boundary = "-" + boundary;
- var b = new StringBuf();
- for (p in params) {
- b.add("--");
- b.add(boundary);
- b.add("\r\n");
- b.add('Content-Disposition: form-data; name="');
- b.add(p.name);
- b.add('"');
- b.add("\r\n");
- b.add("\r\n");
- b.add(p.value);
- b.add("\r\n");
- }
- b.add("--");
- b.add(boundary);
- b.add("\r\n");
- b.add('Content-Disposition: form-data; name="');
- b.add(file.param);
- b.add('"; filename="');
- b.add(file.filename);
- b.add('"');
- b.add("\r\n");
- b.add("Content-Type: " + file.mimeType + "\r\n" + "\r\n");
- uri = b.toString();
- } else {
- for (p in params) {
- if (uri == null)
- uri = "";
- else
- uri += "&";
- uri += StringTools.urlEncode(p.name) + "=" + StringTools.urlEncode('${p.value}');
- }
- }
- var b = new BytesOutput();
- if (method != null) {
- b.writeString(method);
- b.writeString(" ");
- } else if (post)
- b.writeString("POST ");
- else
- b.writeString("GET ");
- if (Http.PROXY != null) {
- b.writeString("http://");
- b.writeString(host);
- if (port != 80) {
- b.writeString(":");
- b.writeString('$port');
- }
- }
- b.writeString(request);
- if (!post && uri != null) {
- if (request.indexOf("?", 0) >= 0)
- b.writeString("&");
- else
- b.writeString("?");
- b.writeString(uri);
- }
- b.writeString(" HTTP/1.1\r\nHost: " + host + "\r\n");
- if (postData != null) {
- postBytes = Bytes.ofString(postData);
- postData = null;
- }
- if (postBytes != null)
- b.writeString("Content-Length: " + postBytes.length + "\r\n");
- else if (post && uri != null) {
- if (multipart || !Lambda.exists(headers, function(h) return h.name == "Content-Type")) {
- b.writeString("Content-Type: ");
- if (multipart) {
- b.writeString("multipart/form-data");
- b.writeString("; boundary=");
- b.writeString(boundary);
- } else
- b.writeString("application/x-www-form-urlencoded");
- b.writeString("\r\n");
- }
- if (multipart)
- b.writeString("Content-Length: " + (uri.length + file.size + boundary.length + 6) + "\r\n");
- else
- b.writeString("Content-Length: " + uri.length + "\r\n");
- }
- b.writeString("Connection: close\r\n");
- for (h in headers) {
- b.writeString(h.name);
- b.writeString(": ");
- b.writeString(h.value);
- b.writeString("\r\n");
- }
- b.writeString("\r\n");
- if (postBytes != null)
- b.writeFullBytes(postBytes, 0, postBytes.length);
- else if (post && uri != null)
- b.writeString(uri);
- try {
- if (Http.PROXY != null)
- sock.connect(new Host(Http.PROXY.host), Http.PROXY.port);
- else
- sock.connect(new Host(host), port);
- if (multipart)
- writeBody(b, file.io, file.size, boundary, sock)
- else
- writeBody(b, null, 0, null, sock);
- readHttpResponse(api, sock);
- sock.close();
- } catch (e:Dynamic) {
- try
- sock.close()
- catch (e:Dynamic) {};
- onError(Std.string(e));
- }
- }
- function writeBody(body:Null<BytesOutput>, fileInput:Null<Input>, fileSize:Int, boundary:Null<String>, sock:Socket) {
- if (body != null) {
- var bytes = body.getBytes();
- sock.output.writeFullBytes(bytes, 0, bytes.length);
- }
- if (boundary != null) {
- var bufsize = 4096;
- var buf = haxe.io.Bytes.alloc(bufsize);
- while (fileSize > 0) {
- var size = if (fileSize > bufsize) bufsize else fileSize;
- var len = 0;
- try {
- len = fileInput.readBytes(buf, 0, size);
- } catch (e:haxe.io.Eof)
- break;
- sock.output.writeFullBytes(buf, 0, len);
- fileSize -= len;
- }
- sock.output.writeString("\r\n");
- sock.output.writeString("--");
- sock.output.writeString(boundary);
- sock.output.writeString("--");
- }
- }
- function readHttpResponse(api:haxe.io.Output, sock:sys.net.Socket) {
- // READ the HTTP header (until \r\n\r\n)
- var b = new haxe.io.BytesBuffer();
- var k = 4;
- var s = haxe.io.Bytes.alloc(4);
- sock.setTimeout(cnxTimeout);
- while (true) {
- var p = sock.input.readBytes(s, 0, k);
- while (p != k)
- p += sock.input.readBytes(s, p, k - p);
- b.addBytes(s, 0, k);
- switch (k) {
- case 1:
- var c = s.get(0);
- if (c == 10)
- break;
- if (c == 13)
- k = 3;
- else
- k = 4;
- case 2:
- var c = s.get(1);
- if (c == 10) {
- if (s.get(0) == 13)
- break;
- k = 4;
- } else if (c == 13)
- k = 3;
- else
- k = 4;
- case 3:
- var c = s.get(2);
- if (c == 10) {
- if (s.get(1) != 13)
- k = 4;
- else if (s.get(0) != 10)
- k = 2;
- else
- break;
- } else if (c == 13) {
- if (s.get(1) != 10 || s.get(0) != 13)
- k = 1;
- else
- k = 3;
- } else
- k = 4;
- case 4:
- var c = s.get(3);
- if (c == 10) {
- if (s.get(2) != 13)
- continue;
- else if (s.get(1) != 10 || s.get(0) != 13)
- k = 2;
- else
- break;
- } else if (c == 13) {
- if (s.get(2) != 10 || s.get(1) != 13)
- k = 3;
- else
- k = 1;
- }
- }
- }
- #if neko
- var headers = neko.Lib.stringReference(b.getBytes()).split("\r\n");
- #else
- var headers = b.getBytes().toString().split("\r\n");
- #end
- var response = headers.shift();
- var rp = response.split(" ");
- var status = Std.parseInt(rp[1]);
- if (status == 0 || status == null)
- throw "Response status error";
- // remove the two lasts \r\n\r\n
- headers.pop();
- headers.pop();
- responseHeaders = new haxe.ds.StringMap();
- var size = null;
- var chunked = false;
- for (hline in headers) {
- var a = hline.split(": ");
- var hname = a.shift();
- var hval = if (a.length == 1) a[0] else a.join(": ");
- hval = StringTools.ltrim(StringTools.rtrim(hval));
- responseHeaders.set(hname, hval);
- switch (hname.toLowerCase()) {
- case "content-length":
- size = Std.parseInt(hval);
- case "transfer-encoding":
- chunked = (hval.toLowerCase() == "chunked");
- }
- }
- onStatus(status);
- var chunk_re = ~/^([0-9A-Fa-f]+)[ ]*\r\n/m;
- chunk_size = null;
- chunk_buf = null;
- var bufsize = 1024;
- var buf = haxe.io.Bytes.alloc(bufsize);
- if (chunked) {
- try {
- while (true) {
- var len = sock.input.readBytes(buf, 0, bufsize);
- if (!readChunk(chunk_re, api, buf, len))
- break;
- }
- } catch (e:haxe.io.Eof) {
- throw "Transfer aborted";
- }
- } else if (size == null) {
- if (!noShutdown)
- sock.shutdown(false, true);
- try {
- while (true) {
- var len = sock.input.readBytes(buf, 0, bufsize);
- if (len == 0)
- break;
- api.writeBytes(buf, 0, len);
- }
- } catch (e:haxe.io.Eof) {}
- } else {
- api.prepare(size);
- try {
- while (size > 0) {
- var len = sock.input.readBytes(buf, 0, if (size > bufsize) bufsize else size);
- api.writeBytes(buf, 0, len);
- size -= len;
- }
- } catch (e:haxe.io.Eof) {
- throw "Transfer aborted";
- }
- }
- if (chunked && (chunk_size != null || chunk_buf != null))
- throw "Invalid chunk";
- if (status < 200 || status >= 400)
- throw "Http Error #" + status;
- api.close();
- }
- function readChunk(chunk_re:EReg, api:haxe.io.Output, buf:haxe.io.Bytes, len) {
- if (chunk_size == null) {
- if (chunk_buf != null) {
- var b = new haxe.io.BytesBuffer();
- b.add(chunk_buf);
- b.addBytes(buf, 0, len);
- buf = b.getBytes();
- len += chunk_buf.length;
- chunk_buf = null;
- }
- #if neko
- if (chunk_re.match(neko.Lib.stringReference(buf))) {
- #else
- if (chunk_re.match(buf.toString())) {
- #end
- var p = chunk_re.matchedPos();
- if (p.len <= len) {
- var cstr = chunk_re.matched(1);
- chunk_size = Std.parseInt("0x" + cstr);
- if (chunk_size == 0) {
- chunk_size = null;
- chunk_buf = null;
- return false;
- }
- len -= p.len;
- return readChunk(chunk_re, api, buf.sub(p.len, len), len);
- }
- }
- // prevent buffer accumulation
- if (len > 10) {
- onError("Invalid chunk");
- return false;
- }
- chunk_buf = buf.sub(0, len);
- return true;
- }
- if (chunk_size > len) {
- chunk_size -= len;
- api.writeBytes(buf, 0, len);
- return true;
- }
- var end = chunk_size + 2;
- if (len >= end) {
- if (chunk_size > 0)
- api.writeBytes(buf, 0, chunk_size);
- len -= end;
- chunk_size = null;
- if (len == 0)
- return true;
- return readChunk(chunk_re, api, buf.sub(end, len), len);
- }
- if (chunk_size > 0)
- api.writeBytes(buf, 0, chunk_size);
- chunk_size -= len;
- return true;
- }
- /**
- Makes a synchronous request to `url`.
- This creates a new Http instance and makes a GET request by calling its
- `request(false)` method.
- If `url` is null, the result is unspecified.
- **/
- public static function requestUrl(url:String):String {
- var h = new Http(url);
- var r = null;
- h.onData = function(d) {
- r = d;
- }
- h.onError = function(e) {
- throw e;
- }
- h.request(false);
- return r;
- }
- }
|