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: