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