Merge lp:~facundo/magicicada-client/kill-qt4 into lp:magicicada-client

Proposed by Facundo Batista
Status: Merged
Approved by: Natalia Bidart
Approved revision: 1439
Merged at revision: 1439
Proposed branch: lp:~facundo/magicicada-client/kill-qt4
Merge into: lp:magicicada-client
Diff against target: 3154 lines (+27/-2840)
24 files modified
bin/ubuntuone-proxy-tunnel (+0/-36)
contrib/check-reactor-import (+0/-76)
contrib/testing/testcase.py (+2/-16)
dependencies.txt (+0/-2)
run-tests (+2/-9)
run-tests.bat (+0/-2)
setup.py (+1/-1)
ubuntuone/proxy/__init__.py (+0/-27)
ubuntuone/proxy/common.py (+0/-73)
ubuntuone/proxy/logger.py (+0/-48)
ubuntuone/proxy/tests/__init__.py (+0/-156)
ubuntuone/proxy/tests/ssl/dummy.cert (+0/-19)
ubuntuone/proxy/tests/ssl/dummy.key (+0/-16)
ubuntuone/proxy/tests/test_tunnel_client.py (+0/-225)
ubuntuone/proxy/tests/test_tunnel_server.py (+0/-743)
ubuntuone/proxy/tunnel_client.py (+0/-202)
ubuntuone/proxy/tunnel_server.py (+0/-405)
ubuntuone/syncdaemon/action_queue.py (+3/-11)
ubuntuone/syncdaemon/tests/test_action_queue.py (+17/-57)
ubuntuone/syncdaemon/tests/test_tunnel_runner.py (+0/-185)
ubuntuone/syncdaemon/tunnel_runner.py (+0/-86)
ubuntuone/syncdaemon/utils.py (+2/-9)
ubuntuone/utils/gsettings.py (+0/-114)
ubuntuone/utils/tests/test_gsettings.py (+0/-322)
To merge this branch: bzr merge lp:~facundo/magicicada-client/kill-qt4
Reviewer Review Type Date Requested Status
Natalia Bidart Approve
Review via email: mp+341428@code.launchpad.net

Commit message

Removed everything PyQt related, including the HTTP(S) Proxy.

Description of the change

Removed everything PyQt related, including the HTTP(S) Proxy.

To post a comment you must log in.
Revision history for this message
Natalia Bidart (nataliabidart) wrote :

Thanks!

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== removed file 'bin/ubuntuone-proxy-tunnel'
2--- bin/ubuntuone-proxy-tunnel 2015-09-29 21:05:26 +0000
3+++ bin/ubuntuone-proxy-tunnel 1970-01-01 00:00:00 +0000
4@@ -1,36 +0,0 @@
5-#!/usr/bin/python
6-#
7-# Copyright 2012 Canonical Ltd.
8-#
9-# This program is free software: you can redistribute it and/or modify it
10-# under the terms of the GNU General Public License version 3, as published
11-# by the Free Software Foundation.
12-#
13-# This program is distributed in the hope that it will be useful, but
14-# WITHOUT ANY WARRANTY; without even the implied warranties of
15-# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
16-# PURPOSE. See the GNU General Public License for more details.
17-#
18-# You should have received a copy of the GNU General Public License along
19-# with this program. If not, see <http://www.gnu.org/licenses/>.
20-#
21-# In addition, as a special exception, the copyright holders give
22-# permission to link the code of portions of this program with the
23-# OpenSSL library under certain conditions as described in each
24-# individual source file, and distribute linked combinations
25-# including the two.
26-# You must obey the GNU General Public License in all respects
27-# for all of the code used other than OpenSSL. If you modify
28-# file(s) with this exception, you may extend this exception to your
29-# version of the file(s), but you are not obligated to do so. If you
30-# do not wish to do so, delete this exception statement from your
31-# version. If you delete this exception statement from all source
32-# files in the program, then also delete it here.
33-"""Tunnel for proxy support."""
34-
35-import sys
36-
37-from ubuntuone.proxy.tunnel_server import main
38-
39-if __name__ == "__main__":
40- main(sys.argv)
41
42=== removed file 'contrib/check-reactor-import'
43--- contrib/check-reactor-import 2016-05-28 23:32:02 +0000
44+++ contrib/check-reactor-import 1970-01-01 00:00:00 +0000
45@@ -1,76 +0,0 @@
46-#! /usr/bin/python
47-#
48-# Copyright (C) 2012 Canonical Ltd.
49-#
50-# This program is free software: you can redistribute it and/or modify it
51-# under the terms of the GNU General Public License version 3, as published
52-# by the Free Software Foundation.
53-#
54-# This program is distributed in the hope that it will be useful, but
55-# WITHOUT ANY WARRANTY; without even the implied warranties of
56-# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
57-# PURPOSE. See the GNU General Public License for more details.
58-#
59-# You should have received a copy of the GNU General Public License along
60-# with this program. If not, see <http://www.gnu.org/licenses/>.
61-#
62-# In addition, as a special exception, the copyright holders give
63-# permission to link the code of portions of this program with the
64-# OpenSSL library under certain conditions as described in each
65-# individual source file, and distribute linked combinations
66-# including the two.
67-# You must obey the GNU General Public License in all respects
68-# for all of the code used other than OpenSSL. If you modify
69-# file(s) with this exception, you may extend this exception to your
70-# version of the file(s), but you are not obligated to do so. If you
71-# do not wish to do so, delete this exception statement from your
72-# version. If you delete this exception statement from all source
73-# files in the program, then also delete it here.
74-"""A script that checks for unintended imports of twisted.internet.reactor."""
75-
76-# NOTE: the goal of this script is to avoid a bug that affects
77-# ubuntuone-control-panel on windows and darwin. Those platforms use
78-# the qt4reactor, and will break if the default reactor is installed
79-# first. This can happen if a module used by control-panel imports reactor.
80-# Only sub-modules that are not used by ubuntuone-control-panel can safely
81-# import reactor at module-level.
82-
83-from __future__ import (unicode_literals, print_function)
84-
85-import __builtin__
86-
87-import os
88-import sys
89-import traceback
90-
91-sys.path.append(os.path.abspath(os.getcwd()))
92-
93-
94-def fake_import(*args, **kwargs):
95- """A wrapper for __import__ that dies when importing reactor."""
96- imp_name_base = args[0]
97-
98- if len(args) == 4 and args[3] is not None:
99- imp_names = ["{0}.{1}".format(imp_name_base, sm)
100- for sm in args[3]]
101- else:
102- imp_names = [imp_name_base]
103-
104- for imp_name in imp_names:
105- if 'twisted.internet.reactor' == imp_name:
106- print("ERROR: should not import reactor here:")
107- traceback.print_stack()
108- sys.exit(1)
109-
110- r = real_import(*args, **kwargs)
111- return r
112-
113-
114-if __name__ == '__main__':
115-
116- real_import = __builtin__.__import__
117- __builtin__.__import__ = fake_import
118-
119- subs = ["", ".tools", ".logger"]
120- for module in ["ubuntuone.platform" + p for p in subs]:
121- m = __import__(module)
122
123=== modified file 'contrib/testing/testcase.py'
124--- contrib/testing/testcase.py 2016-09-17 01:06:23 +0000
125+++ contrib/testing/testcase.py 2018-03-14 21:14:07 +0000
126@@ -1,7 +1,7 @@
127 # -*- coding: utf-8 -*-
128 #
129 # Copyright 2009-2015 Canonical Ltd.
130-# Copyright 2015-2016 Chicharreros (https://launchpad.net/~chicharreros)
131+# Copyright 2015-2018 Chicharreros (https://launchpad.net/~chicharreros)
132 #
133 # This program is free software: you can redistribute it and/or modify it
134 # under the terms of the GNU General Public License version 3, as published
135@@ -41,7 +41,7 @@
136 from collections import defaultdict
137 from functools import wraps
138
139-from twisted.internet import defer, reactor
140+from twisted.internet import defer
141 from twisted.trial.unittest import TestCase as TwistedTestCase
142 from ubuntuone.devtools.testcases import skipIfOS
143 from zope.interface import implements
144@@ -312,17 +312,6 @@
145 return defer.succeed(True)
146
147
148-class FakeTunnelRunner(object):
149- """A fake proxy.tunnel_client.TunnelRunner."""
150-
151- def __init__(self, *args):
152- """Fake a proxy tunnel."""
153-
154- def get_client(self):
155- """Always return the reactor."""
156- return defer.succeed(reactor)
157-
158-
159 class BaseTwistedTestCase(TwistedTestCase):
160 """Base TestCase with helper methods to handle temp dir.
161
162@@ -332,7 +321,6 @@
163 makedirs(path): support read-only shares
164 """
165 MAX_FILENAME = 32 # some platforms limit lengths of filenames
166- tunnel_runner_class = FakeTunnelRunner
167
168 def mktemp(self, name='temp'):
169 """ Customized mktemp that accepts an optional name argument. """
170@@ -439,8 +427,6 @@
171 self.log = logging.getLogger("ubuntuone.SyncDaemon.TEST")
172 self.log.info("starting test %s.%s", self.__class__.__name__,
173 self._testMethodName)
174- self.patch(action_queue.tunnel_runner, "TunnelRunner",
175- self.tunnel_runner_class)
176
177
178 class FakeMainTestCase(BaseTwistedTestCase):
179
180=== modified file 'dependencies.txt'
181--- dependencies.txt 2017-01-07 18:51:07 +0000
182+++ dependencies.txt 2018-03-14 21:14:07 +0000
183@@ -6,6 +6,4 @@
184 python-gi
185 python-protobuf
186 python-pyinotify
187-python-qt4-dbus
188-python-qt4reactor
189 python-twisted
190
191=== modified file 'run-tests'
192--- run-tests 2016-05-29 20:16:26 +0000
193+++ run-tests 2018-03-14 21:14:07 +0000
194@@ -1,6 +1,7 @@
195 #! /bin/bash
196 #
197 # Copyright 2012-2013 Canonical Ltd.
198+# Copyright 2015-2018 Chicharreros (https://launchpad.net/~chicharreros)
199 #
200 # This program is free software: you can redistribute it and/or modify it
201 # under the terms of the GNU General Public License version 3, as published
202@@ -27,8 +28,6 @@
203 # version. If you delete this exception statement from all source
204 # files in the program, then also delete it here.
205
206-PROXY_TESTS_PATH="ubuntuone/proxy/tests"
207-
208 # Allow alternative python executable via environment variable. This is
209 # useful for virtualenv testing.
210 PYTHON=${PYTHON:-'python'}
211@@ -47,22 +46,16 @@
212 if [ "$SYSNAME" == "Darwin" ]; then
213 IGNORE_FILES="test_linux.py,test_windows.py"
214 IGNORE_PATHS="ubuntuone/platform/tests/linux"
215- REACTOR=qt4
216 else
217 # Linux
218 IGNORE_FILES="test_darwin.py,test_fsevents_daemon.py,test_windows.py"
219 IGNORE_PATHS="ubuntuone/platform/tests/windows"
220- REACTOR=gi
221 fi
222
223 echo "*** Running test suite for ""$MODULE"" ***"
224 export SSL_CERTIFICATES_DIR=/etc/ssl/certs
225 $PYTHON ./setup.py build
226-u1trial --reactor=$REACTOR -i "$IGNORE_FILES" -p "$IGNORE_PATHS,$PROXY_TESTS_PATH" "$MODULE"
227-echo "*** Running tests for ubuntuone-client-proxy ***"
228-u1trial --reactor=qt4 -i "$IGNORE_FILES" -p "$IGNORE_PATHS" "$PROXY_TESTS_PATH"
229+u1trial -i "$IGNORE_FILES" -p "$IGNORE_PATHS" "$MODULE"
230 $PYTHON ./setup.py clean
231 rm -rf _trial_temp
232 rm -rf build
233-
234-$PYTHON contrib/check-reactor-import
235
236=== modified file 'run-tests.bat'
237--- run-tests.bat 2013-06-10 19:27:21 +0000
238+++ run-tests.bat 2018-03-14 21:14:07 +0000
239@@ -87,8 +87,6 @@
240 ECHO Performing style checks...
241 "%LINTPATH%"
242
243-"%PYTHONEXEPATH%" contrib\check-reactor-import
244-
245 :: if pep8 is not present, move to the end
246 IF EXIST "%PEP8PATH%" (
247 "%PEP8PATH%" --repeat ubuntuone
248
249=== modified file 'setup.py'
250--- setup.py 2016-08-06 20:02:14 +0000
251+++ setup.py 2018-03-14 21:14:07 +0000
252@@ -1,6 +1,7 @@
253 #!/usr/bin/python
254 #
255 # Copyright 2013 Canonical Ltd.
256+# Copyright 2015-2018 Chicharreros (https://launchpad.net/~chicharreros)
257 #
258 # This program is free software: you can redistribute it and/or modify it
259 # under the terms of the GNU General Public License version 3, as published
260@@ -203,7 +204,6 @@
261 ]
262
263 libexec_scripts = [
264- 'bin/ubuntuone-proxy-tunnel',
265 'bin/ubuntuone-syncdaemon',
266 ]
267
268
269=== removed directory 'ubuntuone/proxy'
270=== removed file 'ubuntuone/proxy/__init__.py'
271--- ubuntuone/proxy/__init__.py 2016-05-29 00:50:05 +0000
272+++ ubuntuone/proxy/__init__.py 1970-01-01 00:00:00 +0000
273@@ -1,27 +0,0 @@
274-# Copyright 2012 Canonical Ltd.
275-#
276-# This program is free software: you can redistribute it and/or modify it
277-# under the terms of the GNU General Public License version 3, as published
278-# by the Free Software Foundation.
279-#
280-# This program is distributed in the hope that it will be useful, but
281-# WITHOUT ANY WARRANTY; without even the implied warranties of
282-# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
283-# PURPOSE. See the GNU General Public License for more details.
284-#
285-# You should have received a copy of the GNU General Public License along
286-# with this program. If not, see <http://www.gnu.org/licenses/>.
287-#
288-# In addition, as a special exception, the copyright holders give
289-# permission to link the code of portions of this program with the
290-# OpenSSL library under certain conditions as described in each
291-# individual source file, and distribute linked combinations
292-# including the two.
293-# You must obey the GNU General Public License in all respects
294-# for all of the code used other than OpenSSL. If you modify
295-# file(s) with this exception, you may extend this exception to your
296-# version of the file(s), but you are not obligated to do so. If you
297-# do not wish to do so, delete this exception statement from your
298-# version. If you delete this exception statement from all source
299-# files in the program, then also delete it here.
300-"""Proxy support."""
301
302=== removed file 'ubuntuone/proxy/common.py'
303--- ubuntuone/proxy/common.py 2012-04-09 20:08:42 +0000
304+++ ubuntuone/proxy/common.py 1970-01-01 00:00:00 +0000
305@@ -1,73 +0,0 @@
306-# -*- coding: utf-8 -*-
307-#
308-# Copyright 2012 Canonical Ltd.
309-#
310-# This program is free software: you can redistribute it and/or modify it
311-# under the terms of the GNU General Public License version 3, as published
312-# by the Free Software Foundation.
313-#
314-# This program is distributed in the hope that it will be useful, but
315-# WITHOUT ANY WARRANTY; without even the implied warranties of
316-# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
317-# PURPOSE. See the GNU General Public License for more details.
318-#
319-# You should have received a copy of the GNU General Public License along
320-# with this program. If not, see <http://www.gnu.org/licenses/>.
321-#
322-# In addition, as a special exception, the copyright holders give
323-# permission to link the code of portions of this program with the
324-# OpenSSL library under certain conditions as described in each
325-# individual source file, and distribute linked combinations
326-# including the two.
327-# You must obey the GNU General Public License in all respects
328-# for all of the code used other than OpenSSL. If you modify
329-# file(s) with this exception, you may extend this exception to your
330-# version of the file(s), but you are not obligated to do so. If you
331-# do not wish to do so, delete this exception statement from your
332-# version. If you delete this exception statement from all source
333-# files in the program, then also delete it here.
334-"""Common classes to the tunnel client and server."""
335-
336-from twisted.protocols import basic
337-
338-CRLF = "\r\n"
339-TUNNEL_PORT_LABEL = "Tunnel port"
340-TUNNEL_COOKIE_LABEL = "Tunnel cookie"
341-TUNNEL_COOKIE_HEADER = "Proxy-Tunnel-Cookie"
342-
343-
344-class BaseTunnelProtocol(basic.LineReceiver):
345- """CONNECT base protocol for tunnelling connections."""
346-
347- delimiter = CRLF
348-
349- def __init__(self):
350- """Initialize this protocol."""
351- self._first_line = True
352- self.received_headers = []
353-
354- def header_line(self, line):
355- """Handle each header line received."""
356- key, value = line.split(":", 1)
357- value = value.strip()
358- self.received_headers.append((key, value))
359-
360- def lineReceived(self, line):
361- """Process a line in the header."""
362- if self._first_line:
363- self._first_line = False
364- self.handle_first_line(line)
365- else:
366- if line:
367- self.header_line(line)
368- else:
369- self.setRawMode()
370- self.headers_done()
371-
372- def remote_disconnected(self):
373- """The remote end closed the connection."""
374- self.transport.loseConnection()
375-
376- def format_headers(self, headers):
377- """Format some headers as a few response lines."""
378- return "".join("%s: %s" % item + CRLF for item in headers.items())
379
380=== removed file 'ubuntuone/proxy/logger.py'
381--- ubuntuone/proxy/logger.py 2012-06-21 18:58:50 +0000
382+++ ubuntuone/proxy/logger.py 1970-01-01 00:00:00 +0000
383@@ -1,48 +0,0 @@
384-# ubuntuone.syncdaemon.logger - logging utilities
385-#
386-# Copyright 2009-2012 Canonical Ltd.
387-#
388-# This program is free software: you can redistribute it and/or modify it
389-# under the terms of the GNU General Public License version 3, as published
390-# by the Free Software Foundation.
391-#
392-# This program is distributed in the hope that it will be useful, but
393-# WITHOUT ANY WARRANTY; without even the implied warranties of
394-# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
395-# PURPOSE. See the GNU General Public License for more details.
396-#
397-# You should have received a copy of the GNU General Public License along
398-# with this program. If not, see <http://www.gnu.org/licenses/>.
399-#
400-# In addition, as a special exception, the copyright holders give
401-# permission to link the code of portions of this program with the
402-# OpenSSL library under certain conditions as described in each
403-# individual source file, and distribute linked combinations
404-# including the two.
405-# You must obey the GNU General Public License in all respects
406-# for all of the code used other than OpenSSL. If you modify
407-# file(s) with this exception, you may extend this exception to your
408-# version of the file(s), but you are not obligated to do so. If you
409-# do not wish to do so, delete this exception statement from your
410-# version. If you delete this exception statement from all source
411-# files in the program, then also delete it here.
412-"""SyncDaemon logging utilities and config."""
413-
414-import logging
415-import os
416-
417-from ubuntuone.logger import (
418- basic_formatter,
419- CustomRotatingFileHandler,
420-)
421-
422-from ubuntuone.platform.logger import ubuntuone_log_dir
423-
424-
425-LOGFILENAME = os.path.join(ubuntuone_log_dir, 'proxy.log')
426-logger = logging.getLogger("ubuntuone.proxy")
427-logger.setLevel(logging.DEBUG)
428-handler = CustomRotatingFileHandler(filename=LOGFILENAME)
429-handler.setFormatter(basic_formatter)
430-handler.setLevel(logging.DEBUG)
431-logger.addHandler(handler)
432
433=== removed directory 'ubuntuone/proxy/tests'
434=== removed file 'ubuntuone/proxy/tests/__init__.py'
435--- ubuntuone/proxy/tests/__init__.py 2016-06-01 18:28:19 +0000
436+++ ubuntuone/proxy/tests/__init__.py 1970-01-01 00:00:00 +0000
437@@ -1,156 +0,0 @@
438-# Copyright 2012 Canonical Ltd.
439-#
440-# This program is free software: you can redistribute it and/or modify it
441-# under the terms of the GNU General Public License version 3, as published
442-# by the Free Software Foundation.
443-#
444-# This program is distributed in the hope that it will be useful, but
445-# WITHOUT ANY WARRANTY; without even the implied warranties of
446-# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
447-# PURPOSE. See the GNU General Public License for more details.
448-#
449-# You should have received a copy of the GNU General Public License along
450-# with this program. If not, see <http://www.gnu.org/licenses/>.
451-#
452-# In addition, as a special exception, the copyright holders give
453-# permission to link the code of portions of this program with the
454-# OpenSSL library under certain conditions as described in each
455-# individual source file, and distribute linked combinations
456-# including the two.
457-# You must obey the GNU General Public License in all respects
458-# for all of the code used other than OpenSSL. If you modify
459-# file(s) with this exception, you may extend this exception to your
460-# version of the file(s), but you are not obligated to do so. If you
461-# do not wish to do so, delete this exception statement from your
462-# version. If you delete this exception statement from all source
463-# files in the program, then also delete it here.
464-"""Tests for the Magicicada proxy support."""
465-
466-from os import path
467-from StringIO import StringIO
468-
469-from twisted.application import internet, service
470-from twisted.internet import defer, ssl
471-from twisted.web import http, resource, server
472-
473-SAMPLE_CONTENT = "hello world!"
474-SIMPLERESOURCE = "simpleresource"
475-DUMMY_KEY_FILENAME = "dummy.key"
476-DUMMY_CERT_FILENAME = "dummy.cert"
477-FAKE_COOKIE = "fa:ke:co:ok:ie"
478-
479-
480-class SaveHTTPChannel(http.HTTPChannel):
481- """A save protocol to be used in tests."""
482-
483- protocolInstance = None
484-
485- def connectionMade(self):
486- """Keep track of the given protocol."""
487- SaveHTTPChannel.protocolInstance = self
488- http.HTTPChannel.connectionMade(self)
489-
490-
491-class SaveSite(server.Site):
492- """A site that let us know when it's closed."""
493-
494- protocol = SaveHTTPChannel
495-
496- def __init__(self, *args, **kwargs):
497- """Create a new instance."""
498- server.Site.__init__(self, *args, **kwargs)
499- self.timeOut = None
500-
501-
502-class BaseMockWebServer(object):
503- """A mock webserver for testing"""
504-
505- def __init__(self):
506- """Start up this instance."""
507- self.root = self.get_root_resource()
508- self.site = SaveSite(self.root)
509- application = service.Application('web')
510- self.service_collection = service.IServiceCollection(application)
511- self.tcpserver = internet.TCPServer(0, self.site)
512- self.tcpserver.setServiceParent(self.service_collection)
513- self.sslserver = internet.SSLServer(0, self.site, self.get_context())
514- self.sslserver.setServiceParent(self.service_collection)
515- self.service_collection.startService()
516-
517- def get_dummy_path(self, filename):
518- """Path pointing at the dummy certificate files."""
519- base_path = path.dirname(__file__)
520- return path.join(base_path, "ssl", filename)
521-
522- def get_context(self):
523- """Return an ssl context."""
524- key_path = self.get_dummy_path(DUMMY_KEY_FILENAME)
525- cert_path = self.get_dummy_path(DUMMY_CERT_FILENAME)
526- return ssl.DefaultOpenSSLContextFactory(key_path, cert_path)
527-
528- def get_root_resource(self):
529- """Get the root resource with all the children."""
530- raise NotImplementedError
531-
532- def get_iri(self):
533- """Build the iri for this mock server."""
534- port_num = self.tcpserver._port.getHost().port
535- return u"http://0.0.0.0:%d/" % port_num
536-
537- def get_ssl_iri(self):
538- """Build the iri for the ssl mock server."""
539- port_num = self.sslserver._port.getHost().port
540- return u"https://0.0.0.0:%d/" % port_num
541-
542- def stop(self):
543- """Shut it down."""
544- if self.site.protocol.protocolInstance:
545- self.site.protocol.protocolInstance.timeoutConnection()
546- return self.service_collection.stopService()
547-
548-
549-class SimpleResource(resource.Resource):
550- """A simple web resource."""
551-
552- def __init__(self):
553- """Initialize this mock resource."""
554- resource.Resource.__init__(self)
555- self.rendered = defer.Deferred()
556-
557- def render_GET(self, request):
558- """Make a bit of html out of the resource's content."""
559- if not self.rendered.called:
560- self.rendered.callback(None)
561- return SAMPLE_CONTENT
562-
563-
564-class MockWebServer(BaseMockWebServer):
565- """A mock webserver."""
566-
567- def __init__(self):
568- """Initialize this mock server."""
569- self.simple_resource = SimpleResource()
570- super(MockWebServer, self).__init__()
571-
572- def get_root_resource(self):
573- """Get the root resource with all the children."""
574- root = resource.Resource()
575- root.putChild(SIMPLERESOURCE, self.simple_resource)
576- return root
577-
578-
579-class FakeTransport(StringIO):
580- """A fake transport that stores everything written to it."""
581-
582- connected = True
583- disconnecting = False
584- cookie = None
585-
586- def loseConnection(self):
587- """Mark the connection as lost."""
588- self.connected = False
589- self.disconnecting = True
590-
591- def getPeer(self):
592- """Return the peer IAddress."""
593- return None
594
595=== removed directory 'ubuntuone/proxy/tests/ssl'
596=== removed file 'ubuntuone/proxy/tests/ssl/dummy.cert'
597--- ubuntuone/proxy/tests/ssl/dummy.cert 2012-02-23 23:58:33 +0000
598+++ ubuntuone/proxy/tests/ssl/dummy.cert 1970-01-01 00:00:00 +0000
599@@ -1,19 +0,0 @@
600------BEGIN CERTIFICATE-----
601-MIIDEDCCAnmgAwIBAgIJAM/bIJ77awBCMA0GCSqGSIb3DQEBBQUAMIGgMQswCQYD
602-VQQGEwJBUjETMBEGA1UECAwKRmFrZSBTdGF0ZTESMBAGA1UEBwwJRmFrZSBDaXR5
603-MRUwEwYDVQQKDAxGYWtlIENvbXBhbnkxFjAUBgNVBAsMDUZha2UgRGl2aXNpb24x
604-EjAQBgNVBAMMCUZha2UgTmFtZTElMCMGCSqGSIb3DQEJARYWZmFrZUBlbWFpbC5h
605-ZGRyZXNzLm5vdDAeFw0xMjAyMjIxOTI0MjBaFw0yMjAyMjMxOTI0MjBaMIGgMQsw
606-CQYDVQQGEwJBUjETMBEGA1UECAwKRmFrZSBTdGF0ZTESMBAGA1UEBwwJRmFrZSBD
607-aXR5MRUwEwYDVQQKDAxGYWtlIENvbXBhbnkxFjAUBgNVBAsMDUZha2UgRGl2aXNp
608-b24xEjAQBgNVBAMMCUZha2UgTmFtZTElMCMGCSqGSIb3DQEJARYWZmFrZUBlbWFp
609-bC5hZGRyZXNzLm5vdDCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA0hbliGty
610-HwfZixU609UHBQdbfO+oObrPIrIawWX5FxD6KhX4ei23idmpyYEcXLK4ivNlT4dW
611-27bvhtpf6/FBbu9e1YdwcdDNoXajr9Ia4NZJyANgo9b5UIsnyTc45NlnpZgRg5zc
612-Oz7Vwwr4qf6r1ljK/I2mAO7rlpH5Ak9J+RkCAwEAAaNQME4wHQYDVR0OBBYEFLwr
613-ps/JLNcfpSuuylMnkvImVvkgMB8GA1UdIwQYMBaAFLwrps/JLNcfpSuuylMnkvIm
614-VvkgMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADgYEAWYDBAr0MgpnBxIne
615-WRz8MX0/c7IqrEuZCYMSGnU7PoX3GdNk1Lkif1ufELKSoG8jY16CDgEl26GPxA1k
616-Tho7MWSLikLbuQYJs2saF9by0Y/Mrau0auxEnpHZ7pkybeKFnrIqiNKvTVMnjo5T
617-FMET5qEOKKvp9IOnezCYX1nYXyY=
618------END CERTIFICATE-----
619
620=== removed file 'ubuntuone/proxy/tests/ssl/dummy.key'
621--- ubuntuone/proxy/tests/ssl/dummy.key 2012-02-23 23:58:33 +0000
622+++ ubuntuone/proxy/tests/ssl/dummy.key 1970-01-01 00:00:00 +0000
623@@ -1,16 +0,0 @@
624------BEGIN PRIVATE KEY-----
625-MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBANIW5Yhrch8H2YsV
626-OtPVBwUHW3zvqDm6zyKyGsFl+RcQ+ioV+Hott4nZqcmBHFyyuIrzZU+HVtu274ba
627-X+vxQW7vXtWHcHHQzaF2o6/SGuDWScgDYKPW+VCLJ8k3OOTZZ6WYEYOc3Ds+1cMK
628-+Kn+q9ZYyvyNpgDu65aR+QJPSfkZAgMBAAECgYBSxFh7TTExjmsjAyMg700LqyFc
629-8CHLVJBkL9ygkqb2cmbMC8nPgJFNSqY8T5Q35OUVQNyJ31zVxJVLAF9H2c0Xy48K
630-IkbS/hntyqlJYK1yfTbTHkDiweToE3Lm+55Do1TX04AyvBrwA1O/jNGi4xIlUEAy
631-1Bs8MrJ1E/j/XDn9/QJBAOuhPTgG3F7bKuBrQzv98CvC5o2Txf3vLY8nL8V24b3l
632-XgqzkDLhUxReBmmkGxZfKAju3+gXFvGGpbP7V8zShg8CQQDkQGs7kArFq/KR/GCh
633-CAmJaDWy4LJkSqzDHoJbTrS7YuqN6X6mW1xPRnWpYSxae38fJsCpG3Vq8Mv1Zl32
634-VPZXAkEAsAeE9JYri7GwFngLgoXzJr4z/xCmmU5VetyLk7l8a6Eu4E/FKj2rE0wq
635-/kDa+5ubDRFntLuLKGSu5gafUST1gQJABhdmBTfp4a6eEaFPntyNDJq4XCa8/Ao2
636-JBrrVa57Ckkwg0sI8z2a8A6sUzHhsiR7lwQ8vgaakpkMiGcL+Of5jwJAA/qX3PW+
637-9JXbjWxpgh7FHnZJNRZ8xSe47REGA7qS/nIlV9iRuf/9M+k3A5VqitfFxrjPwSyI
638-rvKTYkk13dL4hg==
639------END PRIVATE KEY-----
640
641=== removed file 'ubuntuone/proxy/tests/test_tunnel_client.py'
642--- ubuntuone/proxy/tests/test_tunnel_client.py 2016-06-01 18:28:19 +0000
643+++ ubuntuone/proxy/tests/test_tunnel_client.py 1970-01-01 00:00:00 +0000
644@@ -1,225 +0,0 @@
645-# -*- coding: utf-8 -*-
646-#
647-# Copyright 2012 Canonical Ltd.
648-#
649-# This program is free software: you can redistribute it and/or modify it
650-# under the terms of the GNU General Public License version 3, as published
651-# by the Free Software Foundation.
652-#
653-# This program is distributed in the hope that it will be useful, but
654-# WITHOUT ANY WARRANTY; without even the implied warranties of
655-# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
656-# PURPOSE. See the GNU General Public License for more details.
657-#
658-# You should have received a copy of the GNU General Public License along
659-# with this program. If not, see <http://www.gnu.org/licenses/>.
660-#
661-# In addition, as a special exception, the copyright holders give
662-# permission to link the code of portions of this program with the
663-# OpenSSL library under certain conditions as described in each
664-# individual source file, and distribute linked combinations
665-# including the two.
666-# You must obey the GNU General Public License in all respects
667-# for all of the code used other than OpenSSL. If you modify
668-# file(s) with this exception, you may extend this exception to your
669-# version of the file(s), but you are not obligated to do so. If you
670-# do not wish to do so, delete this exception statement from your
671-# version. If you delete this exception statement from all source
672-# files in the program, then also delete it here.
673-"""Tests for the proxy tunnel."""
674-
675-from twisted.internet import defer, protocol, ssl
676-from twisted.trial.unittest import TestCase
677-from twisted.web import client
678-
679-from ubuntuone.devtools.testcases.squid import SquidTestCase
680-
681-from ubuntuone.proxy.tests import (
682- FakeTransport,
683- FAKE_COOKIE,
684- MockWebServer,
685- SAMPLE_CONTENT,
686- SIMPLERESOURCE,
687-)
688-from ubuntuone.proxy import tunnel_client
689-from ubuntuone.proxy.tunnel_client import CRLF, TunnelClient
690-from ubuntuone.proxy.tunnel_server import TunnelServer
691-
692-
693-FAKE_HEADER = (
694- "HTTP/1.0 200 Connected!" + CRLF +
695- "Header1: value1" + CRLF +
696- "Header2: value2" + CRLF +
697- CRLF
698-)
699-
700-
701-class SavingProtocol(protocol.Protocol):
702- """A protocol that saves all that it receives."""
703-
704- def __init__(self):
705- """Initialize this protocol."""
706- self.saved_data = None
707-
708- def connectionMade(self):
709- """The connection was made, start saving."""
710- self.saved_data = []
711-
712- def dataReceived(self, data):
713- """Save the data received."""
714- self.saved_data.append(data)
715-
716- @property
717- def content(self):
718- """All the content so far."""
719- return "".join(self.saved_data)
720-
721-
722-class TunnelClientProtocolTestCase(TestCase):
723- """Tests for the client side tunnel protocol."""
724-
725- timeout = 3
726-
727- @defer.inlineCallbacks
728- def setUp(self):
729- """Initialize this testcase."""
730- yield super(TunnelClientProtocolTestCase, self).setUp()
731- self.host, self.port = "9.9.9.9", 8765
732- fake_addr = object()
733- self.cookie = FAKE_COOKIE
734- self.other_proto = SavingProtocol()
735- other_factory = protocol.ClientFactory()
736- other_factory.buildProtocol = lambda _addr: self.other_proto
737- tunnel_client_factory = tunnel_client.TunnelClientFactory(
738- self.host, self.port, other_factory, self.cookie)
739- tunnel_client_proto = tunnel_client_factory.buildProtocol(fake_addr)
740- tunnel_client_proto.transport = FakeTransport()
741- tunnel_client_proto.connectionMade()
742- self.tunnel_client_proto = tunnel_client_proto
743-
744- def test_sends_connect_request(self):
745- """Sends the expected CONNECT request."""
746- expected = tunnel_client.METHOD_LINE % (self.host, self.port)
747- written = self.tunnel_client_proto.transport.getvalue()
748- first_line = written.split(CRLF)[0]
749- self.assertEqual(first_line + CRLF, expected)
750- self.assertTrue(written.endswith(CRLF * 2),
751- "Ends with a double CRLF")
752-
753- def test_sends_cookie_header(self):
754- """Sends the expected cookie header."""
755- expected = "%s: %s" % (tunnel_client.TUNNEL_COOKIE_HEADER, self.cookie)
756- written = self.tunnel_client_proto.transport.getvalue()
757- headers = written.split(CRLF)[1:]
758- self.assertIn(expected, headers)
759-
760- def test_handles_successful_connection(self):
761- """A successful connection is handled."""
762- self.tunnel_client_proto.dataReceived(FAKE_HEADER)
763- self.assertEqual(self.tunnel_client_proto.status_code, "200")
764-
765- def test_protocol_is_switched(self):
766- """The protocol is switched after the headers are received."""
767- expected = (SAMPLE_CONTENT + CRLF) * 2
768- self.tunnel_client_proto.dataReceived(FAKE_HEADER + SAMPLE_CONTENT)
769- self.other_proto.dataReceived(CRLF + SAMPLE_CONTENT + CRLF)
770- self.assertEqual(self.other_proto.content, expected)
771-
772-
773-class FakeOtherFactory(object):
774- """A fake factory."""
775-
776- def __init__(self):
777- """Initialize this fake."""
778- self.started_called = None
779- self.failed_called = None
780- self.lost_called = None
781-
782- def startedConnecting(self, *args):
783- """Store the call."""
784- self.started_called = args
785-
786- def clientConnectionFailed(self, *args):
787- """Store the call."""
788- self.failed_called = args
789-
790- def clientConnectionLost(self, *args):
791- """Store the call."""
792- self.lost_called = args
793-
794-
795-class TunnelClientFactoryTestCase(TestCase):
796- """Tests for the TunnelClientFactory."""
797-
798- def test_forwards_started(self):
799- """The factory forwards the startedConnecting call."""
800- fake_other_factory = FakeOtherFactory()
801- tcf = tunnel_client.TunnelClientFactory(None, None, fake_other_factory,
802- FAKE_COOKIE)
803- fake_connector = object()
804- tcf.startedConnecting(fake_connector)
805- self.assertEqual(fake_other_factory.started_called, (fake_connector,))
806-
807- def test_forwards_failed(self):
808- """The factory forwards the clientConnectionFailed call."""
809- fake_reason = object()
810- fake_other_factory = FakeOtherFactory()
811- tcf = tunnel_client.TunnelClientFactory(None, None, fake_other_factory,
812- FAKE_COOKIE)
813- fake_connector = object()
814- tcf.clientConnectionFailed(fake_connector, fake_reason)
815- self.assertEqual(fake_other_factory.failed_called,
816- (fake_connector, fake_reason))
817-
818- def test_forwards_lost(self):
819- """The factory forwards the clientConnectionLost call."""
820- fake_reason = object()
821- fake_other_factory = FakeOtherFactory()
822- tcf = tunnel_client.TunnelClientFactory(None, None, fake_other_factory,
823- FAKE_COOKIE)
824- fake_connector = object()
825- tcf.clientConnectionLost(fake_connector, fake_reason)
826- self.assertEqual(fake_other_factory.lost_called,
827- (fake_connector, fake_reason))
828-
829-
830-class TunnelClientTestCase(SquidTestCase):
831- """Test the client for the tunnel."""
832-
833- timeout = 3
834-
835- @defer.inlineCallbacks
836- def setUp(self):
837- """Initialize this testcase."""
838- yield super(TunnelClientTestCase, self).setUp()
839- self.ws = MockWebServer()
840- self.addCleanup(self.ws.stop)
841- self.dest_url = self.ws.get_iri().encode("utf-8") + SIMPLERESOURCE
842- self.dest_ssl_url = (
843- self.ws.get_ssl_iri().encode("utf-8") + SIMPLERESOURCE)
844- self.cookie = FAKE_COOKIE
845- self.tunnel_server = TunnelServer(self.cookie)
846- self.addCleanup(self.tunnel_server.shutdown)
847-
848- @defer.inlineCallbacks
849- def test_connects_right(self):
850- """Uses the CONNECT method on the tunnel."""
851- tunnel_client = TunnelClient("0.0.0.0", self.tunnel_server.port,
852- self.cookie)
853- factory = client.HTTPClientFactory(self.dest_url)
854- scheme, host, port, path = client._parse(self.dest_url)
855- tunnel_client.connectTCP(host, port, factory)
856- result = yield factory.deferred
857- self.assertEqual(result, SAMPLE_CONTENT)
858-
859- @defer.inlineCallbacks
860- def test_starts_tls_connection(self):
861- """TLS is started after connecting; control passed to the client."""
862- tunnel_client = TunnelClient(
863- "0.0.0.0", self.tunnel_server.port, self.cookie)
864- factory = client.HTTPClientFactory(self.dest_ssl_url)
865- scheme, host, port, path = client._parse(self.dest_ssl_url)
866- context_factory = ssl.ClientContextFactory()
867- tunnel_client.connectSSL(host, port, factory, context_factory)
868- result = yield factory.deferred
869- self.assertEqual(result, SAMPLE_CONTENT)
870
871=== removed file 'ubuntuone/proxy/tests/test_tunnel_server.py'
872--- ubuntuone/proxy/tests/test_tunnel_server.py 2016-06-01 18:28:19 +0000
873+++ ubuntuone/proxy/tests/test_tunnel_server.py 1970-01-01 00:00:00 +0000
874@@ -1,743 +0,0 @@
875-# -*- coding: utf-8 -*-
876-#
877-# Copyright 2012-2013 Canonical Ltd.
878-#
879-# This program is free software: you can redistribute it and/or modify it
880-# under the terms of the GNU General Public License version 3, as published
881-# by the Free Software Foundation.
882-#
883-# This program is distributed in the hope that it will be useful, but
884-# WITHOUT ANY WARRANTY; without even the implied warranties of
885-# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
886-# PURPOSE. See the GNU General Public License for more details.
887-#
888-# You should have received a copy of the GNU General Public License along
889-# with this program. If not, see <http://www.gnu.org/licenses/>.
890-#
891-# In addition, as a special exception, the copyright holders give
892-# permission to link the code of portions of this program with the
893-# OpenSSL library under certain conditions as described in each
894-# individual source file, and distribute linked combinations
895-# including the two.
896-# You must obey the GNU General Public License in all respects
897-# for all of the code used other than OpenSSL. If you modify
898-# file(s) with this exception, you may extend this exception to your
899-# version of the file(s), but you are not obligated to do so. If you
900-# do not wish to do so, delete this exception statement from your
901-# version. If you delete this exception statement from all source
902-# files in the program, then also delete it here.
903-"""Tests for the proxy tunnel."""
904-
905-from StringIO import StringIO
906-from urlparse import urlparse
907-
908-from twisted.internet import defer, protocol, reactor
909-from twisted.trial.unittest import TestCase
910-from PyQt4.QtCore import QCoreApplication
911-from PyQt4.QtNetwork import QAuthenticator
912-from ubuntuone.devtools.testcases import skipIfOS
913-from ubuntuone.devtools.testcases.squid import SquidTestCase
914-
915-from ubuntuone.proxy.tests import (
916- FakeTransport,
917- FAKE_COOKIE,
918- MockWebServer,
919- SAMPLE_CONTENT,
920- SIMPLERESOURCE,
921-)
922-from ubuntuone.proxy import tunnel_server
923-from ubuntuone.proxy.tunnel_server import CRLF
924-
925-
926-FAKE_SESSION_TEMPLATE = (
927- "CONNECT %s HTTP/1.0" + CRLF +
928- "Header1: value1" + CRLF +
929- "Header2: value2" + CRLF +
930- tunnel_server.TUNNEL_COOKIE_HEADER + ": %s" + CRLF +
931- CRLF +
932- "GET %s HTTP/1.0" + CRLF + CRLF
933-)
934-
935-FAKE_SETTINGS = {
936- "http": {
937- "host": "myhost",
938- "port": 8888,
939- }
940-}
941-
942-FAKE_AUTH_SETTINGS = {
943- "http": {
944- "host": "myhost",
945- "port": 8888,
946- "username": "fake_user",
947- "password": "fake_password",
948- }
949-}
950-
951-SAMPLE_HOST = "samplehost.com"
952-SAMPLE_PORT = 443
953-
954-FAKE_CREDS = {
955- "username": "rhea",
956- "password": "caracolcaracola",
957-}
958-
959-
960-class DisconnectingProtocol(protocol.Protocol):
961- """A protocol that just disconnects."""
962-
963- def connectionMade(self):
964- """Upon connecting: just disconnect."""
965- self.transport.loseConnection()
966-
967-
968-class DisconnectingClientFactory(protocol.ClientFactory):
969- """A factory that fires a deferred on connection."""
970-
971- def __init__(self):
972- """Initialize this instance."""
973- self.connected = defer.Deferred()
974-
975- def buildProtocol(self, addr):
976- """The connection was made."""
977- proto = DisconnectingProtocol()
978- if not self.connected.called:
979- self.connected.callback(proto)
980- return proto
981-
982-
983-class FakeProtocol(protocol.Protocol):
984- """A protocol that forwards some data."""
985-
986- def __init__(self, factory, data):
987- """Initialize this fake."""
988- self.factory = factory
989- self.data = data
990- self.received_data = []
991-
992- def connectionMade(self):
993- """Upon connection: send the stored data."""
994- self.transport.write(self.data)
995-
996- def dataReceived(self, data):
997- """Some data was received."""
998- self.received_data.append(data)
999-
1000- def connectionLost(self, reason):
1001- """The connection was lost, return the response."""
1002- response = "".join(self.received_data)
1003- if not self.factory.response.called:
1004- self.factory.response.callback(response)
1005-
1006-
1007-class FakeClientFactory(protocol.ClientFactory):
1008- """A factory that forwards some data to the protocol."""
1009-
1010- def __init__(self, data):
1011- """Initialize this fake."""
1012- self.data = data
1013- self.response = defer.Deferred()
1014-
1015- def buildProtocol(self, addr):
1016- """The connection was made."""
1017- return FakeProtocol(self, self.data)
1018-
1019-
1020-class TunnelIntegrationTestCase(SquidTestCase):
1021- """Basic tunnel integration tests."""
1022-
1023- timeout = 3
1024-
1025- @defer.inlineCallbacks
1026- def setUp(self):
1027- """Initialize this testcase."""
1028- yield super(TunnelIntegrationTestCase, self).setUp()
1029- self.ws = MockWebServer()
1030- self.addCleanup(self.ws.stop)
1031- self.dest_url = self.ws.get_iri().encode("utf-8") + SIMPLERESOURCE
1032- self.cookie = FAKE_COOKIE
1033- self.tunnel_server = tunnel_server.TunnelServer(self.cookie)
1034- self.addCleanup(self.tunnel_server.shutdown)
1035-
1036- def test_init(self):
1037- """The tunnel is started."""
1038- self.assertNotEqual(self.tunnel_server.port, 0)
1039-
1040- @defer.inlineCallbacks
1041- def test_accepts_connections(self):
1042- """The tunnel accepts incoming connections."""
1043- ncf = DisconnectingClientFactory()
1044- reactor.connectTCP("0.0.0.0", self.tunnel_server.port, ncf)
1045- yield ncf.connected
1046-
1047- @defer.inlineCallbacks
1048- def test_complete_connection(self):
1049- """Test from the tunnel server down."""
1050- url = urlparse(self.dest_url)
1051- fake_session = FAKE_SESSION_TEMPLATE % (
1052- url.netloc, self.cookie, url.path)
1053- client = FakeClientFactory(fake_session)
1054- reactor.connectTCP("0.0.0.0", self.tunnel_server.port, client)
1055- response = yield client.response
1056- self.assertIn(SAMPLE_CONTENT, response)
1057-
1058-
1059-class FakeClient(object):
1060- """A fake destination client."""
1061-
1062- protocol = None
1063- connection_result = defer.succeed(True)
1064- credentials = None
1065- check_credentials = False
1066- proxy_domain = None
1067-
1068- def connect(self, hostport):
1069- """Establish a connection with the other end."""
1070- if (self.check_credentials and
1071- self.protocol.proxy_credentials != FAKE_CREDS):
1072- self.proxy_domain = "fake domain"
1073- return defer.fail(tunnel_server.ProxyAuthenticationError())
1074- return self.connection_result
1075-
1076- def write(self, data):
1077- """Write some data to the other end."""
1078- if data == 'GET /simpleresource HTTP/1.0\r\n\r\n':
1079- self.protocol.transport.write(SAMPLE_CONTENT)
1080-
1081- def stop(self):
1082- """Stop this fake client."""
1083-
1084- def close(self):
1085- """Reset this client."""
1086-
1087-
1088-class ServerTunnelProtocolTestCase(SquidTestCase):
1089- """Tests for the ServerTunnelProtocol."""
1090-
1091- @defer.inlineCallbacks
1092- def setUp(self):
1093- """Initialize this test instance."""
1094- yield super(ServerTunnelProtocolTestCase, self).setUp()
1095- self.ws = MockWebServer()
1096- self.addCleanup(self.ws.stop)
1097- self.dest_url = self.ws.get_iri().encode("utf-8") + SIMPLERESOURCE
1098- self.transport = FakeTransport()
1099- self.transport.cookie = FAKE_COOKIE
1100- self.fake_client = FakeClient()
1101- self.proto = tunnel_server.ServerTunnelProtocol(
1102- lambda _: self.fake_client)
1103- self.fake_client.protocol = self.proto
1104- self.proto.transport = self.transport
1105- self.cookie_line = "%s: %s" % (tunnel_server.TUNNEL_COOKIE_HEADER,
1106- FAKE_COOKIE)
1107-
1108- def test_broken_request(self):
1109- """Broken request."""
1110- self.proto.dataReceived("Broken request." + CRLF)
1111- self.assertTrue(self.transport.getvalue().startswith("HTTP/1.0 400 "),
1112- "A broken request must fail.")
1113-
1114- def test_wrong_method(self):
1115- """Wrong method."""
1116- self.proto.dataReceived("GET http://slashdot.org HTTP/1.0" + CRLF)
1117- self.assertTrue(self.transport.getvalue().startswith("HTTP/1.0 405 "),
1118- "Using a wrong method fails.")
1119-
1120- def test_invalid_http_version(self):
1121- """Invalid HTTP version."""
1122- self.proto.dataReceived("CONNECT 127.0.0.1:9999 HTTP/1.1" + CRLF)
1123- self.assertTrue(self.transport.getvalue().startswith("HTTP/1.0 505 "),
1124- "Invalid http version is not allowed.")
1125-
1126- def test_connection_is_established(self):
1127- """The response code is sent."""
1128- expected = "HTTP/1.0 200 Proxy connection established" + CRLF
1129- self.proto.dataReceived("CONNECT 127.0.0.1:9999 HTTP/1.0" + CRLF +
1130- self.cookie_line + CRLF * 2)
1131- self.assertTrue(self.transport.getvalue().startswith(expected),
1132- "First line must be the response status")
1133-
1134- def test_connection_fails(self):
1135- """The connection to the other end fails, and it's handled."""
1136- error = tunnel_server.ConnectionError()
1137- self.patch(self.fake_client, "connection_result", defer.fail(error))
1138- expected = "HTTP/1.0 500 Connection error" + CRLF
1139- self.proto.dataReceived("CONNECT 127.0.0.1:9999 HTTP/1.0" + CRLF +
1140- self.cookie_line + CRLF * 2)
1141- self.assertTrue(self.transport.getvalue().startswith(expected),
1142- "The connection should fail at this point.")
1143-
1144- def test_headers_stored(self):
1145- """The request headers are stored."""
1146- expected = [
1147- ("Header1", "value1"),
1148- ("Header2", "value2"),
1149- ]
1150- self.proto.dataReceived("CONNECT 127.0.0.1:9999 HTTP/1.0" + CRLF +
1151- "Header1: value1" + CRLF +
1152- "Header2: value2" + CRLF + CRLF)
1153- self.assertEqual(self.proto.received_headers, expected)
1154-
1155- def test_cookie_header_present(self):
1156- """The cookie header must be present."""
1157- self.proto.received_headers = [
1158- (tunnel_server.TUNNEL_COOKIE_HEADER, FAKE_COOKIE),
1159- ]
1160- self.proto.verify_cookie()
1161-
1162- def test_cookie_header_absent(self):
1163- """The tunnel should refuse connections without the cookie."""
1164- self.proto.received_headers = []
1165- exception = self.assertRaises(tunnel_server.ConnectionError,
1166- self.proto.verify_cookie)
1167- self.assertEqual(exception.code, 418)
1168-
1169- def test_successful_connect(self):
1170- """A successful connect thru the tunnel."""
1171- url = urlparse(self.dest_url)
1172- data = FAKE_SESSION_TEMPLATE % (url.netloc, self.transport.cookie,
1173- url.path)
1174- self.proto.dataReceived(data)
1175- lines = self.transport.getvalue().split(CRLF)
1176- self.assertEqual(lines[-1], SAMPLE_CONTENT)
1177-
1178- def test_header_split(self):
1179- """Test a header with many colons."""
1180- self.proto.header_line("key: host:port")
1181- self.assertIn("key", dict(self.proto.received_headers))
1182-
1183- @defer.inlineCallbacks
1184- def test_keyring_credentials_are_retried(self):
1185- """Wrong credentials are retried with values from keyring."""
1186- self.fake_client.check_credentials = True
1187- self.patch(self.proto, "verify_cookie", lambda: None)
1188- self.patch(self.proto, "error_response",
1189- lambda code, desc: self.fail(desc))
1190- self.proto.proxy_domain = "xxx"
1191- self.patch(tunnel_server.Keyring, "get_credentials",
1192- lambda _, domain: defer.succeed(FAKE_CREDS))
1193- yield self.proto.headers_done()
1194-
1195- def test_creds_are_not_logged(self):
1196- """The proxy credentials are not logged."""
1197- log = []
1198- self.patch(tunnel_server.logger, "info",
1199- lambda text, *args: log.append(text % args))
1200- proxy = tunnel_server.build_proxy(FAKE_AUTH_SETTINGS)
1201- authenticator = QAuthenticator()
1202- username = FAKE_AUTH_SETTINGS["http"]["username"]
1203- password = FAKE_AUTH_SETTINGS["http"]["password"]
1204- self.proto.proxy_credentials = {
1205- "username": username,
1206- "password": password,
1207- }
1208- self.proto.proxy_domain = proxy.hostName()
1209-
1210- self.proto.proxy_auth_required(proxy, authenticator)
1211-
1212- for line in log:
1213- self.assertNotIn(username, line)
1214- self.assertNotIn(password, line)
1215-
1216-
1217-class FakeServerTunnelProtocol(object):
1218- """A fake ServerTunnelProtocol."""
1219-
1220- def __init__(self):
1221- """Initialize this fake tunnel."""
1222- self.response_received = defer.Deferred()
1223- self.proxy_credentials = None
1224-
1225- def response_data_received(self, data):
1226- """Fire the response deferred."""
1227- if not self.response_received.called:
1228- self.response_received.callback(data)
1229-
1230- def remote_disconnected(self):
1231- """The remote server disconnected."""
1232-
1233- def proxy_auth_required(self, proxy, authenticator):
1234- """Proxy credentials are needed."""
1235- if self.proxy_credentials:
1236- authenticator.setUser(self.proxy_credentials["username"])
1237- authenticator.setPassword(self.proxy_credentials["password"])
1238-
1239-
1240-class BuildProxyTestCase(TestCase):
1241- """Tests for the build_proxy function."""
1242-
1243- def test_socks_is_preferred(self):
1244- """Socks overrides all protocols."""
1245- settings = {
1246- "http": {"host": "httphost", "port": 3128},
1247- "https": {"host": "httpshost", "port": 3129},
1248- "socks": {"host": "sockshost", "port": 1080},
1249- }
1250- proxy = tunnel_server.build_proxy(settings)
1251- self.assertEqual(proxy.type(), proxy.Socks5Proxy)
1252- self.assertEqual(proxy.hostName(), "sockshost")
1253- self.assertEqual(proxy.port(), 1080)
1254-
1255- def test_https_beats_http(self):
1256- """HTTPS wins over HTTP, since all of SD traffic is https."""
1257- settings = {
1258- "http": {"host": "httphost", "port": 3128},
1259- "https": {"host": "httpshost", "port": 3129},
1260- }
1261- proxy = tunnel_server.build_proxy(settings)
1262- self.assertEqual(proxy.type(), proxy.HttpProxy)
1263- self.assertEqual(proxy.hostName(), "httpshost")
1264- self.assertEqual(proxy.port(), 3129)
1265-
1266- def test_http_if_no_other_choice(self):
1267- """Finally, we use the host configured for HTTP."""
1268- settings = {
1269- "http": {"host": "httphost", "port": 3128},
1270- }
1271- proxy = tunnel_server.build_proxy(settings)
1272- self.assertEqual(proxy.type(), proxy.HttpProxy)
1273- self.assertEqual(proxy.hostName(), "httphost")
1274- self.assertEqual(proxy.port(), 3128)
1275-
1276- def test_use_noproxy_as_fallback(self):
1277- """If nothing useful, revert to no proxy."""
1278- settings = {}
1279- proxy = tunnel_server.build_proxy(settings)
1280- self.assertEqual(proxy.type(), proxy.DefaultProxy)
1281-
1282-
1283-class RemoteSocketTestCase(SquidTestCase):
1284- """Tests for the client that connects to the other side."""
1285-
1286- timeout = 3
1287-
1288- def get_proxy_settings(self):
1289- return {}
1290-
1291- @defer.inlineCallbacks
1292- def setUp(self):
1293- """Initialize this testcase."""
1294- yield super(RemoteSocketTestCase, self).setUp()
1295- self.ws = MockWebServer()
1296- self.addCleanup(self.ws.stop)
1297- self.dest_url = self.ws.get_iri().encode("utf-8") + SIMPLERESOURCE
1298-
1299- self.addCleanup(tunnel_server.QNetworkProxy.setApplicationProxy,
1300- tunnel_server.QNetworkProxy.applicationProxy())
1301- settings = {"http": self.get_proxy_settings()}
1302- proxy = tunnel_server.build_proxy(settings)
1303- tunnel_server.QNetworkProxy.setApplicationProxy(proxy)
1304-
1305- def test_invalid_port(self):
1306- """A request with an invalid port fails with a 400."""
1307- protocol = tunnel_server.ServerTunnelProtocol(
1308- tunnel_server.RemoteSocket)
1309- protocol.transport = FakeTransport()
1310- protocol.dataReceived("CONNECT 127.0.0.1:wrong_port HTTP/1.0" +
1311- CRLF * 2)
1312-
1313- status_line = protocol.transport.getvalue()
1314- self.assertTrue(status_line.startswith("HTTP/1.0 400 "),
1315- "The port must be an integer.")
1316-
1317- @defer.inlineCallbacks
1318- def test_connection_is_finished_when_stopping(self):
1319- """The client disconnects when requested."""
1320- fake_protocol = FakeServerTunnelProtocol()
1321- client = tunnel_server.RemoteSocket(fake_protocol)
1322- url = urlparse(self.dest_url)
1323- yield client.connect(url.netloc)
1324- yield client.stop()
1325-
1326- @defer.inlineCallbacks
1327- def test_stop_but_never_connected(self):
1328- """Stop but it was never connected."""
1329- fake_protocol = FakeServerTunnelProtocol()
1330- client = tunnel_server.RemoteSocket(fake_protocol)
1331- yield client.stop()
1332-
1333- @defer.inlineCallbacks
1334- def test_client_write(self):
1335- """Data written to the client is sent to the other side."""
1336- fake_protocol = FakeServerTunnelProtocol()
1337- client = tunnel_server.RemoteSocket(fake_protocol)
1338- self.addCleanup(client.stop)
1339- url = urlparse(self.dest_url)
1340- yield client.connect(url.netloc)
1341- client.write("GET /simpleresource HTTP/1.0" + CRLF * 2)
1342- yield self.ws.simple_resource.rendered
1343-
1344- @defer.inlineCallbacks
1345- def test_client_read(self):
1346- """Data received by the client is written into the transport."""
1347- fake_protocol = FakeServerTunnelProtocol()
1348- client = tunnel_server.RemoteSocket(fake_protocol)
1349- self.addCleanup(client.stop)
1350- url = urlparse(self.dest_url)
1351- yield client.connect(url.netloc)
1352- client.write("GET /simpleresource HTTP/1.0" + CRLF * 2)
1353- yield self.ws.simple_resource.rendered
1354- data = yield fake_protocol.response_received
1355- _headers, content = str(data).split(CRLF * 2, 1)
1356- self.assertEqual(content, SAMPLE_CONTENT)
1357-
1358-
1359-class AnonProxyRemoteSocketTestCase(RemoteSocketTestCase):
1360- """Tests for the client going thru an anonymous proxy."""
1361-
1362- get_proxy_settings = RemoteSocketTestCase.get_nonauth_proxy_settings
1363-
1364- def parse_headers(self, raw_headers):
1365- """Parse the headers."""
1366- lines = raw_headers.split(CRLF)
1367- header_lines = lines[1:]
1368- headers_pairs = (l.split(":", 1) for l in header_lines)
1369- return dict((k.lower(), v.strip()) for k, v in headers_pairs)
1370-
1371- @defer.inlineCallbacks
1372- def test_verify_client_uses_proxy(self):
1373- """Verify that the client uses the proxy."""
1374- fake_protocol = FakeServerTunnelProtocol()
1375- client = tunnel_server.RemoteSocket(fake_protocol)
1376- self.addCleanup(client.stop)
1377- url = urlparse(self.dest_url)
1378- yield client.connect(url.netloc)
1379- client.write("GET /simpleresource HTTP/1.0" + CRLF * 2)
1380- yield self.ws.simple_resource.rendered
1381- data = yield fake_protocol.response_received
1382- raw_headers, _content = str(data).split(CRLF * 2, 1)
1383- self.parse_headers(raw_headers)
1384-
1385-
1386-@skipIfOS('linux2', 'LP: #1111880 - ncsa_auth crashing for auth proxy tests.')
1387-class AuthenticatedProxyRemoteSocketTestCase(AnonProxyRemoteSocketTestCase):
1388- """Tests for the client going thru an authenticated proxy."""
1389-
1390- get_proxy_settings = RemoteSocketTestCase.get_auth_proxy_settings
1391-
1392- @defer.inlineCallbacks
1393- def test_proxy_authentication_error(self):
1394- """The proxy credentials were wrong on purpose."""
1395- settings = {"http": self.get_proxy_settings()}
1396- settings["http"]["password"] = "wrong password!!!"
1397- proxy = tunnel_server.build_proxy(settings)
1398- tunnel_server.QNetworkProxy.setApplicationProxy(proxy)
1399- fake_protocol = FakeServerTunnelProtocol()
1400- client = tunnel_server.RemoteSocket(fake_protocol)
1401- self.addCleanup(client.stop)
1402- url = urlparse(self.dest_url)
1403- yield self.assertFailure(client.connect(url.netloc),
1404- tunnel_server.ProxyAuthenticationError)
1405-
1406- @defer.inlineCallbacks
1407- def test_proxy_nobody_listens(self):
1408- """The proxy settings point to a proxy that's unreachable."""
1409- settings = dict(http={
1410- "host": "127.0.0.1",
1411- "port": 83, # unused port according to /etc/services
1412- })
1413- proxy = tunnel_server.build_proxy(settings)
1414- tunnel_server.QNetworkProxy.setApplicationProxy(proxy)
1415- fake_protocol = FakeServerTunnelProtocol()
1416- client = tunnel_server.RemoteSocket(fake_protocol)
1417- self.addCleanup(client.stop)
1418- url = urlparse(self.dest_url)
1419- yield self.assertFailure(client.connect(url.netloc),
1420- tunnel_server.ConnectionError)
1421-
1422- def test_use_credentials(self):
1423- """The credentials are used if present."""
1424- fake_protocol = FakeServerTunnelProtocol()
1425- client = tunnel_server.RemoteSocket(fake_protocol)
1426- proxy = tunnel_server.build_proxy(FAKE_SETTINGS)
1427- authenticator = QAuthenticator()
1428-
1429- client.proxyAuthenticationRequired.emit(proxy, authenticator)
1430- self.assertEqual(proxy.user(), "")
1431- self.assertEqual(proxy.password(), "")
1432- fake_protocol.proxy_credentials = FAKE_CREDS
1433-
1434- client.proxyAuthenticationRequired.emit(proxy, authenticator)
1435- self.assertEqual(authenticator.user(), FAKE_CREDS["username"])
1436- self.assertEqual(authenticator.password(), FAKE_CREDS["password"])
1437-
1438-
1439-class FakeNetworkProxyFactoryClass(object):
1440- """A fake QNetworkProxyFactory."""
1441- last_query = None
1442- use_system = False
1443-
1444- def __init__(self, enabled):
1445- """Initialize this fake instance."""
1446- if enabled:
1447- self.proxy_type = tunnel_server.QNetworkProxy.HttpProxy
1448- else:
1449- self.proxy_type = tunnel_server.QNetworkProxy.NoProxy
1450-
1451- def type(self):
1452- """Return the proxy type configured."""
1453- return self.proxy_type
1454-
1455- @classmethod
1456- def setUseSystemConfiguration(cls, new_value):
1457- """Save the system configuration requested."""
1458- cls.use_system = new_value
1459-
1460- @classmethod
1461- def useSystemConfiguration(cls):
1462- """Is the system configured for proxies?"""
1463- return cls.use_system
1464-
1465- def systemProxyForQuery(self, query):
1466- """A list of proxies, but only type() will be called on the first."""
1467- return [self]
1468-
1469-
1470-class CheckProxyEnabledTestCase(TestCase):
1471- """Tests for the check_proxy_enabled function."""
1472-
1473- @defer.inlineCallbacks
1474- def setUp(self):
1475- """Initialize this testcase."""
1476- yield super(CheckProxyEnabledTestCase, self).setUp()
1477- self.app_proxy = []
1478-
1479- def _assert_proxy_state(self, platform, state, assertion):
1480- """Assert the proxy is in a given state."""
1481- self.patch(tunnel_server.QNetworkProxy, "setApplicationProxy",
1482- lambda proxy: self.app_proxy.append(proxy))
1483- self.patch(tunnel_server.sys, "platform", platform)
1484- ret = tunnel_server.check_proxy_enabled(SAMPLE_HOST, str(SAMPLE_PORT))
1485- self.assertTrue(ret == state, assertion)
1486-
1487- def _assert_proxy_enabled(self, platform):
1488- """Assert that the proxy is enabled."""
1489- self._assert_proxy_state(platform, True, "Proxy is enabled.")
1490-
1491- def _assert_proxy_disabled(self, platform):
1492- """Assert that the proxy is disabled."""
1493- self._assert_proxy_state(platform, False, "Proxy is disabled.")
1494-
1495- def test_platform_linux_enabled(self):
1496- """Tests for the linux platform with proxies enabled."""
1497- self.patch(tunnel_server.gsettings, "get_proxy_settings",
1498- lambda: FAKE_SETTINGS)
1499- self._assert_proxy_enabled("linux3")
1500- self.assertEqual(len(self.app_proxy), 1)
1501-
1502- def test_platform_linux_disabled(self):
1503- """Tests for the linux platform with proxies disabled."""
1504- self.patch(tunnel_server.gsettings, "get_proxy_settings", lambda: {})
1505- self._assert_proxy_disabled("linux3")
1506- self.assertEqual(len(self.app_proxy), 0)
1507-
1508- def test_platform_other_enabled(self):
1509- """Tests for any other platform with proxies enabled."""
1510- fake_netproxfact = FakeNetworkProxyFactoryClass(True)
1511- self.patch(tunnel_server, "QNetworkProxyFactory", fake_netproxfact)
1512- self._assert_proxy_enabled("windows 1.0")
1513- self.assertEqual(len(self.app_proxy), 0)
1514- self.assertTrue(fake_netproxfact.useSystemConfiguration())
1515-
1516- def test_platform_other_disabled(self):
1517- """Tests for any other platform with proxies disabled."""
1518- fake_netproxfact = FakeNetworkProxyFactoryClass(False)
1519- self.patch(tunnel_server, "QNetworkProxyFactory", fake_netproxfact)
1520- self._assert_proxy_disabled("windows 1.0")
1521- self.assertEqual(len(self.app_proxy), 0)
1522- self.assertTrue(fake_netproxfact.useSystemConfiguration())
1523-
1524-
1525-class FakeQCoreApp(object):
1526- """A fake QCoreApplication."""
1527-
1528- fake_instance = None
1529-
1530- def __init__(self, argv):
1531- """Initialize this fake."""
1532- self.executed = False
1533- self.argv = argv
1534- FakeQCoreApp.fake_instance = self
1535-
1536- def exec_(self):
1537- """Fake the execution of this app."""
1538- self.executed = True
1539-
1540- @staticmethod
1541- def instance():
1542- """But return the real instance."""
1543- return QCoreApplication.instance()
1544-
1545-
1546-class MainFunctionTestCase(TestCase):
1547- """Tests for the main function of the tunnel server."""
1548-
1549- @defer.inlineCallbacks
1550- def setUp(self):
1551- """Initialize these testcases."""
1552- yield super(MainFunctionTestCase, self).setUp()
1553- self.called = []
1554- self.proxies_enabled = False
1555-
1556- def fake_is_proxy_enabled(*args):
1557- """Store the call, return false."""
1558- self.called.append(args)
1559- return self.proxies_enabled
1560-
1561- self.patch(tunnel_server, "check_proxy_enabled", fake_is_proxy_enabled)
1562- self.fake_stdout = StringIO()
1563- self.patch(tunnel_server.sys, "stdout", self.fake_stdout)
1564- self.patch(tunnel_server, "QCoreApplication", FakeQCoreApp)
1565-
1566- def test_checks_proxies(self):
1567- """Main checks that the proxies are enabled."""
1568- tunnel_server.main([])
1569- self.assertEqual(len(self.called), 1)
1570-
1571- def test_on_proxies_enabled_prints_port_and_cookie(self):
1572- """With proxies enabled print port to stdout and start the mainloop."""
1573- self.patch(tunnel_server.uuid, "uuid4", lambda: FAKE_COOKIE)
1574- self.proxies_enabled = True
1575- port = 443
1576- tunnel_server.main(["example.com", str(port)])
1577- stdout = self.fake_stdout.getvalue()
1578-
1579- self.assertIn(tunnel_server.TUNNEL_PORT_LABEL + ": ", stdout)
1580- cookie_line = tunnel_server.TUNNEL_COOKIE_LABEL + ": " + FAKE_COOKIE
1581- self.assertIn(cookie_line, stdout)
1582-
1583- def test_on_proxies_disabled_exit(self):
1584- """With proxies disabled, print a message and exit gracefully."""
1585- self.proxies_enabled = False
1586- tunnel_server.main(["example.com", "443"])
1587- self.assertIn("Proxy not enabled.", self.fake_stdout.getvalue())
1588- self.assertEqual(FakeQCoreApp.fake_instance, None)
1589-
1590- def test_qtdbus_installed_on_linux(self):
1591- """The QtDbus mainloop is installed."""
1592- self.patch(tunnel_server.sys, "platform", "linux123")
1593- installed = []
1594- self.patch(
1595- tunnel_server, "install_qt_dbus", lambda: installed.append(None))
1596- self.proxies_enabled = True
1597- tunnel_server.main(["example.com", "443"])
1598- self.assertEqual(len(installed), 1)
1599-
1600- def test_qtdbus_not_installed_on_windows(self):
1601- """The QtDbus mainloop is installed."""
1602- self.patch(tunnel_server.sys, "platform", "win98")
1603- installed = []
1604- self.patch(
1605- tunnel_server, "install_qt_dbus", lambda: installed.append(None))
1606- self.proxies_enabled = True
1607- tunnel_server.main(["example.com", "443"])
1608- self.assertEqual(len(installed), 0)
1609-
1610- def test_fix_turkish_locale_called(self):
1611- """The fix_turkish_locale function is called, always."""
1612- called = []
1613- self.patch(
1614- tunnel_server, "fix_turkish_locale",
1615- lambda *args, **kwargs: called.append((args, kwargs)))
1616- tunnel_server.main(["localhost", "443"])
1617- self.assertEqual(called, [((), {})])
1618
1619=== removed file 'ubuntuone/proxy/tunnel_client.py'
1620--- ubuntuone/proxy/tunnel_client.py 2016-05-29 00:50:05 +0000
1621+++ ubuntuone/proxy/tunnel_client.py 1970-01-01 00:00:00 +0000
1622@@ -1,202 +0,0 @@
1623-# -*- coding: utf-8 -*-
1624-#
1625-# Copyright 2012 Canonical Ltd.
1626-#
1627-# This program is free software: you can redistribute it and/or modify it
1628-# under the terms of the GNU General Public License version 3, as published
1629-# by the Free Software Foundation.
1630-#
1631-# This program is distributed in the hope that it will be useful, but
1632-# WITHOUT ANY WARRANTY; without even the implied warranties of
1633-# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
1634-# PURPOSE. See the GNU General Public License for more details.
1635-#
1636-# You should have received a copy of the GNU General Public License along
1637-# with this program. If not, see <http://www.gnu.org/licenses/>.
1638-#
1639-# In addition, as a special exception, the copyright holders give
1640-# permission to link the code of portions of this program with the
1641-# OpenSSL library under certain conditions as described in each
1642-# individual source file, and distribute linked combinations
1643-# including the two.
1644-# You must obey the GNU General Public License in all respects
1645-# for all of the code used other than OpenSSL. If you modify
1646-# file(s) with this exception, you may extend this exception to your
1647-# version of the file(s), but you are not obligated to do so. If you
1648-# do not wish to do so, delete this exception statement from your
1649-# version. If you delete this exception statement from all source
1650-# files in the program, then also delete it here.
1651-"""Client for the tunnel protocol."""
1652-
1653-import logging
1654-
1655-from twisted.internet import protocol, reactor
1656-
1657-from ubuntuone.clientdefs import NAME
1658-from ubuntuone.proxy.common import (
1659- BaseTunnelProtocol,
1660- CRLF,
1661- TUNNEL_COOKIE_LABEL,
1662- TUNNEL_COOKIE_HEADER,
1663- TUNNEL_PORT_LABEL,
1664-)
1665-
1666-METHOD_LINE = "CONNECT %s:%d HTTP/1.0" + CRLF
1667-LOCALHOST = "127.0.0.1"
1668-
1669-logger = logging.getLogger("ubuntuone.SyncDaemon.TunnelClient")
1670-
1671-
1672-class TunnelClientProtocol(BaseTunnelProtocol):
1673- """Client protocol for the handshake part of the tunnel."""
1674-
1675- def connectionMade(self):
1676- """The connection to the tunnel was made so send request."""
1677- method_line = METHOD_LINE % (self.factory.tunnel_host,
1678- self.factory.tunnel_port)
1679- headers = {
1680- "User-Agent": "%s tunnel client" % NAME,
1681- TUNNEL_COOKIE_HEADER: self.factory.cookie,
1682- }
1683- self.transport.write(method_line +
1684- self.format_headers(headers) +
1685- CRLF)
1686-
1687- def handle_first_line(self, line):
1688- """The first line received is the status line."""
1689- try:
1690- proto_version, self.status_code, description = line.split(" ", 2)
1691- except ValueError:
1692- self.transport.loseConnection()
1693-
1694- def headers_done(self):
1695- """All the headers have arrived. Time to switch protocols."""
1696- remaining_data = self.clearLineBuffer()
1697- if self.status_code != "200":
1698- self.transport.loseConnection()
1699- return
1700- addr = self.transport.getPeer()
1701- other_protocol = self.factory.other_factory.buildProtocol(addr)
1702- self.transport.protocol = other_protocol
1703- other_protocol.transport = self.transport
1704- self.transport = None
1705- if self.factory.context_factory:
1706- other_protocol.transport.startTLS(self.factory.context_factory)
1707- other_protocol.connectionMade()
1708- if remaining_data:
1709- other_protocol.dataReceived(remaining_data)
1710-
1711-
1712-class TunnelClientFactory(protocol.ClientFactory):
1713- """A factory for Tunnel Client Protocols."""
1714-
1715- protocol = TunnelClientProtocol
1716-
1717- def __init__(self, tunnel_host, tunnel_port, other_factory, cookie,
1718- context_factory=None):
1719- """Initialize this factory."""
1720- self.tunnel_host = tunnel_host
1721- self.tunnel_port = tunnel_port
1722- self.other_factory = other_factory
1723- self.context_factory = context_factory
1724- self.cookie = cookie
1725-
1726- def startedConnecting(self, connector):
1727- """Forward this call to the other factory."""
1728- self.other_factory.startedConnecting(connector)
1729-
1730- def clientConnectionFailed(self, connector, reason):
1731- """Forward this call to the other factory."""
1732- self.other_factory.clientConnectionFailed(connector, reason)
1733-
1734- def clientConnectionLost(self, connector, reason):
1735- """Forward this call to the other factory."""
1736- self.other_factory.clientConnectionLost(connector, reason)
1737-
1738-
1739-class TunnelClient(object):
1740- """A client for the proxy tunnel."""
1741-
1742- def __init__(self, tunnel_host, tunnel_port, cookie):
1743- """Initialize this client."""
1744- self.tunnel_host = tunnel_host
1745- self.tunnel_port = tunnel_port
1746- self.cookie = cookie
1747-
1748- def connectTCP(self, host, port, factory, *args, **kwargs):
1749- """A connectTCP going thru the tunnel."""
1750- logger.info("Connecting (TCP) to %r:%r via tunnel at %r:%r",
1751- host, port, self.tunnel_host, self.tunnel_port)
1752- tunnel_factory = TunnelClientFactory(host, port, factory, self.cookie)
1753- return reactor.connectTCP(self.tunnel_host, self.tunnel_port,
1754- tunnel_factory, *args, **kwargs)
1755-
1756- def connectSSL(self, host, port, factory,
1757- contextFactory, *args, **kwargs):
1758- """A connectSSL going thru the tunnel."""
1759- logger.info("Connecting (SSL) to %r:%r via tunnel at %r:%r",
1760- host, port, self.tunnel_host, self.tunnel_port)
1761- tunnel_factory = TunnelClientFactory(
1762- host, port, factory, self.cookie, contextFactory)
1763- return reactor.connectTCP(self.tunnel_host, self.tunnel_port,
1764- tunnel_factory, *args, **kwargs)
1765-
1766-
1767-class TunnelProcessProtocol(protocol.ProcessProtocol):
1768- """The dialog thru stdout with the tunnel server."""
1769-
1770- timeout = 30
1771-
1772- def __init__(self, client_d):
1773- """Initialize this protocol."""
1774- self.client_d = client_d
1775- self.timer = None
1776- self.port = None
1777- self.cookie = None
1778-
1779- def connectionMade(self):
1780- """The process has started, start a timer."""
1781- logger.info("Tunnel process started.")
1782- self.timer = reactor.callLater(self.timeout, self.process_timeouted)
1783-
1784- def process_timeouted(self):
1785- """The process took too long to reply."""
1786- if not self.client_d.called:
1787- logger.info("Timeout while waiting for tunnel process.")
1788- self.client_d.callback(reactor)
1789-
1790- def finish_timeout(self):
1791- """Stop the timer from firing."""
1792- if self.timer and self.timer.active():
1793- logger.debug("canceling timer before connection timeout")
1794- self.timer.cancel()
1795-
1796- def processExited(self, status):
1797- """The tunnel process has exited with some error code."""
1798- self.finish_timeout()
1799- logger.info("Tunnel process exit status %r.", status)
1800- if not self.client_d.called:
1801- logger.debug("Tunnel process exited before TunnelClient created. "
1802- "Falling back to reactor")
1803- self.client_d.callback(reactor)
1804-
1805- def outReceived(self, data):
1806- """Receive the port number."""
1807- if self.client_d.called:
1808- return
1809-
1810- for line in data.split("\n"):
1811- if line.startswith(TUNNEL_PORT_LABEL):
1812- _header, port = line.split(":", 1)
1813- self.port = int(port.strip())
1814- if line.startswith(TUNNEL_COOKIE_LABEL):
1815- _header, cookie = line.split(":", 1)
1816- self.cookie = cookie.strip()
1817-
1818- if self.port and self.cookie:
1819- logger.info("Tunnel process listening on port %r.", self.port)
1820- client = TunnelClient(LOCALHOST, self.port, self.cookie)
1821- self.client_d.callback(client)
1822-
1823- def errReceived(self, data):
1824- logger.debug("Got stderr from tunnel process: %r", data)
1825
1826=== removed file 'ubuntuone/proxy/tunnel_server.py'
1827--- ubuntuone/proxy/tunnel_server.py 2017-01-07 18:51:07 +0000
1828+++ ubuntuone/proxy/tunnel_server.py 1970-01-01 00:00:00 +0000
1829@@ -1,405 +0,0 @@
1830-# -*- coding: utf-8 -*-
1831-#
1832-# Copyright 2012-2013 Canonical Ltd.
1833-# Copyright 2015-2017 Chicharreros (https://launchpad.net/~chicharreros)
1834-#
1835-# This program is free software: you can redistribute it and/or modify it
1836-# under the terms of the GNU General Public License version 3, as published
1837-# by the Free Software Foundation.
1838-#
1839-# This program is distributed in the hope that it will be useful, but
1840-# WITHOUT ANY WARRANTY; without even the implied warranties of
1841-# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
1842-# PURPOSE. See the GNU General Public License for more details.
1843-#
1844-# You should have received a copy of the GNU General Public License along
1845-# with this program. If not, see <http://www.gnu.org/licenses/>.
1846-#
1847-# In addition, as a special exception, the copyright holders give
1848-# permission to link the code of portions of this program with the
1849-# OpenSSL library under certain conditions as described in each
1850-# individual source file, and distribute linked combinations
1851-# including the two.
1852-# You must obey the GNU General Public License in all respects
1853-# for all of the code used other than OpenSSL. If you modify
1854-# file(s) with this exception, you may extend this exception to your
1855-# version of the file(s), but you are not obligated to do so. If you
1856-# do not wish to do so, delete this exception statement from your
1857-# version. If you delete this exception statement from all source
1858-# files in the program, then also delete it here.
1859-"""A tunnel through proxies.
1860-
1861-The layers in a tunneled proxied connection:
1862-
1863-↓ tunnelclient - initiates tcp to tunnelserver, request outward connection
1864-↕ client protocol - started after the tunneclient gets connected
1865----process boundary---
1866-↕ tunnelserver - creates a tunnel instance per incoming connection
1867-↕ tunnel - hold a qtcpsocket to tunnelclient, and srvtunnelproto to the remote
1868-↕ servertunnelprotocol - gets CONNECT from tunnelclient, creates a remotesocket
1869-↕ remotesocket - connects to the destination server via a proxy
1870-↕ proxy server - goes thru firewalls
1871-↑ server - dialogues with the client protocol
1872-
1873-"""
1874-
1875-import sys
1876-import uuid
1877-
1878-from PyQt4.QtCore import QCoreApplication, QTimer
1879-from PyQt4.QtNetwork import (
1880- QAbstractSocket,
1881- QHostAddress,
1882- QNetworkProxy,
1883- QNetworkProxyQuery,
1884- QNetworkProxyFactory,
1885- QTcpServer,
1886- QTcpSocket,
1887-)
1888-from twisted.internet import defer, interfaces
1889-from zope.interface import implements
1890-
1891-from ubuntuone.clientdefs import NAME
1892-from ubuntuone.keyring import Keyring
1893-from ubuntuone.utils import gsettings
1894-from ubuntuone.proxy.common import (
1895- BaseTunnelProtocol,
1896- CRLF,
1897- TUNNEL_COOKIE_HEADER,
1898- TUNNEL_COOKIE_LABEL,
1899- TUNNEL_PORT_LABEL,
1900-)
1901-from ubuntuone.proxy.logger import logger
1902-try:
1903- from ubuntuone.utils.locale import fix_turkish_locale
1904-except ImportError:
1905- def fix_turkish_locale():
1906- return None
1907-
1908-
1909-DEFAULT_CODE = 500
1910-DEFAULT_DESCRIPTION = "Connection error"
1911-
1912-
1913-class ConnectionError(Exception):
1914- """The client failed connecting to the destination."""
1915-
1916- def __init__(self, code=DEFAULT_CODE, description=DEFAULT_DESCRIPTION):
1917- self.code = code
1918- self.description = description
1919-
1920-
1921-class ProxyAuthenticationError(ConnectionError):
1922- """Credentials mismatch going thru a proxy."""
1923-
1924-
1925-def build_proxy(settings_groups):
1926- """Create a QNetworkProxy from these settings."""
1927- proxy_groups = [
1928- ("socks", QNetworkProxy.Socks5Proxy),
1929- ("https", QNetworkProxy.HttpProxy),
1930- ("http", QNetworkProxy.HttpProxy),
1931- ]
1932- for group, proxy_type in proxy_groups:
1933- if group not in settings_groups:
1934- continue
1935- settings = settings_groups[group]
1936- if "host" in settings and "port" in settings:
1937- return QNetworkProxy(proxy_type,
1938- hostName=settings.get("host", ""),
1939- port=settings.get("port", 0),
1940- user=settings.get("username", ""),
1941- password=settings.get("password", ""))
1942- logger.error("No proxy correctly configured.")
1943- return QNetworkProxy(QNetworkProxy.DefaultProxy)
1944-
1945-
1946-class RemoteSocket(QTcpSocket):
1947- """A dumb connection through a proxy to a destination hostport."""
1948-
1949- def __init__(self, tunnel_protocol):
1950- """Initialize this object."""
1951- super(RemoteSocket, self).__init__()
1952- self.protocol = tunnel_protocol
1953- self.connected_d = defer.Deferred()
1954- self.connected.connect(self.handle_connected)
1955- self.proxyAuthenticationRequired.connect(self.handle_auth_required)
1956- self.buffered_data = []
1957-
1958- def handle_connected(self):
1959- """When connected, send all pending data."""
1960- self.disconnected.connect(self.handle_disconnected)
1961- self.connected_d.callback(None)
1962- for d in self.buffered_data:
1963- logger.debug("writing remote: %d bytes", len(d))
1964- super(RemoteSocket, self).write(d)
1965- self.buffered_data = []
1966-
1967- def handle_disconnected(self):
1968- """Do something with disconnections."""
1969- logger.debug("Remote socket disconnected")
1970- self.protocol.remote_disconnected()
1971-
1972- def write(self, data):
1973- """Write data to the remote end, buffering if not connected."""
1974- if self.state() == QAbstractSocket.ConnectedState:
1975- logger.debug("writing remote: %d bytes", len(data))
1976- super(RemoteSocket, self).write(data)
1977- else:
1978- self.buffered_data.append(data)
1979-
1980- def connect(self, hostport):
1981- """Try to establish the connection to the remote end."""
1982- host, port = hostport.split(":")
1983-
1984- try:
1985- port = int(port)
1986- except ValueError:
1987- raise ConnectionError(400, "Destination port must be an integer.")
1988-
1989- self.readyRead.connect(self.handle_ready_read)
1990- self.error.connect(self.handle_error)
1991- self.connectToHost(host, port)
1992-
1993- return self.connected_d
1994-
1995- def handle_auth_required(self, proxy, authenticator):
1996- """Handle the proxyAuthenticationRequired signal."""
1997- self.protocol.proxy_auth_required(proxy, authenticator)
1998-
1999- def handle_error(self, socket_error):
2000- """Some error happened while connecting."""
2001- error_description = "%s (%d)" % (self.errorString(), socket_error)
2002- logger.error("connection error: %s", error_description)
2003- if self.connected_d.called:
2004- return
2005-
2006- if socket_error == self.ProxyAuthenticationRequiredError:
2007- error = ProxyAuthenticationError(407, error_description)
2008- else:
2009- error = ConnectionError(500, error_description)
2010-
2011- self.connected_d.errback(error)
2012-
2013- def handle_ready_read(self):
2014- """Forward data from the remote end to the parent protocol."""
2015- data = self.readAll()
2016- self.protocol.response_data_received(data)
2017-
2018- @defer.inlineCallbacks
2019- def stop(self):
2020- """Finish and cleanup."""
2021- self.disconnectFromHost()
2022- while self.state() != self.UnconnectedState:
2023- d = defer.Deferred()
2024- QTimer.singleShot(100, lambda: d.callback(None))
2025- yield d
2026-
2027-
2028-class ServerTunnelProtocol(BaseTunnelProtocol):
2029- """CONNECT sever protocol for tunnelling connections."""
2030-
2031- def __init__(self, client_class):
2032- """Initialize this protocol."""
2033- BaseTunnelProtocol.__init__(self)
2034- self.hostport = ""
2035- self.client = None
2036- self.client_class = client_class
2037- self.proxy_credentials = None
2038- self.proxy_domain = None
2039-
2040- def error_response(self, code, description):
2041- """Write a response with an error, and disconnect."""
2042- self.write_transport("HTTP/1.0 %d %s" % (code, description) + CRLF * 2)
2043- self.transport.loseConnection()
2044- if self.client:
2045- self.client.stop()
2046- self.clearLineBuffer()
2047-
2048- def write_transport(self, data):
2049- """Write a response in the transport."""
2050- self.transport.write(data)
2051-
2052- def proxy_auth_required(self, proxy, authenticator):
2053- """Proxy authentication is required."""
2054- logger.info("auth_required %r, %r",
2055- proxy.hostName(), self.proxy_domain)
2056- if self.proxy_credentials and proxy.hostName() == self.proxy_domain:
2057- logger.info("Credentials added to authenticator.")
2058- authenticator.setUser(self.proxy_credentials["username"])
2059- authenticator.setPassword(self.proxy_credentials["password"])
2060- else:
2061- logger.info("Credentials needed, but none available.")
2062- self.proxy_domain = proxy.hostName()
2063-
2064- def handle_first_line(self, line):
2065- """Special handling for the first line received."""
2066- try:
2067- method, hostport, proto_version = line.split(" ", 2)
2068- if proto_version != "HTTP/1.0":
2069- self.error_response(505, "HTTP Version Not Supported")
2070- return
2071- if method != "CONNECT":
2072- self.error_response(405, "Only the CONNECT method is allowed")
2073- return
2074- self.hostport = hostport
2075- except ValueError:
2076- self.error_response(400, "Bad request")
2077-
2078- def verify_cookie(self):
2079- """Fail if the cookie is wrong or missing."""
2080- cookie_received = dict(self.received_headers).get(TUNNEL_COOKIE_HEADER)
2081- if cookie_received != self.transport.cookie:
2082- raise ConnectionError(418, "Please see RFC 2324")
2083-
2084- @defer.inlineCallbacks
2085- def headers_done(self):
2086- """An empty line was received, start connecting and switch mode."""
2087- try:
2088- self.verify_cookie()
2089- try:
2090- logger.info("Connecting once")
2091- self.client = self.client_class(self)
2092- yield self.client.connect(self.hostport)
2093- except ProxyAuthenticationError:
2094- if not self.proxy_domain:
2095- logger.info("No proxy domain defined")
2096- raise
2097-
2098- credentials = yield Keyring().get_credentials(
2099- str(self.proxy_domain))
2100- if "username" in credentials:
2101- self.proxy_credentials = credentials
2102- logger.info("Connecting again with keyring credentials")
2103- self.client = self.client_class(self)
2104- yield self.client.connect(self.hostport)
2105- logger.info("Connected with keyring credentials")
2106-
2107- response_headers = {
2108- "Server": "%s proxy tunnel" % NAME,
2109- }
2110- self.write_transport("HTTP/1.0 200 Proxy connection established" +
2111- CRLF + self.format_headers(response_headers) +
2112- CRLF)
2113- except ConnectionError as e:
2114- logger.exception("Connection error")
2115- self.error_response(e.code, e.description)
2116- except Exception:
2117- logger.exception("Unhandled problem while connecting")
2118-
2119- def rawDataReceived(self, data):
2120- """Tunnel all raw data straight to the other side."""
2121- self.client.write(data)
2122-
2123- def response_data_received(self, data):
2124- """Return data coming from the other side."""
2125- self.write_transport(data)
2126-
2127-
2128-class Tunnel(object):
2129- """An instance of a running tunnel."""
2130-
2131- implements(interfaces.ITransport)
2132-
2133- def __init__(self, local_socket, cookie):
2134- """Initialize this Tunnel instance."""
2135- self.cookie = cookie
2136- self.disconnecting = False
2137- self.local_socket = local_socket
2138- self.protocol = ServerTunnelProtocol(RemoteSocket)
2139- self.protocol.transport = self
2140- local_socket.readyRead.connect(self.server_ready_read)
2141- local_socket.disconnected.connect(self.local_disconnected)
2142-
2143- def server_ready_read(self):
2144- """Data available on the local end. Move it forward."""
2145- data = bytes(self.local_socket.readAll())
2146- self.protocol.dataReceived(data)
2147-
2148- def write(self, data):
2149- """Data available on the remote end. Bring it back."""
2150- logger.debug("writing local: %d bytes", len(data))
2151- self.local_socket.write(data)
2152-
2153- def loseConnection(self):
2154- """The remote end disconnected."""
2155- logger.debug("disconnecting local end.")
2156- self.local_socket.close()
2157-
2158- def local_disconnected(self):
2159- """The local end disconnected."""
2160- logger.debug("The local socket got disconnected.")
2161- # TODO: handle this case in an upcoming branch
2162-
2163-
2164-class TunnelServer(object):
2165- """A server for tunnel instances."""
2166-
2167- def __init__(self, cookie):
2168- """Initialize this tunnel instance."""
2169- self.tunnels = []
2170- self.cookie = cookie
2171- self.server = QTcpServer(QCoreApplication.instance())
2172- self.server.newConnection.connect(self.new_connection)
2173- self.server.listen(QHostAddress.LocalHost, 0)
2174- logger.info("Starting tunnel server at port %d", self.port)
2175-
2176- def new_connection(self):
2177- """On a new connection create a new tunnel instance."""
2178- logger.info("New connection made")
2179- local_socket = self.server.nextPendingConnection()
2180- tunnel = Tunnel(local_socket, self.cookie)
2181- self.tunnels.append(tunnel)
2182-
2183- def shutdown(self):
2184- """Terminate every connection."""
2185- # TODO: handle this gracefully in an upcoming branch
2186-
2187- @property
2188- def port(self):
2189- """The port where this server listens."""
2190- return self.server.serverPort()
2191-
2192-
2193-def check_proxy_enabled(host, port):
2194- """Check if the proxy is enabled."""
2195- port = int(port)
2196- if sys.platform.startswith("linux"):
2197- settings = gsettings.get_proxy_settings()
2198- enabled = len(settings) > 0
2199- if enabled:
2200- proxy = build_proxy(settings)
2201- QNetworkProxy.setApplicationProxy(proxy)
2202- else:
2203- logger.info("Proxy is disabled.")
2204- return enabled
2205- else:
2206- QNetworkProxyFactory.setUseSystemConfiguration(True)
2207- query = QNetworkProxyQuery(host, port)
2208- proxies = QNetworkProxyFactory.systemProxyForQuery(query)
2209- return len(proxies) and proxies[0].type() != QNetworkProxy.NoProxy
2210-
2211-
2212-def install_qt_dbus():
2213- """Import and install the qt+dbus integration."""
2214- from dbus.mainloop.qt import DBusQtMainLoop
2215- DBusQtMainLoop(set_as_default=True)
2216-
2217-
2218-def main(argv):
2219- """The main function for the tunnel server."""
2220- fix_turkish_locale()
2221- if not check_proxy_enabled(*argv[1:]):
2222- sys.stdout.write("Proxy not enabled.")
2223- sys.stdout.flush()
2224- else:
2225- if sys.platform.startswith("linux"):
2226- install_qt_dbus()
2227-
2228- app = QCoreApplication(argv)
2229- cookie = str(uuid.uuid4())
2230- tunnel_server = TunnelServer(cookie)
2231- sys.stdout.write("%s: %d\n" % (TUNNEL_PORT_LABEL, tunnel_server.port) +
2232- "%s: %s\n" % (TUNNEL_COOKIE_LABEL, cookie))
2233- sys.stdout.flush()
2234- app.exec_()
2235
2236=== modified file 'ubuntuone/syncdaemon/action_queue.py'
2237--- ubuntuone/syncdaemon/action_queue.py 2017-02-10 01:15:07 +0000
2238+++ ubuntuone/syncdaemon/action_queue.py 2018-03-14 21:14:07 +0000
2239@@ -1,7 +1,7 @@
2240 # -*- coding: utf-8 -*-
2241 #
2242 # Copyright 2009-2015 Canonical Ltd.
2243-# Copyright 2015-2017 Chicharreros (https://launchpad.net/~chicharreros)
2244+# Copyright 2015-2018 Chicharreros (https://launchpad.net/~chicharreros)
2245 #
2246 # This program is free software: you can redistribute it and/or modify it
2247 # under the terms of the GNU General Public License version 3, as published
2248@@ -59,7 +59,6 @@
2249 from ubuntuone.syncdaemon.interfaces import IActionQueue, IMarker
2250 from ubuntuone.syncdaemon.logger import mklog, TRACE
2251 from ubuntuone.syncdaemon import config, offload_queue
2252-from ubuntuone.syncdaemon import tunnel_runner
2253
2254 logger = logging.getLogger("ubuntuone.SyncDaemon.ActionQueue")
2255
2256@@ -856,27 +855,20 @@
2257 self.event_queue.push('SV_VOLUME_NEW_GENERATION',
2258 volume_id=volume_id, generation=generation)
2259
2260- def _get_tunnel_runner(self, host, port):
2261- """Build the tunnel runner."""
2262- return tunnel_runner.TunnelRunner(host, port)
2263-
2264- @defer.inlineCallbacks
2265 def _make_connection(self):
2266 """Do the real connect call."""
2267 connection_info = self.connection_info.next()
2268 logger.info("Attempting connection to %s", connection_info)
2269 host = connection_info['host']
2270 port = connection_info['port']
2271- tunnelrunner = self._get_tunnel_runner(host, port)
2272- client = yield tunnelrunner.get_client()
2273 if connection_info['use_ssl']:
2274 ssl_context = get_ssl_context(
2275 connection_info['disable_ssl_verify'], host)
2276- self.connector = client.connectSSL(
2277+ self.connector = reactor.connectSSL(
2278 host, port, factory=self, contextFactory=ssl_context,
2279 timeout=self.connection_timeout)
2280 else:
2281- self.connector = client.connectTCP(
2282+ self.connector = reactor.connectTCP(
2283 host, port, self, timeout=self.connection_timeout)
2284
2285 def connect(self):
2286
2287=== modified file 'ubuntuone/syncdaemon/tests/test_action_queue.py'
2288--- ubuntuone/syncdaemon/tests/test_action_queue.py 2018-03-08 19:39:13 +0000
2289+++ ubuntuone/syncdaemon/tests/test_action_queue.py 2018-03-14 21:14:07 +0000
2290@@ -1,7 +1,7 @@
2291 # -*- coding: utf-8 -*-
2292 #
2293 # Copyright 2009-2015 Canonical Ltd.
2294-# Copyright 2016-2017 Chicharreros (https://launchpad.net/~chicharreros)
2295+# Copyright 2015-2018 Chicharreros (https://launchpad.net/~chicharreros)
2296 #
2297 # This program is free software: you can redistribute it and/or modify it
2298 # under the terms of the GNU General Public License version 3, as published
2299@@ -1384,9 +1384,13 @@
2300 self.assertTrue(self.handler.check_info("Connection started",
2301 "host 1.2.3.4", "port 4321"))
2302
2303- @defer.inlineCallbacks
2304 def test_connection_info_rotation(self):
2305 """It tries to connect to different servers."""
2306+ # store how connectTCP is called
2307+ called = []
2308+ self.patch(
2309+ reactor, 'connectTCP',
2310+ lambda host, port, *a, **k: called.append((host, port)))
2311
2312 multiple_conn = [
2313 {'host': 'host1', 'port': 'port1', 'use_ssl': False},
2314@@ -1394,61 +1398,14 @@
2315 ]
2316 self.action_queue.connection_info = itertools.cycle(multiple_conn)
2317
2318- self.tunnel_runner = None
2319-
2320- def mitm(*args):
2321- tunnel_runner = SavingConnectionTunnelRunner(*args)
2322- self.tunnel_runner = tunnel_runner
2323- return tunnel_runner
2324-
2325- self.action_queue._get_tunnel_runner = mitm
2326-
2327- yield self.action_queue._make_connection()
2328- self.assertEqual(self.tunnel_runner.host, 'host1')
2329- self.assertEqual(self.tunnel_runner.port, 'port1')
2330-
2331- yield self.action_queue._make_connection()
2332- self.assertEqual(self.tunnel_runner.host, 'host2')
2333- self.assertEqual(self.tunnel_runner.port, 'port2')
2334-
2335- yield self.action_queue._make_connection()
2336- self.assertEqual(self.tunnel_runner.host, 'host1')
2337- self.assertEqual(self.tunnel_runner.port, 'port1')
2338-
2339-
2340-class TunnelRunnerTestCase(FactoryBaseTestCase):
2341- """Tests for the tunnel runner."""
2342-
2343- tunnel_runner_class = SavingConnectionTunnelRunner
2344-
2345- def setUp(self):
2346- result = super(TunnelRunnerTestCase, self).setUp()
2347- self.tunnel_runner = None
2348- orig_get_tunnel_runner = self.action_queue._get_tunnel_runner
2349-
2350- def mitm(*args):
2351- tunnel_runner = orig_get_tunnel_runner(*args)
2352- self.tunnel_runner = tunnel_runner
2353- return tunnel_runner
2354-
2355- self.action_queue._get_tunnel_runner = mitm
2356- return result
2357-
2358- @defer.inlineCallbacks
2359- def test_make_connection_uses_tunnelrunner_non_ssl(self):
2360- """Check that _make_connection uses TunnelRunner."""
2361- self._patch_connection_info(use_ssl=False)
2362- yield self.action_queue._make_connection()
2363- self.assertTrue(self.tunnel_runner.client.tcp_connected,
2364- "connectTCP is called on the client.")
2365-
2366- @defer.inlineCallbacks
2367- def test_make_connection_uses_tunnelrunner_ssl(self):
2368- """Check that _make_connection uses TunnelRunner."""
2369- self._patch_connection_info(use_ssl=True, disable_ssl_verify=False)
2370- yield self.action_queue._make_connection()
2371- self.assertTrue(self.tunnel_runner.client.ssl_connected,
2372- "connectSSL is called on the client.")
2373+ self.action_queue._make_connection()
2374+ self.assertEqual(called[-1], ('host1', 'port1'))
2375+
2376+ self.action_queue._make_connection()
2377+ self.assertEqual(called[-1], ('host2', 'port2'))
2378+
2379+ self.action_queue._make_connection()
2380+ self.assertEqual(called[-1], ('host1', 'port1'))
2381
2382
2383 class ContextRequestedWithHost(FactoryBaseTestCase):
2384@@ -1459,6 +1416,9 @@
2385 @defer.inlineCallbacks
2386 def test_context_request_passes_host(self):
2387 """The context is requested passing the host."""
2388+ # avoid a real connection
2389+ self.patch(reactor, 'connectSSL', lambda *a, **k: None)
2390+
2391 fake_host = "fake_host"
2392 fake_disable_ssl_verify = False
2393
2394
2395=== removed file 'ubuntuone/syncdaemon/tests/test_tunnel_runner.py'
2396--- ubuntuone/syncdaemon/tests/test_tunnel_runner.py 2016-06-04 21:14:35 +0000
2397+++ ubuntuone/syncdaemon/tests/test_tunnel_runner.py 1970-01-01 00:00:00 +0000
2398@@ -1,185 +0,0 @@
2399-# -*- coding: utf-8 -*-
2400-#
2401-# Copyright 2012 Canonical Ltd.
2402-#
2403-# This program is free software: you can redistribute it and/or modify it
2404-# under the terms of the GNU General Public License version 3, as published
2405-# by the Free Software Foundation.
2406-#
2407-# This program is distributed in the hope that it will be useful, but
2408-# WITHOUT ANY WARRANTY; without even the implied warranties of
2409-# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
2410-# PURPOSE. See the GNU General Public License for more details.
2411-#
2412-# You should have received a copy of the GNU General Public License along
2413-# with this program. If not, see <http://www.gnu.org/licenses/>.
2414-#
2415-# In addition, as a special exception, the copyright holders give
2416-# permission to link the code of portions of this program with the
2417-# OpenSSL library under certain conditions as described in each
2418-# individual source file, and distribute linked combinations
2419-# including the two.
2420-# You must obey the GNU General Public License in all respects
2421-# for all of the code used other than OpenSSL. If you modify
2422-# file(s) with this exception, you may extend this exception to your
2423-# version of the file(s), but you are not obligated to do so. If you
2424-# do not wish to do so, delete this exception statement from your
2425-# version. If you delete this exception statement from all source
2426-# files in the program, then also delete it here.
2427-"""Tests for the proxy tunnel runner."""
2428-
2429-from twisted.internet import defer, error, reactor, task
2430-from twisted.trial.unittest import TestCase
2431-
2432-from ubuntuone.proxy.tests import FAKE_COOKIE
2433-from ubuntuone.proxy import tunnel_client
2434-from ubuntuone.syncdaemon import tunnel_runner
2435-
2436-FAKE_HOST = "fs-1.two.ubuntu.com"
2437-FAKE_PORT = 443
2438-
2439-
2440-class TunnelRunnerConstructorTestCase(TestCase):
2441- """Test the tunnel runner constructor."""
2442-
2443- timeout = 3
2444-
2445- def raise_import_error(self, *args):
2446- """Raise an import error."""
2447- raise ImportError
2448-
2449- @defer.inlineCallbacks
2450- def test_proxy_support_not_installed(self):
2451- """The proxy support binary package is not installed."""
2452- self.patch(tunnel_runner.TunnelRunner, "start_process",
2453- self.raise_import_error)
2454- tr = tunnel_runner.TunnelRunner(FAKE_HOST, FAKE_PORT)
2455- client = yield tr.get_client()
2456- self.assertEqual(client, reactor)
2457-
2458- @defer.inlineCallbacks
2459- def test_executable_not_found(self):
2460- """The executable is not found anywhere."""
2461- self.patch(tunnel_runner, "get_tunnel_bin_cmd",
2462- lambda *args, **kwargs: ["this_does_not_exist"])
2463- tr = tunnel_runner.TunnelRunner(FAKE_HOST, FAKE_PORT)
2464- client = yield tr.get_client()
2465- self.assertEqual(client, reactor)
2466-
2467-
2468-class FakeProcessTransport(object):
2469- """A fake ProcessTransport."""
2470-
2471- pid = 0
2472-
2473- def __init__(self):
2474- """Initialize this fake."""
2475- self._signals_sent = []
2476-
2477- def signalProcess(self, signalID):
2478- """Send a signal to the process."""
2479- self._signals_sent.append(signalID)
2480-
2481-
2482-class TunnelRunnerTestCase(TestCase):
2483- """Tests for the TunnelRunner."""
2484-
2485- timeout = 3
2486-
2487- @defer.inlineCallbacks
2488- def setUp(self):
2489- """Initialize this testcase."""
2490- yield super(TunnelRunnerTestCase, self).setUp()
2491- self.spawned = []
2492- self.triggers = []
2493- self.fake_process_transport = FakeProcessTransport()
2494-
2495- def fake_spawn_process(*args, **kwargs):
2496- """A fake spawnProcess."""
2497- self.spawned.append((args, kwargs))
2498- return self.fake_process_transport
2499-
2500- self.patch(tunnel_client.reactor, "spawnProcess", fake_spawn_process)
2501-
2502- def fake_add_system_event_trigger(*args, **kwargs):
2503- """A fake addSystemEventTrigger."""
2504- self.triggers.append((args, kwargs))
2505-
2506- self.patch(tunnel_client.reactor, "addSystemEventTrigger",
2507- fake_add_system_event_trigger)
2508- self.process_protocol = None
2509- self.process_protocol_class = tunnel_client.TunnelProcessProtocol
2510- self.patch(tunnel_client, "TunnelProcessProtocol",
2511- self.storing_process_protocol_factory)
2512- self.tr = tunnel_runner.TunnelRunner("fs-1.one.ubuntu.com", 443)
2513-
2514- def storing_process_protocol_factory(self, *args, **kwargs):
2515- """Store the process protocol just created."""
2516- self.process_protocol = self.process_protocol_class(*args, **kwargs)
2517- return self.process_protocol
2518-
2519- def test_tunnel_process_is_started(self):
2520- """The tunnel process is started."""
2521- self.assertEqual(
2522- len(self.spawned), 1, "The tunnel process is started.")
2523-
2524- def test_system_event_finished(self):
2525- """An event is added to stop the process with the reactor."""
2526- expected = [(("before", "shutdown", self.tr.stop), {})]
2527- self.assertEqual(self.triggers, expected)
2528-
2529- def test_stop_process(self):
2530- """The process is stopped if still running."""
2531- self.tr.process_transport.pid = 1234
2532- self.tr.stop()
2533- self.assertEqual(self.fake_process_transport._signals_sent, ["KILL"])
2534-
2535- def test_not_stopped_if_already_finished(self):
2536- """Do not stop the tunnel process if it's already finished."""
2537- self.tr.process_transport.pid = None
2538- self.tr.stop()
2539- self.assertEqual(self.fake_process_transport._signals_sent, [])
2540-
2541- @defer.inlineCallbacks
2542- def test_tunnel_process_get_client_yielded_twice(self):
2543- """The get_client method can be yielded twice."""
2544- self.process_protocol.processExited(error.ProcessTerminated(1))
2545- client = yield self.tr.get_client()
2546- client = yield self.tr.get_client()
2547- self.assertNotEqual(client, None)
2548-
2549- @defer.inlineCallbacks
2550- def test_tunnel_process_exits_with_error(self):
2551- """The tunnel process exits with an error."""
2552- self.process_protocol.processExited(error.ProcessTerminated(1))
2553- client = yield self.tr.get_client()
2554- self.assertEqual(client, reactor)
2555-
2556- @defer.inlineCallbacks
2557- def test_tunnel_process_exits_gracefully(self):
2558- """The tunnel process exits gracefully."""
2559- self.process_protocol.processExited(error.ProcessDone(0))
2560- client = yield self.tr.get_client()
2561- self.assertEqual(client, reactor)
2562-
2563- @defer.inlineCallbacks
2564- def test_tunnel_process_prints_random_garbage_and_timeouts(self):
2565- """The tunnel process prints garbage and timeouts."""
2566- clock = task.Clock()
2567- self.patch(tunnel_client, "reactor", clock)
2568- self.process_protocol.connectionMade()
2569- self.process_protocol.outReceived("Random garbage")
2570- clock.advance(self.process_protocol.timeout)
2571- client = yield self.tr.get_client()
2572- self.assertEqual(client, clock)
2573-
2574- @defer.inlineCallbacks
2575- def test_tunnel_process_prints_port_number_and_cookie(self):
2576- """The tunnel process prints the port number."""
2577- received = "%s: %d\n%s: %s\n" % (
2578- tunnel_client.TUNNEL_PORT_LABEL, FAKE_PORT,
2579- tunnel_client.TUNNEL_COOKIE_LABEL, FAKE_COOKIE)
2580- self.process_protocol.outReceived(received)
2581- client = yield self.tr.get_client()
2582- self.assertEqual(client.tunnel_port, FAKE_PORT)
2583- self.assertEqual(client.cookie, FAKE_COOKIE)
2584
2585=== removed file 'ubuntuone/syncdaemon/tunnel_runner.py'
2586--- ubuntuone/syncdaemon/tunnel_runner.py 2012-11-28 08:08:10 +0000
2587+++ ubuntuone/syncdaemon/tunnel_runner.py 1970-01-01 00:00:00 +0000
2588@@ -1,86 +0,0 @@
2589-# -*- coding: utf-8 -*-
2590-#
2591-# Copyright 2012 Canonical Ltd.
2592-#
2593-# This program is free software: you can redistribute it and/or modify it
2594-# under the terms of the GNU General Public License version 3, as published
2595-# by the Free Software Foundation.
2596-#
2597-# This program is distributed in the hope that it will be useful, but
2598-# WITHOUT ANY WARRANTY; without even the implied warranties of
2599-# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
2600-# PURPOSE. See the GNU General Public License for more details.
2601-#
2602-# You should have received a copy of the GNU General Public License along
2603-# with this program. If not, see <http://www.gnu.org/licenses/>.
2604-#
2605-# In addition, as a special exception, the copyright holders give
2606-# permission to link the code of portions of this program with the
2607-# OpenSSL library under certain conditions as described in each
2608-# individual source file, and distribute linked combinations
2609-# including the two.
2610-# You must obey the GNU General Public License in all respects
2611-# for all of the code used other than OpenSSL. If you modify
2612-# file(s) with this exception, you may extend this exception to your
2613-# version of the file(s), but you are not obligated to do so. If you
2614-# do not wish to do so, delete this exception statement from your
2615-# version. If you delete this exception statement from all source
2616-# files in the program, then also delete it here.
2617-"""Run the tunnel process and start a client, with a reactor as a fallback."""
2618-
2619-import logging
2620-
2621-from twisted.internet import defer, reactor
2622-
2623-from ubuntuone.clientdefs import LIBEXECDIR
2624-from ubuntuone.syncdaemon.utils import get_tunnel_bin_cmd
2625-
2626-logger = logging.getLogger("ubuntuone.SyncDaemon.TunnelRunner")
2627-
2628-
2629-class TunnelRunner(object):
2630- """Run a tunnel process."""
2631-
2632- def __init__(self, host, port):
2633- """Start this runner instance."""
2634- self.client_d = defer.Deferred()
2635- self.process_transport = None
2636- try:
2637- self.start_process(host, port)
2638- except ImportError:
2639- logger.info("Proxy support not installed.")
2640- self.client_d.callback(reactor)
2641- except Exception:
2642- logger.exception("Error while starting tunnel process:")
2643- self.client_d.callback(reactor)
2644-
2645- def start_process(self, host, port):
2646- """Start the tunnel process."""
2647- from ubuntuone.proxy.tunnel_client import TunnelProcessProtocol
2648- protocol = TunnelProcessProtocol(self.client_d)
2649- tunnel_cmd = get_tunnel_bin_cmd(extra_fallbacks=[LIBEXECDIR])
2650-
2651- args = tunnel_cmd + [host, str(port)]
2652-
2653- self.process_transport = reactor.spawnProcess(protocol, args[0],
2654- env=None, args=args)
2655- reactor.addSystemEventTrigger("before", "shutdown", self.stop)
2656-
2657- def stop(self):
2658- """Stop the tunnel process if still running."""
2659- logger.info("Stopping process %r", self.process_transport.pid)
2660- if self.process_transport.pid is not None:
2661- self.process_transport.signalProcess("KILL")
2662-
2663- def get_client(self):
2664- """A deferred with the reactor or a tunnel client."""
2665-
2666- def client_selected(result, d):
2667- """The tunnel_client or the reactor were selected."""
2668- d.callback(result)
2669- # make sure the result is available for next callback
2670- return result
2671-
2672- d = defer.Deferred()
2673- self.client_d.addCallback(client_selected, d)
2674- return d
2675
2676=== modified file 'ubuntuone/syncdaemon/utils.py'
2677--- ubuntuone/syncdaemon/utils.py 2015-09-20 00:03:47 +0000
2678+++ ubuntuone/syncdaemon/utils.py 2018-03-14 21:14:07 +0000
2679@@ -1,6 +1,7 @@
2680 # -*- coding: utf-8 -*-
2681 #
2682 # Copyright 2012 Canonical Ltd.
2683+# Copyright 2015-2018 Chicharreros (https://launchpad.net/~chicharreros)
2684 #
2685 # This program is free software: you can redistribute it and/or modify it
2686 # under the terms of the GNU General Public License version 3, as published
2687@@ -42,10 +43,8 @@
2688
2689
2690 SYNCDAEMON_EXECUTABLE = 'ubuntuone-syncdaemon'
2691-TUNNEL_EXECUTABLE = 'ubuntuone-proxy-tunnel'
2692
2693-DARWIN_APP_NAMES = {SYNCDAEMON_EXECUTABLE: 'UbuntuOne Syncdaemon.app',
2694- TUNNEL_EXECUTABLE: 'UbuntuOne Proxy Tunnel.app'}
2695+DARWIN_APP_NAMES = {SYNCDAEMON_EXECUTABLE: 'UbuntuOne Syncdaemon.app'}
2696
2697
2698 def _get_bin_cmd(exe_name, extra_fallbacks=[]):
2699@@ -73,9 +72,3 @@
2700 def get_sd_bin_cmd():
2701 """Get cmd + args to launch syncdaemon executable."""
2702 return _get_bin_cmd(SYNCDAEMON_EXECUTABLE)
2703-
2704-
2705-def get_tunnel_bin_cmd(extra_fallbacks):
2706- """Get cmd + args to launch proxy tunnel."""
2707- return _get_bin_cmd(TUNNEL_EXECUTABLE,
2708- extra_fallbacks=extra_fallbacks)
2709
2710=== removed file 'ubuntuone/utils/gsettings.py'
2711--- ubuntuone/utils/gsettings.py 2017-01-07 18:51:07 +0000
2712+++ ubuntuone/utils/gsettings.py 1970-01-01 00:00:00 +0000
2713@@ -1,114 +0,0 @@
2714-# -*- coding: utf-8 -*-
2715-#
2716-# Copyright 2011-2012 Canonical Ltd.
2717-# Copyright 2015-2016 Chicharreros (https://launchpad.net/~chicharreros)
2718-#
2719-# This program is free software: you can redistribute it and/or modify it
2720-# under the terms of the GNU General Public License version 3, as published
2721-# by the Free Software Foundation.
2722-#
2723-# This program is distributed in the hope that it will be useful, but
2724-# WITHOUT ANY WARRANTY; without even the implied warranties of
2725-# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
2726-# PURPOSE. See the GNU General Public License for more details.
2727-#
2728-# You should have received a copy of the GNU General Public License along
2729-# with this program. If not, see <http://www.gnu.org/licenses/>.
2730-#
2731-# In addition, as a special exception, the copyright holders give
2732-# permission to link the code of portions of this program with the
2733-# OpenSSL library under certain conditions as described in each
2734-# individual source file, and distribute linked combinations
2735-# including the two.
2736-# You must obey the GNU General Public License in all respects
2737-# for all of the code used other than OpenSSL. If you modify
2738-# file(s) with this exception, you may extend this exception to your
2739-# version of the file(s), but you are not obligated to do so. If you
2740-# do not wish to do so, delete this exception statement from your
2741-# version. If you delete this exception statement from all source
2742-# files in the program, then also delete it here.
2743-
2744-"""Retrieve the proxy configuration from Gnome."""
2745-
2746-import logging
2747-import subprocess
2748-
2749-
2750-logger = logging.getLogger(__name__)
2751-GSETTINGS_CMDLINE = "gsettings list-recursively org.gnome.system.proxy"
2752-CANNOT_PARSE_WARNING = "Cannot parse gsettings value: %r"
2753-
2754-
2755-def parse_proxy_host(hostname):
2756- """Parse the host to get username and password."""
2757- username = None
2758- password = None
2759- if "@" in hostname:
2760- username, hostname = hostname.rsplit("@", 1)
2761- if ":" in username:
2762- username, password = username.split(":", 1)
2763- return hostname, username, password
2764-
2765-
2766-def parse_manual_proxy_settings(scheme, gsettings):
2767- """Parse the settings for a given scheme."""
2768- host, username, pwd = parse_proxy_host(gsettings[scheme + ".host"])
2769- # if the user did not set a proxy for a type (http/https/ftp) we should
2770- # return None to ensure that it is not used
2771- if host == '':
2772- return None
2773-
2774- settings = {
2775- "host": host,
2776- "port": gsettings[scheme + ".port"],
2777- }
2778- if scheme == "http" and gsettings["http.use-authentication"]:
2779- username = gsettings["http.authentication-user"]
2780- pwd = gsettings["http.authentication-password"]
2781- if username is not None and pwd is not None:
2782- settings.update({
2783- "username": username,
2784- "password": pwd,
2785- })
2786- return settings
2787-
2788-
2789-def get_proxy_settings():
2790- """Parse the proxy settings as returned by the gsettings executable."""
2791- output = subprocess.check_output(GSETTINGS_CMDLINE.split())
2792- gsettings = {}
2793- base_len = len("org.gnome.system.proxy.")
2794-
2795- for line in output.split("\n"):
2796- try:
2797- path, key, value = line.split(" ", 2)
2798- except ValueError:
2799- continue
2800- if value.startswith("'"):
2801- parsed_value = value[1:-1]
2802- elif value.startswith(('[', '@')):
2803- parsed_value = value
2804- elif value in ('true', 'false'):
2805- parsed_value = (value == 'true')
2806- elif value.isdigit():
2807- parsed_value = int(value)
2808- else:
2809- logger.warning(CANNOT_PARSE_WARNING, value)
2810- parsed_value = value
2811- relative_key = (path + "." + key)[base_len:]
2812- gsettings[relative_key] = parsed_value
2813- mode = gsettings["mode"]
2814- if mode == "none":
2815- settings = {}
2816- elif mode == "manual":
2817- settings = {}
2818- for scheme in ["http", "https"]:
2819- scheme_settings = parse_manual_proxy_settings(scheme, gsettings)
2820- if scheme_settings is not None:
2821- settings[scheme] = scheme_settings
2822- else:
2823- # If mode is automatic the PAC javascript should be interpreted
2824- # on each request. That is out of scope so it's ignored for now
2825- settings = {}
2826-
2827- return settings
2828
2829=== removed file 'ubuntuone/utils/tests/test_gsettings.py'
2830--- ubuntuone/utils/tests/test_gsettings.py 2017-01-07 18:51:07 +0000
2831+++ ubuntuone/utils/tests/test_gsettings.py 1970-01-01 00:00:00 +0000
2832@@ -1,322 +0,0 @@
2833-# -*- coding: utf-8 -*-
2834-#
2835-# Copyright 2011-2012 Canonical Ltd.
2836-# Copyright 2015-2017 Chicharreros (https://launchpad.net/~chicharreros)
2837-#
2838-# This program is free software: you can redistribute it and/or modify it
2839-# under the terms of the GNU General Public License version 3, as published
2840-# by the Free Software Foundation.
2841-#
2842-# This program is distributed in the hope that it will be useful, but
2843-# WITHOUT ANY WARRANTY; without even the implied warranties of
2844-# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
2845-# PURPOSE. See the GNU General Public License for more details.
2846-#
2847-# You should have received a copy of the GNU General Public License along
2848-# with this program. If not, see <http://www.gnu.org/licenses/>.
2849-#
2850-# In addition, as a special exception, the copyright holders give
2851-# permission to link the code of portions of this program with the
2852-# OpenSSL library under certain conditions as described in each
2853-# individual source file, and distribute linked combinations
2854-# including the two.
2855-# You must obey the GNU General Public License in all respects
2856-# for all of the code used other than OpenSSL. If you modify
2857-# file(s) with this exception, you may extend this exception to your
2858-# version of the file(s), but you are not obligated to do so. If you
2859-# do not wish to do so, delete this exception statement from your
2860-# version. If you delete this exception statement from all source
2861-# files in the program, then also delete it here.
2862-
2863-"""Test the gsettings parser."""
2864-
2865-import logging
2866-
2867-from twisted.trial.unittest import TestCase
2868-from ubuntuone.devtools.handlers import MementoHandler
2869-
2870-from ubuntuone.utils import gsettings
2871-
2872-TEMPLATE_GSETTINGS_OUTPUT = """\
2873-org.gnome.system.proxy autoconfig-url '{autoconfig_url}'
2874-org.gnome.system.proxy ignore-hosts {ignore_hosts:s}
2875-org.gnome.system.proxy mode '{mode}'
2876-org.gnome.system.proxy.ftp host '{ftp_host}'
2877-org.gnome.system.proxy.ftp port {ftp_port}
2878-org.gnome.system.proxy.http authentication-password '{auth_password}'
2879-org.gnome.system.proxy.http authentication-user '{auth_user}'
2880-org.gnome.system.proxy.http host '{http_host}'
2881-org.gnome.system.proxy.http port {http_port}
2882-org.gnome.system.proxy.http use-authentication {http_use_auth}
2883-org.gnome.system.proxy.https host '{https_host}'
2884-org.gnome.system.proxy.https port {https_port}
2885-org.gnome.system.proxy.socks host '{socks_host}'
2886-org.gnome.system.proxy.socks port {socks_port}
2887-"""
2888-
2889-BASE_GSETTINGS_VALUES = {
2890- "autoconfig_url": "",
2891- "ignore_hosts": ["localhost", "127.0.0.0/8"],
2892- "mode": "none",
2893- "ftp_host": "",
2894- "ftp_port": 0,
2895- "auth_password": "",
2896- "auth_user": "",
2897- "http_host": "",
2898- "http_port": 0,
2899- "http_use_auth": "false",
2900- "https_host": "",
2901- "https_port": 0,
2902- "socks_host": "",
2903- "socks_port": 0,
2904-}
2905-
2906-
2907-class ProxySettingsTestCase(TestCase):
2908- """Test the getting of the proxy settings."""
2909-
2910- def test_gsettings_cmdline_correct(self):
2911- """The command line used to get the proxy settings is the right one."""
2912- expected = "gsettings list-recursively org.gnome.system.proxy".split()
2913- called = []
2914-
2915- def append_output(args):
2916- """Append the output and return some settings."""
2917- called.append(args)
2918- return TEMPLATE_GSETTINGS_OUTPUT.format(**BASE_GSETTINGS_VALUES)
2919-
2920- self.patch(gsettings.subprocess, "check_output", append_output)
2921- gsettings.get_proxy_settings()
2922- self.assertEqual(called[0], expected)
2923-
2924- def test_gsettings_parser_none(self):
2925- """Test a parser of gsettings."""
2926- expected = {}
2927- fake_output = TEMPLATE_GSETTINGS_OUTPUT.format(**BASE_GSETTINGS_VALUES)
2928- self.patch(gsettings.subprocess, "check_output",
2929- lambda _: fake_output)
2930- ps = gsettings.get_proxy_settings()
2931- self.assertEqual(ps, expected)
2932-
2933- def _assert_parser_anonymous(self, scheme):
2934- """Assert the parsing of anonymous settings."""
2935- template_values = dict(BASE_GSETTINGS_VALUES)
2936- expected_host = "expected_host"
2937- expected_port = 54321
2938- expected = {
2939- "host": expected_host,
2940- "port": expected_port,
2941- }
2942- template_values.update({
2943- "mode": "manual",
2944- scheme + "_host": expected_host,
2945- scheme + "_port": expected_port,
2946- })
2947- fake_output = TEMPLATE_GSETTINGS_OUTPUT.format(**template_values)
2948- self.patch(gsettings.subprocess, "check_output",
2949- lambda _: fake_output)
2950- ps = gsettings.get_proxy_settings()
2951- self.assertEqual(ps[scheme], expected)
2952-
2953- def test_gsettings_parser_http_anonymous(self):
2954- """Test a parser of gsettings."""
2955- self._assert_parser_anonymous('http')
2956-
2957- def test_gsettings_parser_https_anonymus(self):
2958- """Test a parser of gsettings."""
2959- self._assert_parser_anonymous('https')
2960-
2961- def test_gsettings_empty_ignore_hosts(self):
2962- """Missing values in the ignore hosts."""
2963- troublesome_value = "@as []"
2964- template_values = dict(BASE_GSETTINGS_VALUES)
2965- template_values["ignore_hosts"] = troublesome_value
2966- fake_output = TEMPLATE_GSETTINGS_OUTPUT.format(**template_values)
2967- self.patch(gsettings.subprocess, "check_output",
2968- lambda _: fake_output)
2969- ps = gsettings.get_proxy_settings()
2970- self.assertEqual(ps, {})
2971-
2972- def test_gsettings_cannot_parse(self):
2973- """Some weird setting that cannot be parsed is logged with warning."""
2974- memento = MementoHandler()
2975- memento.setLevel(logging.DEBUG)
2976- gsettings.logger.addHandler(memento)
2977- self.addCleanup(gsettings.logger.removeHandler, memento)
2978-
2979- troublesome_value = "#bang"
2980- template_values = dict(BASE_GSETTINGS_VALUES)
2981- template_values["ignore_hosts"] = troublesome_value
2982- fake_output = TEMPLATE_GSETTINGS_OUTPUT.format(**template_values)
2983- self.patch(gsettings.subprocess, "check_output",
2984- lambda _: fake_output)
2985- ps = gsettings.get_proxy_settings()
2986- self.assertTrue(memento.check_warning(gsettings.CANNOT_PARSE_WARNING %
2987- troublesome_value))
2988- self.assertEqual(ps, {})
2989-
2990- def test_gsettings_parser_http_authenticated(self):
2991- """Test a parser of gsettings."""
2992- template_values = dict(BASE_GSETTINGS_VALUES)
2993- expected_host = "expected_host"
2994- expected_port = 54321
2995- expected_user = "carlitos"
2996- expected_password = "very secret password"
2997- expected = {
2998- "host": expected_host,
2999- "port": expected_port,
3000- "username": expected_user,
3001- "password": expected_password,
3002- }
3003- template_values.update({
3004- "mode": "manual",
3005- "http_host": expected_host,
3006- "http_port": expected_port,
3007- "auth_user": expected_user,
3008- "auth_password": expected_password,
3009- "http_use_auth": "true",
3010- })
3011- fake_output = TEMPLATE_GSETTINGS_OUTPUT.format(**template_values)
3012- self.patch(gsettings.subprocess, "check_output",
3013- lambda _: fake_output)
3014- ps = gsettings.get_proxy_settings()
3015- self.assertEqual(ps["http"], expected)
3016-
3017- def _assert_parser_authenticated_url(self, scheme):
3018- """Test a parser of gsettings with creds in the url."""
3019- template_values = dict(BASE_GSETTINGS_VALUES)
3020- expected_host = "expected_host"
3021- expected_port = 54321
3022- expected_user = "carlitos"
3023- expected_password = "very secret password"
3024- composed_url = '%s:%s@%s' % (expected_user, expected_password,
3025- expected_host)
3026- expected = {
3027- "host": expected_host,
3028- "port": expected_port,
3029- "username": expected_user,
3030- "password": expected_password,
3031- }
3032- template_values.update({
3033- "mode": "manual",
3034- scheme + "_host": composed_url,
3035- scheme + "_port": expected_port,
3036- "http_use_auth": "false",
3037- })
3038- fake_output = TEMPLATE_GSETTINGS_OUTPUT.format(**template_values)
3039- self.patch(gsettings.subprocess, "check_output",
3040- lambda _: fake_output)
3041- ps = gsettings.get_proxy_settings()
3042- self.assertEqual(ps[scheme], expected)
3043-
3044- def test_gsettings_parser_http_authenticated_url(self):
3045- """Test a parser of gsettings with creds in the url."""
3046- self._assert_parser_authenticated_url('http')
3047-
3048- def test_gsettings_parser_https_authenticated_url(self):
3049- """Test a parser of gsettings with creds in the url."""
3050- self._assert_parser_authenticated_url('https')
3051-
3052- def test_gsettings_auth_over_url(self):
3053- """Test that the settings are more important that the url."""
3054- template_values = dict(BASE_GSETTINGS_VALUES)
3055- expected_host = "expected_host"
3056- expected_port = 54321
3057- expected_user = "carlitos"
3058- expected_password = "very secret password"
3059- composed_url = '%s:%s@%s' % ('user', 'random',
3060- expected_host)
3061- http_expected = {
3062- "host": expected_host,
3063- "port": expected_port,
3064- "username": expected_user,
3065- "password": expected_password,
3066- }
3067- template_values.update({
3068- "mode": "manual",
3069- "http_host": composed_url,
3070- "http_port": expected_port,
3071- "auth_user": expected_user,
3072- "auth_password": expected_password,
3073- "http_use_auth": "true",
3074- })
3075- fake_output = TEMPLATE_GSETTINGS_OUTPUT.format(**template_values)
3076- self.patch(gsettings.subprocess, "check_output",
3077- lambda _: fake_output)
3078- ps = gsettings.get_proxy_settings()
3079- self.assertEqual(ps["http"], http_expected)
3080-
3081- def _assert_parser_empty_url(self, scheme):
3082- """Assert the parsing of an empty url."""
3083- template_values = dict(BASE_GSETTINGS_VALUES)
3084- template_values.update({
3085- "mode": "manual",
3086- scheme + "_host": '',
3087- scheme + "_port": 0,
3088- "http_use_auth": "false",
3089- })
3090- fake_output = TEMPLATE_GSETTINGS_OUTPUT.format(**template_values)
3091- self.patch(gsettings.subprocess, "check_output",
3092- lambda _: fake_output)
3093- ps = gsettings.get_proxy_settings()
3094- self.assertNotIn(scheme, ps)
3095-
3096- def test_gsettings_parser_empty_http_url(self):
3097- """Test when there is no http proxy set."""
3098- self._assert_parser_empty_url('http')
3099-
3100- def test_gsettings_parser_empty_https_url(self):
3101- """Test when there is no https proxy set."""
3102- self._assert_parser_empty_url('https')
3103-
3104-
3105-class ParseProxyHostTestCase(TestCase):
3106- """Test the parsing of the domain."""
3107-
3108- def test_onlyhost(self):
3109- """Parse a host with no username or password."""
3110- sample = "hostname"
3111- hostname, username, password = gsettings.parse_proxy_host(sample)
3112- self.assertEqual(username, None)
3113- self.assertEqual(password, None)
3114- self.assertEqual(hostname, "hostname")
3115-
3116- def test_user_and_host(self):
3117- """Parse host just with the username."""
3118- sample = "username@hostname"
3119- hostname, username, password = gsettings.parse_proxy_host(sample)
3120- self.assertEqual(username, "username")
3121- self.assertEqual(password, None)
3122- self.assertEqual(hostname, "hostname")
3123-
3124- def test_user_pass_and_host(self):
3125- """Test parsing a host with a username and password."""
3126- sample = "username:password@hostname"
3127- hostname, username, password = gsettings.parse_proxy_host(sample)
3128- self.assertEqual(username, "username")
3129- self.assertEqual(password, "password")
3130- self.assertEqual(hostname, "hostname")
3131-
3132- def test_username_with_at(self):
3133- """Test parsing the host with a username with @."""
3134- sample = "username@company.com:password@hostname"
3135- hostname, username, password = gsettings.parse_proxy_host(sample)
3136- self.assertEqual(username, "username@company.com")
3137- self.assertEqual(password, "password")
3138- self.assertEqual(hostname, "hostname")
3139-
3140- def test_username_with_at_nopass(self):
3141- """Test parsing the host without a password."""
3142- sample = "username@company.com@hostname"
3143- hostname, username, password = gsettings.parse_proxy_host(sample)
3144- self.assertEqual(username, "username@company.com")
3145- self.assertEqual(password, None)
3146- self.assertEqual(hostname, "hostname")
3147-
3148- def test_user_pass_with_colon_and_host(self):
3149- """Test parsing the host with a password that contains :."""
3150- sample = "username:pass:word@hostname"
3151- hostname, username, password = gsettings.parse_proxy_host(sample)
3152- self.assertEqual(username, "username")
3153- self.assertEqual(password, "pass:word")
3154- self.assertEqual(hostname, "hostname")

Subscribers

People subscribed via source and target branches

to all changes: