File Coverage

lib/IP/World.xs
Criterion Covered Total %
statement 81 85 95.2
branch 71 90 78.8
condition n/a
subroutine n/a
pod n/a
total 152 175 86.8


line stmt bran cond sub pod time code
1             /* World.xs - XS module of the IP::World module
2              
3             this module maps from IP addresses to country codes, using
4             the free WorldIP database from wipmania.com and
5             the free GeoIPCountry database from maxmind.com */
6            
7             #ifdef __cplusplus
8             extern "C" {
9             #endif
10             #include "EXTERN.h"
11             #include "perl.h"
12             #include "XSUB.h"
13              
14             /* Required by Mac OS with XCode 12
15             Works on Linux and BSD
16             Not found on Windows but works anyway without */
17             #ifndef WIN32
18             #include
19             #endif
20              
21             #ifdef __cplusplus
22             }
23             #endif
24              
25             #if U32SIZE != 4
26             #error IP::World can only be run on a system in which the U32 type is 4 bytes long
27             #endif
28              
29             typedef unsigned char uc;
30              
31             typedef struct {
32             char *addr;
33             union {
34             #ifdef USE_PERLIO
35             PerlIO *p;
36             #endif
37             FILE *f;
38             } io;
39             UV entries;
40             U32 mode;
41             } wip_self;
42              
43             /* there doesn't seem to be a way to check if function inet_pton is defined */
44 644           int ck_ip4(const char *src, uc *dest) {
45             unsigned parts = 0;
46             int part = -1;
47             char c;
48              
49 4333 100         while ((c = *src++)) {
50 4011 100         if (c == '.') {
51 917 50         if (++parts > 3 || part < 0) return 0;
    100          
52 903           *dest++ = (uc)part;
53             part = -1;
54 3094 100         } else if ((c -= '0') >= 0
55 2905 100         && c <= 9) {
56 2814 100         if (part < 0) part = c;
57 3717 100         else if ((part = part*10 + c) > 255) return 0;
58             } else return 0;
59             }
60 322 100         if (part < 0 || parts < 3) return 0;
61 301           *dest = (uc)part;
62 301           return 1;
63             }
64              
65             /* subsequent code is in the specialized 'XS' dialect of C */
66              
67             MODULE = IP::World PACKAGE = IP::World
68              
69             PROTOTYPES: DISABLE
70              
71             SV *
72             allocNew(filepath, fileLen, mode=0)
73             const char *filepath
74             STRLEN fileLen
75             unsigned mode
76             PREINIT:
77             wip_self self;
78             int readLen;
79             CODE:
80             /* XS part of IP::World->new
81             allocate a block of memory and fill it from the ipworld.dat file */
82 9 50         if (mode > 3) croak("operand of IP::World::new = %d, should be 0-3", mode);
83             #ifdef USE_PERLIO
84 9 100         if (mode != 2) self.io.p = PerlIO_open(filepath, "rb");
85             else
86             #endif
87 2           self.io.f = fopen(filepath, "rb");
88 9 50         if (!self.io.f) croak("Can't open %s: %s", filepath, strerror(errno));
89 9           self.mode = mode;
90             #ifdef HAS_MMAP
91             #include
92 9 100         if (mode == 1) {
93             /* experimental feature: use mmap rather than read */
94             #ifdef USE_PERLIO
95 2           int fd = PerlIO_fileno(self.io.p);
96             #else
97             int fd = fileno(self.io.f);
98             #endif
99 2           self.addr = (char *)mmap(0, fileLen, PROT_READ, MAP_SHARED, fd, 0);
100 2 50         if (self.addr == MAP_FAILED)
101 0           croak ("mmap failed on %s: %s\n", filepath, strerror(errno));
102             } else
103             #endif
104 7 100         if (mode < 2) {
105             /* malloc a block of size fileLen */
106             #if (PERL_VERSION==8 && PERL_SUBVERSION > 7) || (PERL_VERSION==9 && PERL_SUBVERSION > 2) || PERL_VERSION > 9
107 3           Newx(self.addr, fileLen, char);
108             #else
109             New(0, self.addr, fileLen, char);
110             #endif
111 3 50         if (!self.addr) croak ("memory allocation for %s failed", filepath);
112             /* read the data from the .dat file into the new block */
113             #ifdef USE_PERLIO
114 3           readLen = PerlIO_read(self.io.p, self.addr, fileLen);
115             #else
116             readLen = fread(self.addr, 1, fileLen, self.io.f);
117             #endif
118 3 50         if (readLen < 0) croak("read from %s failed: %s", filepath, strerror(errno));
119 3 50         if ((STRLEN)readLen != fileLen)
120 0           croak("should have read %d bytes from %s, actually read %d",
121             (int)fileLen, filepath, readLen);
122 3           self.mode = 0;
123             }
124             /* all is well */
125 9 100         if (mode < 2)
126             #ifdef USE_PERLIO
127 5           PerlIO_close(self.io.p);
128             #else
129             fclose(self.io.f);
130             #endif
131             /* For each entry there is a 4 byte address plus a 10 bit country code.
132             At 3 codes/word, the number of entries = 3/16 * the number of bytes */
133 9           self.entries = fileLen*3 >> 4;
134             /* {new} in World.pm will bless the object we return */
135 9           RETVAL = newSVpv((const char *)(&self), sizeof(wip_self));
136             OUTPUT:
137             RETVAL
138              
139             SV*
140             getcc(self_ref, ip_sv)
141             SV* self_ref
142             SV* ip_sv
143             PREINIT:
144             SV* self_deref;
145             char *s;
146 651           STRLEN len = 0;
147             wip_self self;
148             I32 flgs;
149             uc netip[4];
150             register U32 ip, *ips;
151             register UV i, bottom = 0, top;
152             U32 word;
153 651           char c[3] = "**";
154             CODE:
155             /* $new_obj->getcc is only in XS/C
156             check that self_ref is defined ref; dref it; check len; copy to self */
157 651 50         if (sv_isobject(self_ref)) {
158 651           self_deref = SvRV(self_ref);
159 651 50         if (SvPOK(self_deref)) s = SvPV(self_deref, len);
    50          
160             }
161 651 50         if (len != sizeof(wip_self))
162 0           croak("automatic 'self' operand to getcc is not of correct type");
163             memcpy (&self, s, sizeof(wip_self));
164             /* the ip_sv argument can be of 2 types (if error return '**') */
165 651 100         if (!SvOK(ip_sv)) goto set_retval;
    50          
    50          
166 644           flgs = SvFLAGS(ip_sv);
167 644 50         if (!(flgs & (SVp_POK|SVf_NOK|SVp_NOK|SVf_IOK|SVp_IOK))) goto set_retval;
168 644 50         s = SvPV(ip_sv, len);
169             /* if the the ip operand is a dotted string, convert it to network-order U32
170             else if the operand does't look like a network-order U32, lose */
171 644 100         if (ck_ip4(s, netip) > 0) s = (char *)netip;
172 343 100         else if (len != 4) goto set_retval;
173             /* if necessary, convert network order (big-endian) to native endianism */
174 616           ip = (uc)s[0] << 24 | (uc)s[1] << 16 | (uc)s[2] << 8 | (uc)s[3];
175             /* binary-search the IP table */
176 616           top = self.entries;
177 616 100         if (self.mode < 2) {
178             /* memory mode */
179 264           ips = (U32 *)self.addr;
180 5040 100         while (bottom < top-1) {
181             /* compare ip to the table entry halfway between top and bottom */
182 4512           i = (bottom + top) >> 1;
183 4776 100         if (ip < ips[i]) top = i;
184             else bottom = i;
185             }
186             /* the table of country codes (3 per word) follows the table of IPs
187             copy the corresponding 3 entries to word */
188 264           word = *(ips + self.entries + bottom/3);
189             } else {
190             /* DASD mode */
191 6368 100         while (bottom < top-1) {
192             /* compare ip to the table entry halfway between top and bottom */
193 6016           i = (bottom + top) >> 1;
194             #ifdef USE_PERLIO
195 6016 100         if (self.mode == 3) {
196 3008           PerlIO_seek(self.io.p, i<<2, 0);
197 3008           PerlIO_read(self.io.p, &word, 4);
198             } else {
199             #endif
200 3008           fseek(self.io.f, i<<2, 0);
201             fread(&word, 4, 1, self.io.f);
202             #ifdef USE_PERLIO
203             }
204             #endif
205 6016 100         if (ip < word) top = i;
206             else bottom = i;
207             }
208             #ifdef USE_PERLIO
209             /* the table of country codes (3 per word) follows the table of IPs
210             read the corresponding 3 entries into word */
211 352 100         if (self.mode == 3) {
212 176           PerlIO_seek(self.io.p, (self.entries + bottom/3)<<2, 0);
213 176           PerlIO_read(self.io.p, &word, 4);
214             } else {
215             #endif
216 176           fseek(self.io.f, (self.entries + bottom/3)<<2, 0);
217             fread(&word, 4, 1, self.io.f);
218             #ifdef USE_PERLIO
219             }
220             #endif
221             }
222 616           switch (bottom % 3) {
223 245           case 0: word >>= 20; break;
224 189           case 1: word = word>>10 & 0x3FF; break;
225 182           default: word &= 0x3FF;
226             }
227 616 100         if (word == 26*26) c[0] = c[1] = '?';
228             else {
229 455           c[0] = (char)(word / 26) + 'A';
230 455           c[1] = (char)(word % 26) + 'A';
231             }
232             set_retval:
233 651           RETVAL = newSVpv(c, 2);
234             OUTPUT:
235             RETVAL
236              
237             void
238             DESTROY(self_ref)
239             SV* self_ref
240             PREINIT:
241             SV *self_deref;
242             char *s;
243 9           STRLEN len = 0;
244             wip_self self;
245             CODE:
246             /* DESTROY gives back allocated memory
247             check that self_ref is defined ref; dref it; check len; copy to self */
248 9 50         if (sv_isobject(self_ref)) {
249 9           self_deref = SvRV(self_ref);
250 9 50         if (SvPOK(self_deref))
251 9 50         s = SvPV(self_deref, len);
252             }
253 9 50         if (len != sizeof(wip_self))
254 0           croak("automatic 'self' operand to DESTROY is not of correct type");
255             memcpy (&self, s, sizeof(wip_self));
256             #ifdef HAS_MMAP
257 9 100         if (self.mode == 1) munmap((caddr_t)self.addr, (size_t)((self.entries<<4)/3));
258             else
259             #endif
260 7 100         if (self.mode < 2) Safefree(self.addr);
261             else
262             #ifdef USE_PERLIO
263 4 100         if (self.mode == 3) PerlIO_close(self.io.p);
264             else
265             #endif
266 2           fclose(self.io.f);