1# coding=utf-8
2from __future__ import unicode_literals
3
4from text_unidecode import unidecode
5
6from .. import BaseProvider
7
8from ipaddress import ip_address, ip_network, IPV4LENGTH, IPV6LENGTH
9
10# from faker.generator import random
11# from faker.providers.lorem.la import Provider as Lorem
12from faker.utils.decorators import lowercase, slugify, slugify_unicode
13
14
15localized = True
16
17
18IPV4_PUBLIC_NETS = {
19    'a': ip_network('0.0.0.0/1'),
20    'b': ip_network('128.0.0.0/2'),
21    'c': ip_network('192.0.0.0/3')
22}
23IPV4_PRIVATE_NET_BLOCKS = {
24    'a': (
25        ip_network('0.0.0.0/8'),
26        ip_network('10.0.0.0/8'),
27        ip_network('127.0.0.0/8'),
28    ),
29    'b': (
30        ip_network('169.254.0.0/16'),
31        ip_network('172.16.0.0/12')
32    ),
33    'c': (
34        ip_network('192.0.0.0/29'),
35        ip_network('192.0.0.170/31'),
36        ip_network('192.0.2.0/24'),
37        ip_network('192.168.0.0/16'),
38        ip_network('198.18.0.0/15'),
39        ip_network('198.51.100.0/24'),
40        ip_network('203.0.113.0/24')
41    ),
42}
43IPV4_NET_CLASSES = {
44    'a': (167772160, 184549375, 24),
45    'b': (2886729728, 2887778303, 20),
46    'c': (3232235520, 3232301055, 16)
47}
48
49
50class Provider(BaseProvider):
51    safe_email_tlds = ('org', 'com', 'net')
52    free_email_domains = ('gmail.com', 'yahoo.com', 'hotmail.com')
53    tlds = (
54        'com', 'com', 'com', 'com', 'com', 'com', 'biz', 'info', 'net', 'org'
55    )
56
57    uri_pages = (
58        'index', 'home', 'search', 'main', 'post', 'homepage', 'category',
59        'register', 'login', 'faq', 'about', 'terms', 'privacy', 'author'
60    )
61    uri_paths = (
62        'app', 'main', 'wp-content', 'search', 'category', 'tag', 'categories',
63        'tags', 'blog', 'posts', 'list', 'explore'
64    )
65    uri_extensions = (
66        '.html', '.html', '.html', '.htm', '.htm', '.php', '.php', '.jsp',
67        '.asp'
68    )
69
70    user_name_formats = (
71        '{{last_name}}.{{first_name}}',
72        '{{first_name}}.{{last_name}}',
73        '{{first_name}}##',
74        '?{{last_name}}',
75    )
76    email_formats = (
77        '{{user_name}}@{{domain_name}}',
78        '{{user_name}}@{{free_email_domain}}',
79    )
80    url_formats = (
81        'www.{{domain_name}}/',
82        '{{domain_name}}/',
83    )
84    uri_formats = (
85        '{{url}}',
86        '{{url}}{{uri_page}}/',
87        '{{url}}{{uri_page}}{{uri_extension}}',
88        '{{url}}{{uri_path}}/{{uri_page}}/',
89        '{{url}}{{uri_path}}/{{uri_page}}{{uri_extension}}',
90    )
91    image_placeholder_services = (
92        'https://placeholdit.imgix.net/~text'
93        '?txtsize=55&txt={width}x{height}&w={width}&h={height}',
94        'https://www.lorempixel.com/{width}/{height}',
95        'https://dummyimage.com/{width}x{height}',
96    )
97
98    replacements = tuple()
99
100    def _to_ascii(self, string):
101        for search, replace in self.replacements:
102            string = string.replace(search, replace)
103
104        string = unidecode(string)
105        return string
106
107    @lowercase
108    def email(self, domain=None):
109        if domain:
110            email = '{0}@{1}'.format(self.user_name(), domain)
111        else:
112            pattern = self.random_element(self.email_formats)
113            email = "".join(self.generator.parse(pattern).split(" "))
114        return email
115
116    @lowercase
117    def safe_email(self):
118        return '{}@example.{}'.format(
119            self.user_name(), self.random_element(self.safe_email_tlds)
120        )
121
122    @lowercase
123    def free_email(self):
124        return self.user_name() + '@' + self.free_email_domain()
125
126    @lowercase
127    def company_email(self):
128        return self.user_name() + '@' + self.domain_name()
129
130    @lowercase
131    def free_email_domain(self):
132        return self.random_element(self.free_email_domains)
133
134    @lowercase
135    def ascii_email(self):
136        pattern = self.random_element(self.email_formats)
137        return self._to_ascii(
138            "".join(self.generator.parse(pattern).split(" "))
139        )
140
141    @lowercase
142    def ascii_safe_email(self):
143        return self._to_ascii(
144            self.user_name() +
145            '@example.' +
146            self.random_element(self.safe_email_tlds)
147        )
148
149    @lowercase
150    def ascii_free_email(self):
151        return self._to_ascii(
152            self.user_name() + '@' + self.free_email_domain()
153        )
154
155    @lowercase
156    def ascii_company_email(self):
157        return self._to_ascii(
158            self.user_name() + '@' + self.domain_name()
159        )
160
161    @slugify_unicode
162    def user_name(self):
163        pattern = self.random_element(self.user_name_formats)
164        username = self._to_ascii(
165            self.bothify(self.generator.parse(pattern)).lower()
166        )
167        return username
168
169    @lowercase
170    def domain_name(self, levels=1):
171        """
172        Produce an Internet domain name with the specified number of
173        subdomain levels.
174
175        >>> domain_name()
176        nichols-phillips.com
177        >>> domain_name(2)
178        williamson-hopkins.jackson.com
179        """
180        if levels < 1:
181            raise ValueError("levels must be greater than or equal to 1")
182        if levels == 1:
183            return self.domain_word() + '.' + self.tld()
184        else:
185            return self.domain_word() + '.' + self.domain_name(levels - 1)
186
187    @lowercase
188    @slugify_unicode
189    def domain_word(self,):
190        company = self.generator.format('company')
191        company_elements = company.split(' ')
192        company = self._to_ascii(company_elements.pop(0))
193        return company
194
195    def tld(self):
196        return self.random_element(self.tlds)
197
198    def url(self, schemes=None):
199        """
200        :param schemes: a list of strings to use as schemes, one will chosen randomly.
201        If None, it will generate http and https urls.
202        Passing an empty list will result in schemeless url generation like "://domain.com".
203
204        :returns: a random url string.
205        """
206        if schemes is None:
207            schemes = ['http', 'https']
208
209        pattern = '{}://{}'.format(
210            self.random_element(schemes) if schemes else "",
211            self.random_element(self.url_formats)
212        )
213
214        return self.generator.parse(pattern)
215
216    def ipv4(self, network=False, address_class=None, private=None):
217        """
218        Produce a random IPv4 address or network with a valid CIDR.
219
220        :param network: Network address
221        :param address_class: IPv4 address class (a, b, or c)
222        :param private: Public or private
223        :returns: IPv4
224        """
225        if private is True:
226            return self.ipv4_private(address_class=address_class,
227                                     network=network)
228        elif private is False:
229            return self.ipv4_public(address_class=address_class,
230                                    network=network)
231        if address_class is None:
232            ip_range = (0, (2 ** IPV4LENGTH) - 1)
233        else:
234            net = IPV4_PUBLIC_NETS[address_class]
235            ip_range = (net._ip[0], net.ip[-1])
236        address = str(ip_address(self.generator.random.randint(*ip_range)))
237        if network:
238            address += '/' + str(self.generator.random.randint(0, IPV4LENGTH))
239            address = str(ip_network(address, strict=False))
240        return address
241
242    def ipv4_network_class(self):
243        """
244        Returns a IPv4 network class 'a', 'b' or 'c'.
245
246        :returns: IPv4 network class
247        """
248        return self.random_element('abc')
249
250    def ipv4_private(self, network=False, address_class=None):
251        """
252        Returns a private IPv4.
253
254        :param network: Network address
255        :param address_class: IPv4 address class (a, b, or c)
256        :returns: Private IPv4
257        """
258        address_class = address_class or self.ipv4_network_class()
259        min_, max_, netmask = IPV4_NET_CLASSES[address_class]
260        address = str(ip_address(
261            self.generator.random.randint(min_, max_)))
262        if network:
263            address += '/' + str(self.generator.random.randint(netmask, 31))
264            address = str(ip_network(address, strict=False))
265        return address
266
267    def ipv4_public(self, network=False, address_class=None):
268        """
269        Returns a public IPv4 excluding private blocks.
270
271        :param network: Network address
272        :param address_class: IPv4 address class (a, b, or c)
273        :returns: Public IPv4
274        """
275        address_class = address_class or self.ipv4_network_class()
276        public_net = IPV4_PUBLIC_NETS[address_class]
277        private_blocks = IPV4_PRIVATE_NET_BLOCKS[address_class]
278        # Make valid IP ranges, created from private block exclusion
279        net_ranges = []
280        ## Starts at end of 1st block if it's 1st of class
281        if public_net[0] != private_blocks[0][0]:
282            net_ranges = [(public_net[0]._ip, private_blocks[0][0]._ip-1)]
283        ## Loop on private blocks and guess available ranges
284        for i, block in enumerate(private_blocks):
285            if i+1 == len(private_blocks):
286                break
287            net_range = (block[-1]._ip+1, private_blocks[i+1][0]._ip-1)
288            net_ranges.append(net_range)
289        ## Add last range
290        net_ranges.append((private_blocks[-1][-1]._ip-1, public_net[-1]._ip-1))
291        net_ranges = [(i, j) for i, j in net_ranges if (j-i) > 0]
292        # Choose a range
293        min_, max_ = self.generator.random.choice(net_ranges)
294        address = str(ip_address(
295            self.generator.random.randint(min_, max_)))
296        # Add network mask
297        if network:
298            net_masks = {'a': 8, 'b': 16, 'c': 24}
299            min_net_mask = net_masks[address_class]
300            address += '/' + str(self.generator.random.randint(min_net_mask, 31))
301            address = str(ip_network(address, strict=False))
302        return address
303
304    def ipv6(self, network=False):
305        """Produce a random IPv6 address or network with a valid CIDR"""
306        address = str(ip_address(self.generator.random.randint(
307            2 ** IPV4LENGTH, (2 ** IPV6LENGTH) - 1)))
308        if network:
309            address += '/' + str(self.generator.random.randint(0, IPV6LENGTH))
310            address = str(ip_network(address, strict=False))
311        return address
312
313    def mac_address(self):
314        mac = [self.generator.random.randint(0x00, 0xff) for _ in range(0, 6)]
315        return ":".join(map(lambda x: "%02x" % x, mac))
316
317    def uri_page(self):
318        return self.random_element(self.uri_pages)
319
320    def uri_path(self, deep=None):
321        deep = deep if deep else self.generator.random.randint(1, 3)
322        return "/".join(
323            [self.random_element(self.uri_paths) for _ in range(0, deep)]
324        )
325
326    def uri_extension(self):
327        return self.random_element(self.uri_extensions)
328
329    def uri(self):
330        pattern = self.random_element(self.uri_formats)
331        return self.generator.parse(pattern)
332
333    @slugify
334    def slug(self, value=None):
335        """Django algorithm"""
336        if value is None:
337            value = self.generator.text(20)
338        return value
339
340    def image_url(self, width=None, height=None):
341        """
342        Returns URL to placeholder image
343        Example: http://placehold.it/640x480
344        """
345        width_ = width or self.random_int(max=1024)
346        height_ = height or self.random_int(max=1024)
347        placeholder_url = self.random_element(self.image_placeholder_services)
348        return placeholder_url.format(width=width_, height=height_)
349