Update TODO based on work for 0.4.1
[python-rwhoisd.git] / rwhoisd / Rwhois.py
1 # This file is part of python-rwhoisd
2 #
3 # Copyright (C) 2003, David E. Blacka
4 #
5 # This program is free software; you can redistribute it and/or modify
6 # it under the terms of the GNU General Public License as published by
7 # the Free Software Foundation; either version 2 of the License, or
8 # (at your option) any later version.
9 #
10 # This program is distributed in the hope that it will be useful, but
11 # WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13 # General Public License for more details.
14 #
15 # You should have received a copy of the GNU General Public License
16 # along with this program; if not, write to the Free Software
17 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
18 # USA
19
20 # This modules contains classes that are fairly general to RWhois
21 # server operation.
22
23 class RwhoisError(Exception):
24     pass
25
26 # The RWhois error codes.  Most of these won't ever be used.
27 error_codes = { 120 : "Registration Deferred",
28                 130 : "Object Not Authoritative",
29                 230 : "No Objects Found",
30                 320 : "Invalid Attribute",
31                 321 : "Invalid Attribute Syntax",
32                 322 : "Required Attribute Missing",
33                 323 : "Object Reference Not Found",
34                 324 : "Primary Key Not Unique",
35                 325 : "Failed to Update Stale Object",
36                 330 : "Exceeded Response Limit",
37                 331 : "Invalid Limit",
38                 332 : "Nothing To Transfer",
39                 333 : "Not Master for Authority Area",
40                 336 : "Object Not Found",
41                 338 : "Invalid Directive Syntax",
42                 340 : "Invalid Authority Area",
43                 341 : "Invalid Class",
44                 342 : "Invalid Host/Port",
45                 350 : "Invalid Query Syntax",
46                 351 : "Query Too Complex",
47                 352 : "Invalid Security Method",
48                 353 : "Authentication Failed",
49                 354 : "Encryption Failed",
50                 360 : "Corrupt Data. Keyadd Failed",
51                 400 : "Directive Not Available",
52                 401 : "Not Authorized For Directive",
53                 402 : "Unidentified Error",
54                 420 : "Registration Not Authorized",
55                 436 : "Invalid Display Format",
56                 500 : "Memory Allocation Problem",
57                 501 : "Service Not Available",
58                 502 : "Unrecoverable Error",
59                 503 : "Idle Time Exceeded",
60                 560 : ""
61                 }
62
63 def error_message(value):
64     try:
65         code, msg = value
66         code = int(code)
67     except (TypeError, ValueError):
68         try:
69             code = int(value)
70             msg  = None
71         except ValueError:
72             msg  = value
73             code = 402
74     if msg:
75         return "%%error %d %s: %s\r\n" % \
76                (code, error_codes.get(code, 402), msg)
77     else:
78         return "%%error %d %s\r\n" % (code, error_codes.get(code, 402))
79
80 def ok():
81     return "%ok\r\n"
82
83 class rwhoisobject:
84     """This is the standard class for RWhois data objects."""
85
86     def __init__(self):
87         self.data = {}
88         self.attr_order = []
89
90     def get_attr(self, attr, default=None):
91         """This returns a list of values associated with a particular
92         attribute.  The default value, if supplied, must be a single
93         (non-sequence) value."""
94         
95         if default:
96             return self.data.get(attr.strip().lower(), [default])
97         return self.data.get(attr.strip().lower(), [])
98
99     def get_attr_value(self, attr, default=None):
100         """This returns a single value associated with the attribute.
101         If the attribute has multiple values, the first is
102         returned."""
103         
104         return self.data.get(attr.strip().lower(), [default])[0]
105
106     def has_attr(self, attr):
107         return self.data.has_key(attr.strip().lower())
108     
109     def getid(self):
110         """Return the RWhois ID of this object."""
111         
112         return self.get_attr_value("id")
113
114     def add_attr(self, attr, value):
115         """Adds an attribute to the object."""
116         
117         attr = attr.strip().lower()
118         if self.data.has_key(attr): self.data[attr].append(value)
119         else:
120             self.attr_order.append(attr)
121             self.data.setdefault(attr, []).append(value)
122
123     def add_attrs(self, attr_list):
124         """Adds a list of (attribute, value) tuples to the object."""
125         for attr, value in attr_list:
126             self.add_attr(attr, value)
127         
128     def items(self):
129         """Returns the list of (attribute, value) tuples (actually 2
130         elements lists).  Attributes with multiple values produce
131         multiple tuples.  The items are returned in the same order
132         they were added to the object."""
133         
134         return [ [x, y] for x in self.attr_order for y in self.data[x] ]
135
136     def values(self):
137         """Return the list of values in this object."""
138         
139         return [ x for y in self.data.values() for x in y ]
140     
141     def __str__(self):
142         """A convenient string representation of this object"""
143         return '\n'.join([':'.join(x) for x in self.items()])
144
145     def __repr__(self):
146         return "<rwhoisobject: " + self.getid() + ">"
147     
148     def attrs_to_wire_str(self, attrs, prefix=None):
149         """Return specific attributes in a response formatted string
150         (classname:attr:value)"""
151
152         cn = self.get_attr_value("class-name", "unknown-class")
153         items = [ [cn, x, y] for x in attrs for y in self.data[x] ]
154
155         if prefix:
156             res = '\r\n'.join([ prefix + ':'.join(x) for x in items ])
157         else:
158             res = '\r\n'.join([ ':'.join(x) for x in items ])
159
160         if not res.endswith("\r\n"):
161             res += "\r\n"
162
163         return res
164
165     def to_wire_str(self, prefix=None):
166         """Return the response formatted string (classname:attr:value)"""
167
168         return self.attrs_to_wire_str(self.attr_order, prefix)
169     
170
171
172 ## A basic test driver
173 if __name__ == '__main__':
174
175     obj = rwhoisobject()
176     obj.add_attr('id', '001')
177     obj.add_attr("class-name", 'contact')
178     obj.add_attr("class-name", "foo")
179     obj.add_attr('name', 'Aiden Quinn')
180     obj.add_attr('email', 'aquin@yahoo.com')
181     obj.add_attr('org-name', 'YoYoDyne Inc.')
182     obj.add_attr('email', 'aq@aol.net')
183     obj.add_attr('First-Name', 'Aiden ')
184
185     print "obj:\n", obj
186     print "wire:\n", obj.to_wire_str()