| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539 | /* * 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>;	private var responseHeadersSameKey:Map<String, Array<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) || (lua && !lua_vanilla)))				sock = new sys.ssl.Socket();				#elseif (neko || cpp)				throw "Https is only supported with -lib hxssl";				#else				throw new haxe.exceptions.NotImplementedException("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));		}	}	/**		Returns an array of values for a single response header or returns		null if no such header exists.		This method can be useful when you need to get a multiple headers with		the same name (e.g. `Set-Cookie`), that are unreachable via the		`responseHeaders` variable.	**/	public function getResponseHeaderValues(key:String):Null<Array<String>> {		var array = responseHeadersSameKey.get(key);		if (array == null) {			var singleValue = responseHeaders.get(key);			return (singleValue == null) ? null : [ singleValue ];		} else {			return array;		}	}	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 = 0;			while (p != k) {				try {					p += sock.input.readBytes(s, p, k - p);				}				catch (e:haxe.io.Eof) { }			}			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));			{				var previousValue = responseHeaders.get(hname);				if (previousValue != null) {					if (responseHeadersSameKey == null) {						responseHeadersSameKey = new haxe.ds.Map<String, Array<String>>();					}					var array = responseHeadersSameKey.get(hname);					if (array == null) {						array = new Array<String>();						array.push(previousValue);						responseHeadersSameKey.set(hname, array);					}					array.push(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;	}}
 |