1# -*- coding: utf-8 -*-
2#
3# Copyright (C) 2009-2010 John Garland <johnnybg+deluge@gmail.com>
4#
5# This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
6# the additional special exception to link portions of this program with the OpenSSL library.
7# See LICENSE for more details.
8#
9
10from __future__ import unicode_literals
11
12import logging
13import re
14
15from deluge.common import decode_bytes
16
17from .common import IP, BadIP, raises_errors_as
18
19log = logging.getLogger(__name__)
20
21
22class ReaderParseError(Exception):
23    pass
24
25
26class BaseReader(object):
27    """Base reader for blocklist files"""
28
29    def __init__(self, _file):
30        """Creates a new BaseReader given a file"""
31        self.file = _file
32
33    def open(self):
34        """Opens the associated file for reading"""
35        return open(self.file)
36
37    def parse(self, line):
38        """Extracts ip range from given line"""
39        raise NotImplementedError
40
41    def read(self, callback):
42        """Calls callback on each ip range in the file"""
43        for start, end in self.readranges():
44            try:
45                callback(IP.parse(start), IP.parse(end))
46            except BadIP as ex:
47                log.error('Failed to parse IP: %s', ex)
48        return self.file
49
50    def is_ignored(self, line):
51        """Ignore commented lines and blank lines"""
52        line = line.strip()
53        return line.startswith('#') or not line
54
55    def is_valid(self):
56        """Determines whether file is valid for this reader"""
57        blocklist = self.open()
58        valid = True
59        for line in blocklist:
60            line = decode_bytes(line)
61            if not self.is_ignored(line):
62                try:
63                    (start, end) = self.parse(line)
64                    if not re.match(r'^(\d{1,3}\.){4}$', start + '.') or not re.match(
65                        r'^(\d{1,3}\.){4}$', end + '.'
66                    ):
67                        valid = False
68                except Exception:
69                    valid = False
70                break
71        blocklist.close()
72        return valid
73
74    @raises_errors_as(ReaderParseError)
75    def readranges(self):
76        """Yields each ip range from the file"""
77        blocklist = self.open()
78        for line in blocklist:
79            line = decode_bytes(line)
80            if not self.is_ignored(line):
81                yield self.parse(line)
82        blocklist.close()
83
84
85class EmuleReader(BaseReader):
86    """Blocklist reader for emule style blocklists"""
87
88    def parse(self, line):
89        return line.strip().split(' , ')[0].split(' - ')
90
91
92class SafePeerReader(BaseReader):
93    """Blocklist reader for SafePeer style blocklists"""
94
95    def parse(self, line):
96        return line.strip().split(':')[-1].split('-')
97
98
99class PeerGuardianReader(SafePeerReader):
100    """Blocklist reader for PeerGuardian style blocklists"""
101
102    pass
103