File:  [parser3project] / parser3 / src / classes / inet.C
Revision 1.21: download - view: text, annotated - select for diffs - revision graph
Sat Apr 25 13:38:46 2026 UTC (5 weeks, 4 days ago) by moko
Branches: MAIN
CVS tags: HEAD
Copyright year updated, websites links changed to https://

/** @file
	Parser: @b inet parser class.

	Copyright (c) 2001-2026 Art. Lebedev Studio (https://www.artlebedev.com)
	Authors: Konstantin Morshnev <moko@design.ru>, Alexandr Petrosian <paf@design.ru>
*/

#include "pa_vmethod_frame.h"
#include "pa_request.h"
#include "pa_vtable.h"

#ifdef _MSC_VER
#include "winsock2.h"
#include "ws2tcpip.h"
#endif

volatile const char * IDENT_INET_C="$Id: inet.C,v 1.21 2026/04/25 13:38:46 moko Exp $";

class MInet: public Methoded {
public:
	MInet();
};

// global variables

DECLARE_CLASS_VAR(inet, new MInet);


static void _ntoa(Request& r, MethodParams& params){
	unsigned long l=(unsigned long)trunc(params.as_double(0, "parameter must be expression", r));
	static const int ip_cstr_bufsize=3*4+3+1/*zero-teminator*/+1/*for faulty snprintfs*/;
	char* ip_cstr=new(PointerFreeGC) char[ip_cstr_bufsize];
	snprintf(ip_cstr, ip_cstr_bufsize, "%u.%u.%u.%u", (l>>24) & 0xFF, (l>>16) & 0xFF, (l>>8) & 0xFF, l & 0xFF);
	r.write(*new String(ip_cstr));
}

static void _aton(Request& r, MethodParams& params){
	const String ip=params.as_string(0, PARAMETER_MUST_BE_STRING);
	if(ip.is_empty())
		throw Exception(PARSER_RUNTIME, 0, "IP address must not be empty.");

	const char* ip_cstr=ip.cstr();
	ulong result=0;
	uint byte_value=0;
	uint dot_cnt=0;
	bool byte_start=true;
	bool err=false;
	const char* p=ip_cstr;
	while(char c=*p++){
		int digit=(int)(c-'0');	// assume ascii
		if(digit>=0 && digit<=9){
			byte_start=false;
			if((byte_value=byte_value*10+(uint)digit) > 255){
				err=true;
				break;
			}
		} else if(c=='.'){
			if(byte_start){ // two dots in row or IP started with dot
				err=true;
				break;
			} else {
				byte_start=true;
				dot_cnt++;
				result=(result << 8)+(ulong)byte_value;
				byte_value=0;
			}
		} else { // invalid char
			err=true;
			break;
		}
	}

	if(err || dot_cnt!=3 || byte_start){
		throw Exception(PARSER_RUNTIME, 0, "Invalid IP address '%s' specified.", ip_cstr);
	} else {
		result=(result << 8)+(ulong)byte_value;
		r.write(*new VDouble(result));
	}
}

int ipv_format(const String &value){
	if(value == "4") return AF_INET;
	if(value == "6") return AF_INET6;
	if(value == "any") return AF_UNSPEC;
	throw Exception(PARSER_RUNTIME, &value, "ipv option value must be 4 or 6 or any");
}

static void _ip2name(Request& r, MethodParams& params){
	const String sip=params.as_string(0, PARAMETER_MUST_BE_STRING);
	if(sip.is_empty())
		throw Exception(PARSER_RUNTIME, 0, "IP address must not be empty.");

	const char* ip_cstr=sip.cstr();

	struct addrinfo hints, *info=0;
	memset(&hints, 0, sizeof(hints));
	hints.ai_family=AF_INET;
	hints.ai_socktype=SOCK_STREAM;
	hints.ai_flags=AI_NUMERICHOST; // to disable DNS lookup

	if(params.count() == 2)
		if(HashStringValue* options=params.as_hash(1)){
			int valid_options=0;
			if(Value* value=options->get("ipv")){
				hints.ai_family=ipv_format(r.process(*value).as_string());
				valid_options++;
			}
			if(valid_options!=options->count())
				throw Exception(PARSER_RUNTIME, 0, CALLED_WITH_INVALID_OPTION);
		}

	int error=getaddrinfo(ip_cstr, 0, &hints, &info);
	if(error==EAI_NONAME)
		throw Exception(PARSER_RUNTIME, 0, "Invalid IP address '%s' specified", ip_cstr);
	if(error)
		throw Exception(PARSER_RUNTIME, 0, "Invalid IP address '%s': %s", ip_cstr, gai_strerror(error));

	char hbuf[NI_MAXHOST];
	error=getnameinfo(info->ai_addr, info->ai_addrlen, hbuf, sizeof(hbuf), NULL, 0, NI_NAMEREQD);

	freeaddrinfo(info);

	if(!error){
		r.write(*new String(pa_idna_decode(pa_strdup(hbuf), r.charsets.source()), String::L_TAINTED));
	} else if(error!=EAI_NONAME){
		throw Exception(PARSER_RUNTIME, 0, "Can't resolve IP address '%s': %s", ip_cstr, gai_strerror(error));
	}
}

static void _name2ip(Request& r, MethodParams& params){
	const String sname=params.as_string(0, PARAMETER_MUST_BE_STRING);
	if(sname.is_empty())
		throw Exception(PARSER_RUNTIME, 0, "Domain name must not be empty.");

	const char* name_cstr=pa_idna_encode(sname.cstr(), r.charsets.source());

	struct addrinfo hints, *info;
	memset(&hints, 0, sizeof(hints));
	hints.ai_family=AF_INET;
	hints.ai_socktype=SOCK_STREAM;

	Table *table=NULL;

	if(params.count() == 2)
		if(HashStringValue* options=params.as_hash(1)){
			int valid_options=0;
			if(Value* value=options->get("ipv")){
				hints.ai_family=ipv_format(r.process(*value).as_string());
				valid_options++;
			}
			if(Value* value=options->get("table")){
				if(r.process(*value).as_bool()){
					Table::columns_type columns(new ArrayString);
					static const String sip("ip"), sversion("version");
					*columns+=&sip; *columns+=&sversion;
					table=new Table(columns);
				}
				valid_options++;
			}
			if(valid_options!=options->count())
				throw Exception(PARSER_RUNTIME, 0, CALLED_WITH_INVALID_OPTION);
		}

	int error=getaddrinfo(name_cstr, NULL, &hints, &info);

	if(error)
		throw Exception(PARSER_RUNTIME, 0, "Can't resolve domain name '%s': %s", name_cstr, gai_strerror(error));

	char hbuf[INET6_ADDRSTRLEN];

	for(struct addrinfo *cur=info; cur; cur=cur->ai_next) {
		if(error=getnameinfo(cur->ai_addr, cur->ai_addrlen, hbuf, sizeof(hbuf), NULL, 0, NI_NUMERICHOST))
			throw Exception(PARSER_RUNTIME, 0, "Can't translate address: %s", gai_strerror(error));
		String *saddr=new String(pa_strdup(hbuf), String::L_TAINTED);
		if(table){
			Table::element_type row(new ArrayString());
			static const String sv4("4"), sv6("6"), sunknown("unknown");
			*row+=saddr;
			*row+=cur->ai_family == AF_INET ? &sv4 : cur->ai_family == AF_INET6 ? &sv6 : &sunknown;
			*table+=row;
		} else {
			r.write(*saddr);
			break;
		}
	}

	if(table)
		r.write(*new VTable(table));

	freeaddrinfo(info);
}

static void _hostname(Request& r, MethodParams&){
	char buf[MAX_STRING];

	if(gethostname(buf, MAX_STRING))
		throw Exception(PARSER_RUNTIME, 0, "Cant't get hostname");

	r.write(*new String(pa_strdup(buf), String::L_TAINTED));
}

// constructor
MInet::MInet(): Methoded("inet") {
	add_native_method("ntoa", Method::CT_STATIC, _ntoa, 1, 1);
	add_native_method("aton", Method::CT_STATIC, _aton, 1, 1);
	// ^inet:ip2name[ip; $.ipv[4|6|any] ]
	add_native_method("ip2name", Method::CT_STATIC, _ip2name, 1, 2);
	// ^inet:name2ip[name; $.ipv[4|6|any] $.table(true) ]
	add_native_method("name2ip", Method::CT_STATIC, _name2ip, 1, 2);
	// ^inet:hostname[]
	add_native_method("hostname", Method::CT_STATIC, _hostname, 0, 0);
}

E-mail: