From 45412fd404157d4afc1d08eb5b785b94a55e957c Mon Sep 17 00:00:00 2001
From: Kim Alvefur <zash@zash.se>
Date: Wed, 29 Jun 2011 09:20:48 +0800
Subject: Do a weighted choice among the highest prioritized items based on
 weight instead of a weighted choice based on priorities.

---
 sleekxmpp/clientxmpp.py | 17 +++++++++++------
 1 file changed, 11 insertions(+), 6 deletions(-)

diff --git a/sleekxmpp/clientxmpp.py b/sleekxmpp/clientxmpp.py
index fb5b2087..1ac3d8bd 100644
--- a/sleekxmpp/clientxmpp.py
+++ b/sleekxmpp/clientxmpp.py
@@ -168,18 +168,23 @@ class ClientXMPP(BaseXMPP):
 
                     addresses = {}
                     intmax = 0
+                    topprio = 65535
                     for answer in answers:
-                        intmax += answer.priority
-                        addresses[intmax] = (answer.target.to_text()[:-1],
+                        topprio = min(topprio, answer.priority)
+                    for answer in answers:
+                        if answer.priority == topprio:
+                            intmax += answer.weight
+                            addresses[intmax] = (answer.target.to_text()[:-1],
                                              answer.port)
+
                     #python3 returns a generator for dictionary keys
-                    priorities = [x for x in addresses.keys()]
-                    priorities.sort()
+                    items = [x for x in addresses.keys()]
+                    items.sort()
 
                     picked = random.randint(0, intmax)
-                    for priority in priorities:
+                    for item in items:
                         if picked <= priority:
-                            address = addresses[priority]
+                            address = addresses[item]
                             break
 
         if not address:
-- 
cgit v1.2.3


From ad032e5ed77cfd1ec354e68e49d755e004408d0e Mon Sep 17 00:00:00 2001
From: Lance Stout <lancestout@gmail.com>
Date: Wed, 27 Jul 2011 18:40:57 -0700
Subject: Fix error with DNS selection.

Missed a renaming of 'priority' to 'item'
---
 sleekxmpp/clientxmpp.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/sleekxmpp/clientxmpp.py b/sleekxmpp/clientxmpp.py
index 1ac3d8bd..2019b239 100644
--- a/sleekxmpp/clientxmpp.py
+++ b/sleekxmpp/clientxmpp.py
@@ -183,7 +183,7 @@ class ClientXMPP(BaseXMPP):
 
                     picked = random.randint(0, intmax)
                     for item in items:
-                        if picked <= priority:
+                        if picked <= item:
                             address = addresses[item]
                             break
 
-- 
cgit v1.2.3


From e022b2a36c342b6a83d7c7f0a39dfb851cdfd2de Mon Sep 17 00:00:00 2001
From: Lance Stout <lancestout@gmail.com>
Date: Wed, 27 Jul 2011 19:35:03 -0700
Subject: Add support for HTTP Proxy connections.

---
 examples/proxy_echo_client.py    | 167 +++++++++++++++++++++++++++++++++++++++
 sleekxmpp/xmlstream/xmlstream.py |  75 +++++++++++++++++-
 2 files changed, 240 insertions(+), 2 deletions(-)
 create mode 100755 examples/proxy_echo_client.py

diff --git a/examples/proxy_echo_client.py b/examples/proxy_echo_client.py
new file mode 100755
index 00000000..4db9a552
--- /dev/null
+++ b/examples/proxy_echo_client.py
@@ -0,0 +1,167 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+"""
+    SleekXMPP: The Sleek XMPP Library
+    Copyright (C) 2010  Nathanael C. Fritz
+    This file is part of SleekXMPP.
+
+    See the file LICENSE for copying permission.
+"""
+
+import sys
+import logging
+import time
+import getpass
+from optparse import OptionParser
+
+import sleekxmpp
+
+# Python versions before 3.0 do not use UTF-8 encoding
+# by default. To ensure that Unicode is handled properly
+# throughout SleekXMPP, we will set the default encoding
+# ourselves to UTF-8.
+if sys.version_info < (3, 0):
+    reload(sys)
+    sys.setdefaultencoding('utf8')
+
+
+class EchoBot(sleekxmpp.ClientXMPP):
+
+    """
+    A simple SleekXMPP bot that will echo messages it
+    receives, along with a short thank you message.
+    """
+
+    def __init__(self, jid, password):
+        sleekxmpp.ClientXMPP.__init__(self, jid, password)
+
+        # The session_start event will be triggered when
+        # the bot establishes its connection with the server
+        # and the XML streams are ready for use. We want to
+        # listen for this event so that we we can intialize
+        # our roster.
+        self.add_event_handler("session_start", self.start)
+
+        # The message event is triggered whenever a message
+        # stanza is received. Be aware that that includes
+        # MUC messages and error messages.
+        self.add_event_handler("message", self.message)
+
+    def start(self, event):
+        """
+        Process the session_start event.
+
+        Typical actions for the session_start event are
+        requesting the roster and broadcasting an intial
+        presence stanza.
+
+        Arguments:
+            event -- An empty dictionary. The session_start
+                     event does not provide any additional
+                     data.
+        """
+        self.send_presence()
+        self.get_roster()
+
+    def message(self, msg):
+        """
+        Process incoming message stanzas. Be aware that this also
+        includes MUC messages and error messages. It is usually
+        a good idea to check the messages's type before processing
+        or sending replies.
+
+        Arguments:
+            msg -- The received message stanza. See the documentation
+                   for stanza objects and the Message stanza to see
+                   how it may be used.
+        """
+        msg.reply("Thanks for sending\n%(body)s" % msg).send()
+
+
+if __name__ == '__main__':
+    # Setup the command line arguments.
+    optp = OptionParser()
+
+    # Output verbosity options.
+    optp.add_option('-q', '--quiet', help='set logging to ERROR',
+                    action='store_const', dest='loglevel',
+                    const=logging.ERROR, default=logging.INFO)
+    optp.add_option('-d', '--debug', help='set logging to DEBUG',
+                    action='store_const', dest='loglevel',
+                    const=logging.DEBUG, default=logging.INFO)
+    optp.add_option('-v', '--verbose', help='set logging to COMM',
+                    action='store_const', dest='loglevel',
+                    const=5, default=logging.INFO)
+
+    # JID and password options.
+    optp.add_option("-j", "--jid", dest="jid",
+                    help="JID to use")
+    optp.add_option("-p", "--password", dest="password",
+                    help="password to use")
+    optp.add_option("--phost", dest="proxy_host",
+                    help="Proxy hostname")
+    optp.add_option("--pport", dest="proxy_port",
+                    help="Proxy port")
+    optp.add_option("--puser", dest="proxy_user",
+                    help="Proxy username")
+    optp.add_option("--ppass", dest="proxy_pass",
+                    help="Proxy password")
+
+
+
+    opts, args = optp.parse_args()
+
+    # Setup logging.
+    logging.basicConfig(level=opts.loglevel,
+                        format='%(levelname)-8s %(message)s')
+
+    if opts.jid is None:
+        opts.jid = raw_input("Username: ")
+    if opts.password is None:
+        opts.password = getpass.getpass("Password: ")
+    if opts.proxy_host is None:
+        opts.proxy_host = raw_input("Proxy host: ")
+    if opts.proxy_port is None:
+        opts.proxy_port = raw_input("Proxy port: ")
+    if opts.proxy_user is None:
+        opts.proxy_user = raw_input("Proxy username: ")
+    if opts.proxy_pass is None and opts.proxy_user:
+        opts.proxy_pass = getpass.getpass("Proxy password: ")
+
+    # Setup the EchoBot and register plugins. Note that while plugins may
+    # have interdependencies, the order in which you register them does
+    # not matter.
+    xmpp = EchoBot(opts.jid, opts.password)
+    xmpp.register_plugin('xep_0030') # Service Discovery
+    xmpp.register_plugin('xep_0004') # Data Forms
+    xmpp.register_plugin('xep_0060') # PubSub
+    xmpp.register_plugin('xep_0199') # XMPP Ping
+
+    # If you are working with an OpenFire server, you may need
+    # to adjust the SSL version used:
+    # xmpp.ssl_version = ssl.PROTOCOL_SSLv3
+
+    # If you want to verify the SSL certificates offered by a server:
+    # xmpp.ca_certs = "path/to/ca/cert"
+
+    xmpp.use_proxy = True
+    xmpp.proxy_config = {
+        'host': opts.proxy_host,
+        'port': int(opts.proxy_port),
+        'username': opts.proxy_user,
+        'password': opts.proxy_pass}
+
+    # Connect to the XMPP server and start processing XMPP stanzas.
+    if xmpp.connect():
+        # If you do not have the pydns library installed, you will need
+        # to manually specify the name of the server if it does not match
+        # the one in the JID. For example, to use Google Talk you would
+        # need to use:
+        #
+        # if xmpp.connect(('talk.google.com', 5222)):
+        #     ...
+        xmpp.process(threaded=False)
+        print("Done")
+    else:
+        print("Unable to connect.")
diff --git a/sleekxmpp/xmlstream/xmlstream.py b/sleekxmpp/xmlstream/xmlstream.py
index 8b6c08b7..15bbe655 100644
--- a/sleekxmpp/xmlstream/xmlstream.py
+++ b/sleekxmpp/xmlstream/xmlstream.py
@@ -8,6 +8,7 @@
 
 from __future__ import with_statement, unicode_literals
 
+import base64
 import copy
 import logging
 import signal
@@ -23,6 +24,7 @@ try:
 except ImportError:
     import Queue as queue
 
+import sleekxmpp
 from sleekxmpp.thirdparty.statemachine import StateMachine
 from sleekxmpp.xmlstream import Scheduler, tostring
 from sleekxmpp.xmlstream.stanzabase import StanzaBase, ET
@@ -107,7 +109,13 @@ class XMLStream(object):
         stream_header -- The closing tag of the stream's root element.
         use_ssl       -- Flag indicating if SSL should be used.
         use_tls       -- Flag indicating if TLS should be used.
+        use_proxy     -- Flag indicating that an HTTP Proxy should be used.
         stop          -- threading Event used to stop all threads.
+        proxy_config  -- An optional dictionary with the following entries:
+                            host     -- The host offering proxy services.
+                            port     -- The port for the proxy service.
+                            username -- Optional username for the proxy.
+                            password -- Optional password for the proxy.
 
         auto_reconnect      -- Flag to determine whether we auto reconnect.
         reconnect_max_delay -- Maximum time to delay between connection
@@ -180,6 +188,9 @@ class XMLStream(object):
 
         self.use_ssl = False
         self.use_tls = False
+        self.use_proxy = False
+
+        self.proxy_config = {}
 
         self.default_ns = ''
         self.stream_header = "<stream>"
@@ -322,6 +333,12 @@ class XMLStream(object):
             log.debug('Waiting %s seconds before connecting.' % delay)
             time.sleep(delay)
 
+        if self.use_proxy:
+            connected = self._connect_proxy()
+            if not connected:
+                self.reconnect_delay = delay
+                return False
+
         if self.use_ssl and self.ssl_support:
             log.debug("Socket Wrapped for SSL")
             if self.ca_certs is None:
@@ -341,8 +358,10 @@ class XMLStream(object):
                 self.socket = ssl_socket
 
         try:
-            log.debug("Connecting to %s:%s" % self.address)
-            self.socket.connect(self.address)
+            if not self.use_proxy:
+                log.debug("Connecting to %s:%s" % self.address)
+                self.socket.connect(self.address)
+
             self.set_socket(self.socket, ignore=True)
             #this event is where you should set your application state
             self.event("connected", direct=True)
@@ -356,6 +375,58 @@ class XMLStream(object):
             self.reconnect_delay = delay
             return False
 
+    def _connect_proxy(self):
+        """Attempt to connect using an HTTP Proxy."""
+
+        # Extract the proxy address, and optional credentials
+        address = (self.proxy_config['host'], int(self.proxy_config['port']))
+        cred = None
+        if self.proxy_config['username']:
+            username = self.proxy_config['username']
+            password = self.proxy_config['password']
+
+            cred = '%s:%s' % (username, password)
+            if sys.version_info < (3, 0):
+                cred = bytes(cred)
+            else:
+                cred = bytes(cred, 'utf-8')
+            cred = base64.b64encode(cred).decode('utf-8')
+
+        # Build the HTTP headers for connecting to the XMPP server
+        headers = ['CONNECT %s:%s HTTP/1.0' % self.address,
+                   'Host: %s:%s' % self.address,
+                   'Proxy-Connection: Keep-Alive',
+                   'Pragma: no-cache',
+                   'User-Agent: SleekXMPP/%s' % sleekxmpp.__version__]
+        if cred:
+            headers.append('Proxy-Authorization: Basic %s' % cred)
+        headers = '\r\n'.join(headers) + '\r\n\r\n'
+
+        try:
+            log.debug("Connecting to proxy: %s:%s" % address)
+            self.socket.connect(address)
+            self.send_raw(headers, now=True)
+            resp = ''
+            while '\r\n\r\n' not in resp:
+                resp += self.socket.recv(1024).decode('utf-8')
+            log.debug('RECV: %s' % resp)
+
+            lines = resp.split('\r\n')
+            if '200' not in lines[0]:
+                self.event('proxy_error', resp)
+                log.error('Proxy Error: %s' % lines[0])
+                return False
+
+            # Proxy connection established, continue connecting
+            # with the XMPP server.
+            return True
+        except Socket.error as serr:
+            error_msg = "Could not connect to %s:%s. Socket Error #%s: %s"
+            self.event('socket_error', serr)
+            log.error(error_msg % (self.address[0], self.address[1],
+                                       serr.errno, serr.strerror))
+            return False
+
     def disconnect(self, reconnect=False, wait=False):
         """
         Terminate processing and close the XML streams.
-- 
cgit v1.2.3