RAPP Platform  v0.6.0
RAPP Platform is a collection of ROS nodes and back-end processes that aim to deliver ready-to-use generic services to robots
 All Classes Namespaces Files Functions Variables Macros
email_receiver.py
Go to the documentation of this file.
1 #!/usr/bin/env python
2 # -*- encode: utf-8 -*-
3 
4 #Copyright 2015 RAPP
5 
6 #Licensed under the Apache License, Version 2.0 (the "License");
7 #you may not use this file except in compliance with the License.
8 #You may obtain a copy of the License at
9 
10  #http://www.apache.org/licenses/LICENSE-2.0
11 
12 #Unless required by applicable law or agreed to in writing, software
13 #distributed under the License is distributed on an "AS IS" BASIS,
14 #WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 #See the License for the specific language governing permissions and
16 #limitations under the License.
17 
18 # Authors: Aris Thallas
19 # contact: aris.thallas@{iti.gr, gmail.com}
20 
21 import os
22 import datetime
23 import tempfile
24 import shutil
25 import atexit
26 import imaplib
27 import socket
28 import email
29 
30 import rospy
31 
32 from rapp_utilities import RappUtilities
33 
34 from rapp_platform_ros_communications.srv import (
35  ReceiveEmailSrv,
36  ReceiveEmailSrvResponse
37  )
38 
39 from rapp_platform_ros_communications.msg import (
40  MailMsg
41  )
42 
43 
44 ## @class EmailReceiver
45 # @brief Fetches emails from user's email account
46 class EmailReceiver(object):
47 
48  ## Constructor
49  def __init__(self):
50  receiveSrvTopic = rospy.get_param("rapp_email_receive_topic")
51  receiveSrv = rospy.Service(receiveSrvTopic, ReceiveEmailSrv, \
53 
54  ## The callback to receive specified mails from users email account
55  #
56  # @rapam req [rapp_platform_ros_communications::Email::ReceiveEmailSrvRequest] The receive email request
57  #
58  # @rapam res [rapp_platform_ros_communications::Email::ReceiveEmailSrvResponse] The receive email response
59  def receiveEmailSrvCallback(self, req):
60 
61  resp = ReceiveEmailSrvResponse()
62  resp.emails = []
63 
64  RappUtilities.rapp_print('Request:\n' + str(req), 'DEBUG')
65  try:
66  imapConn = self._connectImap(req.email, req.password, req.server, req.port)
67  except (imaplib.IMAP4.error, socket.error), err:
68  resp.status = -1
69  return resp
70 
71  # Select inbox
72  status, mailNum = imapConn.select()
73  if status.lower() != 'OK'.lower():
74  RappUtilities.rapp_print("Requested mail folder not found", 'ERROR')
75  resp.status = -1
76  return resp
77 
78  try:
79  emailUIDs = self._selectEmails(req, imapConn)
80  RappUtilities.rapp_print('# of email selected to fetch: ' + \
81  str(len(emailUIDs)), 'DEBUG')
82  except imaplib.IMAP4.error, err:
83  resp.status = -1
84  return resp
85  else:
86  if len(emailUIDs) == 0:
87  RappUtilities.rapp_print('No emails retrieved')
88  resp.status = 0
89  return resp
90 
91  # Fetch the last N requested
92  if req.numberOfEmails != 0 and len(emailUIDs) > req.numberOfEmails:
93  emailUIDs = emailUIDs[ len(emailUIDs) - req.numberOfEmails : ]
94 
95  emailPath = self._initializePath(req.email)
96 
97  emailCounter = 1
98  for emailID in emailUIDs:
99  emailData = self._fetchEmail(imapConn, emailID, emailPath, emailCounter)
100  resp.emails.append(emailData)
101  emailCounter += 1
102 
103  return resp
104 
105  ## Fetch specified email's data
106  #
107  # @param imap [imaplib::IMAP4_SSL] The connection
108  # @param emailID [int] The emails UID
109  # @param receivePath [string] The absolute path where the contents of the email should be written.
110  # @param emailCounter [int] The number of emails processed
111  #
112  # return mailMsg [rapp_platform_ros_communications::MailMsg] The mail msg to be appended in the service response
113  def _fetchEmail(self, imap, emailID, receivePath, emailCounter):
114  mailMsg = MailMsg()
115 
116  fetchStatus, data = imap.uid( 'fetch', emailID, '(RFC822)' )
117  mail = email.message_from_string( data[0][1] )
118 
119  # Create temporary directory for the email
120  emailPath = tempfile.mkdtemp( prefix = str(emailCounter) + '_', \
121  dir = receivePath)
122 
123  # Create temporary file for the email's body
124  bodyFD, bodyFilePath = tempfile.mkstemp( prefix='body_', \
125  dir = emailPath )
126  # Compose email's header
127  os.write( bodyFD, \
128  'From: ' + str(mail['From']) + '\n' + \
129  'To: ' + str(mail['To']) + '\n' + \
130  'CC: ' + str(mail['cc']) + '\n' + \
131  'Subject: ' + str(mail['Subject']) + '\n' \
132  'Date: ' + mail['Date'] + '\n\n' \
133  )
134 
135  # Create response's email element
136  mailMsg.bodyPath = bodyFilePath
137  mailMsg.subject = str(mail['Subject'])
138  mailMsg.dateTime = str(mail['Date'])
139  mailMsg.sender = str(mail['From'])
140  mailMsg.receivers = []
141  for receiver in str(mail['To']).split(','):
142  mailMsg.receivers.append(receiver.strip())
143 
144  if mail['cc'] is not None:
145  for receiver in str(mail['cc']).split(','):
146  mailMsg.receivers.append(receiver.strip())
147  mailMsg.attachmentPaths = []
148 
149  attachmentCounter = 1
150  #Iterate emails parts to get attachments
151  for part in mail.walk():
152  if part.get_content_maintype() == 'multipart':
153  continue
154  if part.get_content_maintype() == 'text':
155  # Get body text and discard text attachments
156  if part.get('Content-Disposition') is None and \
157  part.get_content_type() != 'text/html':
158  os.write(bodyFD, part.get_payload( decode = True ) )
159  os.write(bodyFD, '\n' )
160  continue
161 
162  if part.get('Content-Disposition') is None:
163  continue
164 
165  # This is an attachment
166  filename = part.get_filename()
167  if not filename:
168  filename = 'attachment-%03d' % attachmentCounter
169  attachmentCounter += 1
170 
171  # Create temporary file for the email's attachment
172  attachmentFD, attachmentPath = tempfile.mkstemp( prefix=filename + "_", \
173  dir = emailPath )
174 
175  os.write(attachmentFD, part.get_payload( decode = True ) )
176  os.close(attachmentFD)
177  mailMsg.attachmentPaths.append( attachmentPath )
178 
179  os.close(bodyFD)
180  return mailMsg
181 
182 
183  ## Create a temporary path for the user emails
184  #
185  # The path will be placed in ~/rapp_platform_files/emails/{username}{random_string}
186  #
187  # @param email [string] The rapp user's email
188  def _initializePath(self, email):
189  basePath = os.path.join( os.environ['HOME'], 'rapp_platform_files', 'emails' )
190 
191  if not os.path.exists( basePath ):
192  RappUtilities.rapp_print("Language temporary directory does not exist. " + \
193  'Path: ' + basePath)
194  os.makedirs( basePath )
195 
196  username = email.split('@')[0]
197  ## The temporary directory containing the configurations
198  finalPath = tempfile.mkdtemp( prefix=username + '_', dir = basePath)
199 
200  RappUtilities.rapp_print('Email receiver path: ' + finalPath, 'DEBUG')
201 
202  # Delete temp file at termination
203  # TODO: Check whether files are preserved long enough for the agent to collect
204  atexit.register(shutil.rmtree, finalPath)
205  return finalPath
206 
207 
208  ## @brief Fetch the emails that match the requests criteria
209  #
210  # @rapam req [rapp_platform_ros_communications::Email::ReceiveEmailSrvRequest] The receive email request
211  #
212  # @return emails [list<int>] The UIDs of the selected emails
213  #
214  # @except imaplib.IMAP4.error General imaplib error
215  def _selectEmails(self, req, imap):
216  if req.requestedEmailStatus == 'ALL':
217  requestedEmailStatus = 'ALL'
218  else:
219  requestedEmailStatus = 'UNSEEN'
220  if req.requestedEmailStatus != 'UNSEEN':
221  RappUtilities.rapp_print( \
222  'Wrong email status provided. ' + \
223  'See EmailReceiveSrv.srv for additional details. ' + \
224  'Falling back to default value: "UNSEEN"', 'WARN')
225 
226  fromDate = toDate = ''
227  if req.fromDate != 0:
228  dateString = datetime.datetime.fromtimestamp(req.fromDate).strftime( \
229  '%d-%b-%Y' )
230  fromDate = ' SINCE "' + dateString + '"'
231  if req.toDate != 0:
232  dateString = datetime.datetime.fromtimestamp(req.toDate).strftime( \
233  '%d-%b-%Y' )
234  toDate = ' BEFORE "' + dateString + '"'
235 
236  searchQuery = '(' + requestedEmailStatus + fromDate + toDate + ')'
237  RappUtilities.rapp_print(searchQuery, 'DEBUG')
238 
239  try:
240  searchStatus, msgIds = imap.uid( 'search', None, searchQuery )
241  except imaplib.IMAP4.error, err:
242  RappUtilities.rapp_print("Could not perform IMPA search. Query: " + \
243  searchQuery, 'ERROR')
244  RappUtilities.rapp_print( err, 'ERROR')
245  raise
246 
247  return msgIds[0].split()
248 
249 
250  ## @brief Create an IMAP connection to the server.
251  #
252  # Initiates an IMAP connection according to the user's email and logins to
253  # the server using user's credentials
254  #
255  # @param email [string] The user's email
256  # @param password [string] The user's password
257  # @param server [string] The email server
258  # @param port [string] The email server's port
259  #
260  # @return imap [imaplib::IMAP4_SSL] The connection
261 
262  # @except imaplib.IMAP4.error General imaplib error
263  # @exception socket.error Socket class exception (connection problem)
264  def _connectImap(self, email, password, server, port):
265 
266  try:
267  socket.setdefaulttimeout(5)
268  if port is not None and port != '':
269  imap = imaplib.IMAP4_SSL( server, port )
270  else:
271  imap = imaplib.IMAP4_SSL( server )
272  except (imaplib.IMAP4.error, socket.error), err:
273  RappUtilities.rapp_print( \
274  "Could not establish a connection to the requested IMAP server: " + \
275  server + ' port: ' + port, 'ERROR')
276  RappUtilities.rapp_print( err, 'ERROR')
277  raise err
278 
279  try:
280  imap.login( email, password )
281  except imaplib.IMAP4.error, err:
282  RappUtilities.rapp_print( \
283  "Could not login to the requested IMAP server: " + \
284  server, 'ERROR')
285  RappUtilities.rapp_print( err, 'ERROR')
286  raise err
287 
288  return imap
289 
290 
291 if __name__ == '__main__':
292  print('Implements server for EmailReceiverSrv. Not supposed to' + \
293  ' be called directly')
294  exit(-1)
def receiveEmailSrvCallback
The callback to receive specified mails from users email account.
def _selectEmails
Fetch the emails that match the requests criteria.
def _initializePath
Create a temporary path for the user emails.
def _fetchEmail
Fetch specified email's data.
Fetches emails from user's email account.
def _connectImap
Create an IMAP connection to the server.
std::vector< std::string > split(std::string str, std::string sep)
Splits string by delimiter.