1 """RFC 2822 message manipulation.
3 Note: This is only a very rough sketch of a full RFC-822 parser; in particular
4 the tokenizing of addresses does not adhere to all the quoting rules.
6 Note: RFC 2822 is a long awaited update to RFC 822. This module should
7 conform to RFC 2822, and is thus mis-named (it's not worth renaming it). Some
8 effort at RFC 2822 updates have been made, but a thorough audit has not been
9 performed. Consider any RFC 2822 non-conformance to be a bug.
11 RFC 2822: http://www.faqs.org/rfcs/rfc2822.html
12 RFC 822 : http://www.faqs.org/rfcs/rfc822.html (obsolete)
16 To create a Message object: first open a file, e.g.:
20 You can use any other legal way of getting an open file object, e.g. use
21 sys.stdin or call os.popen(). Then pass the open file object to the Message()
26 This class can work with any input object that supports a readline method. If
27 the input object has seek and tell capability, the rewindbody method will
28 work; also illegal lines will be pushed back onto the input stream. If the
29 input object lacks seek but has an `unread' method that can push back a line
30 of input, Message will use that to push back illegal lines. Thus this class
31 can be used to parse messages coming from a buffered stream.
33 The optional `seekable' argument is provided as a workaround for certain stdio
34 libraries in which tell() discards buffered data before discovering that the
35 lseek() system call doesn't work. For maximum portability, you should set the
36 seekable argument to zero to prevent that initial \code{tell} when passing in
37 an unseekable object such as a a file object created from a socket object. If
38 it is 1 on entry -- which it is by default -- the tell() method of the open
39 file object is called once; if this raises an exception, seekable is reset to
40 0. For other nonzero values of seekable, this test is not made.
42 To get the text of a particular header there are several methods:
44 str = m.getheader(name)
45 str = m.getrawheader(name)
47 where name is the name of the header, e.g. 'Subject'. The difference is that
48 getheader() strips the leading and trailing whitespace, while getrawheader()
49 doesn't. Both functions retain embedded whitespace (including newlines)
50 exactly as they are specified in the header, and leave the case of the text
53 For addresses and address lists there are functions
55 realname, mailaddress = m.getaddr(name)
56 list = m.getaddrlist(name)
58 where the latter returns a list of (realname, mailaddr) tuples.
60 There is also a method
62 time = m.getdate(name)
64 which parses a Date-like field and returns a time-compatible tuple,
65 i.e. a tuple such as returned by time.localtime() or accepted by
68 See the class definition for lower level access methods.
70 There are also some utility functions here.
72 # Cleanup and extensions by Eric S. Raymond <esr@thyrsus.com>
76 __all__
= ["Message","AddressList","parsedate","parsedate_tz","mktime_tz"]
78 _blanklines
= ('\r\n', '\n') # Optimization for islast()
82 """Represents a single RFC 2822-compliant message."""
84 def __init__(self
, fp
, seekable
= 1):
85 """Initialize the class instance and read the headers."""
87 # Exercise tell() to make sure it works
88 # (and then assume seek() works, too)
91 except (AttributeError, IOError):
94 self
.seekable
= seekable
95 self
.startofheaders
= None
96 self
.startofbody
= None
100 self
.startofheaders
= self
.fp
.tell()
108 self
.startofbody
= self
.fp
.tell()
112 def rewindbody(self
):
113 """Rewind the file to the start of the body (if seekable)."""
114 if not self
.seekable
:
115 raise IOError, "unseekable file"
116 self
.fp
.seek(self
.startofbody
)
118 def readheaders(self
):
119 """Read header lines.
121 Read header lines up to the entirely blank line that terminates them.
122 The (normally blank) line that ends the headers is skipped, but not
123 included in the returned list. If a non-header line ends the headers,
124 (which is an error), an attempt is made to backspace over it; it is
125 never included in the returned list.
127 The variable self.status is set to the empty string if all went well,
128 otherwise it is an error message. The variable self.headers is a
129 completely uninterpreted list of lines contained in the header (so
130 printing them will reproduce the header exactly as it appears in the
135 self
.headers
= lst
= []
139 startofline
= unread
= tell
= None
140 if hasattr(self
.fp
, 'unread'):
141 unread
= self
.fp
.unread
149 startofline
= tell
= None
151 line
= self
.fp
.readline()
153 self
.status
= 'EOF in headers'
155 # Skip unix From name time lines
156 if firstline
and line
.startswith('From '):
157 self
.unixfrom
= self
.unixfrom
+ line
160 if headerseen
and line
[0] in ' \t':
161 # It's a continuation line.
163 x
= (self
.dict[headerseen
] + "\n " + line
.strip())
164 self
.dict[headerseen
] = x
.strip()
166 elif self
.iscomment(line
):
167 # It's a comment. Ignore it.
169 elif self
.islast(line
):
170 # Note! No pushback here! The delimiter line gets eaten.
172 headerseen
= self
.isheader(line
)
174 # It's a legal header line, save it.
176 self
.dict[headerseen
] = line
[len(headerseen
)+1:].strip()
179 # It's not a header line; throw it back and stop here.
181 self
.status
= 'No headers'
183 self
.status
= 'Non-header line where header expected'
184 # Try to undo the read.
188 self
.fp
.seek(startofline
)
190 self
.status
= self
.status
+ '; bad seek'
193 def isheader(self
, line
):
194 """Determine whether a given line is a legal header.
196 This method should return the header name, suitably canonicalized.
197 You may override this method in order to use Message parsing on tagged
198 data in RFC 2822-like formats with special header formats.
202 return line
[:i
].lower()
205 def islast(self
, line
):
206 """Determine whether a line is a legal end of RFC 2822 headers.
208 You may override this method if your application wants to bend the
209 rules, e.g. to strip trailing whitespace, or to recognize MH template
210 separators ('--------'). For convenience (e.g. for code reading from
211 sockets) a line consisting of \r\n also matches.
213 return line
in _blanklines
215 def iscomment(self
, line
):
216 """Determine whether a line should be skipped entirely.
218 You may override this method in order to use Message parsing on tagged
219 data in RFC 2822-like formats that support embedded comments or
224 def getallmatchingheaders(self
, name
):
225 """Find all header lines matching a given header name.
227 Look through the list of headers and find all lines matching a given
228 header name (and their continuation lines). A list of the lines is
229 returned, without interpretation. If the header does not occur, an
230 empty list is returned. If the header occurs multiple times, all
231 occurrences are returned. Case is not important in the header name.
233 name
= name
.lower() + ':'
237 for line
in self
.headers
:
238 if line
[:n
].lower() == name
:
240 elif not line
[:1].isspace():
246 def getfirstmatchingheader(self
, name
):
247 """Get the first header line matching name.
249 This is similar to getallmatchingheaders, but it returns only the
250 first matching header (and its continuation lines).
252 name
= name
.lower() + ':'
256 for line
in self
.headers
:
258 if not line
[:1].isspace():
260 elif line
[:n
].lower() == name
:
266 def getrawheader(self
, name
):
267 """A higher-level interface to getfirstmatchingheader().
269 Return a string containing the literal text of the header but with the
270 keyword stripped. All leading, trailing and embedded whitespace is
271 kept in the string, however. Return None if the header does not
275 lst
= self
.getfirstmatchingheader(name
)
278 lst
[0] = lst
[0][len(name
) + 1:]
281 def getheader(self
, name
, default
=None):
282 """Get the header value for a name.
284 This is the normal interface: it returns a stripped version of the
285 header value for a given header name, or None if it doesn't exist.
286 This uses the dictionary version which finds the *last* such header.
288 return self
.dict.get(name
.lower(), default
)
291 def getheaders(self
, name
):
292 """Get all values for a header.
294 This returns a list of values for headers given more than once; each
295 value in the result list is stripped in the same way as the result of
296 getheader(). If the header is not given, return an empty list.
301 for s
in self
.getallmatchingheaders(name
):
304 current
= "%s\n %s" % (current
, s
.strip())
309 result
.append(current
)
310 current
= s
[s
.find(":") + 1:].strip()
313 result
.append(current
)
316 def getaddr(self
, name
):
317 """Get a single address from a header, as a tuple.
319 An example return value:
320 ('Guido van Rossum', 'guido@cwi.nl')
323 alist
= self
.getaddrlist(name
)
329 def getaddrlist(self
, name
):
330 """Get a list of addresses from a header.
332 Retrieves a list of addresses from a header, where each address is a
333 tuple as returned by getaddr(). Scans all named headers, so it works
334 properly with multiple To: or Cc: headers for example.
337 for h
in self
.getallmatchingheaders(name
):
347 alladdrs
= ''.join(raw
)
348 a
= AddressList(alladdrs
)
351 def getdate(self
, name
):
352 """Retrieve a date field from a header.
354 Retrieves a date field from the named header, returning a tuple
355 compatible with time.mktime().
361 return parsedate(data
)
363 def getdate_tz(self
, name
):
364 """Retrieve a date field from a header as a 10-tuple.
366 The first 9 elements make up a tuple compatible with time.mktime(),
367 and the 10th is the offset of the poster's time zone from GMT/UTC.
373 return parsedate_tz(data
)
376 # Access as a dictionary (only finds *last* header of each type):
379 """Get the number of headers in a message."""
380 return len(self
.dict)
382 def __getitem__(self
, name
):
383 """Get a specific header, as from a dictionary."""
384 return self
.dict[name
.lower()]
386 def __setitem__(self
, name
, value
):
387 """Set the value of a header.
389 Note: This is not a perfect inversion of __getitem__, because any
390 changed headers get stuck at the end of the raw-headers list rather
391 than where the altered header was.
393 del self
[name
] # Won't fail if it doesn't exist
394 self
.dict[name
.lower()] = value
395 text
= name
+ ": " + value
396 for line
in text
.split("\n"):
397 self
.headers
.append(line
+ "\n")
399 def __delitem__(self
, name
):
400 """Delete all occurrences of a specific header, if it is present."""
402 if not name
in self
.dict:
409 for i
in range(len(self
.headers
)):
410 line
= self
.headers
[i
]
411 if line
[:n
].lower() == name
:
413 elif not line
[:1].isspace():
417 for i
in reversed(lst
):
420 def setdefault(self
, name
, default
=""):
421 lowername
= name
.lower()
422 if lowername
in self
.dict:
423 return self
.dict[lowername
]
425 text
= name
+ ": " + default
426 for line
in text
.split("\n"):
427 self
.headers
.append(line
+ "\n")
428 self
.dict[lowername
] = default
431 def has_key(self
, name
):
432 """Determine whether a message contains the named header."""
433 return name
.lower() in self
.dict
435 def __contains__(self
, name
):
436 """Determine whether a message contains the named header."""
437 return name
.lower() in self
.dict
440 return iter(self
.dict)
443 """Get all of a message's header field names."""
444 return self
.dict.keys()
447 """Get all of a message's header field values."""
448 return self
.dict.values()
451 """Get all of a message's headers.
453 Returns a list of name, value tuples.
455 return self
.dict.items()
458 return ''.join(self
.headers
)
464 # XXX Should fix unquote() and quote() to be really conformant.
465 # XXX The inverses of the parse functions may also be useful.
469 """Remove quotes from a string."""
471 if s
.startswith('"') and s
.endswith('"'):
472 return s
[1:-1].replace('\\\\', '\\').replace('\\"', '"')
473 if s
.startswith('<') and s
.endswith('>'):
479 """Add quotes around a string."""
480 return s
.replace('\\', '\\\\').replace('"', '\\"')
483 def parseaddr(address
):
484 """Parse an address into a (realname, mailaddr) tuple."""
485 a
= AddressList(address
)
493 """Address parser class by Ben Escoto.
495 To understand what this class does, it helps to have a copy of
496 RFC 2822 in front of you.
498 http://www.faqs.org/rfcs/rfc2822.html
500 Note: this class interface is deprecated and may be removed in the future.
501 Use rfc822.AddressList instead.
504 def __init__(self
, field
):
505 """Initialize a new instance.
507 `field' is an unparsed address header field, containing one or more
510 self
.specials
= '()<>@,:;.\"[]'
514 self
.atomends
= self
.specials
+ self
.LWS
+ self
.CR
515 # Note that RFC 2822 now specifies `.' as obs-phrase, meaning that it
516 # is obsolete syntax. RFC 2822 requires that we recognize obsolete
517 # syntax, so allow dots in phrases.
518 self
.phraseends
= self
.atomends
.replace('.', '')
520 self
.commentlist
= []
523 """Parse up to the start of the next address."""
524 while self
.pos
< len(self
.field
):
525 if self
.field
[self
.pos
] in self
.LWS
+ '\n\r':
526 self
.pos
= self
.pos
+ 1
527 elif self
.field
[self
.pos
] == '(':
528 self
.commentlist
.append(self
.getcomment())
531 def getaddrlist(self
):
532 """Parse all addresses.
534 Returns a list containing all of the addresses.
537 ad
= self
.getaddress()
540 ad
= self
.getaddress()
543 def getaddress(self
):
544 """Parse the next address."""
545 self
.commentlist
= []
549 oldcl
= self
.commentlist
550 plist
= self
.getphraselist()
555 if self
.pos
>= len(self
.field
):
556 # Bad email address technically, no domain.
558 returnlist
= [(' '.join(self
.commentlist
), plist
[0])]
560 elif self
.field
[self
.pos
] in '.@':
561 # email address is just an addrspec
562 # this isn't very efficient since we start over
564 self
.commentlist
= oldcl
565 addrspec
= self
.getaddrspec()
566 returnlist
= [(' '.join(self
.commentlist
), addrspec
)]
568 elif self
.field
[self
.pos
] == ':':
572 fieldlen
= len(self
.field
)
574 while self
.pos
< len(self
.field
):
576 if self
.pos
< fieldlen
and self
.field
[self
.pos
] == ';':
579 returnlist
= returnlist
+ self
.getaddress()
581 elif self
.field
[self
.pos
] == '<':
582 # Address is a phrase then a route addr
583 routeaddr
= self
.getrouteaddr()
586 returnlist
= [(' '.join(plist
) + ' (' + \
587 ' '.join(self
.commentlist
) + ')', routeaddr
)]
588 else: returnlist
= [(' '.join(plist
), routeaddr
)]
592 returnlist
= [(' '.join(self
.commentlist
), plist
[0])]
593 elif self
.field
[self
.pos
] in self
.specials
:
597 if self
.pos
< len(self
.field
) and self
.field
[self
.pos
] == ',':
601 def getrouteaddr(self
):
602 """Parse a route address (Return-path value).
604 This method just skips all the route stuff and returns the addrspec.
606 if self
.field
[self
.pos
] != '<':
613 while self
.pos
< len(self
.field
):
617 elif self
.field
[self
.pos
] == '>':
620 elif self
.field
[self
.pos
] == '@':
623 elif self
.field
[self
.pos
] == ':':
626 adlist
= self
.getaddrspec()
633 def getaddrspec(self
):
634 """Parse an RFC 2822 addr-spec."""
638 while self
.pos
< len(self
.field
):
639 if self
.field
[self
.pos
] == '.':
642 elif self
.field
[self
.pos
] == '"':
643 aslist
.append('"%s"' % self
.getquote())
644 elif self
.field
[self
.pos
] in self
.atomends
:
646 else: aslist
.append(self
.getatom())
649 if self
.pos
>= len(self
.field
) or self
.field
[self
.pos
] != '@':
650 return ''.join(aslist
)
655 return ''.join(aslist
) + self
.getdomain()
658 """Get the complete domain name from an address."""
660 while self
.pos
< len(self
.field
):
661 if self
.field
[self
.pos
] in self
.LWS
:
663 elif self
.field
[self
.pos
] == '(':
664 self
.commentlist
.append(self
.getcomment())
665 elif self
.field
[self
.pos
] == '[':
666 sdlist
.append(self
.getdomainliteral())
667 elif self
.field
[self
.pos
] == '.':
670 elif self
.field
[self
.pos
] in self
.atomends
:
672 else: sdlist
.append(self
.getatom())
673 return ''.join(sdlist
)
675 def getdelimited(self
, beginchar
, endchars
, allowcomments
= 1):
676 """Parse a header fragment delimited by special characters.
678 `beginchar' is the start character for the fragment. If self is not
679 looking at an instance of `beginchar' then getdelimited returns the
682 `endchars' is a sequence of allowable end-delimiting characters.
683 Parsing stops when one of these is encountered.
685 If `allowcomments' is non-zero, embedded RFC 2822 comments are allowed
686 within the parsed fragment.
688 if self
.field
[self
.pos
] != beginchar
:
694 while self
.pos
< len(self
.field
):
696 slist
.append(self
.field
[self
.pos
])
698 elif self
.field
[self
.pos
] in endchars
:
701 elif allowcomments
and self
.field
[self
.pos
] == '(':
702 slist
.append(self
.getcomment())
703 continue # have already advanced pos from getcomment
704 elif self
.field
[self
.pos
] == '\\':
707 slist
.append(self
.field
[self
.pos
])
710 return ''.join(slist
)
713 """Get a quote-delimited fragment from self's field."""
714 return self
.getdelimited('"', '"\r', 0)
716 def getcomment(self
):
717 """Get a parenthesis-delimited fragment from self's field."""
718 return self
.getdelimited('(', ')\r', 1)
720 def getdomainliteral(self
):
721 """Parse an RFC 2822 domain-literal."""
722 return '[%s]' % self
.getdelimited('[', ']\r', 0)
724 def getatom(self
, atomends
=None):
725 """Parse an RFC 2822 atom.
727 Optional atomends specifies a different set of end token delimiters
728 (the default is to use self.atomends). This is used e.g. in
729 getphraselist() since phrase endings must not include the `.' (which
730 is legal in phrases)."""
733 atomends
= self
.atomends
735 while self
.pos
< len(self
.field
):
736 if self
.field
[self
.pos
] in atomends
:
738 else: atomlist
.append(self
.field
[self
.pos
])
741 return ''.join(atomlist
)
743 def getphraselist(self
):
744 """Parse a sequence of RFC 2822 phrases.
746 A phrase is a sequence of words, which are in turn either RFC 2822
747 atoms or quoted-strings. Phrases are canonicalized by squeezing all
748 runs of continuous whitespace into one space.
752 while self
.pos
< len(self
.field
):
753 if self
.field
[self
.pos
] in self
.LWS
:
755 elif self
.field
[self
.pos
] == '"':
756 plist
.append(self
.getquote())
757 elif self
.field
[self
.pos
] == '(':
758 self
.commentlist
.append(self
.getcomment())
759 elif self
.field
[self
.pos
] in self
.phraseends
:
762 plist
.append(self
.getatom(self
.phraseends
))
766 class AddressList(AddrlistClass
):
767 """An AddressList encapsulates a list of parsed RFC 2822 addresses."""
768 def __init__(self
, field
):
769 AddrlistClass
.__init
__(self
, field
)
771 self
.addresslist
= self
.getaddrlist()
773 self
.addresslist
= []
776 return len(self
.addresslist
)
779 return ", ".join(map(dump_address_pair
, self
.addresslist
))
781 def __add__(self
, other
):
783 newaddr
= AddressList(None)
784 newaddr
.addresslist
= self
.addresslist
[:]
785 for x
in other
.addresslist
:
786 if not x
in self
.addresslist
:
787 newaddr
.addresslist
.append(x
)
790 def __iadd__(self
, other
):
791 # Set union, in-place
792 for x
in other
.addresslist
:
793 if not x
in self
.addresslist
:
794 self
.addresslist
.append(x
)
797 def __sub__(self
, other
):
799 newaddr
= AddressList(None)
800 for x
in self
.addresslist
:
801 if not x
in other
.addresslist
:
802 newaddr
.addresslist
.append(x
)
805 def __isub__(self
, other
):
806 # Set difference, in-place
807 for x
in other
.addresslist
:
808 if x
in self
.addresslist
:
809 self
.addresslist
.remove(x
)
812 def __getitem__(self
, index
):
813 # Make indexing, slices, and 'in' work
814 return self
.addresslist
[index
]
816 def dump_address_pair(pair
):
817 """Dump a (name, address) pair in a canonicalized form."""
819 return '"' + pair
[0] + '" <' + pair
[1] + '>'
825 _monthnames
= ['jan', 'feb', 'mar', 'apr', 'may', 'jun', 'jul',
826 'aug', 'sep', 'oct', 'nov', 'dec',
827 'january', 'february', 'march', 'april', 'may', 'june', 'july',
828 'august', 'september', 'october', 'november', 'december']
829 _daynames
= ['mon', 'tue', 'wed', 'thu', 'fri', 'sat', 'sun']
831 # The timezone table does not include the military time zones defined
832 # in RFC822, other than Z. According to RFC1123, the description in
833 # RFC822 gets the signs wrong, so we can't rely on any such time
834 # zones. RFC1123 recommends that numeric timezone indicators be used
835 # instead of timezone names.
837 _timezones
= {'UT':0, 'UTC':0, 'GMT':0, 'Z':0,
838 'AST': -400, 'ADT': -300, # Atlantic (used in Canada)
839 'EST': -500, 'EDT': -400, # Eastern
840 'CST': -600, 'CDT': -500, # Central
841 'MST': -700, 'MDT': -600, # Mountain
842 'PST': -800, 'PDT': -700 # Pacific
846 def parsedate_tz(data
):
847 """Convert a date string to a time tuple.
849 Accounts for military timezones.
854 if data
[0][-1] in (',', '.') or data
[0].lower() in _daynames
:
855 # There's a dayname here. Skip it
858 # no space after the "weekday,"?
859 i
= data
[0].rfind(',')
861 data
[0] = data
[0][i
+1:]
862 if len(data
) == 3: # RFC 850 date, deprecated
863 stuff
= data
[0].split('-')
865 data
= stuff
+ data
[1:]
870 data
[3:] = [s
[:i
], s
[i
+1:]]
872 data
.append('') # Dummy tz
876 [dd
, mm
, yy
, tm
, tz
] = data
878 if not mm
in _monthnames
:
879 dd
, mm
= mm
, dd
.lower()
880 if not mm
in _monthnames
:
882 mm
= _monthnames
.index(mm
)+1
883 if mm
> 12: mm
= mm
- 12
891 if not yy
[0].isdigit():
914 tzoffset
= _timezones
[tz
]
920 # Convert a timezone offset into seconds ; -0500 -> -18000
927 tzoffset
= tzsign
* ( (tzoffset
//100)*3600 + (tzoffset
% 100)*60)
928 return (yy
, mm
, dd
, thh
, tmm
, tss
, 0, 1, 0, tzoffset
)
932 """Convert a time string to a time tuple."""
933 t
= parsedate_tz(data
)
940 """Turn a 10-tuple as returned by parsedate_tz() into a UTC timestamp."""
942 # No zone info, so localtime is better assumption than GMT
943 return time
.mktime(data
[:8] + (-1,))
945 t
= time
.mktime(data
[:8] + (0,))
946 return t
- data
[9] - time
.timezone
948 def formatdate(timeval
=None):
949 """Returns time format preferred for Internet standards.
951 Sun, 06 Nov 1994 08:49:37 GMT ; RFC 822, updated by RFC 1123
953 According to RFC 1123, day and month names must always be in
954 English. If not for that, this code could use strftime(). It
955 can't because strftime() honors the locale and could generated
959 timeval
= time
.time()
960 timeval
= time
.gmtime(timeval
)
961 return "%s, %02d %s %04d %02d:%02d:%02d GMT" % (
962 ("Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun")[timeval
[6]],
964 ("Jan", "Feb", "Mar", "Apr", "May", "Jun",
965 "Jul", "Aug", "Sep", "Oct", "Nov", "Dec")[timeval
[1]-1],
966 timeval
[0], timeval
[3], timeval
[4], timeval
[5])
969 # When used as script, run a small test program.
970 # The first command line argument must be a filename containing one
971 # message in RFC-822 format.
973 if __name__
== '__main__':
975 file = os
.path
.join(os
.environ
['HOME'], 'Mail/inbox/1')
976 if sys
.argv
[1:]: file = sys
.argv
[1]
979 print 'From:', m
.getaddr('from')
980 print 'To:', m
.getaddrlist('to')
981 print 'Subject:', m
.getheader('subject')
982 print 'Date:', m
.getheader('date')
983 date
= m
.getdate_tz('date')
985 date
= time
.localtime(mktime_tz(date
))
987 print 'ParsedDate:', time
.asctime(date
),
989 hhmm
, ss
= divmod(hhmmss
, 60)
990 hh
, mm
= divmod(hhmm
, 60)
991 print "%+03d%02d" % (hh
, mm
),
992 if ss
: print ".%02d" % ss
,
995 print 'ParsedDate:', None
1002 print 'len =', len(m
)
1003 if 'Date' in m
: print 'Date =', m
['Date']
1004 if 'X-Nonsense' in m
: pass
1005 print 'keys =', m
.keys()
1006 print 'values =', m
.values()
1007 print 'items =', m
.items()