Annotation of parser3/src/classes/inet.C, revision 1.24
1.1 misha 1: /** @file
2: Parser: @b inet parser class.
3:
1.21 moko 4: Copyright (c) 2001-2026 Art. Lebedev Studio (https://www.artlebedev.com)
1.18 moko 5: Authors: Konstantin Morshnev <moko@design.ru>, Alexandr Petrosian <paf@design.ru>
1.1 misha 6: */
7:
8: #include "pa_vmethod_frame.h"
9: #include "pa_request.h"
1.7 moko 10: #include "pa_vtable.h"
1.1 misha 11:
1.8 moko 12: #ifdef _MSC_VER
1.23 moko 13: #include <ws2tcpip.h>
1.8 moko 14: #endif
15:
1.24 ! moko 16: volatile const char * IDENT_INET_C="$Id: inet.C,v 1.23 2026/09/19 22:45:10 moko Exp $";
1.5 moko 17:
1.1 misha 18: class MInet: public Methoded {
19: public:
20: MInet();
21: };
22:
23: // global variables
24:
1.12 moko 25: DECLARE_CLASS_VAR(inet, new MInet);
1.1 misha 26:
27:
28: static void _ntoa(Request& r, MethodParams& params){
29: unsigned long l=(unsigned long)trunc(params.as_double(0, "parameter must be expression", r));
1.24 ! moko 30: static const int ip_cstr_bufsize=3*4+3+1/*zero-teminator*/;
1.1 misha 31: char* ip_cstr=new(PointerFreeGC) char[ip_cstr_bufsize];
1.24 ! moko 32: pa_snprintf(ip_cstr, ip_cstr_bufsize, "%u.%u.%u.%u", (l>>24) & 0xFF, (l>>16) & 0xFF, (l>>8) & 0xFF, l & 0xFF);
1.14 moko 33: r.write(*new String(ip_cstr));
1.1 misha 34: }
35:
36: static void _aton(Request& r, MethodParams& params){
37: const String ip=params.as_string(0, PARAMETER_MUST_BE_STRING);
38: if(ip.is_empty())
1.7 moko 39: throw Exception(PARSER_RUNTIME, 0, "IP address must not be empty.");
1.1 misha 40:
41: const char* ip_cstr=ip.cstr();
42: ulong result=0;
43: uint byte_value=0;
44: uint dot_cnt=0;
45: bool byte_start=true;
46: bool err=false;
47: const char* p=ip_cstr;
48: while(char c=*p++){
1.6 moko 49: int digit=(int)(c-'0'); // assume ascii
1.1 misha 50: if(digit>=0 && digit<=9){
51: byte_start=false;
1.6 moko 52: if((byte_value=byte_value*10+(uint)digit) > 255){
1.1 misha 53: err=true;
54: break;
55: }
56: } else if(c=='.'){
57: if(byte_start){ // two dots in row or IP started with dot
58: err=true;
59: break;
60: } else {
61: byte_start=true;
62: dot_cnt++;
63: result=(result << 8)+(ulong)byte_value;
64: byte_value=0;
65: }
66: } else { // invalid char
67: err=true;
68: break;
69: }
70: }
71:
72: if(err || dot_cnt!=3 || byte_start){
1.7 moko 73: throw Exception(PARSER_RUNTIME, 0, "Invalid IP address '%s' specified.", ip_cstr);
1.1 misha 74: } else {
75: result=(result << 8)+(ulong)byte_value;
1.14 moko 76: r.write(*new VDouble(result));
1.1 misha 77: }
78: }
79:
1.7 moko 80: int ipv_format(const String &value){
81: if(value == "4") return AF_INET;
82: if(value == "6") return AF_INET6;
83: if(value == "any") return AF_UNSPEC;
84: throw Exception(PARSER_RUNTIME, &value, "ipv option value must be 4 or 6 or any");
85: }
86:
87: static void _ip2name(Request& r, MethodParams& params){
88: const String sip=params.as_string(0, PARAMETER_MUST_BE_STRING);
89: if(sip.is_empty())
90: throw Exception(PARSER_RUNTIME, 0, "IP address must not be empty.");
91:
92: const char* ip_cstr=sip.cstr();
93:
94: struct addrinfo hints, *info=0;
95: memset(&hints, 0, sizeof(hints));
96: hints.ai_family=AF_INET;
97: hints.ai_socktype=SOCK_STREAM;
98: hints.ai_flags=AI_NUMERICHOST; // to disable DNS lookup
1.17 moko 99:
1.7 moko 100: if(params.count() == 2)
101: if(HashStringValue* options=params.as_hash(1)){
102: int valid_options=0;
103: if(Value* value=options->get("ipv")){
1.13 moko 104: hints.ai_family=ipv_format(r.process(*value).as_string());
1.7 moko 105: valid_options++;
106: }
107: if(valid_options!=options->count())
108: throw Exception(PARSER_RUNTIME, 0, CALLED_WITH_INVALID_OPTION);
109: }
110:
111: int error=getaddrinfo(ip_cstr, 0, &hints, &info);
112: if(error==EAI_NONAME)
113: throw Exception(PARSER_RUNTIME, 0, "Invalid IP address '%s' specified", ip_cstr);
114: if(error)
115: throw Exception(PARSER_RUNTIME, 0, "Invalid IP address '%s': %s", ip_cstr, gai_strerror(error));
116:
117: char hbuf[NI_MAXHOST];
118: error=getnameinfo(info->ai_addr, info->ai_addrlen, hbuf, sizeof(hbuf), NULL, 0, NI_NAMEREQD);
119:
120: freeaddrinfo(info);
121:
122: if(!error){
1.14 moko 123: r.write(*new String(pa_idna_decode(pa_strdup(hbuf), r.charsets.source()), String::L_TAINTED));
1.7 moko 124: } else if(error!=EAI_NONAME){
125: throw Exception(PARSER_RUNTIME, 0, "Can't resolve IP address '%s': %s", ip_cstr, gai_strerror(error));
126: }
127: }
1.17 moko 128:
1.22 moko 129: // also used in curl.C, returns first address or fills the table if not null
130: const char *pa_name2ip(const char *name, int family, Table *table, const char *exception_type){
131: struct addrinfo hints, *info;
132: memset(&hints, 0, sizeof(hints));
133: hints.ai_family=family;
134: hints.ai_socktype=SOCK_STREAM;
135:
136: int error=getaddrinfo(name, NULL, &hints, &info);
137:
138: if(error)
139: throw Exception(exception_type, 0, "Can't resolve domain name '%s': %s", name, gai_strerror(error));
140:
141: const char *result=0;
142: char hbuf[INET6_ADDRSTRLEN];
143:
144: for(struct addrinfo *cur=info; cur; cur=cur->ai_next) {
145: if(error=getnameinfo(cur->ai_addr, cur->ai_addrlen, hbuf, sizeof(hbuf), NULL, 0, NI_NUMERICHOST))
146: throw Exception(exception_type, 0, "Can't translate address: %s", gai_strerror(error));
147: if(table){
148: Table::element_type row(new ArrayString());
149: static const String sv4("4"), sv6("6"), sunknown("unknown");
150: *row+=new String(pa_strdup(hbuf), String::L_TAINTED);
151: *row+=cur->ai_family == AF_INET ? &sv4 : cur->ai_family == AF_INET6 ? &sv6 : &sunknown;
152: *table+=row;
153: } else {
154: result=pa_strdup(hbuf);
155: break;
156: }
157: }
158:
159: freeaddrinfo(info);
160: return result;
161: }
162:
1.7 moko 163: static void _name2ip(Request& r, MethodParams& params){
164: const String sname=params.as_string(0, PARAMETER_MUST_BE_STRING);
165: if(sname.is_empty())
166: throw Exception(PARSER_RUNTIME, 0, "Domain name must not be empty.");
167:
168: const char* name_cstr=pa_idna_encode(sname.cstr(), r.charsets.source());
169:
1.22 moko 170: int family=AF_INET;
1.7 moko 171: Table *table=NULL;
172:
173: if(params.count() == 2)
174: if(HashStringValue* options=params.as_hash(1)){
175: int valid_options=0;
176: if(Value* value=options->get("ipv")){
1.22 moko 177: family=ipv_format(r.process(*value).as_string());
1.7 moko 178: valid_options++;
179: }
180: if(Value* value=options->get("table")){
1.13 moko 181: if(r.process(*value).as_bool()){
1.7 moko 182: Table::columns_type columns(new ArrayString);
183: static const String sip("ip"), sversion("version");
184: *columns+=&sip; *columns+=&sversion;
185: table=new Table(columns);
186: }
187: valid_options++;
188: }
189: if(valid_options!=options->count())
190: throw Exception(PARSER_RUNTIME, 0, CALLED_WITH_INVALID_OPTION);
191: }
192:
1.22 moko 193: if(const char *ip=pa_name2ip(name_cstr, family, table, PARSER_RUNTIME))
194: r.write(*new String(ip, String::L_TAINTED));
1.7 moko 195:
196: if(table)
1.14 moko 197: r.write(*new VTable(table));
1.7 moko 198: }
199:
1.20 moko 200: static void _hostname(Request& r, MethodParams&){
1.17 moko 201: char buf[MAX_STRING];
202:
203: if(gethostname(buf, MAX_STRING))
204: throw Exception(PARSER_RUNTIME, 0, "Cant't get hostname");
205:
206: r.write(*new String(pa_strdup(buf), String::L_TAINTED));
207: }
208:
1.1 misha 209: // constructor
210: MInet::MInet(): Methoded("inet") {
211: add_native_method("ntoa", Method::CT_STATIC, _ntoa, 1, 1);
212: add_native_method("aton", Method::CT_STATIC, _aton, 1, 1);
1.7 moko 213: // ^inet:ip2name[ip; $.ipv[4|6|any] ]
214: add_native_method("ip2name", Method::CT_STATIC, _ip2name, 1, 2);
215: // ^inet:name2ip[name; $.ipv[4|6|any] $.table(true) ]
216: add_native_method("name2ip", Method::CT_STATIC, _name2ip, 1, 2);
1.17 moko 217: // ^inet:hostname[]
218: add_native_method("hostname", Method::CT_STATIC, _hostname, 0, 0);
1.1 misha 219: }
E-mail: