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