home *** CD-ROM | disk | FTP | other *** search
/ Maximum CD 2011 October / maximum-cd-2011-10.iso / DiscContents / digsby_setup.exe / lib / plugins / component_gmail / gmail.pyo (.txt) < prev    next >
Encoding:
Python Compiled Bytecode  |  2011-06-22  |  19.4 KB  |  584 lines

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