blob: 63cb6d2271a08746e6e0f0e9b26218d6640be79d [file] [log] [blame]
#!/usr/bin/env python
#
# Copyright 2007 Google Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
"""A handler that allows the user to send XMPP messages to their application."""
import cStringIO
import random
import string
from xml.etree import ElementTree
from google.appengine.tools.devappserver2.admin import admin_request_handler
ElementTree.register_namespace('cli', 'jabber:client')
REMOTE_IP = '0.1.0.10'
class _FormData(object):
def __init__(self):
self._data = []
def __eq__(self, o):
return isinstance(o, _FormData) and self._data == o._data
def __str__(self):
_, content = self.get_boundary_and_content()
return content
__repr__ = __str__
def add_text(self, name, value, sub_type):
self._data.append((name, value, sub_type))
def get_boundary_and_content(self):
"""Returns the message boundary and entire content body for the form."""
boundary = '----=' + ''.join(random.choice(string.letters + string.digits)
for _ in range(25))
s = cStringIO.StringIO()
for name, value, sub_type in self._data:
s.write('--%s\r\n' % boundary)
s.write('Content-Type: text/%s; charset="UTF-8"\r\n' % sub_type)
s.write('Content-Disposition: form-data; name="%s"\r\n' % name)
s.write('\r\n')
s.write('%s\r\n' % value.encode('utf-8'))
s.write('--%s--\r\n' % boundary)
return boundary, s.getvalue()
class XmppRequestHandler(admin_request_handler.AdminRequestHandler):
def get(self):
# TODO: Generate a warning if XMPP is not configured when the
# configuration is sorted in the new servers world.
self.response.write(self.render('xmpp.html', {}))
def post(self):
message_type = self.request.get('message_type')
to = self.request.get('to')
from_ = self.request.get('from')
if message_type == 'chat':
self._send_chat(to, from_)
elif message_type == 'presence':
if self.request.get('presence') == 'available':
self._send_presence_available(to, from_)
elif self.request.get('presence') == 'unavailable':
self._send_presence_unavailable(to, from_)
else:
assert 0
elif message_type == 'subscribe':
self._send_subscription(to, from_)
else:
assert 0
def _generate_chat(self, to, from_, body):
data = _FormData()
data.add_text('from', from_, 'plain')
data.add_text('to', to, 'plain')
data.add_text('body', body, 'plain')
message_element = ElementTree.Element(
ElementTree.QName('jabber:client', 'message'),
{'from': from_, 'to': to, 'type': 'chat'})
body_element = ElementTree.SubElement(
message_element,
ElementTree.QName('jabber:client', 'body'))
body_element.text = body
data.add_text('stanza',
ElementTree.tostring(message_element, encoding='utf-8'),
'xml')
return data
def _send_chat(self, to, from_):
body = self.request.get('chat')
form_data = self._generate_chat(to, from_, body)
response = self._send('/_ah/xmpp/message/chat/', form_data)
self.response.status = response.status
def _generate_presence_available(self, to, from_, show=None):
data = _FormData()
data.add_text('from', from_, 'plain')
data.add_text('to', to, 'plain')
# If the "presence" attribute is absent, "available" is assumed and it is
# not sent by Google Talk.
presence_element = ElementTree.Element(
ElementTree.QName('jabber:client', 'presence'),
{'from': from_, 'to': to})
if show: # This is currently a dead code path.
# The show element is optional according to RFC 3921, 2.2.2.1.
data.add_text('show', show, 'plain')
show_element = ElementTree.SubElement(
presence_element,
ElementTree.QName('jabber:client', 'show'))
show_element.text = show
data.add_text('stanza',
ElementTree.tostring(presence_element, 'utf-8'),
'xml')
return data
def _send(self, relative_url, form_data):
boundary, content = form_data.get_boundary_and_content()
return self.dispatcher.add_request(
method='POST',
relative_url=relative_url,
headers=[('Content-Type',
'multipart/form-data; boundary="%s"' % boundary)],
body=content,
source_ip=REMOTE_IP,
fake_login=True)
def _send_presence_available(self, to, from_):
# TODO: Support "show" values i.e. "away", "chat" "dnd" and "xa".
# See RFC 3921, 2.2.2.1.
form_data = self._generate_presence_available(to, from_)
response = self._send('/_ah/xmpp/presence/available/', form_data)
self.response.status = response.status
def _generate_presence_type(self, to, from_, presence_type):
data = _FormData()
data.add_text('from', from_, 'plain')
data.add_text('to', to, 'plain')
presence_element = ElementTree.Element(
ElementTree.QName('jabber:client', 'presence'),
{'from': from_, 'to': to, 'type': presence_type})
data.add_text('stanza',
ElementTree.tostring(presence_element, 'utf-8'),
'xml')
return data
def _send_presence_unavailable(self, to, from_):
form_data = self._generate_presence_type(to, from_, 'unavailable')
response = self._send('/_ah/xmpp/presence/unavailable/', form_data)
self.response.status = response.status
def _send_subscription(self, to, from_):
subscription_type = self.request.get('subscription_type')
# The subscription types defined in RFC 3921, 2.2.1
assert subscription_type in ['subscribe',
'subscribed',
'unsubscribe',
'unsubscribed']
form_data = self._generate_presence_type(to, from_, subscription_type)
response = self._send('/_ah/xmpp/subscription/%s/' % subscription_type,
form_data)
self.response.status = response.status