~lefteris-nikoltsios/+junk/samba-lp1016895

« back to all changes in this revision

Viewing changes to lib/dnspython/dns/tokenizer.py

  • Committer: Package Import Robot
  • Author(s): Chuck Short
  • Date: 2011-12-21 13:18:04 UTC
  • mfrom: (0.39.21 sid)
  • Revision ID: package-import@ubuntu.com-20111221131804-xtlr39wx6njehxxr
Tags: 2:3.6.1-3ubuntu1
* Merge from Debian testing.  Remaining changes:
  + debian/patches/VERSION.patch:
    - set SAMBA_VERSION_SUFFIX to Ubuntu.
  + debian/patches/error-trans.fix-276472:
    - Add the translation of Unix Error code -ENOTSUP to NT Error Code
    - NT_STATUS_NOT_SUPPORTED to prevent the Permission denied error.
  + debian/smb.conf:
    - add "(Samba, Ubuntu)" to server string.
    - comment out the default [homes] share, and add a comment about
      "valid users = %S" to show users how to restrict access to
      \\server\username to only username.
    - Set 'usershare allow guests', so that usershare admins are 
      allowed to create public shares in addition to authenticated
      ones.
    - add map to guest = Bad user, maps bad username to guest access.
  + debian/samba-common.config:
    - Do not change priority to high if dhclient3 is installed.
    - Use priority medium instead of high for the workgroup question.
  + debian/control:
    - Don't build against or suggest ctdb.
    - Add dependency on samba-common-bin to samba.
  + Add ufw integration:
    - Created debian/samba.ufw.profile
    - debian/rules, debian/samba.dirs, debian/samba.files: install
      profile
    - debian/control: have samba suggest ufw
  + Add apport hook:
    - Created debian/source_samba.py.
    - debian/rules, debian/samba.dirs, debian/samba-common-bin.files: install
  + Switch to upstart:
    - Add debian/samba.{nmbd,smbd}.upstart.
  + debian/samba.logrotate, debian/samba-common.dhcp, debian/samba.if-up:
    - Make them upstart compatible
  + debian/samba.postinst: 
    - Avoid scary pdbedit warnings on first import.
  + debian/samba-common.postinst: Add more informative error message for
    the case where smb.conf was manually deleted
  + debian/patches/fix-debuglevel-name-conflict.patch: don't use 'debug_level'
    as a global variable name in an NSS module 
  + Dropped:
    - debian/patches/error-trans.fix-276472
    - debian/patches/fix-debuglevel-name-conflict.patch

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright (C) 2003-2007, 2009, 2010 Nominum, Inc.
 
2
#
 
3
# Permission to use, copy, modify, and distribute this software and its
 
4
# documentation for any purpose with or without fee is hereby granted,
 
5
# provided that the above copyright notice and this permission notice
 
6
# appear in all copies.
 
7
#
 
8
# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES
 
9
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 
10
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR
 
11
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 
12
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 
13
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
 
14
# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 
15
 
 
16
"""Tokenize DNS master file format"""
 
17
 
 
18
import cStringIO
 
19
import sys
 
20
 
 
21
import dns.exception
 
22
import dns.name
 
23
import dns.ttl
 
24
 
 
25
_DELIMITERS = {
 
26
    ' ' : True,
 
27
    '\t' : True,
 
28
    '\n' : True,
 
29
    ';' : True,
 
30
    '(' : True,
 
31
    ')' : True,
 
32
    '"' : True }
 
33
 
 
34
_QUOTING_DELIMITERS = { '"' : True }
 
35
 
 
36
EOF = 0
 
37
EOL = 1
 
38
WHITESPACE = 2
 
39
IDENTIFIER = 3
 
40
QUOTED_STRING = 4
 
41
COMMENT = 5
 
42
DELIMITER = 6
 
43
 
 
44
class UngetBufferFull(dns.exception.DNSException):
 
45
    """Raised when an attempt is made to unget a token when the unget
 
46
    buffer is full."""
 
47
    pass
 
48
 
 
49
class Token(object):
 
50
    """A DNS master file format token.
 
51
 
 
52
    @ivar ttype: The token type
 
53
    @type ttype: int
 
54
    @ivar value: The token value
 
55
    @type value: string
 
56
    @ivar has_escape: Does the token value contain escapes?
 
57
    @type has_escape: bool
 
58
    """
 
59
 
 
60
    def __init__(self, ttype, value='', has_escape=False):
 
61
        """Initialize a token instance.
 
62
 
 
63
        @param ttype: The token type
 
64
        @type ttype: int
 
65
        @ivar value: The token value
 
66
        @type value: string
 
67
        @ivar has_escape: Does the token value contain escapes?
 
68
        @type has_escape: bool
 
69
        """
 
70
        self.ttype = ttype
 
71
        self.value = value
 
72
        self.has_escape = has_escape
 
73
 
 
74
    def is_eof(self):
 
75
        return self.ttype == EOF
 
76
 
 
77
    def is_eol(self):
 
78
        return self.ttype == EOL
 
79
 
 
80
    def is_whitespace(self):
 
81
        return self.ttype == WHITESPACE
 
82
 
 
83
    def is_identifier(self):
 
84
        return self.ttype == IDENTIFIER
 
85
 
 
86
    def is_quoted_string(self):
 
87
        return self.ttype == QUOTED_STRING
 
88
 
 
89
    def is_comment(self):
 
90
        return self.ttype == COMMENT
 
91
 
 
92
    def is_delimiter(self):
 
93
        return self.ttype == DELIMITER
 
94
 
 
95
    def is_eol_or_eof(self):
 
96
        return (self.ttype == EOL or self.ttype == EOF)
 
97
 
 
98
    def __eq__(self, other):
 
99
        if not isinstance(other, Token):
 
100
            return False
 
101
        return (self.ttype == other.ttype and
 
102
                self.value == other.value)
 
103
 
 
104
    def __ne__(self, other):
 
105
        if not isinstance(other, Token):
 
106
            return True
 
107
        return (self.ttype != other.ttype or
 
108
                self.value != other.value)
 
109
 
 
110
    def __str__(self):
 
111
        return '%d "%s"' % (self.ttype, self.value)
 
112
 
 
113
    def unescape(self):
 
114
        if not self.has_escape:
 
115
            return self
 
116
        unescaped = ''
 
117
        l = len(self.value)
 
118
        i = 0
 
119
        while i < l:
 
120
            c = self.value[i]
 
121
            i += 1
 
122
            if c == '\\':
 
123
                if i >= l:
 
124
                    raise dns.exception.UnexpectedEnd
 
125
                c = self.value[i]
 
126
                i += 1
 
127
                if c.isdigit():
 
128
                    if i >= l:
 
129
                        raise dns.exception.UnexpectedEnd
 
130
                    c2 = self.value[i]
 
131
                    i += 1
 
132
                    if i >= l:
 
133
                        raise dns.exception.UnexpectedEnd
 
134
                    c3 = self.value[i]
 
135
                    i += 1
 
136
                    if not (c2.isdigit() and c3.isdigit()):
 
137
                        raise dns.exception.SyntaxError
 
138
                    c = chr(int(c) * 100 + int(c2) * 10 + int(c3))
 
139
            unescaped += c
 
140
        return Token(self.ttype, unescaped)
 
141
 
 
142
    # compatibility for old-style tuple tokens
 
143
 
 
144
    def __len__(self):
 
145
        return 2
 
146
 
 
147
    def __iter__(self):
 
148
        return iter((self.ttype, self.value))
 
149
 
 
150
    def __getitem__(self, i):
 
151
        if i == 0:
 
152
            return self.ttype
 
153
        elif i == 1:
 
154
            return self.value
 
155
        else:
 
156
            raise IndexError
 
157
 
 
158
class Tokenizer(object):
 
159
    """A DNS master file format tokenizer.
 
160
 
 
161
    A token is a (type, value) tuple, where I{type} is an int, and
 
162
    I{value} is a string.  The valid types are EOF, EOL, WHITESPACE,
 
163
    IDENTIFIER, QUOTED_STRING, COMMENT, and DELIMITER.
 
164
 
 
165
    @ivar file: The file to tokenize
 
166
    @type file: file
 
167
    @ivar ungotten_char: The most recently ungotten character, or None.
 
168
    @type ungotten_char: string
 
169
    @ivar ungotten_token: The most recently ungotten token, or None.
 
170
    @type ungotten_token: (int, string) token tuple
 
171
    @ivar multiline: The current multiline level.  This value is increased
 
172
    by one every time a '(' delimiter is read, and decreased by one every time
 
173
    a ')' delimiter is read.
 
174
    @type multiline: int
 
175
    @ivar quoting: This variable is true if the tokenizer is currently
 
176
    reading a quoted string.
 
177
    @type quoting: bool
 
178
    @ivar eof: This variable is true if the tokenizer has encountered EOF.
 
179
    @type eof: bool
 
180
    @ivar delimiters: The current delimiter dictionary.
 
181
    @type delimiters: dict
 
182
    @ivar line_number: The current line number
 
183
    @type line_number: int
 
184
    @ivar filename: A filename that will be returned by the L{where} method.
 
185
    @type filename: string
 
186
    """
 
187
 
 
188
    def __init__(self, f=sys.stdin, filename=None):
 
189
        """Initialize a tokenizer instance.
 
190
 
 
191
        @param f: The file to tokenize.  The default is sys.stdin.
 
192
        This parameter may also be a string, in which case the tokenizer
 
193
        will take its input from the contents of the string.
 
194
        @type f: file or string
 
195
        @param filename: the name of the filename that the L{where} method
 
196
        will return.
 
197
        @type filename: string
 
198
        """
 
199
 
 
200
        if isinstance(f, str):
 
201
            f = cStringIO.StringIO(f)
 
202
            if filename is None:
 
203
                filename = '<string>'
 
204
        else:
 
205
            if filename is None:
 
206
                if f is sys.stdin:
 
207
                    filename = '<stdin>'
 
208
                else:
 
209
                    filename = '<file>'
 
210
        self.file = f
 
211
        self.ungotten_char = None
 
212
        self.ungotten_token = None
 
213
        self.multiline = 0
 
214
        self.quoting = False
 
215
        self.eof = False
 
216
        self.delimiters = _DELIMITERS
 
217
        self.line_number = 1
 
218
        self.filename = filename
 
219
 
 
220
    def _get_char(self):
 
221
        """Read a character from input.
 
222
        @rtype: string
 
223
        """
 
224
 
 
225
        if self.ungotten_char is None:
 
226
            if self.eof:
 
227
                c = ''
 
228
            else:
 
229
                c = self.file.read(1)
 
230
                if c == '':
 
231
                    self.eof = True
 
232
                elif c == '\n':
 
233
                    self.line_number += 1
 
234
        else:
 
235
            c = self.ungotten_char
 
236
            self.ungotten_char = None
 
237
        return c
 
238
 
 
239
    def where(self):
 
240
        """Return the current location in the input.
 
241
 
 
242
        @rtype: (string, int) tuple.  The first item is the filename of
 
243
        the input, the second is the current line number.
 
244
        """
 
245
 
 
246
        return (self.filename, self.line_number)
 
247
 
 
248
    def _unget_char(self, c):
 
249
        """Unget a character.
 
250
 
 
251
        The unget buffer for characters is only one character large; it is
 
252
        an error to try to unget a character when the unget buffer is not
 
253
        empty.
 
254
 
 
255
        @param c: the character to unget
 
256
        @type c: string
 
257
        @raises UngetBufferFull: there is already an ungotten char
 
258
        """
 
259
 
 
260
        if not self.ungotten_char is None:
 
261
            raise UngetBufferFull
 
262
        self.ungotten_char = c
 
263
 
 
264
    def skip_whitespace(self):
 
265
        """Consume input until a non-whitespace character is encountered.
 
266
 
 
267
        The non-whitespace character is then ungotten, and the number of
 
268
        whitespace characters consumed is returned.
 
269
 
 
270
        If the tokenizer is in multiline mode, then newlines are whitespace.
 
271
 
 
272
        @rtype: int
 
273
        """
 
274
 
 
275
        skipped = 0
 
276
        while True:
 
277
            c = self._get_char()
 
278
            if c != ' ' and c != '\t':
 
279
                if (c != '\n') or not self.multiline:
 
280
                    self._unget_char(c)
 
281
                    return skipped
 
282
            skipped += 1
 
283
 
 
284
    def get(self, want_leading = False, want_comment = False):
 
285
        """Get the next token.
 
286
 
 
287
        @param want_leading: If True, return a WHITESPACE token if the
 
288
        first character read is whitespace.  The default is False.
 
289
        @type want_leading: bool
 
290
        @param want_comment: If True, return a COMMENT token if the
 
291
        first token read is a comment.  The default is False.
 
292
        @type want_comment: bool
 
293
        @rtype: Token object
 
294
        @raises dns.exception.UnexpectedEnd: input ended prematurely
 
295
        @raises dns.exception.SyntaxError: input was badly formed
 
296
        """
 
297
 
 
298
        if not self.ungotten_token is None:
 
299
            token = self.ungotten_token
 
300
            self.ungotten_token = None
 
301
            if token.is_whitespace():
 
302
                if want_leading:
 
303
                    return token
 
304
            elif token.is_comment():
 
305
                if want_comment:
 
306
                    return token
 
307
            else:
 
308
                return token
 
309
        skipped = self.skip_whitespace()
 
310
        if want_leading and skipped > 0:
 
311
            return Token(WHITESPACE, ' ')
 
312
        token = ''
 
313
        ttype = IDENTIFIER
 
314
        has_escape = False
 
315
        while True:
 
316
            c = self._get_char()
 
317
            if c == '' or c in self.delimiters:
 
318
                if c == '' and self.quoting:
 
319
                    raise dns.exception.UnexpectedEnd
 
320
                if token == '' and ttype != QUOTED_STRING:
 
321
                    if c == '(':
 
322
                        self.multiline += 1
 
323
                        self.skip_whitespace()
 
324
                        continue
 
325
                    elif c == ')':
 
326
                        if not self.multiline > 0:
 
327
                            raise dns.exception.SyntaxError
 
328
                        self.multiline -= 1
 
329
                        self.skip_whitespace()
 
330
                        continue
 
331
                    elif c == '"':
 
332
                        if not self.quoting:
 
333
                            self.quoting = True
 
334
                            self.delimiters = _QUOTING_DELIMITERS
 
335
                            ttype = QUOTED_STRING
 
336
                            continue
 
337
                        else:
 
338
                            self.quoting = False
 
339
                            self.delimiters = _DELIMITERS
 
340
                            self.skip_whitespace()
 
341
                            continue
 
342
                    elif c == '\n':
 
343
                        return Token(EOL, '\n')
 
344
                    elif c == ';':
 
345
                        while 1:
 
346
                            c = self._get_char()
 
347
                            if c == '\n' or c == '':
 
348
                                break
 
349
                            token += c
 
350
                        if want_comment:
 
351
                            self._unget_char(c)
 
352
                            return Token(COMMENT, token)
 
353
                        elif c == '':
 
354
                            if self.multiline:
 
355
                                raise dns.exception.SyntaxError('unbalanced parentheses')
 
356
                            return Token(EOF)
 
357
                        elif self.multiline:
 
358
                            self.skip_whitespace()
 
359
                            token = ''
 
360
                            continue
 
361
                        else:
 
362
                            return Token(EOL, '\n')
 
363
                    else:
 
364
                        # This code exists in case we ever want a
 
365
                        # delimiter to be returned.  It never produces
 
366
                        # a token currently.
 
367
                        token = c
 
368
                        ttype = DELIMITER
 
369
                else:
 
370
                    self._unget_char(c)
 
371
                break
 
372
            elif self.quoting:
 
373
                if c == '\\':
 
374
                    c = self._get_char()
 
375
                    if c == '':
 
376
                        raise dns.exception.UnexpectedEnd
 
377
                    if c.isdigit():
 
378
                        c2 = self._get_char()
 
379
                        if c2 == '':
 
380
                            raise dns.exception.UnexpectedEnd
 
381
                        c3 = self._get_char()
 
382
                        if c == '':
 
383
                            raise dns.exception.UnexpectedEnd
 
384
                        if not (c2.isdigit() and c3.isdigit()):
 
385
                            raise dns.exception.SyntaxError
 
386
                        c = chr(int(c) * 100 + int(c2) * 10 + int(c3))
 
387
                elif c == '\n':
 
388
                    raise dns.exception.SyntaxError('newline in quoted string')
 
389
            elif c == '\\':
 
390
                #
 
391
                # It's an escape.  Put it and the next character into
 
392
                # the token; it will be checked later for goodness.
 
393
                #
 
394
                token += c
 
395
                has_escape = True
 
396
                c = self._get_char()
 
397
                if c == '' or c == '\n':
 
398
                    raise dns.exception.UnexpectedEnd
 
399
            token += c
 
400
        if token == '' and ttype != QUOTED_STRING:
 
401
            if self.multiline:
 
402
                raise dns.exception.SyntaxError('unbalanced parentheses')
 
403
            ttype = EOF
 
404
        return Token(ttype, token, has_escape)
 
405
 
 
406
    def unget(self, token):
 
407
        """Unget a token.
 
408
 
 
409
        The unget buffer for tokens is only one token large; it is
 
410
        an error to try to unget a token when the unget buffer is not
 
411
        empty.
 
412
 
 
413
        @param token: the token to unget
 
414
        @type token: Token object
 
415
        @raises UngetBufferFull: there is already an ungotten token
 
416
        """
 
417
 
 
418
        if not self.ungotten_token is None:
 
419
            raise UngetBufferFull
 
420
        self.ungotten_token = token
 
421
 
 
422
    def next(self):
 
423
        """Return the next item in an iteration.
 
424
        @rtype: (int, string)
 
425
        """
 
426
 
 
427
        token = self.get()
 
428
        if token.is_eof():
 
429
            raise StopIteration
 
430
        return token
 
431
 
 
432
    def __iter__(self):
 
433
        return self
 
434
 
 
435
    # Helpers
 
436
 
 
437
    def get_int(self):
 
438
        """Read the next token and interpret it as an integer.
 
439
 
 
440
        @raises dns.exception.SyntaxError:
 
441
        @rtype: int
 
442
        """
 
443
 
 
444
        token = self.get().unescape()
 
445
        if not token.is_identifier():
 
446
            raise dns.exception.SyntaxError('expecting an identifier')
 
447
        if not token.value.isdigit():
 
448
            raise dns.exception.SyntaxError('expecting an integer')
 
449
        return int(token.value)
 
450
 
 
451
    def get_uint8(self):
 
452
        """Read the next token and interpret it as an 8-bit unsigned
 
453
        integer.
 
454
 
 
455
        @raises dns.exception.SyntaxError:
 
456
        @rtype: int
 
457
        """
 
458
 
 
459
        value = self.get_int()
 
460
        if value < 0 or value > 255:
 
461
            raise dns.exception.SyntaxError('%d is not an unsigned 8-bit integer' % value)
 
462
        return value
 
463
 
 
464
    def get_uint16(self):
 
465
        """Read the next token and interpret it as a 16-bit unsigned
 
466
        integer.
 
467
 
 
468
        @raises dns.exception.SyntaxError:
 
469
        @rtype: int
 
470
        """
 
471
 
 
472
        value = self.get_int()
 
473
        if value < 0 or value > 65535:
 
474
            raise dns.exception.SyntaxError('%d is not an unsigned 16-bit integer' % value)
 
475
        return value
 
476
 
 
477
    def get_uint32(self):
 
478
        """Read the next token and interpret it as a 32-bit unsigned
 
479
        integer.
 
480
 
 
481
        @raises dns.exception.SyntaxError:
 
482
        @rtype: int
 
483
        """
 
484
 
 
485
        token = self.get().unescape()
 
486
        if not token.is_identifier():
 
487
            raise dns.exception.SyntaxError('expecting an identifier')
 
488
        if not token.value.isdigit():
 
489
            raise dns.exception.SyntaxError('expecting an integer')
 
490
        value = long(token.value)
 
491
        if value < 0 or value > 4294967296L:
 
492
            raise dns.exception.SyntaxError('%d is not an unsigned 32-bit integer' % value)
 
493
        return value
 
494
 
 
495
    def get_string(self, origin=None):
 
496
        """Read the next token and interpret it as a string.
 
497
 
 
498
        @raises dns.exception.SyntaxError:
 
499
        @rtype: string
 
500
        """
 
501
 
 
502
        token = self.get().unescape()
 
503
        if not (token.is_identifier() or token.is_quoted_string()):
 
504
            raise dns.exception.SyntaxError('expecting a string')
 
505
        return token.value
 
506
 
 
507
    def get_identifier(self, origin=None):
 
508
        """Read the next token and raise an exception if it is not an identifier.
 
509
 
 
510
        @raises dns.exception.SyntaxError:
 
511
        @rtype: string
 
512
        """
 
513
 
 
514
        token = self.get().unescape()
 
515
        if not token.is_identifier():
 
516
            raise dns.exception.SyntaxError('expecting an identifier')
 
517
        return token.value
 
518
 
 
519
    def get_name(self, origin=None):
 
520
        """Read the next token and interpret it as a DNS name.
 
521
 
 
522
        @raises dns.exception.SyntaxError:
 
523
        @rtype: dns.name.Name object"""
 
524
 
 
525
        token = self.get()
 
526
        if not token.is_identifier():
 
527
            raise dns.exception.SyntaxError('expecting an identifier')
 
528
        return dns.name.from_text(token.value, origin)
 
529
 
 
530
    def get_eol(self):
 
531
        """Read the next token and raise an exception if it isn't EOL or
 
532
        EOF.
 
533
 
 
534
        @raises dns.exception.SyntaxError:
 
535
        @rtype: string
 
536
        """
 
537
 
 
538
        token = self.get()
 
539
        if not token.is_eol_or_eof():
 
540
            raise dns.exception.SyntaxError('expected EOL or EOF, got %d "%s"' % (token.ttype, token.value))
 
541
        return token.value
 
542
 
 
543
    def get_ttl(self):
 
544
        token = self.get().unescape()
 
545
        if not token.is_identifier():
 
546
            raise dns.exception.SyntaxError('expecting an identifier')
 
547
        return dns.ttl.from_text(token.value)