home *** CD-ROM | disk | FTP | other *** search
/ Maximum CD 2009 June / maximum-cd-2009-06.iso / DiscContents / digsby_setup.exe / lib / mail / gmail.pyo (.txt) < prev    next >
Encoding:
Python Compiled Bytecode  |  2009-02-26  |  15.6 KB  |  455 lines

  1. # Source Generated with Decompyle++
  2. # File: in.pyo (Python 2.5)
  3.  
  4. from __future__ import with_statement
  5. import re
  6. import time
  7. import urllib2
  8. import cookielib
  9. import threading
  10. from threading import Lock
  11. from urllib import urlencode
  12. from datetime import datetime
  13. from traceback import print_exc
  14. from util.net import UrlQuery, GetDefaultHandlers, WebFormData
  15. from util import threaded, scrape_clean, EmailAddress, Storage
  16. from mail import Email
  17. from common import pref
  18. from common.emailaccount import EmailAccount
  19. from logging import getLogger
  20. log = getLogger('gmail')
  21. info = log.info
  22.  
  23. class Gmail(EmailAccount):
  24.     protocol = 'gmail'
  25.     baseAuthUrl = 'https://www.google.com'
  26.     authUrl = '/accounts/ClientAuth'
  27.     tokenUrl = '/accounts/IssueAuthToken'
  28.     messageIdMatcher = re.compile('message_id=([a-z0-9]+?)&')
  29.     jsredirectMatcher = re.compile('location\\.replace\\("(.*)"\\)')
  30.     default_domain = 'gmail.com'
  31.     
  32.     def __init__(self, **k):
  33.         EmailAccount.__init__(self, **k)
  34.         self.internal_token = ''
  35.         self.external_token = ''
  36.         self.token_lock = threading.RLock()
  37.         self.datatoken = ''
  38.         self.updated_emails = None
  39.         self.updated_count = None
  40.         self.update_lock = Lock()
  41.         self.emailaddress = EmailAddress(self.name, 'gmail.com')
  42.         if self.emailaddress.domain in ('gmail.com', 'googlemail.com'):
  43.             self.baseMailUrl = '://mail.google.com/mail/'
  44.         else:
  45.             self.baseMailUrl = '://mail.google.com/a/' + self.emailaddress.domain + '/'
  46.         self.browser_http = 'https'
  47.         self.init_jar()
  48.  
  49.     can_has_preview = True
  50.     
  51.     def _reset_state(self):
  52.         self.init_jar()
  53.         self.internal_token = self.external_token = ''
  54.  
  55.     
  56.     def browserBaseMailUrl(self):
  57.         return self.browser_http + self.baseMailUrl
  58.  
  59.     browserBaseMailUrl = property(browserBaseMailUrl)
  60.     
  61.     def internalBaseMailUrl(self):
  62.         return 'https' + self.baseMailUrl
  63.  
  64.     internalBaseMailUrl = property(internalBaseMailUrl)
  65.     
  66.     def init_jar(self):
  67.         self.internal_jar = cookielib.CookieJar()
  68.         self.internal_http_opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(self.internal_jar), *GetDefaultHandlers())
  69.         self.internal_http_opener.addheaders = [
  70.             ('Content-type', 'application/x-www-form-urlencoded'),
  71.             ('Cache-Control', 'no-cache')]
  72.         self.external_jar = cookielib.CookieJar()
  73.         self.external_http_opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(self.external_jar), *GetDefaultHandlers())
  74.         self.external_http_opener.addheaders = [
  75.             ('Content-type', 'application/x-www-form-urlencoded'),
  76.             ('Cache-Control', 'no-cache')]
  77.  
  78.     
  79.     def inbox_url(self):
  80.         return self._external_url(UrlQuery(self.browserBaseMailUrl, search = 'inbox'))
  81.  
  82.     inbox_url = property(inbox_url)
  83.     
  84.     def urlForEmail(self, email):
  85.         return self._external_url(UrlQuery(self.browserBaseMailUrl, fs = '1', tf = '1', view = 'cv', search = 'all', th = str(email.id)))
  86.  
  87.     
  88.     def markAsRead(self, email):
  89.         EmailAccount.markAsRead(self, email)
  90.         self._do_action('read', email)
  91.  
  92.     
  93.     def archive(self, email):
  94.         EmailAccount.archive(self, email)
  95.         self._do_action('archive', email)
  96.         if pref('gmail.markive', False):
  97.             self.markAsRead(email)
  98.         
  99.  
  100.     
  101.     def delete(self, email):
  102.         EmailAccount.delete(self, email)
  103.         self._do_action('delete', email)
  104.  
  105.     
  106.     def reportSpam(self, email):
  107.         EmailAccount.reportSpam(self, email)
  108.         self._do_action('spam', email)
  109.  
  110.     
  111.     def _do_action(self, action, email):
  112.         
  113.         try:
  114.             self.gmail_at
  115.         except KeyError:
  116.             self.new_token()
  117.  
  118.         (url, params) = self._actionUrl(action, email.id)
  119.         response = self.webrequest(url, **params)
  120.         log.debug_s('Action %r result: %r', action, response)
  121.  
  122.     _do_action = threaded(_do_action)
  123.     
  124.     def compose(self, to = '', subject = '', body = '', cc = '', bcc = ''):
  125.         extra = dict(fs = '1', view = 'cm')
  126.         su = subject
  127.         for name in 'to su body cc bcc'.split():
  128.             if vars()[name]:
  129.                 extra[name] = vars()[name]
  130.                 continue
  131.         
  132.         return self._external_url(UrlQuery(self.browserBaseMailUrl, **extra))
  133.  
  134.     
  135.     def _external_url(self, url):
  136.         if self.web_login:
  137.             self.new_token(internal = False)
  138.         
  139.         if self.web_login and self.external_token:
  140.             return UrlQuery('https://www.google.com/accounts/TokenAuth?', **{
  141.                 'auth': self.external_token,
  142.                 'service': 'mail',
  143.                 'continue': url,
  144.                 'source': 'googletalk' })
  145.         else:
  146.             return url
  147.  
  148.     
  149.     def _actionUrl(self, action, message_id):
  150.         action_names = dict(archive = 'rc_^i', delete = 'tr', read = 'rd', spam = 'sp')
  151.         if action not in action_names.values():
  152.             action = action_names[action]
  153.         
  154.         url = UrlQuery(self.internalBaseMailUrl, ik = '', search = 'all', view = 'tl', start = '0')
  155.         params = dict(act = action, at = self.gmail_at, vp = '', msq = '', ba = 'false', t = message_id, fs = '1')
  156.         return (url, params)
  157.  
  158.     
  159.     def send_email(self, to = '', subject = '', body = '', cc = '', bcc = ''):
  160.         log.info('sending a mail')
  161.         data = dict(nvp_bu_send = 'Send')
  162.         for name in 'to subject body cc bcc'.split():
  163.             if vars()[name]:
  164.                 data[name] = vars()[name].encode('utf-8')
  165.                 continue
  166.         
  167.         if not hasattr(self, 'sendpath'):
  168.             response = self.internal_http_opener.open(self.internalBaseMailUrl + '?ui=html')
  169.             urlparse = urlparse
  170.             import urllib2
  171.             respurl = urlparse.urlparse(response.geturl())
  172.             
  173.             try:
  174.                 response.close()
  175.             except:
  176.                 pass
  177.  
  178.             del response
  179.             self.sendpath = respurl.path
  180.         
  181.         url = 'https://mail.google.com' + self.sendpath
  182.         
  183.         try:
  184.             at = self.gmail_at
  185.         except KeyError:
  186.             at = ''
  187.  
  188.         params = dict(at = at, v = 'b', pv = 'tl', s = 's', fv = 'b', cpt = 'c', cs = 'c')
  189.         if not self.hosted:
  190.             params.update(fv = 'b', cpt = 'c', cs = 'c')
  191.         else:
  192.             params.update(cs = 'b', s = 's')
  193.         url = UrlQuery(url, params)
  194.         response = self.webrequest(url, follow_js_redirects = True, **data)
  195.         log.info('sent a mail')
  196.         log.info('send mail success: %r', bool('Your message has been sent.' in response))
  197.         return True
  198.  
  199.     send_email = threaded(send_email)
  200.     
  201.     def _get_notifier_data(self):
  202.         return self.webrequest(url = UrlQuery(self.internalBaseMailUrl, ui = 'pb'), data = '')
  203.  
  204.     
  205.     def hosted(self):
  206.         domain = self.emailaddress.domain
  207.         return None if domain != 'gmail.com' else None
  208.  
  209.     hosted = property(hosted)
  210.     
  211.     def authenticate(self, task = None):
  212.         self.internal_token = token = self.new_token(internal = True)
  213.         self.webrequest(UrlQuery('https://www.google.com/accounts/TokenAuth?', **{
  214.             'auth': token,
  215.             'service': 'mail',
  216.             'continue': self.internalBaseMailUrl,
  217.             'source': 'googletalk' }), internal = True)
  218.         self.new_token(False)
  219.         return True
  220.  
  221.     
  222.     def new_token(self, internal = True):
  223.         password = self._decryptedpw()
  224.         data = self.webrequest(self.baseAuthUrl + self.authUrl, internal = internal, data = WebFormData(Email = self.name, Passwd = password, PersistentCookie = 'false', accountType = 'HOSTED_OR_GOOGLE', skipvpage = 'true'))
  225.         if not data or data.find('Error=badauth') != -1:
  226.             log.warning('Invalid username or password')
  227.             self.bad_pw()
  228.             return False
  229.         
  230.         d1 = dict((lambda .0: for b in .0:
  231. b.split('='))(data.split()))
  232.         d1.update(Session = 'true', skipvpage = 'true', service = 'gaia')
  233.         token = self.webrequest(self.baseAuthUrl + self.tokenUrl, WebFormData(**d1))
  234.         token = token.strip()
  235.         if not internal:
  236.             self.external_token = token
  237.         
  238.         return token
  239.  
  240.     
  241.     def gmail_at(self):
  242.         hosted = self.hosted
  243.         at_path = None if hosted else '/mail'
  244.         return self.internal_jar._cookies['mail.google.com'][at_path]['GMAIL_AT'].value
  245.  
  246.     gmail_at = property(gmail_at)
  247.     
  248.     def update(self):
  249.         log.info('update at %s', time.ctime(time.time()))
  250.         EmailAccount.update(self)
  251.         self.real_update(success = self.finish_update, error = self.on_error)
  252.  
  253.     
  254.     def finish_update(self, updates):
  255.         if updates is sentinel:
  256.             log.warning('two updates were running at the same time')
  257.             return None
  258.         
  259.         
  260.         try:
  261.             (updated_emails, updated_count) = updates
  262.         except (TypeError, ValueError):
  263.             log.error('Update failed for %s, assuming auth error', self.name)
  264.             return None
  265.  
  266.         log.info('%s got %d new messages %s', self, updated_count, time.ctime(time.time()))
  267.         self._received_emails(updated_emails[:25], updated_count)
  268.  
  269.     
  270.     def real_update(self):
  271.         if self.update_lock.acquire(False):
  272.             
  273.             try:
  274.                 if not self.internal_token:
  275.                     info('no auth token yet, authenticating')
  276.                     if not self.authenticate():
  277.                         log.info('auth failed, returning None from real_update')
  278.                         return None
  279.                     
  280.                 
  281.                 info('updating Gmail account %s at %s' % (self.name, time.ctime(time.time())))
  282.                 notifier_data = self._get_notifier_data()
  283.                 
  284.                 try:
  285.                     (updated_emails, updated_count, _data) = parse_datapacks(notifier_data)
  286.                     updated_emails = chunks_to_emails(updated_emails)
  287.                 except Exception:
  288.                     log.critical('could not transform notifier_data: %r', notifier_data)
  289.                     raise 
  290.  
  291.                 return (updated_emails, updated_count)
  292.             finally:
  293.                 self.update_lock.release()
  294.  
  295.         else:
  296.             return sentinel
  297.  
  298.     real_update = threaded(real_update)
  299.     
  300.     def webrequest(self, url, data = '', follow_js_redirects = False, internal = True, **kwparams):
  301.         http_opener = None if internal else self.external_http_opener
  302.         
  303.         try:
  304.             response = http_opener.open(url, data + urlencode(kwparams.items()))
  305.             resp = response.read()
  306.             if follow_js_redirects:
  307.                 match = self.jsredirectMatcher.search(resp)
  308.                 if match:
  309.                     new_url = match.groups()[0]
  310.                     response = http_opener.open(self.baseAuthUrl + new_url)
  311.                     resp = response.read()
  312.                 
  313.             
  314.             return resp
  315.         except (urllib2.HTTPError, urllib2.URLError):
  316.             e = None
  317.             if getattr(e, 'code', None) == 403:
  318.                 log.warning('Invalid username or password')
  319.                 self.bad_pw()
  320.                 return False
  321.             else:
  322.                 print_exc()
  323.                 import sys
  324.                 print >>sys.stderr, 'url: %s' % url
  325.         except Exception:
  326.             e = None
  327.             print_exc()
  328.  
  329.         return False
  330.  
  331.  
  332. from util import utf7_to_int as u7i
  333.  
  334. def chunk_datapack(data):
  335.     data = data[1:]
  336.     (num, numbytes) = u7i(data)
  337.     data = data[numbytes:]
  338.     return (data[:num], data[num:])
  339.  
  340.  
  341. def get_chunk(data):
  342.     (type_, numbytes) = u7i(data)
  343.     data = data[numbytes:]
  344.     if type_ == 184:
  345.         (value, length_) = u7i(data)
  346.     elif type_ == 152:
  347.         (value, length_) = u7i(data)
  348.     else:
  349.         (length_, numbytes) = u7i(data)
  350.         data = data[numbytes:]
  351.     return (type_, data[:length_], data[length_:])
  352.  
  353.  
  354. def get_mid_date(data):
  355.     orig_length = len(data)
  356.     (length_, numbytes) = u7i(data)
  357.     expected_length = orig_length - length_ - numbytes
  358.     data = data[numbytes:]
  359.     (msgid, numbytes) = u7i(data)
  360.     data = data[numbytes:]
  361.     (_unknown, numbytes) = u7i(data)
  362.     data = data[numbytes:]
  363.     (time_in_ms, numbytes) = u7i(data)
  364.     data = data[numbytes:]
  365.     return (msgid, time_in_ms, data)
  366.  
  367. from collections import defaultdict
  368.  
  369. def parse_chunk(chunk):
  370.     retval = defaultdict(list)
  371.     (mid, time_in_ms, data) = get_mid_date(chunk)
  372.     retval['mid'] = mid
  373.     retval['time'] = time_in_ms
  374.     while data:
  375.         (t, v, data) = get_chunk(data)
  376.         if t == 146:
  377.             v = parse_from(v)
  378.         elif t == 184:
  379.             v = u7i(v)[0]
  380.         
  381.         retval[t].append(v)
  382.     return retval
  383.  
  384.  
  385. def chunks_to_emails(dictionaries):
  386.     
  387.     def safe_transform(x):
  388.         
  389.         try:
  390.             return dict_to_email(x)
  391.         except Exception:
  392.             e = None
  393.             log.error('Could not transform this dictionary into an email: %r', x)
  394.             raise e
  395.  
  396.  
  397.     return filter(bool, map(safe_transform, dictionaries))
  398.  
  399.  
  400. def parse_datapacks(data):
  401.     retval = []
  402.     while data[0] == '\n':
  403.         (chunk, data) = chunk_datapack(data)
  404.         retval.append(parse_chunk(chunk))
  405.     num_messages = 0
  406.     (type_, numbytes) = u7i(data)
  407.     data = data[numbytes:]
  408.     if type_ == 136:
  409.         (num_messages, numbytes) = u7i(data)
  410.         data = data[numbytes:]
  411.     
  412.     return (retval, num_messages, data)
  413.  
  414.  
  415. def parse_from(from_):
  416.     retval = { }
  417.     from_ = from_[1:]
  418.     (retval['mystery_bytes1'], from_) = from_.split('\n', 1)
  419.     (length_, numbytes) = u7i(from_)
  420.     from_ = from_[numbytes:]
  421.     retval['email_addr'] = from_[:length_]
  422.     from_ = from_[length_:]
  423.     (type_, numbytes) = u7i(from_)
  424.     if type_ == 18:
  425.         from_ = from_[numbytes:]
  426.         (length_, numbytes) = u7i(from_)
  427.         from_ = from_[numbytes:]
  428.         retval['from_text'] = from_[:length_]
  429.         from_ = from_[length_:]
  430.     
  431.     retval['remainder_bytes'] = from_
  432.     return retval
  433.  
  434.  
  435. decode = lambda s: scrape_clean(s.decode('utf-8'))
  436.  
  437. class GMAIL(object):
  438.     LABELS = 130
  439.     AUTHOR = 146
  440.     SUBJECT = 162
  441.     SNIPPET = 170
  442.     ATTACHMENTS = 178
  443.  
  444.  
  445. def dict_to_email(d):
  446.     msgid = d['mid']
  447.     author_email = d[GMAIL.AUTHOR][-1]['email_addr']
  448.     author_name = decode(d[GMAIL.AUTHOR][-1].get('from_text', ''))
  449.     subject = decode(d[GMAIL.SUBJECT][-1])
  450.     snippet = decode(d[GMAIL.SNIPPET][-1])
  451.     attachments = [ Storage(name = a) for a in d[GMAIL.ATTACHMENTS] ]
  452.     labels = _[2]
  453.     return Email(id = '%x' % msgid, fromname = author_name, fromemail = author_email, sendtime = datetime.fromtimestamp(d['time'] // 1000), subject = subject, content = snippet, attachments = attachments, labels = labels)
  454.  
  455.